cancel
Showing results for 
Search instead for 
Did you mean: 

Maintaining bit definition order in bitmapped structures

RBack.1
Senior

I am trying to use bitmapped structures to send data to an i2c DAC. I defined the fields as:

typedef enum {
    mcp4726_command_code_write_volatile_dac_register = 0b000,
    mcp4726_command_code_write_volatile_memory = 0b010,
    mcp4726_command_code_write_all_memory = 0b011,
    mcp4726_command_code_write_volatile_configuration_bits = 0b100
} mcp4726_command_code_e;
 
typedef enum {
    mcp4726_config_ref_select_vdd = 0b00,
    mcp4726_config_ref_select_vref_unbuffered = 0b10,
    mcp4726_config_ref_select_vref_buffered = 0b11
} mcp4726_config_ref_select_e;
 
typedef enum {
    mcp4726_config_power_down_select_not_powered_down = 0b00,
    mcp4726_config_power_down_select_powered_down_1k = 0b01,
    mcp4726_config_power_down_select_powered_down_100k = 0b10,
    mcp4726_config_power_down_select_powered_down_500k = 0b11
} mcp4726_config_power_down_select_e;
 
typedef enum {
    mcp4726_config_gain_select_1x = 0b0,
    mcp4726_config_gain_select_2x = 0b1
} mcp4726_config_gain_select_e;
 
typedef struct {
    mcp4726_command_code_e              command_code : 3;
    mcp4726_config_ref_select_e         ref_select: 2;
    mcp4726_config_power_down_select_e  power_down_select: 2;
    mcp4726_config_gain_select_e        gain_select: 1;
    unsigned                            data: 12;
    unsigned                            unused: 4;
} __attribute__((packed)) mcp4726_config_t;

I am then defining my i2C data as:

mcp4726_config_t data;
    data.command_code = mcp4726_command_code_write_all_memory;
    data.ref_select = mcp4726_config_ref_select_vdd;
    data.power_down_select = mcp4726_config_power_down_select_not_powered_down;
    data.gain_select = mcp4726_config_gain_select_1x;
    data.data = MCP4726_CONFIG_DAC;
    data.unused = 0;

This should result in three bytes: [0x60, 0x00, 0x00]. What I'm actually getting, however, is [0x03, 0x00, 0x00]. Is there some other way that I can force these bitmaps to be in the order I expect?

6 REPLIES 6
TDK
Guru

This is compiler-dependent. There is no way to force the order, but it should be consistent for the same compiler, so changing the order of the field definitions will fix it.

Could also try:

https://stackoverflow.com/questions/6728218/how-to-enforce-the-struct-bit-order-with-the-gcc-compiler

If you feel a post has answered your question, please click "Accept as Solution".

Thanks, I suspected that. I guess I'll rewrite to build the bytes explicitly. It'll be ugly but not compiler dependent.

TDK
Guru

I think compiler dependent code is fine. Put a sanity check in there to verify the ordering is correct so if something changes in the future, you can spot it quickly.

But yes, bit masks are the typical way of doing this.

If you feel a post has answered your question, please click "Accept as Solution".
gbm
Lead II

It's architecture-dependent, not compiler-dependent, so it will be the same with any compiler for ARM, other than "packed" attribute. There are few problems with your declarations. Declare all the small fields as uint8_t with proper widths and see what happens - watch the possible compiler warnings (if any). Using enums for this purpose may be a trap, since enums are signed and the compiler may complain about truncation yielding incorrect value (the value will be correct, but the warning may appear). If this happens, convert enums to #defines.

Oh, and don't use typedefs...:) - definitely not a modern programming practice.

struct mcp4726_config_ {
    uint8_t command_code : 3,
         ref_select: 2,
         power_down_select: 2,
         gain_select: 1,
    uint16_t data: 12;
};
struct mcp4726_config_ __attribute__((packed)) mcp4726_config;

 Another possibility: all fields uint32_t, no "packed" attribute, but then the compiler-reported sizeof will be 4 with the last byte unused.

Well I converted to explicit masks and shifts last night, but I am interested in your solution. I will try it after work today. Thanks!

Ordering of bits/bitfields within bytes is LSbit-first, in the same way as ordering of bytes within words is LSByte-first. So, I reordered the bitfields within the struct:

    typedef struct {
        mcp4726_command_code_e              command_code : 3;
        mcp4726_config_ref_select_e         ref_select: 2;
        mcp4726_config_power_down_select_e  power_down_select: 2;
        mcp4726_config_gain_select_e        gain_select: 1;
        unsigned                            data: 12;
        unsigned                            unused: 4;
    } __attribute__((packed)) mcp4726_config_t;
 
    typedef struct {
        mcp4726_config_gain_select_e        gain_select: 1;
        mcp4726_config_power_down_select_e  power_down_select: 2;
        mcp4726_config_ref_select_e         ref_select: 2;
        mcp4726_command_code_e              command_code : 3;
        unsigned                            data: 12;
        unsigned                            unused: 4;
    } __attribute__((packed)) mcp4726_new_config_t;
 
    union {
      mcp4726_config_t data;
      uint8_t bytes[4];
    } volatile mcp4726;
 
    union {
      mcp4726_new_config_t data;
      uint8_t bytes[4];
    } volatile mcp4726_new;

initialized both in the same way:

        mcp4726.data.command_code = mcp4726_command_code_write_all_memory;
        mcp4726.data.ref_select = mcp4726_config_ref_select_vdd;
        mcp4726.data.power_down_select = mcp4726_config_power_down_select_not_powered_down;
        mcp4726.data.gain_select = mcp4726_config_gain_select_1x;
        mcp4726.data.data = 0; // MCP4726_CONFIG_DAC;
        mcp4726.data.unused = 0;
 
        mcp4726_new.data.command_code = mcp4726_command_code_write_all_memory;
        mcp4726_new.data.ref_select = mcp4726_config_ref_select_vdd;
        mcp4726_new.data.power_down_select = mcp4726_config_power_down_select_not_powered_down;
        mcp4726_new.data.gain_select = mcp4726_config_gain_select_1x;
        mcp4726_new.data.data = 0; // MCP4726_CONFIG_DAC;
        mcp4726_new.data.unused = 0;

and that results in:

(gdb) p /x mcp4726
$9 = {data = {command_code = 0x3, ref_select = 0x0, power_down_select = 0x0, gain_select = 0x0,
    data = 0x0, unused = 0x0}, bytes = {0x3, 0x0, 0x0, 0x0}}
(gdb) p /x mcp4726_new
$10 = {data = {gain_select = 0x0, power_down_select = 0x0, ref_select = 0x0, command_code = 0x3,
    data = 0x0, unused = 0x0}, bytes = {0x60, 0x0, 0x0, 0x0}}
(gdb)

I am not sure what do you expect from the data bitfield.

JW