Converting 24 bit two's complement from adc to voltage

Hi Folks.

I am having a bit of trouble converting the data from chip adc to actual voltage. The device spits out 3 x 24 bits, 1st for status bits, then channel 1 and channel 2. I am just interested in the middle 24 bits so I only display them.

My code works fine for testing and displaying the internal test signal which is a square wave so I am capturing the data from the device ok. When I test in normal mode which involves simulated signal I think I am not converting the two’s compliment data correctly. I thought a “long” automatically took care of this but I am not getting what I expect. Is it that I am not accounting for the negative values or the unused 8 bits from the 32 bit long correctly? How do I properly convert from two’s compliment to decimal. My code is below.

Thanks,

Mark.

while(1)
 { 
         
          {
          
                  //wait for DRDY==0
                  
                  while((digitalRead(PIN_DRDY)) != LOW)
                  {
                        ;
                  }
                  
                  digitalWrite(PIN_CS, LOW);
                    
                  Data1 = SPI.transfer(0x00); 
                  Data2 =SPI.transfer(0x00); 
                  Data3 =SPI.transfer(0x00); 
                  Data4 =SPI.transfer(0x00); 
                  Data5 =SPI.transfer(0x00); 
                  Data6 = SPI.transfer(0x00);
                  Data7 =SPI.transfer(0x00); 
                  Data8 =SPI.transfer(0x00); 
                  Data9 = SPI.transfer(0x00);
                    
                  
                  digitalWrite(PIN_CS, HIGH);             

                  datacombined = (Data4<<16) | (Data5<<8) | (Data6);
                  
                  voltage = datacombined * 0.0000000481;
                  
                  Serial.println(voltage);
                 
                 
          }
 
 }
          
          

      
}

I presume datacombined is of type long?

You are pasting three bytes together into the 24 low bits of a long, so the top byte of the long will be zero - hence it cannot be negative.

You need to propagate the sign bit left - standard sign-extension for 2's complement, which for 24 to 32 bit could be done like this:

  if (datacombined & 0x00800000)
    datacombined |= 0xFF000000 ;

datacombined is long. The input is from an ecg simulator set to 80bpm at 3mV. The 24 data bit I recieve for this looks like the following:

100100000100000 100001000001001 100000101010111 11111111111111111101111101000100 11001111000011 11111111111111111010110001010100 11111111111111111010000101110111 101111011000001 11111111111111111100101010011100 1101101011010 1100011001010 10101011100111 11111111111111111011110111001110 11111111111111111111100011111110 110111100110001

Should they all be 32 bits long, how can I do this? And then do I need to convert back from two's complement? It should look like an ecg signal but just looks like noise.

What is the part# for your 2-channel 24 bit ADC? Is it HX711? http://forum.arduino.cc/index.php?topic=283926.0

Its texas instruments ADS1292R

Where does the magic number 0.0000000481 from? // 0.0000000481 can also be written as 4.81E-8

does it really have only 3 significant digits?

if so you should think about if you really need 24 bits

(24 bits == ~7 significant digits == far more than 3 significant digits)

+VREF / (2^ 23 – 1) * gain of 6. works out to be 0.0000000481. The 24 bits are from the device every sample. I need to then convert this to voltage using the above formula. Is there a better way to do it?

MarkG123: +VREF / (2^ 23 – 1) * gain of 6. works out to be 0.0000000481. The 24 bits are from the device every sample. I need to then convert this to voltage using the above formula. Is there a better way to do it?

2.42 / 2 / 6 / 2^24 = 4.80811e-8

should i process the combined binary sample data more before I use the formula to convert it to a voltage??

MarkG123: 110111100110001

Should they all be 32 bits long, how can I do this?

They are 32 bits long but the Arduino print function ignores leading zeros. (also for hexa- and decimal)

MarkT:
You need to propagate the sign bit left - standard sign-extension for 2’s complement, which for 24 to 32 bit could be done like this:

if? We don’t need no stinkin’ if

void setup() 
{
  Serial.begin( 115200 );

  long datacombined;
  uint8_t Data4;
  uint8_t Data5;
  uint8_t Data6;

  Data4 = 0x7F; Data5 = 0xFF; Data6 = 0xFF;
  datacombined = ((((long)Data4<<24) | ((long)Data5<<16) | ((long)Data6<<8)) >> 8);
  Serial.println( datacombined );
  Serial.println( (long)0x007FFFFF );

  Data4 = 0x80; Data5 = 0x00; Data6 = 0x00;
  datacombined = ((((long)Data4<<24) | ((long)Data5<<16) | ((long)Data6<<8)) >> 8);
  Serial.println( datacombined );
  Serial.println( (long)0xFF800000 );
}

void loop() 
{
}

(But casting to long before shifting is required.)

Coding Badly could you clarify a few things please.

The code in your example above should that be executed once in setup or in the main loop?

Do the Data4 values you picked have significance or are they just for demonstration purposes?

Why use uint8_t instead of int as the data reads are only 8 bits long and then cast to long. Could it not be long to begin with??

Thanks,

Mark.

MarkG123:
The code in your example above should that be executed once in setup or in the main loop?

This is the only line of significance…

datacombined = ((((long)Data4<<24) | ((long)Data5<<16) | ((long)Data6<<8)) >> 8);

It is a drop-in replacement for the if in MarkT’s post. Use whichever you will understand in six months. I suggest the if.

Do the Data4 values you picked have significance or are they just for demonstration purposes?

Demonstration.

Why use uint8_t…

uint8_t is the type returned by transfer.

…instead of int as the data reads are only 8 bits long…

int is 16 bits. It is not the correct type for collecting data from transfer. It is not the correct type for the transformation to 32 bits.

…and then cast to long.

If the cast to long is excluded, as in your original post, the compiler performs the bitshift using a 16 bit int.

Could it not be long to begin with??

Yes.

However, making them uint8_t and then casting when needed clearly illustrates to the reader what has happened (I read a byte) and what is expected to happen (I am transforming that byte).

    long datacombined = 
       (((long)(signed char)Data4) <<16) 
     | ((long)Data5<<8) 
     | ((long)Data6);

Cast Data4 to a signed char, then cast it to a signed long. The compiler will do the sign extension for you.

Here is a working example.

void setup() {
  Serial.begin(9600);
  Serial.println("PAUSING TO GIVE YOU TIME TO OPEN THE SERIAL CONSOLE :)");
  for (int i = 5; i >= 0; i--) {
    delay(1000);
    Serial.print(i);
    Serial.print(' ');
  }
  Serial.println();
  Serial.println("begin!");

  byte Data4, Data5, Data6;

  Data5 = 0x55;
  Data6 = 0x66;

  long datacombined;

  // positive example

  Serial.println("Positive example");
  Data4 = 0x44;

  datacombined =
       (((long)(signed char)Data4) <<16) 
     | ((long)Data5<<8) 
     | ((long)Data6);

  printlnBits(datacombined);

  Serial.println("Negative example");
  Data4 = 0xC4; // top bit set - a negative number

  datacombined =
       (((long)(signed char)Data4) <<16) 
     | ((long)Data5<<8) 
     | ((long)Data6);

  printlnBits(datacombined);

}

void printlnBits(long b) {
  Serial.print("0B");
  for (int i = 32 - 1; i >= 0; i--) {
    Serial.print((b >> i) & 1 ? '1' : '0');
  }

  Serial.print(" 0x");
  for (int i = 32 - 4; i >= 0; i -= 4) {
    Serial.print(
      (char) (((b >> i) & 0xf) + ( ((b >> i) & 0xf) > 10 ? 'A' - 10 : '0'))
    );
  }

  Serial.print(' ');
  Serial.print(b);
  Serial.println();
}

void loop() {}

–EDIT-- after reading a bit further, I have explicitly added “signed char” even though it isn’t necessary when compiling against the arduino.

PaulMurrayCbr: --EDIT-- after reading a bit further, I have explicitly added "signed char" even though it isn't necessary when compiling against the arduino.

begin!
Positive example
0B00000000010001000101010101100110 0x00445566 4478310
Negative example
0B00000000110001000101010101100110 0x00C45566 12866918

Arduino IDE 1.6.4 / Uno. The signed char cast is required. Even when compiling for the Arduino.

Thanks for the suggestions folks, however none seem to work. When I set the signal generator with a negative offset at around the 3V range I can recover a signal (using Coding Badlys method) whether sine, square or triangular. But I want it to be centred on zero in the millivolt range.

Any ideas??

MarkG123: Thanks for the suggestions folks...

You are welcome.

Any ideas??

I do not know what this means...

But I want it to be centred on zero in the millivolt range.

...so I will not be able to help.

[quote author=Coding Badly link=msg=2224959 date=1431196295] Arduino IDE 1.6.4 / Uno. The signed char cast is required. Even when compiling for the Arduino. [/quote]

How about that.

I had it going ok on a LeoStick. It just goes to show how useful these little proof-of-concept sketches are.

I think it may be to do with the signed numbers. When I put an offset on all the numbers have the same sign, but if I have the signal centred on zero there are both negatives and positives. I think the handling of these is not right. Is there any good links or youtube vids that demonstrate the code you guys provided. I think if I get a better understanding I can debug it better.

There is an example here that uses a slightly different method for combining the data. They also use filtering. https://github.com/Protocentral/Xively_writeYUN7_BPM/blob/master/Xively_writeYUN7_BPM.ino