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.
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?
Comments
Search
Archive
- November 2022
- Incremental Progress
- August 2021
- Self-Hosting for Fun and Personal Freedom
- July 2019
- Closing Channels Twice in Go
- May 2019
- On Life, Legacy, and JavaScript
- March 2018
- Refactoring, Now With Generics!
- November 2017
- Packages 3.2 released!
- September 2017
- Introducing the MOTKI CLI
- July 2017
- Decoupling Yourself From Dependencies
- May 2017
- Model Rocketry Update
- April 2017
- Dynamic DNS with homedns