homeGrown homeGrown - 1 year ago 72
C Question

Splitting 32 bit register in variable size bytes

I want to split a general purpose register into three separate registers; two 8 bit registers & one 16 bit register. This is the approach I am using but I think it may be wrong.

typedef struct {
volatile uint8_t reg_0;
volatile uint8_t reg_1;
volatile uint16_t reg_2;
} reg_split;

#define REG_BASE (0xA040000C)
#define REG ((reg_split *)REG_BASE)

And this is how I am accessing the register:

REG->reg_0 = 0xFF;

Is this the wrong approach or is their a cleaner solution?

Answer Source

What's most important for all kinds of memory mappings like these, is that you verify that the C code yields the expected result. You have to consider padding and alignment.

Some minor nit-picks with your example:

  • Always ensure that all hex literals in your code have a type that makes sense. 0xA040000C in your case is of a signed type, most likely int. On most systems it doesn't make sense to have signed addresses. "Sloppy types" can cause all manner of subtle bugs, particularly when combined with various forms of integer promotion.
  • Move the volatile qualifier out of the struct. Avoid using type qualifiers inside structs if possible - they usually cause lots of type compatibility hiccups.
  • Pointer notation for hardware registers is a bit peculiar. The most common style is to regard registers as declared variables.

With the above remarks in mind, your code could be re-written as:

typedef struct {
  uint8_t  reg_0;
  uint8_t  reg_1;
  uint16_t reg_2;
} reg_split;

#define REG_BASE (0xA040000Cu)
#define REG (*(volatile reg_split *)REG_BASE)

_Static_assert(sizeof(reg_split) == 4, "Unwanted padding detected");


REG.reg_0 = 0xFF;