Reading RS485 packet - one byte always wrong - HELP PLEASE

I am reading a 5 byte data packet from a device sending every 200msecs using a Mega The received data packet has a fixed header byte = 0x02 and the packet always ends with 0x04. The variable data is in the 2nd,3rd,4th bytes. The device sends using RS485 at 9600 baud and I am using a MAX485 line transceiver in half duplex 2 wire to the device. I am monitoring this simultaneously using a Serial print to PC screen and same time decoding the Receive line directly on a scope. The first 4 bytes are always correct but the Mega reads the last byte incorrectly as 0xFF instead of 0x04. I can see every data packet on the scope and the scope shows no noise and the last byte on scope is (correctly) always 0x04.

My program action waits for the last byte to be 0x04 to be sure the packet is complete so I can interpret the 2nd,3rd,4th bytes is correct sequence.

Here is the code:

if ((Serial1.available () > 4))  // at least 5 bytes in buffer
  {
    do
    {
      receivePacket[0] = Serial1.read(); // in case out of sync start with 0x02
    } 
    while (receivePacket[0] != 0x02);
   
    for (int i = 1; i < 5; i++)
    {
      receivePacket[i] = Serial1.read();  
      Serial.println(receivePacket[i]);
    }
if (receivePacket [4] == 0x04) // check to make sure last byte is 0X04
    { 
      // interpret 2,3,4 byte and do something
    }

I never get to the last "if" statement because receivePacket[4] always gives me 0xFF. Is this weird ???

Any advice very much appreciated.

I'm not very experienced with this sort of thing. but since no one else has replied I have an idea that might help you troubleshoot.

that is to send 6 bytes of data, and check to see if the mega is reading the 5th and 6th bytes correctly. Then I'd think you'd atleast know if the problem was reading the last byte in the array, or something explicitly wrong with the fifth byte. you could also try to change the fifth byte a different value..

that is if you can adjust the bytes that are being sent...

maybe the device sends the fifth byte at a different baud rate? Though, I assume that would show up on the scope.

The code is just wrong

you do check if there are at least 5 bytes in the pipeline, but the amount you read can be far more.

I'll rewrite after a coffee

k1jos:
I am reading a 5 byte data packet from a device sending every 200msecs using a Mega

if ((Serial1.available () > 4))  // at least 5 bytes in buffer

{
   do
   {
     receivePacket[0] = Serial1.read(); // in case out of sync start with 0x02
   }
   while (receivePacket[0] != 0x02);

You check if there are at least 5 bytes but then your WHILE might read 10 or 20 bytes before it gets a valid start marker, following which you try to read 4 more bytes. Not very logical.

This demo may be useful. It uses < and > as start- and end-markers and you could probably easily change them to 0x02 and 0x04 - assuming neither of those values can occur in the 3 payload bytes.

If those byte values can arise in the payload it will make things a bit more complex and I won't waste time on it now.

...R

moderator: fixed mangled code/quote tags

Note: it is impossible to write the optimal code without the context of the rest of the sketch, but OK

bool startByteFound = false;

// GET IN SYNC
while ( (Serial.available() > 0) && (startByteFound == false) ) 
{
  receivePacket[0] = Serial1.read();
  startByteFound  = (receivePacket[0] == 0x02);
}

if (startByteFound  &&  (Serial.available() >= 4))
{
  for (int i = 1; i < 5; i++)
  {
    receivePacket[i] = Serial1.read();
    Serial.println(receivePacket[i]);
  }
  if (receivePacket [4] == 0x04) // check to make sure last byte is 0X04
  { 
    processPacket(receivePacket);
  }
}

The code above is not necessarily correct as it can happen that to adjacent packets of five have a 0x02 and a 0x04 in the middle causing the SYNC part to fail.

An example: assume these 2 packets (of 5) and the code misses the first byte (02) to sync:

02 00 02 00 04 - 02 04 00 00 04

Then it syncs on the 02 in the middle of the first package (of 5) and just by chance the second package (of 5) has a 04 in the right spot
the code will think it received this packet (of 5)

02 00 04 02 04

As I do not know the contents of possible packets I cannot guarantee this will not happen.
If the value 02 will never occur in the middle of a packet the above code is pretty robust.

You'll have much less grief if you use blocking I/O for testing this,
even if you then recode it as non-blocking state-machine driven for
the final sketch:

int myRead ()
{
  while (Serial.available () == 0)
  {}
  return Serial.read () ;
}

Use myRead and you can't see 0xFF's due to buffer underruns, which seems
to be what's happening.

The generic approach to non-blocking I/O should be like this:

void loop ()
{
  ...
  if (Serial.available () > 0)
    handle_next_char (Serial.read ()) ;
  ...
}

And handle_next_char is not allowed to call Serial.read(). This way
again you prevent any issues with reading from an empty buffer
or blocking on I/O holding up the rest of loop()

Thank you to all for the helpful advice. The device I am reading via RS485 is proprietary so I can't change the number of bytes in the data packet but the good news there can never be any duplicate bytes, that is each byte will always be unique. The header (byte 0) is 0x02, the terminator (byte 4) is 0x04 and there are only 10 unique patterns for bytes 1,2,3 without dups within the 3 variable bytes. The device is a remote servo controller turning on /off 5 different motors and the RS485 bus allows some very long distance wiring. I need some to digest your comments and see how I can alter my current code to follow your advice. I will post my (hopefully successful) results in a few hours. Again many thanks for your generous support

If you know that a valid pack always contains 5 bytes (e.g., "23454"), this might work:

#define PACKETLENGTH 5

char receivePacket[6];

void setup() {
  Serial.begin(115200);
}

void loop() {
  int bytesRead;

  if ((Serial.available () > 0)) {
    bytesRead = Serial.readBytesUntil('\n', receivePacket, PACKETLENGTH); 
    Serial.print("bytesRead = ");
    Serial.println(bytesRead);
    receivePacket[bytesRead] = '\0';                              // Make it a C string
    if (bytesRead == PACKETLENGTH && receivePacket[4] == '4') {   // Right message?
      Serial.println(receivePacket);
    }
  }

}

The readBytesUntil() method in the example above reads a max of 5 chars of data. bytesRead tells you how many bytes were actually read. You then append a null to the character array to make it a C string. The if block simply tests to see if it conforms to the type of data your are expecting. In this example, I'm using the Serial monitor to send the data. Since your "real" data is receiving hex numerics, you'd need to adjust accordingly. However, the general idea might work.

The readBytesUntil() method in the example above reads a max of 5 chars of data. bytesRead tells you how many bytes were actually read.

The device sending OP info does not end the packet with a \n, so that code won't work.

k1jos:
Thank you to all for the helpful advice. The device I am reading via RS485 is proprietary so I can't change the number of bytes in the data packet but the good news there can never be any duplicate bytes, that is each byte will always be unique. The header (byte 0) is 0x02, the terminator (byte 4) is 0x04 and there are only 10 unique patterns for bytes 1,2,3 without dups within the 3 variable bytes. The device is a remote servo controller turning on /off 5 different motors and the RS485 bus allows some very long distance wiring. I need some to digest your comments and see how I can alter my current code to follow your advice. I will post my (hopefully successful) results in a few hours. Again many thanks for your generous support

link to datasheet?

Paul: You're right. He could try modifying the line:

    bytesRead = Serial.readBytesUntil('\n', receivePacket, PACKETLENGTH);

to be:

    bytesRead = Serial.readBytes(receivePacket, PACKETLENGTH);

checking that bytesRead is the packet length and convert the string to numerics as needed.