Синхронизация передачи данных по UART

Deamount
Offline
Зарегистрирован: 07.05.2012

казалось бы, задача простая и ранее много обсуждаемая
передаю с одного устройства (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.  чрезмерно не усложняя код, т.е. передача только в одну сторону, не наворачивать двусторонним обменом с проверкой контрольной суммы и т.д... 

mixail844
Offline
Зарегистрирован: 30.04.2012
принимать по 1му байту , и обрабатывать когда накопиться 22
 то есть 
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 ;
}

это только пример , а не реализация

 

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Использовать признак конца или начала пакета.

gfo
Offline
Зарегистрирован: 20.03.2021

а если символ конца пакета выпал в шлак например через переполнеие буфера? и пошел прием второго пакета? 

Мне кажется лучше принять байт начала пакета, потом 22 байта даннных, а потом если 24 байтом пришел байт конца пакета, то можно считать пакет сьедобным и обрабатывать... если нет, ждать начало следующего пакета. Тоесть юзать и начало и конец пакета.... 

ну про контрольные суммы наверно лишнее будет... хотя смотря че эти пакеты будут делать... 

MaksVV
Offline
Зарегистрирован: 06.08.2015

код приемника и передатчика. используются сообщения вида:   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;
     }   
}

 

vde69
Offline
Зарегистрирован: 10.01.2016
	while(Serial.available()) { 
		c = Serial.read();    // принять байт как символ
		if (c == '<') {		// начало параметра
			// тут подготовка к получению данных
		} else if (c == '>') {		// окончание параметра
                       // тут делаем что-то с полученными данными			
		} else if ((uint8_t)c < 32) {  // эти символы пропускаем

		} else {					
                       // добавляем символ в буфер
		}
	

 

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Если качество связи таково, что выпадают байты, то нужна контрольная сумма. Если выпадает признак конца, то и признак начала может отвалить или любой информационный байт.

Берется циклический буфер на 23 байта, туда складываются байты и одновременно накапливается контрольная сумма. Если КС не совпадает, а пришло уже больше 23 байт, первый пришедший вычитается из кс и продолжаем пока пришедший байт не совпадет с контрольной суммой. Как только это чудо произошло - мы получили пакет. Обрабатываем его и очищаем буфер, а точнее обнуляем счетчик байт и кс. Ждем следующий пакет.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Может для начала разобраться что происходит? Откуда этот шлак берётся? Почему нормально не приходит?

Давайте, снимайте завесу секретности. Кто передаёт, кто принимает (аппаратура и скетчи), расстояние, схема соединения, всё подробненько. Скетчи полностью, а не огрызки, ибо откуда мы знаем, это передача сбоит или изначально данные так подготовлены (с мусором) а передаются правильно? Тоже, ведь, возможно.

А то Вам ту уже советуют не насморк лечить а сопли пылесосом отсасывать.

MaksVV
Offline
Зарегистрирован: 06.08.2015

ЕвгенийП пишет:
А то Вам ту уже советуют не насморк лечить а сопли пылесосом отсасывать.

всегда детям так насморк лечил - очень действенно, есть приблуда специальная к пылесосу.  Так что я за пылесос с КС и т.д.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

MaksVV пишет:
Так что я за пылесос с КС и т.д.
Ну, тогда всё в порядке :-)

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Ну я не сильно верю, что при простом сериале данные могут теряться. Другой вопрос, если доказано, что сие имеет место быть нужно смотреть что будет в результате обработки неправильных данных. И как в дальнейшем эти пакеты должны синхронизироваться, то же не понятно. На вскидку я бы сделал что то такое:

  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++;
     }
  }

Ногами не пинать , могут быть ошибки. Лениво у себя искать рабочий пример. Можно сделать красивее, если буфер расширить до размера кратного степени двойки. Думаю это все знают.

 

gfo
Offline
Зарегистрирован: 20.03.2021

Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО.  у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

gfo пишет:

Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО.  у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.

Дико извиняюсь.... Какие помехи ? Холодильник рядом работает ? Намного проще и правильнее избавится от помех, чем программно их фиксить. Если цифровой интерфейс полон мусора, то место ему в момойке, вместе с мусором.

gfo
Offline
Зарегистрирован: 20.03.2021

brokly пишет:

Ну я не сильно верю, что при простом сериале данные могут теряться. 

Могут ) лично проверял когда перешел с ардуины на есп. Есп стал сильно бытсро слать данные в порт так как моща выросла, и битрейта на передачу уже не хватило.. в итоге через переполнения буфера все че не влезо в него улетало в игнор. Плата менялась с частотником по MODBUS на одном из HardwareSerial.

А еще могут из за того что приемник не успевает их принимать... там тоже буфер не безразмерный.. 

gfo
Offline
Зарегистрирован: 20.03.2021

brokly пишет:

gfo пишет:

Да ну помехи ж никто не отменял. я думаю от туда и берутся.. ИМХО.  у меня например в блютузсериал тоже время от времени выскакивает левый байт... хз. не стал я глубоко копать просто проверяю че пришло, шлак в мусор. правда у меня там пакеты по 3 байта.

Дико извиняюсь.... Какие помехи ? Холодильник рядом работает ? Намного проще и правильнее избавится от помех, чем программно их фиксить. Если цифровой интерфейс полон мусора, то место ему в момойке, вместе с мусором.

То что правильнее - согласен. То что проще - ни разу.

если гаджет питается от батареек то может помех будет меньше чуть чуть)))  а если от бп и сети то будут... холодильник работает в той же сети. А еще космические лучи.. могут случайным образом активировать или деактивировать ячеку памяти. А еще мабила может лежать рядом.. госпади в нашем мире источников помех столько что даже не перечислить... Почему ж тогда в в большинстве устройств после апаратной корекции ошибок (например оперативная память с поддеркой ЕСС) используют еще програмный контроль и коррекцию... наверное их не дураки придумывали

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Не знаю, что у вас за схемотехника. Ни разу за 30 лет не видел беспричинно глючного последовательного интерфейса. Если все так плохо переходите на RS485.

ESP не исключение. И работают в таких условиях, что там мухи дохнут от магнитных полей.

А ну вы же счаз начнете рассказывать, что я вам не так помогаю, не тот фреймворк, экспрессиффф козлы и вообще.... :)

gfo
Offline
Зарегистрирован: 20.03.2021

Я в курсе. Собсно modbus протокол и работает поверх апаратного rs485. Но обмен есп и микрухи max485 всерано по uart. Но помехи жеж нк только в проводах возникают... а и на саной плате и внутри чипа могут наводки быть. Не зря ж всякие кондерчики rc lc фильтра ставят... как не крути а без програмной коррекции и контроля не обойтись. Даже тот же модбас обратно шлет контрольную сумму принятых данных чтоб сверить ее с отправленой, даже при том что rs485придумат так чтоб работать где мухи дохнут.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

gfo пишет:
Я в курсе. Собсно modbus протокол и работает поверх апаратного rs485. Но обмен есп и микрухи max485 всерано по uart

Я знаю в чем ваша проблема. Но вы как всегда не показываете свой код. А мне , очень интересно как вы на есп контролируете опустошение аппаратного буфера UART для правильного переключения направления передачи на MAX485 :)))))) 

Что нет во "фреймворке" драйверов 485 интерфейса :) Это вам не дуплекс :) 

gfo
Offline
Зарегистрирован: 20.03.2021

brokly пишет:

А ну вы же счаз начнете рассказывать, что я вам не так помогаю, не тот фреймворк, экспрессиффф козлы и вообще.... :)


Соскучились по срачу?))) Так то в этот раз вы не мне помогаете если че. А то что вы не встечали совсем не значит что их нет)))

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Так а че вы тогда тут свои три копейки вставляете ? Пишите по делу, или как там у вас... "Вали мимо " :) Где ваше "по делу" ?

gfo
Offline
Зарегистрирован: 20.03.2021

"Нахрен лесом молча" там было))) раз уж беретесь цитировать то соблюдайте соответствие оригиналу. В правилах не написано что мне низя всталять пять копеек. Вот и вставляю. Человеку мож помогу а че низя было? Офтопить кстате вы начали. И деструктив тоже. А я все писал по теме

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

По какой теме ? 

Синхронизация передачи данных по UART

По этой ? То что у вас там какие то помехи где то :))))  Может все же лесом ?

Так ладно, я пример ТС дал, все остальное оффтоп, только в ответ на вашу ерунду про помехи. Не засоряйте тему.

gfo
Offline
Зарегистрирован: 20.03.2021

Согласен. Помехи хрень полнейшая их надо искоренять. Лесом так лесом. Буду за Вами. Доброй ночи.