Search code examples
c++castingintegercan-bus

Can I convert an unit16_t to an "uint12_t"?


I have the issue that I need to compress two uint16_t numbers into an 2byte space. I know that I lose information but the numbers higher than 12bit are not relevant for me. The first 12bit shall be the one number and the last 12bit shall be the secound. The output is needed for an CAN Message. Has anyone an idea on how to solve this problem in C++?

this was my first try but it resulted in two bytes sized numbers as expected.

  byte OUT[2];

  union OUT_byte {
    byte bufferOUT[2];
    uint16_t OUT_array_int;
  } OUTb;

  OUTb.OUT_array_int = (double)(round(INPUT)); // casting from an double originaly

  for (int i = 0; i < sizeof(OUTb.bufferOUT); i++) {
    OUT[i] = OUTb.bufferOUT[i];
  }

This gives me two bytes an an array with my information. Now I need two numbers compressed to 12bit in 3bytes.


Solution

  • This is normally solved by using a large enough type by a multiple of 8 bits. So in your case you would use uint16_t and ignore the 4 top bits.

    However, if you actually need a bit-field consisting of multiple 12 bit adjacent chunks, you'd use a uint8_t array instead. Example (assuming big endian):

    uint8_t can_data[8] =
    {
        (data0 & 0xFF0) >> 4, 
        (data0 & 0x00F) << 4 | (data1 & 0xF00) >> 8, 
        (data1 & 0x0FF), 
        ...
    };
    

    That is:

    • data0 bits 11-4 into 1st byte bit 7-0
    • data0 bits 3-0 into 2nd byte bits 7-4
    • data1 bits 11-8 into 2nd byte bits 3-0
    • data 1 bits 7-0 into 3rd byte bits 7-0

    ... and so on. As you can tell, this is a very cumbersome format to work with. It is a space optimization which comes at the cost of readability and performance both. So we'd typically avoid this format unless as a last resort. I have actually used something very similar myself in the real world, when I designed a compact wireless protocol that contained multiple 12 bit analog values.

    In the world of CAN, then notably CANopen decided to make all analog values 16 bit, even though not many devices actually use that high resolution. It saved them from a lot of fuss like the above bit packing exercise though, so it was probably the correct call for an industry standard.