Receiving serial with null character

Hi,
Following the serial basics tutorials
I'm trying to receive a string of bytes via RS485 that includes HEX 00 (null character).
I assume that I need to use Serial.readBytes() instead of Serial.read, but cant get it to work
An example of the string I need to read is:
53 54 3C 10 01 00 08 62 75 74 74 6F 6E 31 02 3E 45 54 62 01
The example given works, but stops reading after the 01, presumably because the next byte is a null character.
I modified the example as follows, but the compiler spits back the Serial1.readBytes call and I can't figure out why. I'm sure its something simple.
I changed the char array to byte, as well as the rc buffer.

// Example 2 - Receive with an end-marker
#include <AST_RS485.h>  //RS485 library for hardware

const byte numChars = 60;
int readLen = 1;
//char receivedChars[numChars];   // an array to store the received data
byte receivedChars[numChars];   // Changed to byte
byte dummy[numChars];
boolean newData = false;


void setup() {
    rsInit();                           // Initialise hardware RS485 port (pulls /RE and /SHDN high to enable transciever)
    Serial.begin(115200);
    Serial1.begin(115200);  // Hardware UART tied to RS485 transciever
    Serial.println("<Arduino is ready>");
}

void loop() {
    recvWithEndMarker();
    showNewData();
}

void recvWithEndMarker() {
    static byte ndx = 0;
    //char endMarker = '\x3E';
    //char endMarker = '\0';
    //char endMarker = '\n';
    //char rc;
    char endMarker = '>';
    byte rc;
    while (Serial1.available() > 0 && newData == false) {
        //rc = Serial1.read();
        Serial1.readBytes(rc,readLen);  // <<- throws error "Compilation error: no matching function for call to 'readBytes(byte&, int&)'"
        if (rc != endMarker) {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) {
                ndx = numChars - 1;
            }
        }
        else {
            Serial.println("end");
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
        }
    }
}

void showNewData() {
    if (newData == true) {
        Serial.print("This just in ... ");
        Serial.write(receivedChars,60);
        newData = false;
    }
}

Thanks for any help!

the first parameter to readBytes is a pointer to an array of bytes the second is the length, e.g.

 byte rc[100]={0};
 while (Serial1.available() > 0 && newData == false) {
        //rc = Serial1.read();
        Serial1.readBytes(rc,sizeof(rc));  +

you then need to go thru the array rc processing the data

EDIT: what microcontroller are you using? e.g. UNO, ESP32, RP2040, etc

Serial.read() can read NULL characters. It can read any value in fact.

I believe that example will read all the characters, but the print will stop on the NULL character, because "strings" in C are expected to be NULL terminated.

2 Likes

'0' is one matter - and a Null Character, '\0', is another.

Hello Horace,
Its an AST CAN485

what is the target RS485 device?
do you have a specification of the data format?

The target device is a Stone HMI.


The DATA payload is variable length, as given by LEN.
I only need up to the end of the DATA section. I'm trying to terminate on '>'
The current code below works the first time, however on subsequent reads the leftover "ET" and the CRC are printed at the beginning of the buffer.

// Example 2 - Receive with an end-marker
#include <AST_RS485.h>

const byte numChars = 30;

char receivedChars[numChars];   // an array to store the received data

boolean newData = false;


void setup() {
    rsInit();                           // Initialise rs485 port
    Serial.begin(115200);
    Serial1.begin(115200);  // Hardware UART tied to RS485 transciever
    Serial.println("<Arduino is ready>");
}

void loop() {
    recvWithEndMarker();
    showNewData();
}

void recvWithEndMarker() {
    static byte ndx = 0;
    char endMarker = '\x3E';
    //char endMarker = '\0';
    //char endMarker = '>';
    //char endMarker = '\n';
    char rc;
    //byte rc;
    while (Serial1.available() > 0 && newData == false) {
        rc = Serial1.read();
        //Serial1.readBytes(dummy,1);
        //Serial1.readBytes(rc,1);
        if (rc != endMarker) {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) {
                ndx = numChars - 1;
            }
        }
        else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
        }
    }
}

void showNewData() {
    if (newData == true) {
        //Serial.print("This just in ... ");
        //Serial.write(receivedChars,30);
        for (int i = 0; i <= 29; i++)
          //Serial.write(receivedChars[i],1);
          Serial.write(receivedChars[i]);
        
        newData = false;
    }
}

I think @bobcousins found the problem

For reading variable length data, readBytes() may not be a good fit. At least you need to call setTimeout(), and use the number of bytes returned.

To process the extra chars, I would just read the extra 4 after detecting '>'. I assume the data cannot contain 0x3E.

He did, and I changed the printout routine as shown above to print the buffer byte by byte, now I'm just trying to figure out why the portion of the data after the termination character is ending up in the beginning of the array on the next transmission.
I don't need to terminate on the '>', receiving the entire transmission into the buffer, including the CRC would be OK/preferred.
I just used '>' ('\x3E') as it's predictable and unique near the end of the string.

BTW, terminating on \n doesn't work either.

Obviously not.... that is in the example which is different to your data!

BTW, Stone provide an Arduino library for receiving data. https://www.stoneitech.com/support/stone-library/

edit; add link

This happens anyway, so you need to empty the buffer before you take the next read in.
Try

 else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
            while (Serial1.available() > 0) Serial1.read();
        }

try scanning for the start marker '>', read the frame then check for the end marker - this makes sure it is a valid frame, e.g.

void recvWithEndMarker() {
  char startMarker = 0x3C;
  char endMarker = 0x3E;
  while (Serial1.available() > 0)
    if (Serial1.read() == startMarker) {
      Serial.println("\nstart marker found");
      int cmd = Serial1.read() << 8 | Serial1.read();
      Serial.printf("cmd 0x%x\n", cmd);
      int len = Serial1.read() << 8 | Serial1.read();
      Serial.printf("len %d\ndata ", len);
      for (int i = 0; i < len; i++)
        Serial.printf("0x%x ", Serial1.read());
      if (Serial1.read() == endMarker)
        Serial.println("\nend marker found");
      else Serial.println("\nend marker NOT found");
    }
}

with the data stream "53 54 3C 10 01 00 08 62 75 74 74 6F 6E 31 02 3E 45 54 62 01" the serial monitor should display

start marker found
cmd 0x1001
len 8
data 0x62 0x75 0x74 0x74 0x6f 0x6e 0x31 0x2 
end marker found

EDIT: if the the end marker is found OK you can then check the CRC
if the end marker is not found or the CRC fails start scanning again
in a more sophisticated system you could buffer the received bytes, on end marker/CRC fail you goback and start scanning from the character following the start marker

I'll give these a try tomorrow, need to head out to work.
Thanks!

I saw that and tried it. Was not able to get it to work.
Seemed like lot of code for what I need (just reading a few button presses).

Of course, you don't need to use the whole code. You can just extract the function that reads a message.

/*********************************************************************************************************
**  @FunctionName               Uart_HMI_Data_Receive
**  @Description                Data reception and processing
**  @InputParam                 NONE
**  @OutputParam				        NONE
**  @ReturnValue                CRC16 value
**
*********************************************************************************************************/
void Uart_HMI_Data_Receive()
{
  int index=0;
  unsigned int length =0;   //The length of the complete data frame
  int start = 0;            //This parameter is used to determine the frame header(ST<) length
  int end =0;               //This parameter is used to determine the frame end(>ET) length
  int crc_num = 0;          //Used to determine whether two CRC check bits are received
  
  while(Serial.available()>0){
    byte receiveByte = Serial.read();

    if(start==0&&receiveByte==0x53){
      start = 1;
      data[index++]=0x53;
    }
    else if(start==1&&receiveByte==0x54){
      start = 2;
      data[index++]=0x54;
    }
    else if(start==2&&receiveByte==0x3C){
      start = 3;
      data[index++]=0x3C;
    }
    else if(start==3&&receiveByte!=0x3E&&end==0){
      data[index++] = receiveByte;
    }
    
    else if(start==3&&receiveByte==0x3E){
      end = 1;
      data[index++] = 0x3E;
    }
    else if(end==1&&receiveByte==0x45)
    {
      end = 2;
      data[index++]=0x45;
    }
    else if(end==2&&receiveByte==0x54){
        end = 3;
        data[index++]=0x54;
    }
    else if(end==3&&crc_num<=2){
      data[index++] = receiveByte;
      crc_num++;
      if(crc_num==2)
      {
        break;
      }
    }
    else{
      start = 0;
      end = 0;
      index = 0;
    }
    
  }
  //Print the received data
  if (index > 0) {
    length = index;
    for (int i = 0; i < length; i++) {
      Serial.print(data[i],HEX);
      Serial.print(" ");
    }
    Serial.println(); 
  }

  uint16_t expectedCRC = (data[length-2] << 8) | data[length-1];    //The received CRC check bit
  uint16_t calculatedCRC = calculateCRC16Modbus(data, length-2);    //The CRC check bit is calculated from the received data

  //Print data message
  if (length>0 && calculatedCRC == expectedCRC) {
    Serial.println("CRC Check success!");
    uint32_t num = data[3]<<8 | data[4];            
    Serial.print("CMD:");
    Serial.print("0x");
    Serial.println(num,HEX);
    uint16_t data_length = data[5]<<8 | data[6];    
    Serial.print("DATA_LEN:");
    Serial.print("0x");
    Serial.println(data_length,HEX);
    Serial.print("HEX_DATA: ");
    for(int i=7;i<data_length+7;i++)
    {
      Serial.print(data[i],HEX);
      Serial.print(" ");
    }
    Serial.println();
    Serial.print("String_Data: ");
    for(int i=7;i<data_length+7;i++)
    {
      Serial.print(char(data[i]));
    }
    Serial.println();
    Serial.println();

    //Store data to a structure
    hmi_msg.len = data_length;
    hmi_msg.crc = expectedCRC;
    hmi_msg.cmd = num;
    memcpy(hmi_msg.data, &data[7], data_length);

  } 
  else if(length>0 && calculatedCRC != expectedCRC){
    Serial.println("CRC Check failure!");
    hmi_msg.len = 0;
  }
  else
  {
    hmi_msg.len = 0;
  }
  // Uart_HMI_Data_Analysis(data,length);
}

Thank you for the link to the sample code.
I had seen other stone sample code (including what shipped on a thumbdrive with the HMI) that did not compile.
I was able to use parts of that code to get a minimal send / receive framework working.
I'm sure it's not the most elegant code, but it's working:
edit: added for loop in main loop to test dynamic updating of label

#include <AST_RS485.h>
#include <ArduinoJson.h>

const int HMI_MAX_DATA_LEN = 256;

byte data[HMI_MAX_DATA_LEN];
String str;

struct hmi_message {
  uint16_t cmd;                   // Stone HMI CMD code 			
  uint16_t len;                   // Stone HMI widget data len; only the data len 
  uint8_t widget[64];             //Stone HMI widget name 
  uint8_t data[HMI_MAX_DATA_LEN]; // Stone HMI cmd data,data type is int float or text 
  uint16_t crc;                   // Stone HMI cmd CRC value 
} hmi_msg;


void setup() {
  rsInit();  // Initialise rs485 port
  Serial.begin(115200);
  Serial1.begin(115200);        // Hardware UART tied to RS485 transciever

 
}

void loop() {
  Uart_HMI_Data_Receive();

  for(byte i = 5; i <= 20; i++){

    char tempChar[3];
    String(i).toCharArray(tempChar, 3);
    HMIsend("set_text", "label", "label1", tempChar);

    delay(200);
  }

    
}


uint16_t calculateCRC16Modbus(const uint8_t *data, size_t length)
{
  uint16_t crc = 0xFFFF; 
  for (size_t i = 0; i < length; i++) {
    crc ^= (data[i]); 
    for (uint8_t j = 0; j < 8; j++) { 
      if (crc & 0x0001) {
        crc = (crc >> 1) ^ 0xA001; 
      } else {
        crc >>= 1; 
      }
    }
  }
  return crc;
}

void Uart_HMI_Data_Receive()
{
  int index=0;
  unsigned int length =0;   //The length of the complete data frame
  int start = 0;            //This parameter is used to determine the frame header(ST<) length
  int end =0;               //This parameter is used to determine the frame end(>ET) length
  int crc_num = 0;          //Used to determine whether two CRC check bits are received
  
  while(Serial1.available()>0){
    byte receiveByte = Serial1.read();

    if(start==0&&receiveByte==0x53){
      start = 1;
      data[index++]=0x53;
    }
    else if(start==1&&receiveByte==0x54){
      start = 2;
      data[index++]=0x54;
    }
    else if(start==2&&receiveByte==0x3C){
      start = 3;
      data[index++]=0x3C;
    }
    else if(start==3&&receiveByte!=0x3E&&end==0){
      data[index++] = receiveByte;
    }
    
    else if(start==3&&receiveByte==0x3E){
      end = 1;
      data[index++] = 0x3E;
    }
    else if(end==1&&receiveByte==0x45)
    {
      end = 2;
      data[index++]=0x45;
    }
    else if(end==2&&receiveByte==0x54){
        end = 3;
        data[index++]=0x54;
    }
    else if(end==3&&crc_num<=2){
      data[index++] = receiveByte;
      crc_num++;
      if(crc_num==2)
      {
        break;
      }
    }
    else{
      start = 0;
      end = 0;
      index = 0;
    }
    
  }
  //Print the received data
  if (index > 0) {
    length = index;
    for (int i = 0; i < length; i++) {
      Serial.print(data[i],HEX);
      Serial.print(" ");
    }
    Serial.println(); 
  }

  uint16_t expectedCRC = (data[length-2] << 8) | data[length-1];    //The received CRC check bit
  uint16_t calculatedCRC = calculateCRC16Modbus(data, length-2);    //The CRC check bit is calculated from the received data

  //Print data message
  if (length>0 && calculatedCRC == expectedCRC) {
    Serial.println("CRC Check success!");
    uint32_t num = data[3]<<8 | data[4];            
    Serial.print("CMD:");
    Serial.print("0x");
    Serial.println(num,HEX);
    uint16_t data_length = data[5]<<8 | data[6];    
    Serial.print("DATA_LEN:");
    Serial.print("0x");
    Serial.println(data_length,HEX);
    Serial.print("HEX_DATA: ");
    for(int i=7;i<data_length+7;i++)
    {
      Serial.print(data[i],HEX);
      Serial.print(" ");
    }
    Serial.println();
    Serial.print("String_Data: ");
    for(int i=7;i<data_length+7;i++)
    {
      Serial.print(char(data[i]));
    }
    Serial.println();
    Serial.println();

    //Store data to a structure
    hmi_msg.len = data_length;
    hmi_msg.crc = expectedCRC;
    hmi_msg.cmd = num;
    memcpy(hmi_msg.data, &data[7], data_length);

  } 
  else if(length>0 && calculatedCRC != expectedCRC){
    Serial.println("CRC Check failure!");
    hmi_msg.len = 0;
  }
  else
  {
    hmi_msg.len = 0;
  }
  // Uart_HMI_Data_Analysis(data,length);
}


void HMIsend (char jCode[], char jType[], char jWidget[], char jText[]){

  char Head[] = "ST<";
  char Payload[128];
  char Tail[] = ">ET";
  
  // Allocate the JSON document
  JsonDocument jDoc;

  // Add values in the document
  jDoc["cmd_code"] = jCode;
  jDoc["type"] = jType;
  jDoc["widget"] = jWidget;
  jDoc["text"] = jText;

  serializeJson(jDoc, Payload);

  Serial.print (Head);
  Serial.print (Payload);
  Serial.println(Tail);

  Serial1.print (Head);
  Serial1.print (Payload);
  Serial1.println(Tail);

}




This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.