RS485 using CRC16 telegram termination

Hello everyone,

I currently want to stabilize my communication between RS485 devices. Therefore I tried to implement a CRC check. Some devices I already maintaining use the MODBUS protocol (commercial devices with user friendly software). On page 3 in a protocol guide (link) the function for a 2-byte CRC check is outlined.

Here is an outline of my RS485 code to detect a telegram

unsigned int RS485::scan() {
  
  while(Serial.available()>0 && incoming == false) {
    
    byte read = Serial.read();
    
    switch(read) {
      case '\n': // terminating null byte
        BUSDATA[pos] = '\0';
        incoming = true;
        break;      
      default:
        if(pos<(MAX_BUS_INPUT-1)) {
          BUSDATA[pos++] = read;
        }
        break;
    }

    if(incoming) {
      return pos;
    }
  }
  return 0;
}

This is part of the code from the Serial basics tutorial. Here the termination is '\n' (or 10 in Dec).

One of my testtelegrams is the char

x12CRC

which yields the CRC in Dec-format

lowByte = '10'
highByte = '108'

The sent telegram now looks like

x12CRC + lowByte + highByte

Here I ran into a problem when scanning the telegram. Since the termination in the RS485-Slave code is a newline ('\n'), which coincidentally is one of the CRC bytes. This leads to prematurely ending the scanning and the sent data can not be used reliable.

My master code sends data like

void RS485::send(const char* c) {
  digitalWrite(exPin,HIGH);
  Serial.println(c);
  Serial.flush();
  digitalWrite(exPin,LOW);
}

where the Serial.println() adds a '\n' at the end. Changing this to

void RS485::send(const char* c) {
  digitalWrite(exPin,HIGH);
  Serial.print(c);
  Serial.print('\0');
  Serial.flush();
  digitalWrite(exPin,LOW);
}

and on the slave side to

unsigned int RS485::scan() {
  
  while(Serial.available()>0 && incoming == false) {
    
    byte read = Serial.read();
    
    switch(read) {
      case '\0': // terminating null byte
        BUSDATA[pos] = '\0';
        incoming = true;
        break;      
      default:
        if(pos<(MAX_BUS_INPUT-1)) {
          BUSDATA[pos++] = read;
        }
        break;
    }

    if(incoming) {
      return pos;
    }
  }
  return 0;
}

Is it allowed to scan for the Null-Terminator? Currently only the characters are read, but the '\0' is never detected

Output

x
1
2
C
R
C

l

This are the characters from the telegram. The character after ´l´ ('108') should be '\0', but the case ' \0' is never reached.

Wouldn't it be better to terminate an incoming message using '\0'?
Is it possible to scan for '\0'?
Is there a reliable character to terminate a telegram, since the CRC bytes could be anything?

Maybe i'm missing the obvious here, but if your data is binary (rather than printable ASCII characters), then sooner or later whatever you choose as the terminating "value", will also appear as one of the CRC or data bytes.

It might be like tossing an unlit stick of dynamite, but to receive packets you have to consider the termination in light of the entire packet. So you have to accept the fact that a terminating character might belong to a message. You can tell the difference by running the CRC on the characters that you've already accumulated.

That's the problem. Every character might appear.

What can be used against it? How to terminate correctly?

You can switch to ASCII packets which makes things easier.

Or you could use fixed length packets with a known preamble/header. That way you keep storing the received bytes in a small FIFO queue the same size as your fixed length packets. When you see the preamble/header at the start of your FIFO, you quickly do a CRC on the contents of the FIFO and if it passes, then that's your message.

You could also use variable length packets but you still need some recognisable pattern to start the packet, if only to know how many bytes should be in the packet and therefore where the packet CRC is.

You can tell the difference by running the CRC on the characters that you've already accumulated.

Wouldn't this lead to a timing issue? Breaking the message on multiple parts. There are also mutliple devices with different addresses. The master must set the writeEnable to low, which indicates end of transmission on the master side. In the whole telegram is a address, function, n-data bytes and the CRC.

Does the CRC not need the 'data' part to calculate the CRC and then compare it to the last two bytes?

Yes, that is why you need to accumulate the message in a receive buffer.

do you mean calculating the CRC after each arrived byte?

first byte

x -> calc CRC

second byte to buffer

x1 -> calc CRC

third byte to buffer

x12 -> calc CRC

Against which values of the CRC do you compare it to, since the CRC of the whole message is different from the CRC of the smaller packages?

Of course not. That would never work. I told you, you need to buffer the incoming message. I forget to ask, are the messages fixed length or not?

IIRC when I designed some packets, the terminating character was not included in the CRC value, so it could be detected directly...

This is what I am doing until the terminator is received.

No. The data part can be n-byte long (I have a random limit of 50). I am not sure if this is the best practice. Since speed is not a necessity a fixed length might be the solution.

Does the standard MODBUS-addressing does not run into the same problem? There has to be a combination of data where the '\n' is part of the CRC

Give your packet some structure. A header for instance, that includes a preamble byte (or byte sequence) and a byte 'N' that describes the number bytes to follow in the message. The value of N could be the number of data bytes and the 16-bit CRC bytes are two addition assumed to follow or N could represent the total number of data and CRC bytes to follow.

The header could be expanded to include source/destination address info, a command/echo field etc. But for the purposes of knowing when to terminate reception of a binary message and length byte/word is a good way to go.

This might be the message of this whole conversation. A fixed length or an identifier how long the message is, can be a good identifier for when the 'listening' process is finished. This can come in handy for timeout purposes also.

I will make my telegram more presentable and report back with results

In all fairness, in post #5 @markd833 alluded to this as well. I didn't actually notice his post until a few moments ago.

Correct. I was waiting if there is another viable solution.