Concatenate Bytes into single value

Hello Everyone,

So i am working on a project that entails reading conversion data from an ADC that communicates via SPI. The ADC generates 24 bit data in two's compliment. The problem is that the conversion data for any of the given channels comes in the form of 3 consecutive bytes (the first byte being the MSB and so on). I can store the three bytes into an array of bytes. The problem is that i have not found a way to combine/transform this data into what it is actually supposed to represent.

I thank you in advance for your response.

the first byte being the MSB and so on

That may be a clue.

crys17:
Hello Everyone,

So i am working on a project that entails reading conversion data from an ADC that communicates via SPI. The ADC generates 24 bit data in two's compliment. The problem is that the conversion data for any of the given channels comes in the form of 3 consecutive bytes (the first byte being the MSB and so on). I can store the three bytes into an array of bytes. The problem is that i have not found a way to combine/transform this data into what it is actually supposed to represent.

I thank you in advance for your response.

tip: google the c data type 'union'

Which ADC?

If you have 3 bytes, and they represent D23-D16, D15-D8, and D7-D0 of your data, then simple shifting and ORing of data will work:

unsigned long combined = 0; // clear it out
combined = (byte2<<16) | (byte1<<8) | (byte0);
  • would also work because bit-wise 0+0 = 0, 1+0 = 1, and since you started with 0, 1+1 cannot happen.
combined = (byte2<<16) + (byte1<<8) +(byte0);
1 Like

You have to shift these values, using the left-shift operator ‚Äė<<‚Äô and add them together. Since Arduinos do not have 24 bit integers, you will have to use a ‚Äėlong int‚Äô which is 32 bits which means you need to take care if your number is negative

  long int value;

  value = ((long)val[0] << 16) + ((long)val[1] << 8) + ((long)val[2]);
  if ( value > 0x7FFFFF ) value = value - 0x1000000;
1 Like

CrossRoads:

unsigned long combined = 0; // clear it out

combined = (byte2<<16) | (byte1<<8) | (byte0);

Considering UNO and 16-bit default processing buffer size, I am afraid that the above codes will not produce the desired result without casting,

Demonstration codes-A: (does not produce 234567 but 4567 for the given data)

byte byte2 = 0x23;
byte byte1 = 0x45;
byte byte0 = 0x67;

void setup()
{
  Serial.begin(9600);
  unsigned long combined = 0; // clear it out
  combined = (byte2 << 16) | (byte1 << 8) | (byte0);
  Serial.print(combined, HEX);  //shows: 4567 and not 234567
}

void loop()
{

}

Demonstration Codes-B: (produces 234567 when casting is done)

byte byte2 = 0x23;
byte byte1 = 0x45;
byte byte0 = 0x67;

void setup()
{
  Serial.begin(9600);
  unsigned long combined= 0; // clear it out
  long x2 = (long)byte2<<16;
  long x1 = (long)byte1<<8;
  long x0 = (long)byte0;
  combined = x2 | x1 | x0;
  Serial.print(combined, HEX);  //shows: 234567
}

void loop() 
{
  
}

@OP
The following diagram depicts the conceptual view of the storage mapping of your 24-bit ADC data as discrete 3-byte data of an array. It also shows the 24-bit integrated piece when the bytes are combined/oriented together. You can write codes for this diagram following the above Demonstration Codes-B.
array-4.png

array-4.png

1 Like

Thank you for the responses. Sorry it took so long to get back but i was expecting an email notification from the forum and when i checked messages there was nothing so i had to manually go tot the thread.

The ADC is an ADS1261. And as a lot of people have mentioned i have to do bit shifting. I have done something like this in my code but i am doing it incorrectly since i am getting inconsistent values jumping from - 100000 and something to plus 40000 so i am guessing i am either doing the bit shifting wrong or i am not addressing the two’s complement conversion properly. I will try the code examples given in the replies. Thanks again for all the replies.

crys17:
Thank you for the responses. Sorry it took so long to get back but i was expecting an email notification from the I have done something like this in my code but i am doing it incorrectly since i am getting inconsistent values jumping from - 100000 and something to plus 40000 so i am guessing i am either doing the bit shifting wrong or i am not addressing the two’s complement conversion properly. I will try the code examples given in the replies. Thanks again for all the replies.

Since your data is 24-bit twos compliment (sounds like an HX711), you need to do sign extension to produce a 32-bit signed integer:

void setup() {
  int32_t combined = 0;
  uint8_t byte2 = 0x80;
  uint8_t byte1 = 0x12;
  uint8_t byte0 = 0x34;

  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting");
  combined = ((uint32_t)byte2 << 16) | ((uint32_t)byte1 << 8) | (uint32_t)byte0;
  Serial.print("Before Sign Extension: ");
  Serial.println(combined, HEX);

  if (combined & 0x800000) {
    combined |= 0xFF000000;
  }
  Serial.print("After Sign Extension: ");
  Serial.println(combined, HEX);
}

void loop() {
}

Output:

Starting
Before Sign Extension: 801234
After Sign Extension: FF801234

I see someone beat me to it, but this should product the same results:

value = (((val[0] & B10000000) == 0) ? 0x00000000 : 0xFF000000) | ((long)val[0] << 16) | ((long)val[1] << 8) | ((long)val[2]);

david_2018:
I see someone beat me to it, but this should product the same results:

value = (((val[0] & B10000000) == 0) ? 0x00000000 : 0xFF000000) | ((long)val[0] << 16) | ((long)val[1] << 8) | ((long)val[2]);

Your code works well?

byte val[3] = {0x23, 0x45, 0x67};

void setup()
{
  Serial.begin(9600);
  long value = (((val[0] & B10000000) == 0) ? 0x00000000 : 0xFF000000) | ((long)val[0] << 16) | ((long)val[1] << 8) | ((long)val[2]);
  Serial.print(value, HEX);  //shows: 234567
}

void loop()
{

}

But why is it so cryptic?

Things should be literate -- simple, readable, communicable, reproducible, and modifiable?

Three bytes discrete data; they are to be combined into 24-bit data. That means that the bytes are be placed in the following fashion.
array-4x.png

1. val[1] must be place to the left of val[2] and then ORed with val[2] to get 16-bit.
2. val[0] must be placed before val[1]val[2] and then ORed with val[1]val[2] to get 24-bit.

Your code certainly contains the above operations; unfortunately, they are not clear from the understanding level of the OP. We can exercise simplified and logical codes/steps?

byte val[3] = {0x23, 0x45, 0x67};

void setup()
{
  Serial.begin(9600);
  //long value = (((val[0] & B10000000) == 0) ? 0x00000000 : 0xFF000000) | ((long)val[0] << 16) | ((long)val[1] << 8) | ((long)val[2]);
  //byte x1 = val[1];
  int x1 = (int)val[1]<<8;  //casting is necessary to expose the 16-bit processing buffer size
  x1 = x1|(int)val[2];          //casting is done to male val[2] 16-bit by placing 8 zeros to its left
  Serial.println(x1, HEX);  //shows 4567
  //--------------------
  //byte x0 = val[0];
  long x0 = (long)val[0]<<16; //casting enlarges processing buffer size so that 8-bit val[0] is not lost 
  long value = x0|(long)x1;
  Serial.println(value, HEX);  //shows: 234567
}

void loop()
{

}

array-4x.png

You can avoid casting all together:

void setup() {
  int32_t combined = 0;
  uint8_t byte2 = 0x80;
  uint8_t byte1 = 0x12;
  uint8_t byte0 = 0x34;

  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting");

  combined = byte2;
  combined = (combined << 8) | byte1;
  combined = (combined << 8) | byte0;

  Serial.print("Before Sign Extension: ");
  Serial.println(combined, HEX);

  if (combined & 0x800000) {
    combined |= 0xFF000000;
  }
  Serial.print("After Sign Extension: ");
  Serial.println(combined, HEX);
}

void loop() {
}

Ok so i still can’t seem to get it to work correctly. This is the code i wrote for the conversion:

       data_buff[2] = SPI.transfer(0x00);
       data_buff[1] = SPI.transfer(0x00);
       data_buff[0] = SPI.transfer(0x00);
     
    raw_data = ((uint32_t)data_buff[2]<<16) | ((uint32_t)data_buff[1]<<8) | (uint32_t)data_buff[0];
    if (raw_data & 0x800000)
        {
          raw_data |= 0xFF000000;
        }

data_buff is an array of size 3 of type unit8_t and raw data is int32_t. The channel (set to differential mode )that i am reading from is connected to a wheatstone bridge that is balanced so i would expect the output to be close to zero instead i am seeing random values with no discernible pattern(tried also to unbalance the bridge manually and see if there was a pattern in the shift in values but it doesn’t look like it changes any differently then the random situation).Of course the best option would be to plot the data in a graph and see how it actually varies but before that i want to make sure that i am doing the conversion right.

Again thanks for all the responses

Have your Serial Printed out the individual elements of the data_buff array? Maybe you're getting garbage data to begin with. Next time post Complete code.

gfvalvo:
Have your Serial Printed out the individual elements of the data_buff array? Maybe you’re getting garbage data to begin with. Next time post Complete code.

The full code is here:

#include<SPI.h>
#include <stdio.h>



const int CS = 53;
const int DRDY = 7;
const int PWDN = 5;
const int RESET = 6;
const int START_HW = 8;
bool flag = false;
bool flag2 = false;
uint8_t data_buff [3];
char char_buff[24];

int value = 0;
int cmd = 0;
String cmd_id;
int address = 0;
int ADC_regval;
int32_t raw_data;
int x;
uint32_t buffer;  //24 bits of data
uint8_t size = 3;   //number of bytes
String ss;
String ss2;
int total;
int identifier;
int reg_id;
int r_reg;
int w_reg;

// Commands //
#define NOP     0x00 
#define RESET   0x06 
#define START   0x08 
#define STOP    0x0A 
#define RDATA   0x12 
#define SYOCAL  0x16 
#define GANCAL  0x17
#define SFOCAL  0x19 
#define RREG    0x20
#define WREG    0x40 
#define LOCK    0xF2 
#define UNLOCK  0xF5

//Register rrh address//
//(when writing and reading use this value plus the base WREG and RREG address)
#define ID      0x00 
#define STATUS  0x01 
#define MODE0   0x02 
#define MODE1   0x03 
#define MODE2   0x04 
#define MODE3   0x05 
#define REF     0x06 
#define OFCAL0  0x07 
#define OFCAL1  0x08 
#define OFCAL2  0x09 
#define FSCAL0  0x0A 
#define FSCAL1  0x0B 
#define FSCAL2  0x0C 
#define IMUX    0x0D 
#define IMAG    0x0E 
#define RESERVED  0x0F 
#define PGA     0x10 
#define INPMUX  0x11 
#define INPBIAS 0x12 

void ADC_settings()
{
  W_ADC_REG(STATUS,0x04);
  W_ADC_REG(MODE0,0x64);
  W_ADC_REG(PGA,0x85);
  W_ADC_REG(INPMUX,0x21);
  identifier = 0;
}


void display_param()

{
    if (identifier == 32)
    {
      Serial.println();
      Serial.print("RegID:");
      Serial.print(reg_id);
      Serial.print("\t");
      Serial.print("Reg_Value:");
      Serial.println(ADC_regval);
     
      delay(1000); 
    }
    else if(identifier == 64)
    {
      Serial.println();
      Serial.print("Modieified Register at address:");
      Serial.println(x);
      delay(100);
    }
    else if(identifier == 18)
    {
      Serial.println();
      Serial.print("\t");     
      Serial.print("DATA:");
      Serial.println( raw_data);
      Serial.print("\t");
       Serial.print(data_buff[0],BIN);
       Serial.print("\t");
       Serial.print(data_buff[1],BIN);
       Serial.print("\t");
       Serial.print(data_buff[2],BIN);
       Serial.print("\t");
    
      
      delay(10);
    }
    else
    {
      Serial.println();
      Serial.print("Executed command:");
      Serial.print(cmd_id);
      delay(1000);
    }
      
}


void read_reg_address()
{
  if (flag2 == false)
  {
    flag = true;
  }
  else 
  {
    flag = false;
  }
  if (Serial.available() > 0){
    
     ss2 = Serial.readString();
     
             r_reg = ss2.toInt();
             if (r_reg == 19)
                {
                  flag = false;
                  flag2 = true;
                  identifier = 0;
                }
             else
             {
              
               reg_id = r_reg;
               R_ADC(r_reg);   
             }
  }
 
}

void W_ADC_REG(int address, int value) {

  digitalWrite(CS, LOW);
  delay(100);
  SPI.transfer(WREG + address);
  x = SPI.transfer(value);
  delay(100);
  digitalWrite(CS, HIGH);
  display_param();
}

int R_ADC(int address){
  
  digitalWrite(CS, LOW);
  delay(100);
  SPI.transfer(RREG + address);
  SPI.transfer(0x00);
  ADC_regval =SPI.transfer(0x00);
  delay(100);
  digitalWrite(CS, HIGH);
}


int ADC_CMD(int cmd) {
  // take the CS pin low to select the chip:
  
  if (cmd == RDATA )
     {
        //if (digitalRead(DRDY)==LOW)
       // {
        digitalWrite(CS, LOW);
        delay(100);
        //  send in the address and value via SPI:
       SPI.transfer(cmd);
       SPI.transfer(0x00);
  
       SPI.transfer(0x00);
       data_buff[2] = (uint8_t)SPI.transfer(0x00);
       data_buff[1] = (uint8_t)SPI.transfer(0x00);
       data_buff[0] = (uint8_t)SPI.transfer(0x00);
     //  raw_data = data_buff[2]|(data_buff[1]<<8)|(data_buff[0]<<16);
    raw_data = ((uint32_t)data_buff[2]<<16) | ((uint32_t)data_buff[1]<<8) | (uint32_t)data_buff[0];
    if (raw_data & 0x800000)
        {
          raw_data |= 0xFF000000;
        }
  
       delay(100);
       digitalWrite(CS, HIGH);
       // }
       // else 
       // {
       //  identifier = 18;
       // }
     }
  else
     {
       digitalWrite(CS, LOW);
       delay(100);
       //  send in the address and value via SPI:
       SPI.transfer(cmd);
       SPI.transfer(0x00);
       delay(100);
       // take the CS pin high to de-select the chip:
       digitalWrite(CS, HIGH);
      }
}


void setup() {
   pinMode(DRDY, INPUT);
   pinMode(PWDN, OUTPUT);
   pinMode(RESET, OUTPUT);
   pinMode(START_HW, OUTPUT);
   pinMode(CS, OUTPUT);
   Serial.begin(9600); 
   digitalWrite(CS, LOW);
   
   
   SPI.begin();
   SPI.beginTransaction(SPISettings(9000000, MSBFIRST, SPI_MODE1));
   SPI.endTransaction();
   //SPI.setBitOrder(MSBFIRST);
  // SPI.setClockDivider(SPI_CLOCK_DIV4);
   //SPI.setDataMode(SPI_MODE1);
   digitalWrite(CS, HIGH);
   digitalWrite(PWDN, HIGH);
   digitalWrite(RESET, HIGH);
   digitalWrite(START_HW, HIGH);
  // initialize SPI:
  
  //SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
  //SPI.endTransaction();
 

}

void loop() {

I didn’t post earlier because it is rather large and not completely cleaned up from vestiges of previious iterations also the site complains that it exceeds the maximum length so i will put a smaller version of it.What ist’s missing is the loop that symply is used to switch between different modes of operation. I implemented a number of functions into the code in order to more easily modify the registers of the ADC and give it commands.

One thing that i notice when i look at the direct binary output of each data_buff element is a lot of truncated values (example instead of 00001110 i see 1110) and i am not sure if this is just done by the print function or if the values are actually truncated inside the variables(i am thinking not though since i am casting each incoming byte to uint8_t now ).

crys17:
One thing that i notice when i look at the direct binary output of each data_buff element is a lot of truncated values (example instead of 00001110 i see 1110) and i am not sure if this is just done by the print function or if the values are actually truncated inside the variables(i am thinking not though since i am casting each incoming byte to uint8_t now ).

That is the way Serial.print() works. It does not print leading zeros.

The print function will suppress leading zeros. But, you're missing the point of the exercise. The first question to answer is whether you're correctly concatenating the bytes into the int32_t. So, print out each of the individual data bytes, in HEX. The leading zeros will be suppressed for values less than 0x10, so assume it's there. Then, print out the values of the resultant int32_t, again in HEX. Now check if the individual bytes have been shifted to their proper places in the 32-bit word. Check it with paper and pencil. If it's correct, then your problem lies elsewhere.