0

I'm using a 24 bit I2C ADC with the Arduino and there is no 3 byte (24 bit) data type so I instead used the uint32_t which is a 32 bit unsigned int. My actual output however, is a 24 bit signed number as you can see below:

Also here is the code that I used to read the results if you're interested:

uint32_t readData(){
  Wire.beginTransmission(address);
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(address,3);
  byte dataMSB = Wire.read();
  byte data = Wire.read();
  byte dataLSB = Wire.read();
  uint32_t data32 = dataMSB;
  data32 <<= 8;
  data32 |= data;
  data32 <<= 8;
  data32 |= dataLSB;
  return data32;
}

In order for this number to be useful, I need to convert it back to a 24 bit signed integer (I'm not sure how to do that or eve if it's possible because 24 is not a power of 2) so I'm a bit stuck. It would be great if somebody can help me as I'm almost finished with the project and this is one of the last few steps.

gre_gor
  • 6,669
  • 9
  • 47
  • 52
OM222O
  • 310
  • 2
  • 11

3 Answers3

2

The problem is that there’s no safe and portable way to use shifting for sign extension in C — at best it is implementation defined. So if you want to do it portably, you need to convert your 2s-complement value manually into a signed integer.

int32_t cvt24bit(uint32_t val) {
    val &= 0xffffff;  // limit to 24 bits -- may not be necessary
    if (val >= (UINT32_C(1) << 23))
        return (int32_t)val - (INT32_C(1) << 24);
    else
        return val;
}

this will take your 24-bit two’s-complement value in a uint32_t and convert it to a (signed) int32_t.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 1
    This can be done without branching and without as much type decoration with `int 32_t x = val;` (including `& 0xffffff` if desired or suitable) followed by `return x - ((x >> 23 & 1) << 24);`. – Eric Postpischil Dec 25 '18 at 15:28
  • 1
    Actually, if the `& 0xffffff` is included, then the `return` statement can be just `return x - (x >> 23 << 24);`. – Eric Postpischil Dec 25 '18 at 17:00
  • someone mentioned this code: if(input & 0x80000000 != 0) input = input | 0xFF000000; which I think is a lot less resource intensive compared to bit shifting. I'm not sure how it's done in the MCU but if it's actually being shifted with a shift register, that's a lot of wasted cycles. – OM222O Dec 25 '18 at 19:40
  • @OM222O: the shifts here are all constants, so will occur at compile time on any reasonable compiler. In addition, if the branchless code is better for your target, the compiler will probably do that as well. – Chris Dodd Dec 25 '18 at 23:59
2

Conversion from 24-bit two’s complement in a uint32_t to int32_t can be done with:

int32_t Convert(uint32_t x)
{
    int32_t t = x & 0xffffff;
    return t - (t >> 23 << 24);
}

The x & 0xffffff ensures the number has no spurious bits above bit 23. If it is certain no such bits are set, then that line can be just int32_t t = x;.

Then t >> 23 removes bits 0 to 22, leave just bit 23, which is the sign bit for a 24-bit integer. Then << 24 scales this, producing either 0 (for positive numbers) or 224 (for negative numbers). Subtracting that from t produces the desired value.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • This is a nice solution. You write "Conversion from 24-bit two's complement ... can be done with...". But isn't your solution correct also for signed number representations other than two's complement? The only premise is that the most significant bit is the sign bit, isn't it? – Benjamin Bihler Feb 21 '22 at 08:44
  • 1
    @BenjaminBihler: Consider the bits 0x800000, which represent −0 in sign-and-magnitude or −8,388,607 in one’s complement. Starting with these bits in the `uint32_t x`, they represent 8,388,608, so `t` is set to that. Then `t >> 23` is 1 and `t << 24` is 16,777,216. So `t - (t >> 23 << 24)` yields 8,388,608 − 16,777,216 = −8,388,608, so that is what is returned. Even if the type of `t` were a sign-and-magnitude or a one’s complement 32-bit integer type, the arithmetic would produce −8,388,608. – Eric Postpischil Feb 21 '22 at 15:09
-2

Use int32_t instead of unit32_t for data32. Then before returning the value, shift it left by 8, then right by 8 to sign extend it.

So this code:

uint32_t readData(){
  Wire.beginTransmission(address);
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(address,3);
  byte dataMSB = Wire.read();
  byte data = Wire.read();
  byte dataLSB = Wire.read();
  int32_t data32 = dataMSB;
  data32 <<= 8;
  data32 |= data;
  data32 <<= 8;
  data32 |= dataLSB;
  return (data32 << 8) >> 8;
}
gre_gor
  • 6,669
  • 9
  • 47
  • 52
vicatcu
  • 5,407
  • 7
  • 41
  • 65
  • 2
    Just for anyone else looking at this answer remember right shift on a signed integer may or may not sign extend it; you have to check how the compiler is implemented. If you aren't sure then consider just dividing by 256 to guarantee the correct sign is used. https://stackoverflow.com/questions/4009885/arithmetic-bit-shift-on-a-signed-integer – fdk1342 Dec 25 '18 at 05:11
  • When sign bit of the 24-bit value is set, the C standard does not define the behavior of the left shift here. When the 32-bit value is negative, the result of the right shift is implementation-defined. – Eric Postpischil Dec 25 '18 at 12:29
  • Left shift is totally unambiguous... and GCC at least sign extends shift right afaik – vicatcu Dec 26 '18 at 00:38
  • @vicatcu `<<` always shifts in a 0, but `If E1 has a signed type and nonnegative value, and E1 × 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.` So during left shift it is undefined behavior when trying to switch from positive to negative value. – fdk1342 Dec 28 '18 at 03:27