Search code examples
cembeddedmicrochipmplabpic32

Bit-packing Problem in a Union (Register Mapping)


I am trying to get a union together to map out some bit fields in a register map. The code I have is the following:

typedef union __attribute__((packed)) {
    struct {
    uint8_t     MODE:3;
    uint8_t     VSHCT:3;
    uint8_t     VBUSCT:3;
    uint8_t     AVG:3;
    uint8_t     RSVD:3;
    uint8_t     RST:1;
    };
    
    struct {
    uint8_t     lsbyte:8;
    uint8_t     msbyte:8;
    };
    uint16_t    w;
    
} CON_MAP_t;

I am initializing the fields with:

CON_MAP_t map = {
    .RST =      0,
    .RSVD =     4,
    .AVG =      0,
    .VBUSCT =   4,
    .VSHCT =    4,
    .MODE =     7
}

So far this is all fine, no compiler issues or warnings.

I expect the binary/hex representation to be 01000001_00100111 / 0x4127.

However, in the debugger I end up with a value for 'w' of: 00000100_00100111 The least significant byte is correct, but the msb(yte) is not.

I am not sure if I'm missing something fundamental here and I've just been staring at it too long, but any insight would be highly appreciated!

I am using: MPLABX v6.05 Latest XC32 Compiler

Device is a PIC32MX130F064D debugging with a PICKIT4.


Solution

  • When it comes to using compilers that supports some additional features over standard C compilers, like XC32 Compiler, it always is better to refer to its guides and manuals for special cases like this one.

    The XC32 does fully support bit fields in structures and also guarantee the order as the first defined bit as to be the Least Significant bit. Here is how it described in section 8.6.2 Bit Fields in Structures of XC32 C Compiler User's Guide:

    MPLAB XC32 C/C++ Compiler fully supports bit fields in structures. Bit fields are always allocated within 8-bit storage units, even though it is usual to use the type unsigned int in the definition. Storage units are aligned on a 32-bit boundary, although this can be changed using the packed attribute.

    The first bit defined will be the Least Significant bit of the word in which it will be stored. When a bit field is declared, it is allocated within the current 8-bit unit if it will fit; otherwise, a new byte is allocated within the structure. Bit fields can never cross the boundary between 8-bit allocation units.

    For example, the declaration:

    struct {
         unsigned lo    : 1;
         unsigned dummy : 6;
         unsigned hi    : 1;
    } foo;
    

    will produce a structure occupying 1 byte.

    So according to the guide description you should see the same order as per your bit definition.

    Also note that packed attribute is meant to use only if you want to alter the 32-bit boundary. But it is not necessary since yours is only 16-bits.

    Here is a demo showing the expected result following with the code:

    enter image description here

    Demo code shown in the screenshot:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    
    typedef union  {
        struct {
            unsigned     MODE:3;
            unsigned     VSHCT:3;
            unsigned     VBUSCT:3;
            unsigned     AVG:3;
            unsigned     RSVD:3;
            unsigned     RST:1;
        };
        
        struct {
            uint8_t     lsbyte:8;
            uint8_t     msbyte:8;
        };
        uint16_t    w;
        
    } CON_MAP_t;
    
    int main(int argc, char** argv) {
        
        CON_MAP_t map = {
            .RST =      0,
            .RSVD =     4,
            .AVG =      0,
            .VBUSCT =   4,
            .VSHCT =    4,
            .MODE =     7
        };
        
        if(map.lsbyte == 0x27 && map.msbyte == 0x41)
            return (EXIT_SUCCESS);
        else
            return (EXIT_FAILURE);
    }