Синхронизация передачи данных по UART
- Войдите на сайт для отправки комментариев
казалось бы, задача простая и ранее много обсуждаемая
передаю с одного устройства (esp32) на другое данные. пакеты по 22 байт
отправление:
if (Serial2.availableForWrite() >= 22)
{
Serial2.write(raw+4,22);
//delay(5);
}
получение:
// сторона B - принимаем по uart дпнные
while ((k=Serial2.available()) >= 22)
{
Serial2.readBytes(raw+4, 22);
PrintHexBuffer(raw+4, 22);
}
//дочитываю в мусор неполный пакет...
if (k > 0){
Serial2.readBytes(tmp, Serial2.available());
//PrintHexBuffer(tmp, 22);
}
В итоге передача работает. но раз в несколько секунд пакет передаётся не полностью
меньше 22 байт и попадает в мусор.
есть вариант если меньше 22 байт в буфере приема, то ждать пока не наполнится 22.
но он мне не подходит, потому что задержка недопустима.
delay больше 10мс недопустимо использовать ни при отправке, не при приёме.
Я пробовал сделать задержку, с ней тоже также работает.
Вопрос, как грамотно синхронизировать обмен данными, и при этом:
1. не теряя данные
2. не добавляя delay
3. чрезмерно не усложняя код, т.е. передача только в одну сторону, не наворачивать двусторонним обменом с проверкой контрольной суммы и т.д...
uint8_t rcvLen = 0 ; uint8_t rawBuff[32]; if((Serial2.available()) > 0) { rcvLen += Serial2.readBytes(rawBuff+ rcvLen + 4, 1); } if( rcvLen == 22) { //do Some stuff i.e. PrintHexBuffer(rawBuff + 4 , rcvLen ); rcvLen = 0 ; }это только пример , а не реализация
Использовать признак конца или начала пакета.
а если символ конца пакета выпал в шлак например через переполнеие буфера? и пошел прием второго пакета?
Мне кажется лучше принять байт начала пакета, потом 22 байта даннных, а потом если 24 байтом пришел байт конца пакета, то можно считать пакет сьедобным и обрабатывать... если нет, ждать начало следующего пакета. Тоесть юзать и начало и конец пакета....
ну про контрольные суммы наверно лишнее будет... хотя смотря че эти пакеты будут делать...
код приемника и передатчика. используются сообщения вида: 0xАА 0x55 размер_тела сами_данные КС(сложение)
//#define BUS Serial // UART на котором висит применяемая шина #include <SoftwareSerial.h> SoftwareSerial BUS (10, 11); // (RX, TX) UART на котором висит применяемая шина const bool echo = 0; // меняем тут на 1 , если шина имеет эхо, т.е. когда отправленные байты сразу видим в приёмнике, (например к-лайн) const byte bufsize = 80; // размер буфера принятого сообщения (т.е. максимальный размер сообщения) byte buf [bufsize] = {0}; // буфер принятого сообщения uint32_t currmillis = 0; // переменная системного времени #define DEBUG // раскоментировать для отладки в монитор Serial порта (выводит лог сообщений на шине) #define SEND (byte*) & int VarI = 785; // подопытные отправляемые переменные float VarF= -1.53; // и int VarI_array[] = {23, 568 , -56}; // массивы byte VarB_array[]= {0x26,0x78,0xFD}; // так, для примера отправки void setup() { #ifdef DEBUG Serial.begin(38400); #endif BUS.begin(38400); sendMessage (SEND VarI, sizeof(VarI)); // например отправляем переменную int sendMessage (SEND VarF, sizeof(VarF)); // например отправляем переменную float sendMessage (SEND VarI_array, sizeof(VarI_array)); // например отправляем массив int sendMessage (SEND VarB_array, sizeof(VarB_array)); // например отправляем массив byte pinMode (13, OUTPUT); } void loop() { currmillis = millis(); // снимок системного времени BUSread(); // читаем шину // тут остальной код } void messageOKreceived(const byte &Size ) { // ТУТ ДЕЛАЕМ ЧТО-ТО НУЖНОЕ - функция будет выполняться только когда СООБЩЕНИЕ УСПЕШНО ПРИНЯТО! // принятые данные в массиве buf , размер массива Size. Например, распечатаем в отладку #ifdef DEBUG for (int g = 0; g < Size+1; g++) {Serial.print(buf[g], HEX); Serial.print(F(" "));} Serial.println(); #endif } //ниже функция отправки сообщения на шину. //кидаем в эту функцию нужный массив байт и она запакует его в необходимый пакет и отправит в шину void sendMessage(const byte *payload, const size_t size) { #ifdef DEBUG Serial.print (F(" TX: ")); #endif const byte siZe = size+4; byte Mes[siZe]; byte Checksum = 0; for(byte i=0; i<siZe; i++) { if (i==0) {Mes[i] = 0xAA;} if (i==1) {Mes[i] = 0x55;} if (i==2) {Mes[i]=size;} if (i==3) { for (byte t=0; t<size; t++ ) { Mes[i]=payload[t]; Checksum+=Mes[i]; BUS.write (Mes[i]); #ifdef DEBUG Serial.print (Mes[i], HEX); Serial. print (" "); #endif if (echo) BUS.read(); i++; } } if (i!=siZe-1) Checksum+=Mes[i]; else Mes[i] = Checksum; BUS.write (Mes[i]); if (echo) BUS.read(); #ifdef DEBUG Serial.print (Mes[i], HEX); Serial. print (" "); #endif } #ifdef DEBUG Serial.println(); #endif } // функция чтения сообщений из шины и распаковки в массив void BUSread() { static byte header = 0; // состояние заголовка static byte message_size = 0; // размер тела принимаемого сообщения, кол-во байт static byte j = 0; // инкремент static byte checksum = 0xFF; // контрольная сумма входящего сообщения static uint32_t prevRESETheader=0; // таймер сброса заголовка если в момент приёма сообщения данные оборвались static bool RESETheader_timer = 0; // таймер сброса заголовка если в момент приёма сообщения данные оборвались if (BUS.available()){ // первый старт байт if (header == 0) { if (BUS.read()==0xAA){header++; RESETheader_timer=1; prevRESETheader=currmillis;} } // второй старт байт else if (header == 1) { if (BUS.read()==0x55){ header++;prevRESETheader=currmillis;} else {header = 0; RESETheader_timer=0;} } // третий байт - размер тела сообщения else if (header == 2) { message_size=BUS.read(); header++; prevRESETheader=currmillis; checksum = 0xFF; if (message_size>bufsize){header = 0; RESETheader_timer=0;} } // пишем тело сообщения else if (header == 3 && j<message_size+1) { buf[j] = BUS.read(); if (j<message_size) checksum+= buf[j]; // подсчёт КС if (j==message_size) {header++;} prevRESETheader=currmillis; j++; } } // end of BUS.available() // сообщение приняли, действуем if (header == 4) { checksum+=message_size; // прибавляем к контрольной сумме шапку сообщения // если контрольная сумма верна: if (buf[message_size] == checksum) { messageOKreceived(message_size); // ТУТ ДЕЛАЕМ ЧТО-ТО НУЖНОЕ, ПОТОМУ ЧТО СООБЩЕНИЕ УСПЕШНО ПРИНЯТО! } //ниже в else что-то делаем, если контрольная сумма не совпала: else { #ifdef DEBUG Serial.println("Checksumm fail!!!" ); #endif } message_size = 0; header=0; RESETheader_timer=0; j=0; checksum = 255; } // таймер сброса сборки пакета, если данные перестали поступать более чем полсекунды if (RESETheader_timer && currmillis - prevRESETheader > 500) { // тут можно что-то делать, когда сообщение обрывается по середине // например плюс к счетчику ошибок #ifdef DEBUG Serial.println("Fail! Receive timeout!!!" ); #endif RESETheader_timer = 0; header = 0; message_size = 0; j=0; } }while(Serial.available()) { c = Serial.read(); // принять байт как символ if (c == '<') { // начало параметра // тут подготовка к получению данных } else if (c == '>') { // окончание параметра // тут делаем что-то с полученными данными } else if ((uint8_t)c < 32) { // эти символы пропускаем } else { // добавляем символ в буфер }Если качество связи таково, что выпадают байты, то нужна контрольная сумма. Если выпадает признак конца, то и признак начала может отвалить или любой информационный байт.
Берется циклический буфер на 23 байта, туда складываются байты и одновременно накапливается контрольная сумма. Если КС не совпадает, а пришло уже больше 23 байт, первый пришедший вычитается из кс и продолжаем пока пришедший байт не совпадет с контрольной суммой. Как только это чудо произошло - мы получили пакет. Обрабатываем его и очищаем буфер, а точнее обнуляем счетчик байт и кс. Ждем следующий пакет.
Может для начала разобраться что происходит? Откуда этот шлак берётся? Почему нормально не приходит?
Давайте, снимайте завесу секретности. Кто передаёт, кто принимает (аппаратура и скетчи), расстояние, схема соединения, всё подробненько. Скетчи полностью, а не огрызки, ибо откуда мы знаем, это передача сбоит или изначально данные так подготовлены (с мусором) а передаются правильно? Тоже, ведь, возможно.
А то Вам ту уже советуют не насморк лечить а сопли пылесосом отсасывать.
всегда детям так насморк лечил - очень действенно, есть приблуда специальная к пылесосу. Так что я за пылесос с КС и т.д.
Ну я не сильно верю, что при простом сериале данные могут теряться. Другой вопрос, если доказано, что сие имеет место быть нужно смотреть что будет в результате обработки неправильных данных. И как в дальнейшем эти пакеты должны синхронизироваться, то же не понятно. На вскидку я бы сделал что то такое:
static uint8_t arrowStart=0; static uint8_t arrowEnd=0; static uint8_t dataCnt=0; static uint8_t cs=0; static uint8_t dataReady[22]; static uint8_t buff[sizeof(dataReady)]; while(Serial.available()){ uint8_t temp=Serial.read(); if(dataCnt==sizeof(dataReady)){ if(cs==temp){// в буфере правильный пакет for(uint8_t i=0; i<sizeof(dataReady); i++){ dataReady[i]=buff[arrowStart++]; if(arrowStart==sizeof(dataReady)){ arrowStart=0; } } // тут обработать данные из буфера dataReady arrowStart=0; arrowEnd=0; dataCnt=0; cs=0; } else { // контрольная сумма не совпала cs-=buff[arrowStart++]; if(arrowStart==sizeof(dataReady)){ arrowStart=0; } buff[arrowEnd++]=temp; cs+=temp; if(arrowEnd==sizeof(dataReady)){ arrowEnd=0; } } } else { cs+=temp; buff[arrowEnd++]=temp; dataCnt++; } }Ногами не пинать , могут быть ошибки. Лениво у себя искать рабочий пример. Можно сделать красивее, если буфер расширить до размера кратного степени двойки. Думаю это все знают.
Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО. у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.
Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО. у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.
Дико извиняюсь.... Какие помехи ? Холодильник рядом работает ? Намного проще и правильнее избавится от помех, чем программно их фиксить. Если цифровой интерфейс полон мусора, то место ему в момойке, вместе с мусором.
Ну я не сильно верю, что при простом сериале данные могут теряться.
Могут ) лично проверял когда перешел с ардуины на есп. Есп стал сильно бытсро слать данные в порт так как моща выросла, и битрейта на передачу уже не хватило.. в итоге через переполнения буфера все че не влезо в него улетало в игнор. Плата менялась с частотником по MODBUS на одном из HardwareSerial.
А еще могут из за того что приемник не успевает их принимать... там тоже буфер не безразмерный..
Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО. у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.
Дико извиняюсь.... Какие помехи ? Холодильник рядом работает ? Намного проще и правильнее избавится от помех, чем программно их фиксить. Если цифровой интерфейс полон мусора, то место ему в момойке, вместе с мусором.
То что правильнее - согласен. То что проще - ни разу.
если гаджет питается от батареек то может помех будет меньше чуть чуть))) а если от бп и сети то будут... холодильник работает в той же сети. А еще космические лучи.. могут случайным образом активировать или деактивировать ячеку памяти. А еще мабила может лежать рядом.. госпади в нашем мире источников помех столько что даже не перечислить... Почему ж тогда в в большинстве устройств после апаратной корекции ошибок (например оперативная память с поддеркой ЕСС) используют еще програмный контроль и коррекцию... наверное их не дураки придумывали
Не знаю, что у вас за схемотехника. Ни разу за 30 лет не видел беспричинно глючного последовательного интерфейса. Если все так плохо переходите на RS485.
ESP не исключение. И работают в таких условиях, что там мухи дохнут от магнитных полей.
А ну вы же счаз начнете рассказывать, что я вам не так помогаю, не тот фреймворк, экспрессиффф козлы и вообще.... :)
Я в курсе. Собсно modbus протокол и работает поверх апаратного rs485. Но обмен есп и микрухи max485 всерано по uart. Но помехи жеж нк только в проводах возникают... а и на саной плате и внутри чипа могут наводки быть. Не зря ж всякие кондерчики rc lc фильтра ставят... как не крути а без програмной коррекции и контроля не обойтись. Даже тот же модбас обратно шлет контрольную сумму принятых данных чтоб сверить ее с отправленой, даже при том что rs485придумат так чтоб работать где мухи дохнут.
Я знаю в чем ваша проблема. Но вы как всегда не показываете свой код. А мне , очень интересно как вы на есп контролируете опустошение аппаратного буфера UART для правильного переключения направления передачи на MAX485 :))))))
Что нет во "фреймворке" драйверов 485 интерфейса :) Это вам не дуплекс :)
А ну вы же счаз начнете рассказывать, что я вам не так помогаю, не тот фреймворк, экспрессиффф козлы и вообще.... :)
Соскучились по срачу?))) Так то в этот раз вы не мне помогаете если че. А то что вы не встечали совсем не значит что их нет)))
Так а че вы тогда тут свои три копейки вставляете ? Пишите по делу, или как там у вас... "Вали мимо " :) Где ваше "по делу" ?
"Нахрен лесом молча" там было))) раз уж беретесь цитировать то соблюдайте соответствие оригиналу. В правилах не написано что мне низя всталять пять копеек. Вот и вставляю. Человеку мож помогу а че низя было? Офтопить кстате вы начали. И деструктив тоже. А я все писал по теме
По какой теме ?
Синхронизация передачи данных по UART
По этой ? То что у вас там какие то помехи где то :)))) Может все же лесом ?
Так ладно, я пример ТС дал, все остальное оффтоп, только в ответ на вашу ерунду про помехи. Не засоряйте тему.
Согласен. Помехи хрень полнейшая их надо искоренять. Лесом так лесом. Буду за Вами. Доброй ночи.