Mapping Peripheral Memory Addresses to Structures
In this lesson, you will learn how to organize disorganized hardware register addresses into structured C objects, allowing you to access peripherals using intuitive syntax.
When you look at a microcontroller datasheet, you will see a "Memory Map" listing peripheral registers as individual hexadecimal addresses. For example, a UART peripheral might have a Status register at 0x40001000, a Control register at 0x40001004, and a Data register at 0x40001008. Writing to these by manually calculating offsets for every operation is error-prone and unreadable.
Instead, we can use the C struct to group these registers logically. Since hardware registers are mapped to specific memory locations, we define a structure that matches the layout of the hardware, then cast the base address of the peripheral to a pointer of that structure type.
Defining the Register Map
To ensure the compiler does not optimize away reads or writes to these memory-mapped registers, we must use the volatile keyword.
typedef struct {
volatile uint32_t STATUS; // Offset 0x00
volatile uint32_t CTRL; // Offset 0x04
volatile uint32_t DATA; // Offset 0x08
} UART_TypeDef;
#define UART0_BASE 0x40001000
#define UART0 ((UART_TypeDef *) UART0_BASE)
By defining UART0 as a pointer to UART_TypeDef at the base address, the compiler automatically calculates the correct offset for each member. When you write UART0->DATA = 0xAA;, the compiler translates this to a memory write at 0x40001008.
This approach serves two major purposes:
- Readability: It replaces cryptic pointer arithmetic with descriptive field names.
- Safety: It forces a rigid memory layout that mimics the hardware exactly, preventing accidental memory access outside the peripheral range.
Because we are mapping directly to memory-mapped IO (MMIO), we ensure that every structure member aligns perfectly with the hardware's expected register order. This is the foundation for almost all robust bare-metal driver development.
Key Takeaway: By overlaying a volatile structure onto a base peripheral address, you transform raw, hazardous memory offsets into a clean, intuitive C interface for hardware interaction.