Simple packet protocol for tiny microcontrollers

Recently, I needed a reliable method for a master microcontroller to send commands to a set of uniquely identified slave nodes. In this network, the master microcontroller (ATMEGA328P) communicates with the slave nodes (ATTINY85) by broadcasting the unique node identifier along with a set of commands for that particular node on a shared serial line. Thus, only the node whose ID matches, alters its behavior while the rest discard the data. While we can send human readable communications (e.g. “Node ID: 5, Command:Blink, Buzz”) across the serial line, this method is often resource and bandwidth intensive, especially for smaller microcontrollers. In my particular case, the ATTINY85 slave nodes were also running on their internal oscillators and simulating serial in software. As I found out, this particular approach is sensitive to transfer errors since the internal oscillators are less accurate than their external counterparts. Thus, I also needed some form of error control.

To address these issues, I created a small library called SimplePacket that forms binary data packets for software serial communication. While the library is written for software serial, it can be easily modified to accommodate other methods such as I2C. The library essentially forms a packet as shown in the figure below. It includes a start and end flag to frame the user data along with a XOR checksum to address any transfer errors.

Figure 1: Packet Structure

The data is specified through a struct and passed to the SimplePacket send function. The only restriction is that the data structure needs to be the same between the sender and receiver. A rudimentary data structure for the master-slave communication network would be:

struct DATA_STRUCT {
  uint8_t node_ID;
  uint8_t command;

The library itself is fairly simple and consists of three functions: init, send, and receive.

void init(SoftwareSerialXT *serialt, uint8_t length);
void send(uint8_t *msg, uint8_t length);
boolean receive(uint8_t *msg, uint8_t length);

The init function expects a pointer to the software serial connection and the size of the data structure you expect to transmit or receive. This allocates a temporary buffer for receiving data. The send and receive functions expect a pointer to the data and the size of the data. These functions construct and deconstruct the packet respectively. The receive functions only returns true when a full error-free packet has been successfully received.

The library can be downloaded from my Github repository. It also contains examples for sender and receiver.