Binary serial communication ESP32 to Arduino Uno

Hi

I have been successfully communicating using text based serial between by ESP32-CAM and an Arduino Uno, but I would like to make it faster by changing it to binary, but I am getting some odd output.

On the ESP32 I am sending using:

struct data
{
  int x = 0;
  int y = 0;
};
data d;
d.x = -512;
d.y = 512;
Serial.write((byte*)&d, sizeof(data));

And on the Arduino Uno I am receiving using this code (this is using software Serial rather than hardware, but I don't think that should make a difference):

struct data
{
  int x = 0;
  int y = 0;
};
data d;

if (esp32Serial.available() > 0) {
    esp32Serial.readBytes((byte*)&d, sizeof(data));

    Serial.print("x:");
    Serial.print(d.x);
    Serial.print("y:");
    Serial.println(d.y);
}

But I actually get the output:

x:-1y:512
x:0y:-512

Instead of what I would expect:

x:-512y:512
x:-512y:512

Is there some sort of byte swapping I need to do? Or some sort of offset? Or a serial timing issue? I think both systems are little endian?

Note that I have a delay(1000) in my loop() function so I don't think this data is being sent to quickly (although the actual program will be faster) and I am using 115200 baud.

thanks for any help!

Simon

using native int..
on esp32 this is 32 bit, on the Uno it's 16 bit..

good luck.. ~q

1 Like

try this structure on both sides..

struct  __attribute__((__packed__)) data
{
  int16_t x = 0;
  int16_t y = 0;
};

the packed attrib makes sure the data packed to bytes, think the default is word which works fine with the structure as it sits but if you add data this may change, so as a rule of thumb of the structure will be streamed, then pack it..

oh, a quick serial.print of the sizeof struct would have shown your orig issue..

good luck.. ~q

Thank you! Using packed attribute seems much better - although the struct members appear to be swapped?

x: 512 y: -512 sizeOf data: 4 sizeOf char: 1 sizeOf byte: 1 sizeOf int: 2

That was not obvious! (to me at least)

Simon

endianness??

~q

I changed the numbers to x=-64 and y=1 and seem to be getting them the correct way around now (I need to test with much larger numbers too):

 x: -64 y: 1 size data: 4 size char: 1 size byte: 1 size int: 2
 x: -64 y: 1 size data: 4 size char: 1 size byte: 1 size int: 2
 x: -64 y: -16320 size data: 4 size char: 1 size byte: 1 size int: 2
 x: 511 y: -16384 size data: 4 size char: 1 size byte: 1 size int: 2
 x: 511 y: -16384 size data: 4 size char: 1 size byte: 1 size int: 2

But I also seems to be getting some corruption too. Is there anything I can do about this? I have tried adding a temporary buffer that I copy into rather than sending the data directly (is this the correct way to do this?):

d.x = -64;
d.y = 1;

byte buffer[sizeof(data)] = {0};
memcpy(&buffer, &d, sizeof(data));
     
Serial.write(buffer, sizeof(data));

And use a similar temporary buffer when receiving:

if (esp32Serial.available() > sizeof(data)) {
    byte buffer[sizeof(data)] = {0};
    esp32Serial.readBytes((byte*)&buffer, sizeof(data));
    memcpy(&d, &buffer, sizeof(data));
}

Or maybe I need to reduce my baud rate? Currently I have it set to 115200

thanks again!

Simon

Lowering the baud rate to 9600 definitely helped with consistent numbers, but the numbers are completely wrong!

I am now sending larger numbers (to make sure it is more than a single byte):

d.x = 1088;
d.y = -4608;

And I am getting this result

x: 4 y: 16622 

hmm I must have got something wrong just now (maybe I didn't restart the ESP32 properly?) as it has gone back to the correct numbers but the wrong way around!

x: -4608 binary: 11111111111111111110111000000000
y: 1088 binary: 11111111111111111110111000000000

Please show your complete code for both sides
It is not clear for me what is

Why 32 bits?

And why did you print a size of char on every line? Do you expect that it could change? :slight_smile:

I have worked out what the problem was. It was a synchronisation issue. I was just continuously writing a stream of data to UART and the receiver might read an arbitrary point in that stream. Adding a third member to the struct made that more obvious!

There seem to be 2 ways around this - either I have a "header" byte that I look out for before doing a proper read. The only trouble with this is that my data is going to be -512 to 512.

Or I add some 2 way communication and send an explicit poll to the ESP32 to tell it to send a packet of data. This one might be the better approach.

Simon

Exactly! So, add sync pattern before sending the structure as natural binary.

In the following sketches, ESP32 is sendig struct over its UART2 Port and UNO is receiving via its sep32Serial(6, 7) Port.

Sender Sketch:

struct __attribute__((packed, aligned(1))) data
{
  int16_t x;  //MCU independent data size; int is 32-bit for ESP32, 8-bit for UNO
  int16_t y;
  float z;
};

data d;

uint8_t syncHeader[] = {0xDE, 0xAD, 0xBE, 0xEF}; // Synchronization header

//uint8_t *dataPtr = reinterpret_cast<uint8_t*>(&d);

void setup()
{
  Serial.begin(9600);
  Serial2.begin(9600);
  d.x = -512;
  d.y = 512;
  d.z = 32.75;
}

void loop()
{
  for (size_t i = 0; i < sizeof syncHeader; i++)
  {
    Serial2.write(syncHeader[i]);
  }
  Serial2.write(sizeof d);  //number of bytes in the struct
  Serial.println(sizeof d);  //debugg
  Serial2.write((byte*)&d, sizeof d);

  delay(1000);
}

Receiver Sketch:

#include<SoftwareSerial.h>
SoftwareSerial esp32Serial(6, 7); //SRX = 6, STX = 7

struct __attribute__((packed, aligned(1))) data
{
  int16_t x;  //MCU independent data size; int is 32-bit for ESP32, 8-bit for UNO
  int16_t y;
  float z;
};

data d;

void setup()
{
  Serial.begin(9600);
  esp32Serial.begin(9600);
}

void loop()
{
  byte n = esp32Serial.available();
  {
    if (n != 0)
    {
      if (n >= 4)
      {
        //Serial.println(n);//debugg
        uint32_t syncPatt =
          (uint32_t)esp32Serial.read() << 24 | (uint32_t)esp32Serial.read() << 16
          | (uint32_t)esp32Serial.read() << 8 | esp32Serial.read();
        //Serial.println(syncPatt, HEX);//debugg
        delay(1);  //without delay, the Receiver does not work! Why?
        if (syncPatt == 0xDEADBEEF)
        {
          byte p = esp32Serial.read(); //number of bytes in struct
          byte m = esp32Serial.readBytes((byte*)&d, p);
          Serial.print("x = "); Serial.print(d.x); Serial.print(' ');
          Serial.print("y = "); Serial.print(d.y); Serial.print(' ');
          Serial.print("z = "); Serial.print(d.z, 2); Serial.println(' ');
          Serial.println("===========================");
        }
      }
    }
  }
}

Output:

===========================
x = -512 y = 512 z = 32.75 
===========================
x = -512 y = 512 z = 32.75 
===========================
x = -512 y = 512 z = 32.75 
===========================

You may read this thread (post #69 @J-M-L) to see the details on why sync pattern is needed for natural binary transmission and also for ASCII transmission.

It is also needed for text transmission but usually it’s easier to have a start or end marker as just one byte as you have bytes that are not part of the alphabet / numbers .

For example \n is often used to denote the end of the line and to separate two payloads - but other bytes have special meaning in communication and could be put to good use as well - they are known as control flow bytes

  1. Start of Heading (SOH) - ASCII code 01
  2. Start of Text (STX) - ASCII code 02
  3. End of Text (ETX) - ASCII code 03
  4. End of Transmission (EOT) - ASCII code 04
  5. Enquiry (ENQ) - ASCII code 05
  6. Acknowledgement (ACK) - ASCII code 06
  7. Bell (BEL) - ASCII code 07
  8. Backspace (BS) - ASCII code 08
  9. Horizontal Tab (HT) - ASCII code 09
  10. Line Feed (LF) - ASCII code 10
  11. Vertical Tab (VT) - ASCII code 11
  12. Form Feed (FF) - ASCII code 12
  13. Carriage Return (CR) - ASCII code 13
  14. Shift Out (SO) - ASCII code 14
  15. Shift In (SI) - ASCII code 15
1 Like

Cheers! I forgot to the add the above as I was busy to understand your way of decteing the sync pattern. I am editing my post.

The receiver sketch of my post #11 does not work without the insertion of some delay time. I am implementing your way of detecting the sysn patter and see if it helps; else, your intervention is expected.

I’m just on my iPhone at the moment - no access to a computer for some time today :slight_smile:

1 Like

Fine! I see if I can resolve the isuue.

It's not as simple as that. Since binary data can contain any value, it's entirely possible for the sync byte(s) to end up in the payload data stream. Check out the source code for the SerialTranser Library for one technique to get around this problem.

How much is the probability that the payload will contain these 4-byte 0xDE, 0xAD, 0xBE, 0xEF sync pattren as consecutive data bytes?

@J-M-L has tested this scheme in post #69 of this thread for 256-byte payload.

It's greater than zero.

If you use a proper encoding technique (I gave you one example), then the probability of having a problem is precisely zero.

Is that link of post #17?

sure - it's not 100% fool proof. Remember it's only to denote the start of a payload, so you might get a false start if this happens in the middle of a payload but then if the payload also include a size and a CRC you'll know it was a bogus packet. is some cases where you stream constant updates of your humidity and temperature, that might not be such a big issue.

I agree there are safer strategies when needed - a typical one is to use a start marker (say 0x00) and to have an escape sequence byte say (0x01) and two replacement values (one for the escape sequence byte (say 0x02) and one for the start marker (say 0x03)

when you stream your payload, you send the start marker 0x00
then before writing the bytes of the payload you check them.

  • if it's the start marker (0x00) you send the escape sequence followed by its replacement values so 2 bytes : 0x01 0x03
  • if it's the escape sequence byte, similarly you send the the escape sequence followed by its replacement 0x01 0x02
  • any other byte goes unchanged

on the receiving end

  • if you see 0x00 you know it's the start of the payload
  • if you see 0x01 (the escape sequence) you ignore it but know that the next byte is either 0x02 or 0x03.
    • if you get 0x02 you store 0x01 in the received payload
    • if you get 0x03 you store 0x00 in the received payload
  • if you see anything else you store it directly in the received payload (if you get 0x02 or 0x03 without having received first the escape byte then it's the real value of that byte)

That's pretty fast and only costs one look ahead byte in terms of latency from time to time. (that "random" latency can create issues possibly)