Передача struct между двумя ESP32

Как передать структуру между двумя ESP32 по проводам?
Структура:

struct street {
  float TEMPERATURE;
  float RH;                
};

struct room {
  float TEMPERATURE;  
  float RH;           
  uint32_t CO2;  
};

struct DATA {
  street STREET;
  room ROOM;
  uint8_t CRC;
};

Код передатчик:

#include <CRC8.h>
#include <CRC.h>

uint16_t TIME = 0;
DATA DATA;

void setup() {
  Serial2.begin(115200, SERIAL_8N1, U2_RX, U2_TX);

  DATA.STREET.TEMPERATURE = 27;
  DATA.STREET.RH = 67;

  DATA.CRC= crc8((byte*)&data, sizeof(data) - 1)
}
void loop() {
  if (millis() - TIME < 1000)
    return;
  TIME = millis();
  Serial2.write((byte*)&DATA, sizeof(DATA));
}

Код приёмника:

void setup(void) {
  SERIAL_2.begin(115200, SERIAL_8N1, U3_RX, U3_TX);
}
void loop() {

  if (SERIAL_2.available())
  {
    if (SERIAL_2.readBytes((byte*)&DATA, sizeof(DATA))) 
    {
        Serial.println(DATA.STREET.TEMPERATURE);
        Serial.println(DATA.STREET.RH);
    }
  }
 if (millis() - TIME < 1000)
    return;
  TIME = millis();
  [ещё код...]
}

Получаю всякую дичь:

0.00
0.00

27.00
67.00

0.00
0.00

0.00
0.00

47465094
0.00

Правильные данные 1 из 100 и то пока для двух параметров.
Продавал и так:

#pragma pack(1)
struct street {
  float TEMPERATURE;
  float RH;                
};

struct room {
  float TEMPERATURE;  
  float RH;           
  uint32_t CO2;  
};

struct DATA {
  street STREET;
  room ROOM;
  uint8_t CRC;
};
#pragma pack()

Эффекта ни какого.

Как я понял структура отправляет, например uint16_t в 2 байта, а ESP32 ожидает 4 байта, из-за этого данные читаются не верно, поэтому я не использовал такой тип данных при написании структуры, но это не помогло, данные всё равно приходят не верные.

Кроме ожидаемого размера есть еще выравнивание данных.

2 лайка

Дааа, выравнивание потрепало мозг в свое время. Нужно объявлять структуры примерно так:
struct __attribute__((__packed__)) structName {}
и будет проще жить при передаче между разными контроллерами

2 лайка

Логичнее сделать uint32_t CRC;.

Первую минуту после включения данные отправляются раз в секунду, а дальше сыпятся непрерывно.

Писал уже, по первости убили неделю, для выявлентя такой “фичи”, когда из делфей передавали в длл на сях)
packed все решило.

1 лайк

Спасибо, это действительно помогло, но @ Pyotr прав, через минуту начинается разсинхрон и данные из “сыпятся” не верные.

Как победить проблему, когда данные через некоторое время начинают приходить не верные?

Надо отправлять пакет определенной структуры, а не сырые данные.

Элементарно, Ватсон: отправлять верные данные и правильно их принимать!

Почему так много внимания выравниванию?
как может повлиять выравнивание, если с обоих сторон есп32 ?

А какая разница, если уходят перекуроченные данные?

а разве перекуроченные? я не понимаю

Передавать не сырые данные, а оформить их в виде кадра. На приеме проверять CRC кадра и отбрасывать все где CRC не соответсвует данным.

игнорировать их))
Если данные передаются быстрее, чем читаются, то приемный буфер переполняется, и новые данные затирают старые. Получается каша.
Чтоб условие if (millis() - TIME < 1000) работало правильно, нужно или миллис() привести к типу uint16_t , или TIME сделать uint32_t

Простите, всю тему не читал, может Вам это уже сказали.

У Вас очевидная ошибка в

коде приёмника
void setup(void) {
  SERIAL_2.begin(115200, SERIAL_8N1, U3_RX, U3_TX);
}
void loop() {

  if (SERIAL_2.available())
  {
    if (SERIAL_2.readBytes((byte*)&DATA, sizeof(DATA))) 
    {
        Serial.println(DATA.STREET.TEMPERATURE);
        Serial.println(DATA.STREET.RH);
    }
  }
 if (millis() - TIME < 1000)
    return;
  TIME = millis();
  [ещё код...]
}

Вот Вы строке №6 проверяете и убеждаетесь, что “что-то пришло”. А в строке №8 радостно кидаетесь читать sizeof(DATA) байтов! А кто Вам сказал, что они уже все пришли? Может пока только один байт пришёл? Или два? Вы этого не проверяете и читаете “на удачу”. Здесь Вы подвесили себя на случайность. Если все байты и вправду пришли - отлично! А если часть ещё не успело, у Вас проблемы.

Для чего Вы поставили if в строке №8? Что именно Вы там проверяете? Вы знаете, что возвращает readBytes? Он возвращает количество прочитанных байтов. Т.е. Ваше условие читается так: “Если хоть один бат прочитали, то … печатаем всё, как прочитанное”.

1 лайк

у меня структура начинается с заголовка и заканчивается CRC и проблем с приёмом нет

Всё таки не нужно, так как byte передаётся без проблем, в отличие от int, а с uint32_t CRC; только усложняются расчёты.

Спасибо за наводку, добавил в структуру стартовый байт uint8_t FIRST; Теперь по нему определяю начало пакета данных.

Читаю так

uint8_t* LINK = (uint8_t*)&DATA;
  if (SERIAL_2.available())
  {   
      uint32_t SIZE_DATA  = sizeof(DATA);
      for (uint32_t i = 0; i < SIZE_DATA; ++i)
      {
          uint8_t BYTE = SERIAL_2.read();
          if (BYTE == 255)                                   
          {
              DATA    = {};                                   
              i       = 0;                                    
              LINK[i] = BYTE;
              continue;                                       
          }
          LINK[i] = BYTE;
      }
  }

ну и вывод:

if (crc8((uint8_t*)&DATA, sizeof(DATA)) == 0)
    [ДАННЫЕ]

Я бы в устловие в строку №5 добавил А) проверку на то, есть доступный байт

и ещё Б) вставил бы маленькую задержку перед концом цикла, чтобы байт успел прийти.

1 лайк
SERIAL_2.read();
Возвращаемое значение:
Следующий доступный байт или -1 если его нет (*int* )

То есть, это:

uint8_t BYTE = SERIAL_2.read();

Заменить на это?

 for (uint32_t i = 0; i < SIZE_DATA; ++i)
{
	int16_t BYTE = SERIAL_2.read();
	if (BYTE != -1)
	{
		if (BYTE == 255)                                   
		{
			DATA    = {};                                   
			i       = 0;                                    
			LINK[i] = BYTE;
			continue;                                       
		}
		LINK[i] = BYTE;
	}
}

Ну, не совсем. Вот представьте, что данные ещё просто не успели прийти или приходят медленно по каким-то причинам. Вы SIZE_DATA раз дернетесь и прекратите попытки, а данные таки придут, только чуть позже. Я бы таки использовал available() для каждого байта, как я Вам написал. Только надо отработать случай, когда они вовсе не пришли (через таймаут), иначе может получиться вечный цикл.

Я думал над этим, если получаю от SERIAL_2.read() = -1 , то сделать несколько попыток чтения ещё, но в моём случае проще потерять эти данные, чем затормозить дальнейший процесс, который не связан с этими данными.
Ну и спасибо за совет, сейчас данные приходят стабильно, уже несколько дней.