Problem with Bit Fields

I’m using a union to store the config register of a TI TMP117 temperature sensor:

typedef union ConfigRegister {
    struct {
        bool unused : 1;
        bool soft_reset : 1;
        bool alert_pin_select : 1;
        bool pin_pol : 1;
        bool therm_alert_mode : 1;
        uint8_t avg : 2;
        uint8_t conv : 3;
        uint8_t mode : 2;
        bool EEPROM_busy : 1;
        bool data_ready : 1;
        bool alert_low : 1;
        bool alert_high : 1;
    } bits;
    uint16_t reg;

    ConfigRegister() {
        reg = 0;
    };
} ConfigRegister;

I read the register from the sensor, flip the bytes to fix endianness and store the data in reg. If read the value 0x2220 from the sensor, I expect the values in the union to be:

bool unused  = 0b;
bool soft_reset = 0b;
bool alert_pin_select = 0b;
bool pin_pol = 0b;
bool therm_alert_mode = 0b;
uint8_t avg = 01b;
uint8_t conv : 100b;
uint8_t mode : 00b;
bool EEPROM_busy : 0b;
bool data_ready : 1b;
bool alert_low : 0b;
bool alert_high : 0b;

but instead, the values are:

bool unused  = 0b;
bool soft_reset = 0b;
bool alert_pin_select = 0b;
bool pin_pol = 0b;
bool therm_alert_mode = 0b;
uint8_t avg = 01b;
uint8_t conv : 010b;
uint8_t mode : 00b;
bool EEPROM_busy : 1b;
bool data_ready : 0b;
bool alert_low : 0b;
bool alert_high : 0b;

so the most significant byte seems to be shifted by one bit, conv seems to start at the first bit of the most significant byte instead of the last bit of the least significant byte.
I am not sure if this is a straddling issue or not, because I am using a similar union (with bit fields spanning 2 bytes) for a different sensor which works just fine:

    struct {
        uint8_t unused0 : 4;
        uint16_t value : 12;

        bool unused1 : 1;
        uint8_t pd_mode : 2;

        uint8_t unused2 : 2;
        uint8_t write_mode : 3;
    } bits;
    uint8_t content[3];

    DataWrite() {
        memset(content, 0, 3);
    };
} DataWrite;

I’m compiling with arm gcc 9.2.1 (mbed 6.2.0 for a STM32F407)
Does anyone know what the issue could be?

Yes, it’s a straddling issue. conv is of type uint8_t, i.e. it’s a byte. And it will never be placed across a byte boundary.

The solution is to declare conv as a uint16_t. Then it can cross byte boundaries at odd offsets (but not at even offsets).

1 Like

Thank you for your response. It was indeed a straddling issue, the other union I was using did not exhibit this issue because the fields happened to be aligned correctly.
I have chosen to go the safe route and dropped the bit field for this application.

With some compilers you can also use the ‘packed’ attribute.
Susan

you are correct, this should also work as a solution:

#pragma pack(1)
typedef union ConfigRegister {
    struct {
        bool unused : 1;
        bool soft_reset : 1;
        bool alert_pin_select : 1;
        bool pin_pol : 1;
        bool therm_alert_mode : 1;
        uint8_t avg : 2;
        uint8_t conv : 3;
        uint8_t mode : 2;
        bool EEPROM_busy : 1;
        bool data_ready : 1;
        bool alert_low : 1;
        bool alert_high : 1;
    } bits;
    uint16_t reg;

    ConfigRegister() {
        reg = 0;
    };
} ConfigRegister;

I chose not to do it because I found that

It’s undefined behavior to read from the member of the union that wasn’t most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

https://en.cppreference.com/w/cpp/language/union