how to alias a struct of bitfields as a single byte

Below I use a struct containing struct members that mirrors the registers and status bits of a I2C AS6500 chip in SRAM memory. It all works fine except for only one thing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
uint8_t         init_flag;                                  // Flag to check f AS5600 is initialized, Call 
    uint8_t         zmco;                                       // Prog Counter that shows nr of time MPOS and ZPOS has been written prog cmd=0x40, max value 0x02 (3x)
    uint16_t        zpos;                                       // Zero postion angle 
    uint16_t        mpos;                                       // Max  postion angle 
    uint16_t        mang;                                       // Max Angle range 18...360 degree.
    struct conf {
        uint8_t     pm      : 2;                                // Power Mode : 00 = NOM, 01 = LPM1, 10 = LPM2, 11 = LPM3       
        uint8_t     hyst    : 2;                                // Hysteresis : 00 = OFF, 01 = 1 LSB, 10 = 2 LSBs, 11 = 3 LSBs      
        uint8_t     outs    : 2;                                // Ouput Stage Mode : 00 = analog (range 0%..100%),  01 = analog range 10%..90%, (Range GND..VDD),  10 = digital PWM    
        uint8_t     pwmf    : 2;                                // PWM Frequency:   00 = 115 Hz; 01 = 230 Hz; 10 = 460 Hz; 11 = 920 Hz
        uint8_t     sf      : 2;                                // Slow Filter  : 00 = 16x (1); 01 = 8x; 10 = 4x; 11 = 2x
        uint8_t     fth     : 3;                                // Fast Filter trhreshold : 000 = slow filter only, 001 = 6 LSBs, 010 = 7 LSBs, 011 = 9 LSBs,100 = 18 LSBs, 101 = 21 LSBs, 110 = 24 LSBs, 111 = 10 LSBs
        uint8_t     wd      : 1;                                // Watch Dog    :   0 = OFF, 1 = ON
        uint8_t     dummy0  : 2;                                // dummy bits   
    }conf;
    uint16_t        raw_angle;                                  // Unscale un modified angle value
    uint16_t        angle;                                      // Scale modified angle value
    struct stat {
        uint8_t     dummy1  : 3;                                // dummy bits   
        uint8_t     mh      : 1;                                // Magnetic field to high Flag  
        uint8_t     ml      : 1;                                // Magnetic field to low Flag       
        uint8_t     md      : 1;                                // Magnetic field available flag
        uint8_t     dummy2  : 2;                                // dummy bits       
    } stat;
    uint8_t         agc;                                        // Value of the AGC feedback amplifier
    uint16_t        magnitude;                                  // Magnetic field value
    uint8_t         burn;                                       // Command register to burn settings cmd=0x40 or burn angle values cmd=0x80
                                                                // MANG can only be programmed when ZMCO=0x00, max time programmable 1x
} __attribute__((packed, aligned(1))) AS5600_reg = { false,  0,0x00A,0x0FEC,0x0000, {0,2,0,2, 0,0x01,0,0}, 0,0, {0,0,0,0,0} ,0,0,0 };



To address one struct member I can simply write : AS5600_reg.angle or any other struct member like AS5600_reg.status.md
My problem is how to access the status register, AS5600_reg.stat, as a complete 8 bit register or how to alias a struct of bit-fields as a single byte? I would like to do this as all other struct members like AS5600_reg.stat but that's generating compiler errors. My solution is to address it using a typecast as (uint8_t) &AS5600_reg.stat but that's quite unreadable and not similar the way I access all other struct members. The same issue I have accessing (uint16_t) &AS5600_reg.conf

Is there a solution for this problem meaning accessing a sub struct as being a member of a struct.
Regards Oscar Goos
Last edited on
Unfortunately, bit fields are a crapshoot. While they look pretty, there is no way to portably enforce bit field packing order — not only across platforms, but even with the same compiler!

You are better off just acknowledging that they are bit fields and explicitly treat them as such in code. This will also fix your problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct
{
    uint8_t         init_flag;                                  // Flag to check f AS5600 is initialized, Call 
    uint8_t         zmco;                                       // Prog Counter that shows nr of time MPOS and ZPOS has been written prog cmd=0x40, max value 0x02 (3x)
    uint16_t        zpos;                                       // Zero postion angle 
    uint16_t        mpos;                                       // Max  postion angle 
    uint16_t        mang;                                       // Max Angle range 18...360 degree.
    uint16_t        conf;                                       // BIT FIELDS:
    //  uint8_t     pm      : 2;                                //   Power Mode : 00 = NOM, 01 = LPM1, 10 = LPM2, 11 = LPM3       
    //  uint8_t     hyst    : 2;                                //   Hysteresis : 00 = OFF, 01 = 1 LSB, 10 = 2 LSBs, 11 = 3 LSBs      
    //  uint8_t     outs    : 2;                                //   Ouput Stage Mode : 00 = analog (range 0%..100%),  01 = analog range 10%..90%, (Range GND..VDD),  10 = digital PWM    
    //  uint8_t     pwmf    : 2;                                //   PWM Frequency:   00 = 115 Hz; 01 = 230 Hz; 10 = 460 Hz; 11 = 920 Hz
    //  uint8_t     sf      : 2;                                //   Slow Filter  : 00 = 16x (1); 01 = 8x; 10 = 4x; 11 = 2x
    //  uint8_t     fth     : 3;                                //   Fast Filter trhreshold : 000 = slow filter only, 001 = 6 LSBs, 010 = 7 LSBs, 011 = 9 LSBs,100 = 18 LSBs, 101 = 21 LSBs, 110 = 24 LSBs, 111 = 10 LSBs
    //  uint8_t     wd      : 1;                                //   Watch Dog    :   0 = OFF, 1 = ON
    //  uint8_t     dummy0  : 2;                                //   dummy bits   
    uint16_t        raw_angle;                                  //   Unscale un modified angle value
    uint16_t        angle;                                      //   Scale modified angle value
    uint8_t         stat;                                       // BIT FIELDS:
    //  uint8_t     dummy1  : 3;                                //   dummy bits   
    //  uint8_t     mh      : 1;                                //   Magnetic field to high Flag  
    //  uint8_t     ml      : 1;                                //   Magnetic field to low Flag       
    //  uint8_t     md      : 1;                                //   Magnetic field available flag
    //  uint8_t     dummy2  : 2;                                //   dummy bits       
    uint8_t         agc;                                        // Value of the AGC feedback amplifier
    uint16_t        magnitude;                                  // Magnetic field value
    uint8_t         burn;                                       // Command register to burn settings cmd=0x40 or burn angle values cmd=0x80
                                                                // MANG can only be programmed when ZMCO=0x00, max time programmable 1x
} __attribute__((packed, aligned(1))) AS5600_reg = { false,  0,0x00A,0x0FEC,0x0000, {0,2,0,2, 0,0x01,0,0}, 0,0, {0,0,0,0,0} ,0,0,0 };

The C way of doing it would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum AS5600_BIT_FIELDS
{
  CONF_POWER_MODE            = 0xC000,  CONF_POWER_MODE_SHIFT            = 14,
  CONF_HYSTERESIS            = 0x3000,  CONF_HYSTERESIS_SHIFT            = 12,
  CONF_OUTPUT_STAGE_MODE     = 0x0C00,  CONF_OUTPUT_STAGE_MODE_SHIFT     = 10,
  CONF_PWM_FREQUENCY         = 0x0300,  CONF_PWM_FREQUENCY_SHIFT         =  8,
  CONF_SLOW_FILTER           = 0x00C0,  CONF_SLOW_FILTER_SHIFT           =  6,
  CONF_FAST_FILTER_THRESHOLD = 0x0038,  CONF_FAST_FILTER_THRESHOLD_SHIFT =  3,
  CONF_WATCH_DOG             = 0x0004,  CONF_WATCH_DOG_SHIFT             =  2,
  CONF_DUMMY0                = 0x0003,  CONF_DUMMY0_SHIFT                =  0,
};

AS5600_reg.conf &= ~CONF_POWER_MODE;                         // Clear the power mode
AS5600_reg.conf |= POWER_MODE_NOM << CONF_POWER_MODE_SHIFT;  // Set the power mode 

In C++ you can make things a little easier by wrapping it with an object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct AS5600_Conf
{
  uint16_t& bits;
  AS5600_Conf( uint16_t& bits ): bits{bits} { }
  
  enum { Nominal, Low_Power_Mode_1, Low_Power_Mode_2, Low_Power_Mode_3 };
  unsigned     power_mode() const { return bits >> 14; }
  AS5600_Conf& power_mode( unsigned pm ) { bits = (bits & 0x3FFF) | ((pm & 3) << 14; return *this; }
  
  enum { Off, One_Bit, Two_Bits, Three_Bits };
  unsigned     hysteresis() const { return (bits >> 12) & 3; }
  AS5600_Conf& hysteresis( unsigned n ) { bits = (bits & 0xCFFF) | ((n & 3) << 12; return *this; }
  
  ...
};

Now you can play with bits easily enough:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Set values
AS5600_Conf( AS5600_reg.conf )
    .power_mode( AS5600_Conf.Nominal )
    .hysteresis( AS5600_Conf.Off )
    .output_stage_mode( AS5600_Conf.Digital )
    ...
    ;
  
// Get Individual Field Values
if (AS5600_Conf( AS5600_reg.conf ).power_mode() == AS5600_Conf.Nominal)

// Get it as a word:
if (AS5600_reg.conf)

And so on. How you wind up doing it is up to you.

Hope this helps.
Topic archived. No new replies allowed.