Отправить файл прошивки устройству через COM порт - C#
Формулировка задачи:
День добрый.
Я совсем недавно взялся за с#, и в качестве первого задания хочу написать программу автоматической настройки модемов. По работе бывает, что приносят нам модем с заводскими настройками, и надо быстренько все перебить на свои (то есть хочется вместо забивания команд ручками по бумажке, подключить модем, нажать одну кнопку и пойти пить чай).
Правда вылезла проблема. Если с тем, как кидать команды модему я вроде разобрался, то остается вопрос как его перепрошивать.
То есть вбивая команды, в определенный момент времени, устройству нужно сказать "AT+WDWL" (то есть принимай файл), и в терминале, используя протокол передачи "1K Xmodem", кинуть ему некий файл прошивки на 100 с чем-то килобайт.
И как это сделать на с# - ума не приложу... Естественно пошел гуглить, но толи народу это малоактуально, толи я неправильно вопрос формулирую - ничего толкового найти не получается.
Если кто сталкивался - подскажите пожалуйста.
ps
Не уверен, что смогу отвечать сегодня, но завтра точно буду на месте.
Решение задачи: «Отправить файл прошивки устройству через COM порт»
textual
Листинг программы
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace ArcadiaUtility_v3._0._0._0
{
public class WriteArcadia
{
//События
public event EventHandler<BarEventArgs> barOnEvent; //Включение и установки прогресса
public event EventHandler barStepEvent; //Шаг прогресса
public event EventHandler barOffEvent; //Отключение прогресса
public event EventHandler<ExceptionEventArgs> exceptionEvent;//Обработка ошибок
public event EventHandler<BarEventArgs> correctWriteEvent; //Считанные данные корректны
public event EventHandler correctApplEvent; //Данные соответствуют устройству
//Поля
private SerialPort port; //Переменная порта
private List<byte> inData; //Переменная входных данных для записи
private int mcuType; //Тип МК
private string swVersion; //Версия ПО
private int configSize; //Макс. размер конфигурации
private int moduleType; //Тип устройства
//Конструктор
public WriteArcadia(string portName, List<byte> data)
{
port = new SerialPort() { PortName = portName, BaudRate = 38400, ReadTimeout = 500 };
inData = data;
}
//Внешний метод записи(в отдельном потоке)
public void WriteModule()
{
//Коррекция размера(до заполнения блока)
int fileSize = (inData[14] * 0x100 + inData[15]) * 2;
fileSize = (fileSize % 0x40 == 0) ? fileSize : fileSize + (0x40 - fileSize % 0x40);
CorrectFileSize(fileSize);
//WriteData();
Task.Factory.StartNew((Action)delegate { WriteData(); });
}
//Метод коррекции загруженного файла по размеру
private void CorrectFileSize(int corrLength)
{
if (inData.Count == corrLength)
return;
if (inData.Count < corrLength)
inData.AddRange(new byte[corrLength - inData.Count]);
else
inData.RemoveRange(corrLength, inData.Count - corrLength);
}
//Внутренний метод записи
private void WriteData()
{
byte[] connectData = { 0xa5, 0xee, 0x02, 0x95, 0x49, 0x02, 0x90, 0x20, 0x25 };
byte[] writeAsc = { 0xa5, 0xee, 0x02, 0x95, 0x4f, 0x43, 0x90, 0x00, 0x00 };
int startAddress=0;
try
{
port.Open();
List<byte> tempList = new List<byte>();
//Цикл попыток соединения
for (int i = 0; i <= 10; i++)
{
if (!ReadBlock(40, connectData, tempList))
{
Thread.Sleep(200);
if (i == 10)
{
throw new CommonException("Устройство не отвечает!", "Ошибка соединения");
}
}
else
break;
}
//Фильтрация считанного блока
tempList.RemoveRange(0, 7);
tempList.RemoveAt(tempList.Count - 1);
//Получение основной информации об устройстве
mcuType = tempList[0];
moduleType = BitConverter.ToInt16(new byte[] { tempList[2], tempList[1] }, 0);
swVersion = BitConverter.ToString(tempList.GetRange(3, 3).ToArray(), 0);
configSize = tempList[12];
startAddress = tempList[8];
//Событие для вывода информации об устройстве
if (correctApplEvent != null)
correctApplEvent(this, EventArgs.Empty);
//Проверка на соответствие модуля
//размеру загруженного фала конфигурации
if (mcuType == 0x00 && inData.Count > 0x2800)
{
string msg = "Размер данных не соответствует типу устройства!";
throw new CommonException(msg, "Ошибка типа устройства");
}
//Стирание устройства
if (!CommonErase(mcuType, configSize))
{
throw new CommonException("Устройство не отвечает!", "Ошибка стирания");
}
//Включение и установки прогресса
if (barOnEvent != null)
barOnEvent(this, new BarEventArgs(inData.Count, 0x40));
//Установка стартового адреса записи
writeAsc[8] = (byte)startAddress;
//Основной цикл записи
for (int i = 0; i < inData.Count; i += 0x40)
{
List<byte> writeData = new List<byte>();
writeData.AddRange(writeAsc);
writeData.AddRange(inData.GetRange(i, 0x40).ToArray());
byte checkSum = (byte)writeData.Sum(n => (int)n);
writeData.Add(checkSum);
tempList.Clear();
//Внутренний цикл записи, 5 попыток записи блока
for (int j = 0; j <= 5; j++)
{
if (ReadBlock(11, writeData.ToArray(), tempList))
break;
if (j == 5)
{
throw new CommonException("Устройство не отвечает!", "Ошибка записи");
}
}
//Изменение адресов в запросе записи
writeAsc[7] = (byte)((writeAsc[7] + 0x40) & 0xff);
if (writeAsc[7] == 0)
writeAsc[8] = (byte)((writeAsc[8] + 1) & 0xff);
//Шаг прогресса
if (barStepEvent != null)
barStepEvent(this, EventArgs.Empty);
}
//Сброс массива данных
tempList.Clear();
//Проверочное чтение из устройства
if (!CheckRead(tempList, inData.Count, startAddress))
{
throw new CommonException("Устройство не отвечает!", "Ошибка чтения");
}
//Проверочное сравнение данных
if (!CompareData(inData, tempList))
{
throw new CommonException("Ошибка при записи! Повторите!", "Ошибка проверки");
}
//Сообщение об успешном завершении операции
if (correctWriteEvent != null)
correctWriteEvent(this, new BarEventArgs("Запись прошла успешно!"));
}
catch (Exception ex)
{
//Переменные для исключения
string Message = ex.Message;
string Caption = string.Empty;
//Получение типа исключения
if (ex is CommonException)
{
Caption = ((CommonException)ex).Caption;
}
else
{
Caption = "Ошибка!";
}
//Обработка ошибок с аргументами исключения
if (exceptionEvent != null)
exceptionEvent(this, new ExceptionEventArgs(Message, Caption));
}
finally
{
port.Close();
}
}
//Метод проверочного сравнения после записи
private bool CompareData(List<byte> inData, List<byte> tempData)
{
int fileSize = (inData[14] * 0x100 + inData[15]) * 2;
for (int i = 0; i < fileSize; i++)
{
if (inData[i] != tempData[i])
return false;
}
return true;
}
//Метод проверочного чтения
private bool CheckRead(List<byte> tempList, int size, int startAddress)
{
//Запрос на чтение
byte[] readData = { 0xa5, 0xee, 0x02, 0x95, 0x3f, 0x05, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00 };
//Внутренний массив для считанных данных
List<byte> temp = new List<byte>();
//Коррекция адресов
readData[9] = 0xff;
readData[8] = readData[10] = (byte)startAddress;
////Включение и установки прогресса
if (barOnEvent != null)
{
barOnEvent(this, new BarEventArgs(size, 0x100));
}
//Цикл чтения данных
for (int j = 0; j < size; j += 0x100)
{
//Коррекция адресов и КС
readData[11] = 0;
readData[11] = (byte)readData.Sum(n => n);
//Чтение блока
if (!ReadBlock(264, readData, temp))
{
return false;
}
//Шаг прогресса
if (barStepEvent != null)
{
barStepEvent(this, EventArgs.Empty);
}
//Фильтрация
temp.RemoveRange(0, 7);
temp.RemoveAt(temp.Count - 1);
tempList.AddRange(temp);
//Коррекция адресов
readData[8] = readData[10] = (byte)(readData[8] + 1);
}
//Отключение прогресса
if (barOffEvent != null)
{
barOffEvent(this, EventArgs.Empty);
}
return true;
}
//Общий метод стирания с выбором метода от типа МК
private bool CommonErase(int mcuType, int eraseSize=0)
{
if (mcuType == 0x00)
{
if (EraseMotorola(eraseSize))
return true;
}
else
{
if (EraseRenesas())
return true;
}
return false;
}
//Стирание МК Ренесас
private bool EraseRenesas()
{
byte[] errErase = { 0xa5, 0xee, 0x02, 0x9d, 0x5f, 0x02, 0x90, 0x02, 0x25 };
byte[] dataErase = { 0xa5, 0xee, 0x02, 0x95, 0x4e, 0x03, 0x90, 0x00, 0x10, 0x1b };
byte[] statErase = { 0xa5, 0xee, 0x02, 0x95, 0x53, 0x02, 0x90, 0x53, 0x62 };
List<byte> temp = new List<byte>();
//Сброс ошибок
if (!ReadBlock(1, errErase, temp))
return false;
//Стирание данных
if (!ReadBlock(11, dataErase, temp))
return false;
//Сброс статистики
if (!ReadBlock(11, statErase, temp))
return false;
return true;
}
//Стирание МК Моторола(с коррекцией размера)
private bool EraseMotorola(int configLength)
{
byte[] errErase = { 0xa5, 0xee, 0x02, 0x9d, 0x5f, 0x02, 0x90, 0x02, 0x25 };
byte[] dataErase = { 0xa5, 0xee, 0x02, 0x95, 0x4e, 0x03, 0x90, 0x00, 0x1a, 0x25 };
byte[] statErase = { 0xa5, 0xee, 0x02, 0x95, 0x53, 0x02, 0x90, 0x53, 0x62 };
List<byte> temp = new List<byte>();
int eraseSize=0;
//Определение размера стирания
eraseSize = GetEraseLength(configLength);
//Сброс ошибок
if(!ReadBlock(1,errErase,temp))
return false;
temp.Clear();
//Цикл стирания
for (int i = 0; i < eraseSize / 512; i++)
{
//Внутренний цикл, 5 попыток стирания
for (int j = 0; j <= 5; j++)
{
if (ReadBlock(11, dataErase, temp))
break;
if (j == 5)
return false;
}
//Сброс массива данных
temp.Clear();
//Смена адресов в запросе
dataErase[8] += 2;
dataErase[9] = 0;
dataErase[9] = (byte)dataErase.Sum(n => (int)n);
}
temp.Clear();
if (!ReadBlock(11, statErase, temp))
return false;
return true;
}
//Получение размера стирания(для МК Моторола)
private int GetEraseLength(int type)
{
int eraseSize = 0;
switch (type)
{
case 0x38:
eraseSize = 0x1000;
break;
case 0x48:
eraseSize = 0x1400;
break;
case 0x58:
eraseSize = 0x1800;
break;
case 0x68:
eraseSize = 0x1c00;
break;
case 0x78:
eraseSize = 0x2000;
break;
case 0x98:
eraseSize = 0x2800;
break;
default:
eraseSize = 0x2800;
break;
}
return eraseSize;
}
//Внутренний метод чтения блока
private bool ReadBlock(int count, byte[] sendData, List<byte> readData)
{
//Сброс данных
readData.Clear();
//Индекс ожидания
int index = 0;
//Массив для чтения блока
byte[] temp = new byte[count];
//Запись данных в порт
port.Write(sendData, 0, sendData.Length);
//Сброс буфера порта
port.DiscardInBuffer();
//Ожидание заполнения буфера порта
while (port.BytesToRead != count)
{
Thread.Sleep(100);
index++;
//При отсутствии данных за 10 попыток-возврат false
if (index == 10 && port.BytesToRead < count)
return false;
}
//Чтение блока данных
port.Read(temp, 0, count);
//Копирование в выходной массив
readData.AddRange(temp);
return true;
}
//Свойство типа МК
public string MCUType
{
get
{
switch (mcuType)
{
case 0x00:
return "Motorola GB60/GB60A";
case 0x51:
return "Renesas HD64H36079";
default:
return "Тип не определен";
}
}
}
//Свойство версии ПО
public string SWVersion
{
get { return swVersion.Replace("-", "."); }
}
//Свойство максимального размера конфигурации
public string ConfigSize
{
get
{
switch (configSize)
{
case 0x38:
return "4 кB";
case 0x48:
return "5 kB";
case 0x58:
return "6 kB";
case 0x68:
return "7 kB";
case 0x78:
return "8 kB";
case 0x98:
return "10 kB";
case 0x05:
return "16 kB";
default:
return "Размер не определен";
}
}
}
//Свойство типа устройства
public string ModuleType
{
get
{
switch (moduleType)
{
case 0x0128:
return "DW DEA 600/DEA 601";
case 0x0129:
return "DW DEA 602";
case 0x0228:
return "WM Arcadia";
case 0x0229:
return "WM Arcadia-2";
case 0x0300:
return "CK HOT 2005";
case 0x0328:
return "CK HOT 2005";
case 0x0020:
return "CL CL 2005/CL 2008";
case 0x002D:
return "CL Artica";
default:
return "Тип не определен";
}
}
}
}
//Класс для аргумента событий
public class BarEventArgs : EventArgs
{
public readonly int barSize; //Размер прогресса
public readonly int barStep; //Шаг прогресса
public readonly string Message; //Сообщение
//Цепочка конструкторов
public BarEventArgs()
:this(0,0,"") { }
public BarEventArgs(string mes)
: this(0, 0, mes) { }
public BarEventArgs(int size, int step)
: this(size, step, "") { }
private BarEventArgs(int size, int step, string mes)
{
barSize = size;
barStep = step;
Message = mes;
}
}
//Класс аргумента события для исключения
public class ExceptionEventArgs : EventArgs
{
public readonly string Message;
public readonly string Caption;
public ExceptionEventArgs(string msg,string cap)
{
Message = msg;
Caption = cap;
}
}
//Класс исключения
public class CommonException : ApplicationException
{
public readonly string Caption;
public CommonException(string msg, string cap)
: base(msg)
{
Caption = cap;
}
}
}