SerialProtocol is a software protocol for sending structured data via serial between two Arduino, two Raspberry Pi, or an Arduino and a Raspberry Pi.
Sending data through serial is trivial both on Arduino and Raspberry Pi. On Arduino, you can use the Serial
class and on Raspberry Pi you can use, termios or an other library. The problem is how to send structured data that can be understood by both the sender and receiver.
For the purpose of this blog post I am going to use the following data structure:
struct MyMessage
{
uint8_t MessageId;
uint8_t Value;
}
It represents a message with an identifier and a value corresponding to that message. You can imagine this as being the data sent by sensors from Arduino. For example, the message with ID 100
is the value sent by a temperature sensor and the message with ID 101
is from an accelerometer.
Message structure
Data through the serial port is sent sequential. That means that you get a series of bytes at the other end of the wire and you have to be able to give them a meaning. Also, the data must be intact - meaning no bytes were lost or altered.
The message starts with a Header
byte. This is just a marker that signals the start of the message and prevents the program from doing more work if the data received is protocol data.
Following the header, is another byte that specifies the Size
of the message. This byte has dual purpose: (1) to tell how many bytes to read after it and (2) it is the first defense against incorrect data. If the size of the message does not match the expected size, everything read so far is discarded.
The Payload
is the actual data.
The last byte, the Checksum
, is used to verify the integrity of the message. Its only purpose is to validate the bytes received so far. If the checksum is incorrect, the message read so far is discarded. The checksum is composed of the Size
field and the Payload
.
The structure above (MyMessage
) is two bytes in size (one byte for each uint8_t
field). Let’s say that the temperature sensor (MessageId = 100) sends the value 42. When this message is sent, the Size
field has the value 2. After that, the two fields are sent and, finally, the checksum. The checksum is computed by applying the XOR
operator on all the message bytes and the size. In our case 2 XOR 100 XOR 42 = 76
.
After the message is received, and before it is passed to the user code, it is validated against the received checksum. The receiver computes the checksum from the bytes it got and it looks to see if it matches the checksum in the message.
Usage
In order to use SerialProtocol in your applications, you have to include the appropriate header and compile the code along with your application.
The headers are:
ArduinoSerialProtocol
for ArduinoStreamSerialProtocol
for Raspberry Pi
Complete installation instructions and samples, along with the complete source code are available here.
The hardware setup for running the samples is simple: connect the Arduino using a USB cable to Raspberry PI. No other components are needed.
Caveats
When sending data between two architecturally difference devices, you must make sure that both encode/decode the data in the same way. The following structure is 3 bytes on Arduino but 4 on Raspberry Pi (with the default compiler optimization and structure padding). This is because the A
field is padded with an extra byte.
struct Data
{
uint8_t A;
uint16_t B;
}
If you try to send the structure, as is, from Arduino to Raspberry Pi, it will fail because the sent Size
field will be 3 while the expected Size
field, on Raspberry Pi, will be 4. In order to prevent structure padding, decorate the structure with __attribute__((packed))
as shown in these samples.
More resources
The full source code, samples, and installation instructions are available on GitHub.