Bitfields, unions, and surprising results

I haven't touched bit fields (or unions) in over 20 years, but last night I was helping a friend with a project where he has 8 12-bit values that need to be SPI'd to a device as 96bits (or 12 bytes). Instead of bit manipulations, I suggested using a union of a bit field structure and a unsigned char array. I know that the order of bit fields is compiler dependent, but thought that would be easy to determine.

HOWEVER, I got some results, which I find undeciferable. Here is the test code I created.

#include <Arduino.h>
#include <SPI.h>

const int SPI_CLK = 13;
const int SPI_IN = 11;
const int SPI_OUT = 12;
const int SPI_CS = 10;

struct digit
{
  unsigned int a:6;
  unsigned int b:6;
  unsigned int c:6;
  unsigned int d:6;  
};

union shared_digits
{
  struct digit digits;
  unsigned char byte_array[3];
};

void setup() {
  Serial.begin(9600);
  Serial.print("sizeof(shared_digits)=");
  Serial.println(sizeof(shared_digits));             // Should be 3 bytes

  union shared_digits ti_chip;        

  // Assign some values to the ti_chip structure
  // 11111101 01010000 00101010  0xFD 0x50 0x2A
  ti_chip.digits.a = B111111;
  ti_chip.digits.b = B010101;
  ti_chip.digits.c = B000000;
  ti_chip.digits.d = B101010;

  Serial.print("ti_chip.digits.a = ");
  Serial.println(ti_chip.digits.a, BIN);
  Serial.print("ti_chip.digits.b = ");
  Serial.println(ti_chip.digits.b, BIN);
  Serial.print("ti_chip.digits.c = ");
  Serial.println(ti_chip.digits.c, BIN);
  Serial.print("ti_chip.digits.d = ");
  Serial.println(ti_chip.digits.d, BIN);
  
  Serial.print(ti_chip.byte_array[0], BIN);
  Serial.print(" ");
  Serial.print(ti_chip.byte_array[1], BIN);
  Serial.print(" ");
  Serial.println(ti_chip.byte_array[2], BIN);
  // Displays the following results
  // 01111111 00000101 10101000  0x7F 0x05 0xAB
  
  pinMode(SPI_CS, OUTPUT);
  SPI.begin();
}

void loop() {
}

As you can see it appears to be swapping nibbles, but the first and last values are changing one of those nibbles. Any suggestions, or is this approach not functional with modern C? (Or is my memory disfunctional and was it never a valid approach)

This is the expected behavior, since Arduino is little endian. This means that the bit fields of every byte are reversed:
Big endian:
111111.01 - 0101.0000 - 00.101010
Little endian (bit fields are swapped within byte):
01.111111 - 0000.0101 - 101010.00

( . separates bit fields, - separates bytes)

Pieter

Note that one nibble in both the first and last bytes are not simply being reverse (which I expected), but actually changed.

Input byte pattern

// 11111101 01010000 00101010 0xFD 0x50 0x2A

While the output is

// 01111111 00000101 10101000 0x7F 0x05 0xAB

So in the first byte the D is getting changed to a 7 while in the last byte the 2 is getting changed to an A

1111 11.01 - 0101.0000 - 00.10 1010 // Big Endian
1111 11.10 - 1010.0000 - 00.01 0101 // Little Endian (bits within each bit field are reversed)
01.11 1111 - 0000.0101 - 1010 10.00 // Little Endian printed as Big Endian (bits within each byte are reversed)

When converting to HEX, you only look at the different nibbles, but your bit fields don't match up with the nibbles, so the HEX value changes. In the second byte, it does match up with the bit fields, so the HEX representation remains the same.

Pieter

PieterP:
This is the expected behavior, since Arduino is little endian. This means that the bit fields of every byte are reversed:
Big endian:
111111.01 - 0101.0000 - 00.101010
Little endian (bit fields are swapped within byte):
01.111111 - 0000.0101 - 101010.00

( . separates bit fields, - separates bytes)

Pieter

For instance if one takes just the first byte as an example; 11111101 if the nibbles are swapped, shouldn't it be 1101111?

The nibbles aren't swapped. The bit fields are swapped.

Pieter

It appears the nibbles are correct based on 6-bit swapping high/low nibbles, except the last value of 0xAB doesn't match the binary representation.

PieterP:
The nibbles aren't swapped. The bit fields are swapped.

Pieter

Ah, thanks! I knew i must be missing something obvious!