Wednesday, September 25, 2013

Combine 2 bytes into int on an Arduino

Recently I have been involved in a project using an iRobot Create. While writing programs for it, I reached an irritating roadblock. The incoming sensor values are transmitted over serial one byte at a time, but the values that actually had meaning were the int values that resulted when the two bytes were combined. Let me clarify.

I receive 2 values, 1 and 213. Now these two numbers are actually stored as 8 binary bits. The values of those bits are 00000001 and 11010101 respectively (HERE is a convenient calculator). The value I want is the 16 bit int variable that results when these two are added together. ie I want the number represented by 0000000111010101; that would be 469. Sounds like I need to do some Arduino data type conversion.

I Googled around and asked one of my computer science friends, but there doesn't appear to be a ready made solution for this. I should point out that if you are reading the value directly from a serial port, you can just use parseInt(). My problem is that I am using an external library to get the sensor data and then need to combine the bytes afterward.

After some deliberation I acquired a list of ways I thought I could combine two 8-bit bytes into one 16- bit int (or short) variable. Some of these ways I have not pursued, but a few I have. I have tested them, and clocked the processing times for a few values on an Arduino Mega2560 r3.

Combination using bitRead(): 108 microseconds
This is the first method I thought of. It reads each bit in the byte and then copies it over to the int. This is not very efficient, but it does get the job done. It does take quite a bit more clock time than the other methods, but it is a rather intuitive approach.

int BitReadCombine( unsigned int x_high, unsigned int x_low)
{
  int x;
  for( int t = 7; t >= 0; t--)
  {
    bitWrite(x, t,  bitRead(x_low, t));
  }
  for( int t = 7; t >= 0; t--)
  {
    bitWrite(x, t + 8,  bitRead(x_high, t));
  }
  return x;
}
Combination using bit shifting: 4 microseconds
In this method, a bit shift operator is applied to move the high byte into the correct position in the final int. This, to me, is the most elegant solution. I don't know that you will find anything much faster. Note that the micros() function has a resolution of 4, so measuring something like this directly is a little difficult. Code modified from HERE.
int BitShiftCombine( unsigned char x_high, unsigned char x_low)
{
  int combined;
  combined = x_high;              //send x_high to rightmost 8 bits
  combined = combined<<8;         //shift x_high over to leftmost 8 bits
  combined |= x_low;                 //logical OR keeps x_high intact in combined and fills in                                                             //rightmost 8 bits
  return combined;
}
Combination using multiplication: 4 microseconds
This method is basically identical to the one above. I read conflicting arguments for whether it would be any slower or not. If it is, it is pretty negligible. Basically, instead of shifting the bits using a bit shift, I  just multiply by 256. This is like if I wanted to shift the "1" in 10 to the 1000th place. I would multiply by 100 (10^2). In binary I want to shift it 8 places, so I multiply the variable by 2^8 or 256.

int MultiplicationCombine(unsigned int x_high, unsigned int x_low)
{
  int combined;
  combined = x_high;
  combined = combined*256;
  combined |= x_low;
  return combined;
}
Other possibilities
It was suggested to me that I should use strings. This seems like a rather roundabout way of getting there, but I would think it would work. You need to combine each byte into a binary string and then concatenate them by adding them together. Then you can convert them back to an int and you're good to go. This could be accomplished several ways with one being the itoa and atoi functions.

Edit: Thanks to Nigel Parker for adding another method and elaborating calculating the speed of each more precisely. In his comment he details how to use the union constructor to combine bytes into different data types. I have saved his code in a sketch available HERE. He notes that this method can be faster than any of the previous methods mentioned when used correctly.

Conclusion
There are probably other methods of combing bytes into an int that I have not looked at. If so, post a link in the comments. That will only broaden the scope of this post. All of the methods here could be adapted to match a 32 bit long if necessary and could be put into an unsigned variable just as easily as a signed one. With that in mind, I will probably use the bit shift method from now on. It seems like the most elegant and efficient solution.

I hope this post helped someone out. If you want my script to check the clock times for yourself, find it HERE. It also might help if you're a little fuzzy on exactly what is going on. If something doesn't work for you, comment below, and I'll do my best to help you.

-Matthew

3 comments:

  1. There is a construct in C designed for use in these circumstances: the union. Its use is probably discouraged in an academic setting because it introduces machine dependencies, but this problem is machine dependent so its use is appropriate here. I've used it in embedded systems to have byte access to int, long, float and double, and to help in translating in communication between systems with different endedness. First a declaration:

    union flintbyte {
    float ff; //to gain access to bytes of float
    unsigned int ii; //to gain access to bytes of int
    unsigned char cc[4]; //as many as needed to cover the largest other element
    } fib;

    and to use it:

    fib.ii=0; //initialise to ensure top 2 bytes are clear, not needed if these are set in the usage code

    fib.cc[0]=213; //set the lo byte
    fib.cc[1]=1; //2ls byte
    Serial.print("Concatenation of 1 and 213=");
    Serial.println(fib.ii);
    Serial.print('=');Serial.println(fib.ii,HEX);
    Serial.print('=');Serial.println(fib.ii,BIN);

    fib.ff=1.23; //load a float
    Serial.print(fib.ff);Serial.print('=');
    Serial.print(fib.cc[3],HEX);Serial.print(','); //print the bytes, 3F,9D,70,A4
    Serial.print(fib.cc[2],HEX);Serial.print(',');
    Serial.print(fib.cc[1],HEX);Serial.print(',');
    Serial.println(fib.cc[0],HEX);
    fib.cc[0] ^= 1; //toggle LSB of mantissa in IEEE754
    Serial.print(fib.ff);Serial.print(',');
    Serial.println((fib.ff-1.23)*1000000.0); //difference is 1.2e-7

    No calling of routines, no calculations, just the load and access that you have to do anyway - can't get faster than that. Actually, under some circumstances you can use a cast to avoid even those, but the problem as stated does not lend itself to that.:

    float afloat=1.23;
    (*((flintbyte *)(&afloat))).cc[2] ^= 0x40; //flip MSB of mantissa
    Serial.println(afloat); //adds 0.5 in this case



    ...but it starts to get a bit obscure, although useful if you need to wring out every last bit of performance - the flip MSB line would translate to an absolute minimum of assembly, probably one instruction.

    ReplyDelete
  2. Thanks for your time in such a details comment. That is useful. I have updated the post to include this and have saved your example in a sketch in case this comment should disappear for whatever reason.


    Thanks again for your helpful input
    Matthew

    ReplyDelete
  3. Thanks man :-)

    ReplyDelete