The below can be a framework.
First you can define a struct that can hold all relevant information of a received message.
struct MESSAGE
{
byte startBytes[2];
uint16_t length;
byte payload[1000]; // 1000 max
};
You can add additional fields and/or adjust to your needs; I had to limit the size of the payload as I'm using an Uno.
Next you can declare a message variable
// message
MESSAGE msg;
And some additional variables and constants
// size of the header in the message
const uint16_t headerSize = sizeof(msg.startBytes) + sizeof(msg.length);
// expected start bytes
const byte startBytes[2] = {'a', '5'};
// receive timeout in milliseconds
const uint16_t rxTimeout = 5000;
If you add fields in the struct, you will have to recalculate the headerSize. I picked two start bytes that could be typed in serial monitor; adjust to the real ones. I've also added a timeout variable to be able to detect interruption, e.g. when somebody pulls the cable out or when the sending device stops working.
There are a few possible error conditions that I could see
Incorrect start bytes
Incorrect length which might result in buffer overflow
Timeout error
Checksum/crc error (if your message contains that type of information)
I've therefore created an enum type with defined errors that can occur; it does not only hold error codes but also the possible status of the receive 'operation'.
enum RXSTATUS
{
RX_COMPLETE, // message complete
RX_INCOMPLETE, // message not complete yet
RX_ERR_START, // startbytes mismatch
RX_ERR_TIMEOUT, // timeout error
RX_ERR_SIZE, // payload bytes will not fit
RX_ERR_CS // checksum error
};
Next you can write a function to read a message; the basics
/*
read message on serial port
Returns:
status of operation
*/
RXSTATUS readMessage()
{
return RX_INCOMPLETE;
}
And in loop() you can call this function as show below
void loop()
{
RXSTATUS status = readMessage();
if (status == RX_COMPLETE)
{
// complete message received
}
else if (status != RX_INCOMPLETE)
{
// error handling
switch (status)
{
}
}
else
{
// message not complete yet
}
}
Now we can start filling in readMessage(). The function uses a byte pointer that will allow you to treat the message variable as an array of bytes. It also uses an index variable in the same way as Robin's code does to indicate where the next byte must be stored in the 'array'. You will also need a variable to hold the extracted payload length and the time that the last byte was received.
In the first step, the code will check if a timeout occured
/*
read message on serial port
Returns:
status of operation
*/
RXSTATUS readMessage()
{
// pointer to message
byte *ptr;
// index where to store received byte
static uint16_t index;
// extracted payload length from received bytes
uint16_t payloadLength = 0;
// when last byte was received
static uint32_t lastByteTime;
// setup pointer to point to beginning of message
ptr = (byte*)&msg;
// check for timout if we already received something
if (index != 0 && millis() - lastByteTime > rxTimeout)
{
// cleanup
index = 0;
return RX_ERR_TIMEOUT;
}
In the next step, the code checks if serial data is available and store it in the 'array'. If a byte is received, the timeout is 'reset'
// if something to read
if (Serial.available() > 0)
{
lastByteTime = millis();
ptr[index] = Serial.read();
//Serial.print(ptr[index], HEX);
index++;
}
Next the code checks if the complete header is received.
// if header complete
if (index >= headerSize)
{
With the header in place, the code can check for correct start bytes and extract the payload length
// check if we have correct start bytes
if (ptr[0] != startBytes[0] || ptr[1] != startBytes[1])
{
// cleanup
index = 0;
return RX_ERR_START;
}
//Serial.println(payloadLength, HEX);
// determine payload length
payloadLength = ptr[2] * 256 + ptr[3];
//payloadLength = (ptr[2] - '0') * 256 + ptr[3] - '0'; // this is a hack to be able to use serial monitor as input device
// check if payload will fit
if (payloadLength > sizeof(msg.payload))
{
// cleanup
index = 0;
return RX_ERR_SIZE;
}
Note that the length bytes might be reversed in the data coming from your device; in that case you have to swap the ptr[2] and ptr[3] in the calculation of payloadLength.
Next the code checks if the message is complete. If so, reset the index variable and indicate to the caller that a complete message is received. The below also prints the message for debugging purposes.
// if message complete
if (index >= headerSize + payloadLength)
{
// reset index for next message
index = 0;
// print received message for debugging
Serial.print("message: ");
ptr = (byte*)&msg;
for (uint16_t cnt = 0; cnt < sizeof(msg); cnt++)
{
if (*ptr < 16) Serial.print(0);
Serial.print(*ptr, HEX);
Serial.print(" ");
ptr++;
}
Serial.println();
return RX_COMPLETE;
}
}
The full function
/*
read message on serial port
Returns:
status of operation
*/
RXSTATUS readMessage()
{
// pointer to message
byte *ptr;
// index where to store received byte
static uint16_t index;
// extracted payload length from received bytes
uint16_t payloadLength = 0;
// when last byte was received
static uint32_t lastByteTime;
// setup pointer to point to beginning of message
ptr = (byte*)&msg;
// check timout if we already received something
if (index != 0 && millis() - lastByteTime > rxTimeout)
{
// cleanup
index = 0;
return RX_ERR_TIMEOUT;
}
// if something to read
if (Serial.available() > 0)
{
lastByteTime = millis();
ptr[index] = Serial.read();
//Serial.print(ptr[index], HEX);
index++;
}
// if header complete
if (index >= headerSize)
{
// check if we have correct start bytes
if (ptr[0] != startBytes[0] || ptr[1] != startBytes[1])
{
// cleanup
index = 0;
return RX_ERR_START;
}
//Serial.println(payloadLength, HEX);
// determine payload length
payloadLength = ptr[2] * 256 + ptr[3];
//payloadLength = (ptr[2] - '0') * 256 + ptr[3] - '0'; // this is a hack to be able to use serial monitor as input device
// check if payload will fit
if (payloadLength > sizeof(msg.payload))
{
// cleanup
index = 0;
return RX_ERR_SIZE;
}
// if message complete
if (index >= headerSize + payloadLength)
{
// reset index for next message
index = 0;
// print received message for debugging
Serial.print("message: ");
ptr = (byte*)&msg;
for (uint16_t cnt = 0; cnt < sizeof(msg); cnt++)
{
if (*ptr < 16) Serial.print(0);
Serial.print(*ptr, HEX);
Serial.print(" ");
ptr++;
}
Serial.println();
return RX_COMPLETE;
}
}
return RX_INCOMPLETE;
}
The below loop demonstrates how it can be used.
void loop() {
RXSTATUS status = readMessage();
if (status == RX_COMPLETE)
{
Serial.print("start bytes: ");
for (uint8_t cnt = 0; cnt < sizeof(msg.startBytes); cnt++)
{
if (msg.startBytes[cnt] < 16) Serial.print(0);
Serial.print(msg.startBytes[cnt], HEX);
Serial.print(" ");
}
Serial.println();
Serial.print("payload length: ");
Serial.println(msg.length, HEX);
Serial.print("payload: ");
byte *ptr;
ptr = msg.payload;
for (uint16_t cnt = 0; cnt < sizeof(msg.payload); cnt++)
{
if (*ptr < 16) Serial.print(0);
Serial.print(*ptr, HEX);
Serial.print(" ");
ptr++;
}
Serial.println();
}
else if (status != RX_INCOMPLETE)
{
// error handling
switch (status)
{
case RX_ERR_START:
Serial.println("startbytes incorrect");
break;
case RX_ERR_TIMEOUT:
Serial.println("a timeout occured while receiving a message");
break;
case RX_ERR_CS:
Serial.println("message corrupted; checksum error");
break;
case RX_ERR_SIZE:
Serial.println("payload length exceeds buffer size");
break;
default:
break;
}
}
}