Skip to content

xqms/libucomm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

libucomm aims to be a generic C++ library for serial communication between space-constrained low-power devices such as microcontrollers.

The library is heavily templated to adapt to any usage pattern with minimal overhead.

Features

  • Small C++ header library, no linking required.
  • Optimizes away to no overhead in most cases
  • Very little assumptions about communication architecture
  • Flexible checksum system (add your own checksum method)

Envelope formats

The "envelope" protects your data on the wire. This could be a serial connection, for example.

libucomm supports two envelope formats:

  • default (EnvelopeWriter/EnvelopeReader): Very basic format providing hard synchronization (magic start sequence) with simple escaping of the payload. Is able to operate without a buffer.
  • COBS (COBSWriter / COBSReader): Also provides hard synchronization using a magic byte value. Stuffs the payload with minimal overhead using the COBS algorithm. Needs a random-access buffer during packet sending, though.

The COBS envelope format is recommended for new protocol designs.

Checksums

libucomm comes with the following checksum implementations:

  • simple 8-bit modular sum
  • inverted 8-bit modular sum
  • Fletcher-16

It's very easy to implement your own checksumming function (see checksum.h).

Caveats

  • Assumes endianness is the same on both endpoints. If you want to improve on this, it should be fairly easy to fix in parse.py.

Usage

Protocol definition

The messaging protocol is defined in a C-like format:

struct USSensorData
{
    uint8_t flags;
    uint16_t distance;
};

msg SensorDataMessage
{
    uint8_t temperature;
    USSensorData sensors[];
};

msg Alert
{
    uint8_t code;
};

This file can be parsed by a small python script (parse.py), which creates a C++ header ready for use in your application.

Sending data

First some handy typedefs (you can see the flexibility here):

typedef uc::EnvelopeWriter< uc::InvertedModSumGenerator > EnvelopeWriter;
typedef uc::IO< EnvelopeWriter, uc::IO_W > SimpleWriter;
typedef Proto< SimpleWriter > WProto;

Setting up I/O:

class MyWriter : public uc::CharWriter
{
public:
    bool writeChar(uint8_t c)
    {
        // Send c away
        return true; // success!
    }
};


MyWriter writer;
EnvelopeWriter output(&writer);

To send a simple message without arrays, just fill in the C structure and pass it into the EnvelopeWriter:

WProto::Alert alert;
alert.code = 0xA5;
output << alert;

Arrays require a bit more infrastructure, since we do not want to waste memory. They can be pre-filled:

WProto::SensorDataMessage msg;
WProto::USSensorData sensors[5] = {...}; // Fill in some data
msg.sensors.setData(sensors, sizeof(sensors));
msg.temperature = 0; // brr..
output << msg;

Or filled on the go while outputting the packet (requires least memory):

template<class SizeType>
bool fillInSensor(WProto::USSensorData* data, SizeType index)
{
    data->distance = 4 * index; // Fake some distance data
    return true;
}

WProto::SensorDataMessage msg;
// Create 5 elements and lazily call fillInSensor to get data when required
msg.sensors.setCallback(&fillInSensor, 5);
msg.temperature = 0; // brr..
output << msg;

Reading data

... is even more simple. First, some typedefs!

typedef uc::EnvelopeReader< uc::InvertedModSumGenerator, 1024 > EnvelopeReader;
typedef uc::IO< EnvelopeReader, uc::IO_R > SimpleReader;
typedef Proto< SimpleReader > RProto;


EnvelopeReader input;

while(1)
{
    uint8_t byte = ...; // Got some input!
    if(input.take(byte) == EnvelopeReader::NEW_MESSAGE)
    {
        // We got a message
        switch(input.msgCode())
        {
            case RProto::Alert::MSG_CODE:
            {
                RProto::Alert alert;
                input >> alert;
                printf("Got an alert with code %d\n", alert.code);
            }
                break;
            case RProto::SensorDataMessage::MSG_CODE:
            {
                RProto::SensorDataMessage msg;
                input >> msg;

                printf("Temperature: %d\n", msg.temperature);

                RProto::USSensorData sensor;
                while(msg.sensors.next(&sensor))
                {
                    printf("Distance: %d\n", sensor.distance);
                }
            }
                break;
        }
    }
}

Convinced?

TODO

  • Hide some "abstraction uglyness" like the typedefs away
  • Support for more basic types (floats?)
  • Strings
  • Real API documentation
  • Flexible list sizes (it's all there)

About

Low-power C++ serial communication template library

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published