One reads data from the serial port one byte at a time. This is fine for single by data types like char, but what if the data you’re receiving represents one or more multi-byte data types, such as floats or ints? How do you put the pieces back together again? There are several approaches, some for specific cases and other for more general cases. I came across the need to do this when dealing with a compass module. I’m sure there’s more than what I’ve stumbled across, so feel free to add more via comments.
One of the simplest cases is an int in two bytes. For this case, you can read the two bytes into a variable of type byte, then shift the higher value to the left by eight bits and add them into an unsigned int:
higherByte = compass.read();
lowerByte = compass.read();
bearing = ((higherByte<<8)+lowerByte)/10
In this example, bearing has been declared an integer, and the compass module returns a value between 0 and 3600. Since bearing is an int, which is two bytes long, each byte is converted to an int, the higher byte value is shifted left 8 bits, and the values added.
Another way of doing this is to take advantage of the Word data type, which for Arduino’s and other AVR devices (and on many other systems) is 16 bits. One can combine the two bytes into one word and then cast it as the appropriate type, e.g.,
x = (int) word(xHigh, xLow);
I put together some test code for the Devantech CMPS10 module’s bearing and raw data feeds using these two approaches. If you’re curious or need a quick and simple test program for your compass, checkout https://github.com/ViennaMike/CMPS10SerialTest.
If the value being read uses less than the full 16 bits AND IS SIGNED, such as data from Parallax’s compass module, things get tricky. You need to read the highest bit used (which will be the sign bit) and see if it’s a 1 or a 0. If a 0, the number is positive, and you can just shift the data as above. If it’s negative, there is an additional step after you shift left. You need to have the new sign bit set to 1 and the unused highest order bits ALSO set to 1, since negative numbers are stored using twos complement arithmetic. So you want to bitwise OR the result with a mask, where the highest order bits in the mask, up to the number of unused bits in the original data, are set to 1, and the other bits to 0. See http://www.arduino.cc/playground/Main/HM55B for an example of this.
But what if you have more than two bytes in your structure, or you want to easily handle a long string where, perhaps, you read 6 bytes, with 2 bytes each for x, y, and z parameters? Then, my friends, you will learn that there is power in a “union.” A union is another data type. It allows the same portion of memory to be accessed as different data types. So you can define the particular union to be BOTH a sequence of bytes, read in sequentially over a serial port, perhaps, AND whatever the bytes represent (say a set of 3 ints, or 4 ints and 2 chars). Here’s a simple example:
union Data
{
byte b[2];
int value;
};
int main( )
{
union Data data;
data.b[0] = compass.read();
data.b[1] = compass.read();
bearing =data.value;
There’s more to union’s, and I recommend C-Unions and C++ Other Data Types for an introduction. There’s also a direct serial port on arduino discussion at Float Value through Serial Port..
p.s.: The title of this post comes from the title of a song by labor activist Joe Hill, written in 1913. For a current version, check out Billy Bragg’s version on You Tube.
Pingback: C++ Union learning notes | tlfong01
Why not using simply compass.parseInt(); ??
Good point, but it only works for simpler cases. I think I was using an earlier version than 1.0 of the Arduino IDE, inc which case software serial didn’t have parseInt(), however I may just have missed it. parseInt() and parseFloat() are indeed a better way to deal with straight integers and floats than what I described in paragraphs 2 through 6.
HOWEVER, for the compass, it wouldn’t work, because the compass interface only uses PART of the higher byte, with a sign bit in the middle. In cases like this, I believe, you still need code such as what I laid out.
Union is great for this kind of job.
I loved COBOL, with its “REDEFINES” option. It is a simpler way to deal with these issues, as accessing the same portion of the memory, but in different way, size and type. Obviously, taking care to don’t make mistakes…