Jul '16

20

Packing Data Into Byte Arrays

Embedded applications require special care when it comes to memory usage and allocations. Granted, Arduinos pack enough RAM to generally not be a cause of worry, but what about sending and receiving data between Arduinos?

I'm working on a transponder application that transmits many different sensor readings to a remote Arduino using CC1101 transceivers. These have a maximum packet size of 64 bytes, and though it is possible to send more data over multiple packets, the ideal solution is to pack all the readings into a single packet.

 

The naive approach

One solution is to use String objects, as this allows simple debugging and intuitive handling of the data being transferred. A naive approach might look something like:


void sendResponse() {
    String ack = "ack ";
    ack += lastVibration;   // Some of these are ints
    ack += ' ';
    ack += lastVcc;
    ack += ' ';
    ack += lastRssi;
    ack += ' ';
    ack += lastRoll;        // Most are floats
    ack += ' ';
    ack += lastPitch;
    ack += ' ';
    ack += lastHeading;
    ack += ' ';
    ack += lastTemp;
    ack += ' ';
    ack += lastAltitude;
    ack += ' ';
    ack += lastGyroX;
    ack += ' ';
    ack += lastGyroY;
    ack += ' ';
    ack += lastGyroZ;

    // then send ack
}

However, this is not only going to create messages that are larger than 64 bytes, it has the potential to wreak havoc on your Arduino by doing a boatload of dynamic allocations as Strings are created and destroyed.

 

A Better Solution

Since all scalar types in C are simply multiples of byte, we can convert the various data into a byte array. A char is one byte, an int is two bytes, a long is four, a float is four, and so on.

Packing integers is fairly straightforward. Arduino even ships with some helpers to do the lifting for you.

I've used char in place of byte  in the examples, but they are effectually the same.

int someInt = 10023;

// Pack "someInt" into a byte array.
char bytes[2];
bytes[0] = highByte(someInt);
bytes[1] = lowByte(someInt);

// Unpack "bytes" into an int.
int unpackedInt = word(bytes[0], bytes[1]);

// someInt == unpackedInt

This hides away the bitwise operations necessary to make this work, but all we're doing is separating the two bytes that make up an int.

Packing other types might seem daunting, but C has a powerful type called union that makes this a breeze. An in-depth explanation of unions is outside the scope of this post, but essentially, a union is a structure with multiple data types that reference the same location in memory.


float someFloat = 123.45;

typedef union {
    float f;         // Assigning fVal.f will also populate fVal.bytes;
    char bytes[4];   // Both fVal.f and fVal.bytes share the same 4 bytes of memory.
} fVal;

char packed[12];   // Some arbitrary message, note the size.
fVal packedFloat;

packedFloat.f = someFloat;
packed[6] = packedFloat.bytes[0];   // We can put these bytes anywhere in our byte array.
packed[7] = packedFloat.bytes[1];
packed[8] = packedFloat.bytes[2];
packed[9] = packedFloat.bytes[3];

fVal unpackedFloat;
unpackedFloat.bytes[0] = packed[6]; // Reference the same indexes we wrote to.
unpackedFloat.bytes[1] = packed[7];
unpackedFloat.bytes[2] = packed[8];
unpackedFloat.bytes[3] = packed[9];

float unpacked = unpackedFloat.f;

Isn't that neat?

arduinoc/c++Programming

Comments

No comments yet! Say something.