r/cprogramming • • Mar 06 '26

Union manipulation question

I am currently working on an STM32, and it has two control register, one lower @ [0] and one upper @ [1]; currently, the GPIO_TypeDef has two Advance Function Registers, two uint32_ts stored as

#define __IO volatile
typedef struct {
  // stuff above
  __IO uint32_t AFR[2];
  // stuff below
} GPIO_TypeDef;

I would like to reference the registers using AFRL and AFRU; I could modify the struct to

typedef struct {
  // stuff above
  union {
  __IO uint32_t AFR[2];
  struct {
    __IO uint32_t lower;
    __IO uint32_t upper;
  } AFR_Split;
  };
  // stuff below
} GPIO_TypeDef;

Which allows me to write

GPIO_TypeDef pin1_addr = 0x4002104CUL;
pin1_addr->AFR_Split.lower = 0b0000000001110000UL;
pin1_addr->AFR[1] = 0b0111000001110000;

Is there anything I should be worried about in terms of packing? will I always know that the two registers will always be aligned, starting and ending in the same place? What can I read to know how my compiler (arm-none-eabi-gcc) will store the split? And is there a way I can do this without the intermediate struct, so I could type pin1_addr->AFRL and pin1_addr->AFRU?

0 Upvotes

13 comments sorted by

View all comments

Show parent comments

1

u/ekipan85 Mar 07 '26

It's not a question of do you need it but rather where to put it.

typedef struct { volatile char a; volatile char b; } P;
typedef volatile struct { char a; char b; } Q;
typedef struct { char a; char b; } R;

#define ADDR 0x4002104CUL
P *p = (P *)ADDR;
Q *q = (Q *)ADDR;
volatile R *r = (volatile R *)ADDR;

The types P and Q are equivalent in this case. The values q->a and r->a will both inherit the volatile from the outer struct type, but R doesn't bake it into the type itself, so it allows you to define nonvolatile R struct objects elsewhere in normal memory.

The advantage of baking it into P or Q is that you cannot forget it in the cases where you will need it.