Контроль передачи данных
- Войдите на сайт для отправки комментариев
День добрый.
Возник вопрос контроля передачи данных в виде строк. С проверкой длины строки, корректности строки а также подтверждением получения и синхронизацией номеров строк. Проблема в том, что я не могу допустить потери, хоть 1 строки. Если такое происходит, программа должна остановится.
Структура проекта.
Компьютер, основная Mega (возможно Due, если ресурсов не хватит), вспомогательная Mega, дополнительная Mega.
Передача в обе стороны между Компьютером и основной Mega (Serial, UDP), основная с вспомогательной Mega (Serial). Дополнительная живет своей жизью.
Передача между компьютером и основной Mega, должна осуществляться как можно быстрее. В идеале, передача должна быть до 20 строк в секунду.
Формат данных
?NumberPacket(Номер пакета);XXX(Длина пакета);XXX(Тип операции);XXX(Параметр);XX(Тип данных);XXXXXXXXXX(Значение)!\n
Пока работа не закончена. Вот второй результат, в первом был delayMicroseconds(100), убрал по совету leshak.
Я любить, поэтому не судите строго.
/****************************** Переменные перечисления **********************************/ /// <summary> /// Перечисление - Тип протокола передачи данных /// </summary> enum ConnectionTypeEnum { /// <summary> /// Не выбран /// </summary> CT_None = 0, /// <summary> /// Serial порт /// </summary> CT_Serial = 1, /// <summary> /// UDP /// </summary> CT_UDP = 2 }; /// <summary> /// Переменная определяющая тип связи с компьютером /// </summary> ConnectionTypeEnum connectionType = CT_Serial; /// <summary> /// Перечисление - Тип операции /// </summary> enum OperationTypeEnum { /// <summary> /// Не выбрана /// </summary> OT_None = 0, /// <summary> /// Отправка данных с Arduino /// </summary> OT_SendDataFromArduino = 1, /// <summary> /// Отправка данных с PC /// </summary> OT_SendDataFromPC = 2, /// <summary> /// Получение данных с Arduino /// </summary> OT_ReciveDataFromArduino = 3, /// <summary> /// Получение данных с PC /// </summary> OT_ReciveDataFromPC = 4, /// <summary> /// Проверка вернувшихся данных с Arduino /// </summary> OT_CheckReturnFromArduino = 5, /// <summary> /// Проверка вернувшихся данных с PC /// </summary> OT_CheckReturnFromPC = 6, /// <summary> /// Старт ТПА /// </summary> OT_StartTPA = 7, /// <summary> /// Начало инициализации Arduino /// </summary> OT_InitArduinoBegin = 8, /// <summary> /// Инициализация Arduino /// </summary> OT_InitArduino = 9, /// <summary> /// Конец инициализации Arduino /// </summary> OT_InitArduinoFinish = 10, /// <summary> /// Начало настройки Arduino /// </summary> OT_SetupArduinoBegin = 11, /// <summary> /// Настройка Arduino /// </summary> OT_SetupArduino = 12, /// <summary> /// Конец настройки Arduino /// </summary> OT_SetupArduinoFinish = 13, /// <summary> /// Проверка соединения с Arduino /// </summary> OT_CheckConnectionFromArduino = 14, /// <summary> /// Проверка соединения с PC /// </summary> OT_CheckConnectionFromPC = 15, /// <summary> /// Команда ручного режима /// </summary> OT_CommandManualMode = 16, /// <summary> /// Стоп ТПА /// </summary> OT_StopTPA = 17 }; /// <summary> /// Переменная хранящая тип операции /// </summary> OperationTypeEnum operationType = OT_None; /// <summary> /// Перечисление - Тип параметра /// </summary> enum ParametrEnum { /// <summary> /// Не установлен /// </summary> P_None = 0 }; /// <summary> /// Перечисление - Тип данных параметра /// </summary> enum DataTypeEnum { /// <summary> /// Не указан /// </summary> DT_None = 0, /// <summary> /// Int /// </summary> DT_Int = 1 }; /****************************** /Переменные перечисления **********************************/ /****************************** Переменные для работы с Serial портом **********************************/ /// <summary> /// Константа - Скорость Serial порта /// </summary> #define speedSerial 115200 /****************************** /Переменные для работы с Serial портом **********************************/ /****************************** Переменные для работы передачи данных **********************************/ /// <summary> /// Константа - Символ новой строки /// </summary> #define newLine '\n' /// <summary> /// Константа - Символ начала пакета /// </summary> #define beginChar '?' /// <summary> /// Константа - Символ разделитель параметров пакета /// </summary> #define breakChar ';' /// <summary> /// Константа - Символ конца пакета /// </summary> #define endChar '!' /// <summary> /// Константа - Символ для очистки значения char /// </summary> #define clearChar '\0' /// <summary> /// Переменная хранящая завершено ли получения данных /// </summary> boolean finishReceiveData = false; /// <summary> /// Константа хранящая мак. размер массива хранящего пакет данных /// </summary> #define receiveSizeArrayCOM 38 /// <summary> /// Переменная хранящее номер пакета в системе /// </summary> int globalNumberPacket = 0; /// Объект String для хранения полученной строки /// </summary> String receiveData = ""; /// <summary> /// Переменная хранящее номер символа начала пакета в пакете /// </summary> int indexBeginCharInPackage; /// <summary> /// Переменная хранящее номер символа конца пакета в пакете /// </summary> int indexEndCharInPackage; /// <summary> /// Переменная кол-во символов разделителя блоков данных в пакете /// </summary> int breakCharCountInPackage; /// <summary> /// Переменная хранящее номер пакета /// </summary> String numberPacket; /// <summary> /// Переменная хранящее длину пакета /// </summary> String lenPacket; /// <summary> /// Переменная хранящее тип операции пакета /// </summary> String operationTypePacket; /// <summary> /// Переменная хранящее тип параметра пакета /// </summary> String parametrPacket; /// <summary> /// Переменная хранящее тип данных параметра пакета /// </summary> String dataTypePacket; /// <summary> /// Переменная хранящее значение параметра пакета /// </summary> String valuePacket; /// <summary> /// Переменная хранящее номер пакета /// </summary> int receiveNumberIntPacket = 0; /// <summary> /// Переменная хранящее тип операции пакета /// </summary> int receiveLenIntPacket; /// <summary> /// Переменная хранящее тип операции пакета /// </summary> int receiveOperationTypeIntPacket; /// <summary> /// Переменная хранящее реальную длину пакета, проверка после получения /// </summary> int receiveLength; /****************************** /Переменные для работы передачи данных **********************************/ /****************************** Переменные запуска ТПА **********************************/ /// <summary> /// Переменная хранящее состояние программы запущена или нет /// </summary> boolean beginWorkTPA = false; /// <summary> /// Переменная хранящее состояние программы инициализирован ли ТПА /// </summary> boolean beginInitializationTPA = false; /****************************** /Переменные запуска ТПА **********************************/ /****************************** Переменные отладки **********************************/ /// <summary> /// Переменная хранящее время старта цикла, для проверки скорости выполнения цикла /// </summary> unsigned long timeCycleWork = 0; #define IS_DEBUG true /****************************** /Переменные отладки **********************************/ /// <summary> /// Настройка программы /// </summary> void setup () { // TODO: Добавить описание switch (connectionType) { case CT_Serial: Serial.begin(speedSerial); break; } } /// <summary> /// Основной цикл /// </summary> void loop() { // Проверка времени обработки данных //timeCycleWork = millis(); // /Проверка времени обработки данных // Выбираем протокол передачи данных switch (connectionType) { case CT_Serial: // Протокол передачи данных на основе Serial порта // Получение данных с Serial порта GetReceiveDataFromSerial(); break; } // Завершено ли получения данных if (finishReceiveData) { // Парсинг полученных данных ParseReceiveData(); } } /****************************** Функции работы с Serial портом **********************************/ /// <summary> /// Получение данных с Serial порта /// </summary> void GetReceiveDataFromSerial() { // TODO: Добавить описание while(Serial.available()) { /// <summary> /// Переменная хранящая последний полученный char /// </summary> char incomingChar = (char)Serial.read(); if (incomingChar != newLine) { receiveData += incomingChar; } else { Serial.flush(); finishReceiveData = true; } } } /// <summary> /// Отправка данных по Serial порту /// </summary> void SendDataFromSerial(String SendValue, int OperationType, int Parametr, int DataType) { // TODO: Добавить описание /*if (globalNumberPacket > 500) { Serial.println("Errore before"); } globalNumberPacket++; if (globalNumberPacket > 500) { Serial.println("Errore after"); }*/ RepeatSendDataFromSerial(SendValue, OperationType, Parametr, DataType); } /// <summary> /// TODO: Добавить описание /// </summary> void SendConfirmationDataFromSerial(String SendValue, int Parametr, int DataType) { // TODO: Добавить описание RepeatSendDataFromSerial(SendValue, OT_CheckReturnFromArduino, Parametr, DataType); ClearReceiveDataVariable(); } /// <summary> /// Повтор отправки данных по Serial порту /// </summary> void RepeatSendDataFromSerial(String SendValue, int OperationType, int Parametr, int DataType) { // TODO: Добавить описание int sendValueLen = String(globalNumberPacket).length() + String(OperationType).length() + String(Parametr).length() + String(DataType).length() + SendValue.length() + 7; sendValueLen += String(sendValueLen).length(); String sendPacket = beginChar + String(globalNumberPacket) + breakChar + String(sendValueLen) + breakChar + OperationType + breakChar + Parametr + breakChar + DataType + breakChar + SendValue + endChar + "\n"; Serial.print(sendPacket); } /****************************** /Функции работы с Serial портом **********************************/ /****************************** Функции работы с полученными данными **********************************/ /// <summary> /// Очистка переменных получения данных, после получения данный /// </summary> void ClearReceiveDataVariable() { // TODO: Добавить описание indexBeginCharInPackage = indexEndCharInPackage = breakCharCountInPackage = receiveLenIntPacket = receiveLength = receiveOperationTypeIntPacket = 0; receiveData = numberPacket = lenPacket = operationTypePacket = parametrPacket = dataTypePacket = valuePacket = ""; finishReceiveData = false; } /// <summary> /// Парсинг полученных данных /// </summary> void ParseReceiveData() { // Получаем длину пакета receiveLength = receiveData.length(); // Пробегаемся по массиву хранящий пакет данных for (int i = 0; i < receiveLength; i++) { char receiveDataCurrentChar = receiveData.charAt(i); switch (receiveDataCurrentChar) { case beginChar: // Получаем индекс символ начала пакета в пакете indexBeginCharInPackage = i; break; case endChar: // Получаем индекс символ конца пакета в пакете indexEndCharInPackage = i; break; case breakChar: // Получаем кол-во символов разделителя блоков данных в пакете breakCharCountInPackage++; break; default: // В зависимости от номера блока данных в пакете, заполняем переменные блоков switch (breakCharCountInPackage) { case 0: // Номер пакета в системе numberPacket += receiveDataCurrentChar; break; case 1: // Длина пакета lenPacket += receiveDataCurrentChar; break; case 2: // Тип операции пакета operationTypePacket += receiveDataCurrentChar; break; case 3: // Тип операции пакета parametrPacket += receiveDataCurrentChar; break; case 4: // Тип данных параметра пакета dataTypePacket += receiveDataCurrentChar; break; case 5: // Значение параметра пакета valuePacket += receiveDataCurrentChar; break; } break; } } if (breakCharCountInPackage != 5) { ErroreDataReceived("1",1); return; } // Номер пакета в системе, Int receiveNumberIntPacket = numberPacket.toInt(); // Номер пакета в системе, Int receiveLenIntPacket = lenPacket.toInt(); // Тип операции пакета, Int receiveOperationTypeIntPacket = operationTypePacket.toInt(); Serial.println(receiveData); // Проверка корректности полученного пакета if (indexBeginCharInPackage == 0 && breakCharCountInPackage == 5 // Индекс символа начала пакета, должен быть равен 0, кол-во блоков данных должно быть 5 && indexEndCharInPackage == (receiveLength - 1) // Индекс символа конца пакета, должен быть равен значению блока длины пакета - 1 && receiveLenIntPacket == receiveLength) // Реальная длина пакета должна быть равна значению блока длины пакета { // Определяем тип операции пакета switch(receiveOperationTypeIntPacket) { case OT_StartTPA: // Старт ТПА SendConfirmationDataFromSerial(valuePacket, parametrPacket.toInt(), dataTypePacket.toInt()); // Устанавливаем состояние программы запущена beginWorkTPA = true; break; default: int globalNumberPacketAndOne = globalNumberPacket; if (receiveOperationTypeIntPacket != OT_CheckReturnFromPC && receiveNumberIntPacket == globalNumberPacketAndOne) { SendConfirmationDataFromSerial(valuePacket, parametrPacket.toInt(), dataTypePacket.toInt()); } else { if (receiveNumberIntPacket == globalNumberPacketAndOne) { switch(receiveOperationTypeIntPacket) { case OT_StopTPA: // Стоп ТПА SendConfirmationDataFromSerial(valuePacket, parametrPacket.toInt(), dataTypePacket.toInt()); // Устанавливаем состояние программы остановлена beginWorkTPA = false; break; } } else { ErroreDataReceived("2",2); } } break; } } else { ErroreDataReceived("3",3); } } /****************************** /Функции работы с полученными данными **********************************/ /****************************** Функции работы с ошибками **********************************/ /// <summary> /// Ошибка получения данных /// </summary> void ErroreDataReceived(String DataReceived, int TypeErrore) { // TODO: Добавить описание ClearReceiveDataVariable(); Errore(DataReceived, 0); } /// <summary> /// Ошибка получения данных /// </summary> void Errore(String Data, int TypeErrore) { // TODO: Добавить описание //FullStopTPA(); Log(Data, TypeErrore, true); } /****************************** /Функции работы с ошибками **********************************/ /****************************** Функции работы с логом **********************************/ /// <summary> /// Логирование /// </summary> void Log(String Data, int TypeLog, bool isErrore) { // TODO: Добавить описание switch (connectionType) { case CT_Serial: Serial.println(Data); break; } } /****************************** /Функции работы с логом **********************************/
Работа, еще не закончена. Осталось побороть 1 ошибку, сделать контроль номера пакета, оптимизировать и нормально закомментировать.
Ошибка, даже не в ардуине, а в принимающей программе на C#. Получаю, странные символы перед ответом
����. Если пользоваться монитором Сом порта ардуины, то все норм.
Вот настройки соединения порта в программе.
ArduinoSerialPort.PortName = "COM25"; ArduinoSerialPort.BaudRate = 115200; ArduinoSerialPort.Parity = Parity.None; ArduinoSerialPort.DataBits = 8; ArduinoSerialPort.StopBits = StopBits.One; //ArduinoSerialPort.ReadTimeout = 500; //ArduinoSerialPort.WriteTimeout = 500; ArduinoSerialPort.Encoding = Encoding.UTF8; ArduinoSerialPort.NewLine = "\n"; ArduinoSerialPort.DataReceived += new SerialDataReceivedEventHandler(ArduinoSerialPort_DataReceived); ArduinoSerialPort.Open();
Может кто-то сталкивался с такой проблемой.
Вообще возможно я изобретаю велосипед, сейчас буду читать Modbus-ASCII и Wake. Но в этом проекте свою наработку оставлю, зря, что ли трудился. Плюс это дополнительный опыт, даже если в дальнейших проектах буду использовать Modbus-ASCII или Wake.
Забыл, строка для проверки
?0;14;7;0;1;1!
Вернул версию с delayMicroseconds(100), работает без ошибки.
До 35 запросов в 1с. с возвратом посылаемому устройству. И отображение в программе.
Думаю, можно и больше, но надо оптимизировать код.
Пока, протестирую и дополню номером пакет.
Сильно не вникал.... но не вижу нескольких нужных вещей:
1. Игнорирование символа \r (Вы вкурсе что Serial.println() шлет в конце строки \r\n?)
2. При обнаружении конца строки, в массив receiveCharArrayCOM нигде не записывается ноль. Все сишные функции работы со строками предполагают \0 в качестве символа конца строки). Без этого, все будет работать "как повезет".
3. Не видно проверок выхода за границы массива receiveCharArrayCOM . А вдруг прилезет строка длинней чем вы выделили память под буффер? Кстати не забудте, что принять мы можем не больше чем "размер буфера - 1". Так как под нулевой символ должен быть всегда.
Погодите, похоже что вы делаете
ClearReceiveDataVariable()
в начале каждого loop().
Ну, само собой, если в этому момент успела прилететь только часть строки.... то "готовой строки целиком" - еще нет.
Вот вам и приходится втыкать delay(). Что-бы строка успела накопится в буфере самого UART-та, и ее можно было "прочитать" за один проход loop().
Большое спасибо, за замечания.
leshak пишет:
1. Игнорирование символа \r (Вы вкурсе что Serial.println() шлет в конце строки \r\n?)
Во втором варианте, у меня нет Serial.println(). Но узнал, не давно, поэтому в 1 варианте без delay есть.
2. При обнаружении конца строки, в массив receiveCharArrayCOM нигде не записывается ноль. Все сишные функции работы со строками предполагают \0 в качестве символа конца строки). Без этого, все будет работать "как повезет".
Не знал.
3. Не видно проверок выхода за границы массива receiveCharArrayCOM . А вдруг прилезет строка длинней чем вы выделили память под буффер? Кстати не забудте, что принять мы можем не больше чем "размер буфера - 1". Так как под нулевой символ должен быть всегда.
Да упустил из виду.
Погодите, похоже что вы делаете
ClearReceiveDataVariable()
в начале каждого loop().
Ну, само собой, если в этому момент успела прилететь только часть строки.... то "готовой строки целиком" - еще нет.
Вот вам и приходится втыкать delay(). Что-бы строка успела накопится в буфере самого UART-та, и ее можно было "прочитать" за один проход loop().
Да, во втором варианте, это есть.
Сейчас попробую в 1 вариант, без delay перенести, наработки второй версии и то, что Вы отметили.
Большое спасибо.
Переделал код
Работает стабильно.
Пока не дополнял доп. проверками и не наводил порядок.
Но ошибка, есть, но на стороне программы C#.
После внимательного изучения, понял, что проблема в потоках и взаимодействии между ними на стороне программы C#.
Нашел ошибку у себя в C#, кроме потоков. Неправильная инициализация соединения.
Как должно быть.
Не хватало
ArduinoSerialPort.RtsEnable = false;
ArduinoSerialPort.DtrEnable = true;
Если не установить эти настройки, будут проблемы. При первой отправке на ардуину к запросу будет добавляться мусор, могут быть и другие проблемы.