SerialProtocol - let the devices talk



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.

...
A SerialProtocol message

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.

...
Sample message

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:

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.