Course contentsShow
← Embedded Systems & Computer Architecture
Lesson 247 of 9363. Microcontroller Fundamentals and Bare-Metal CFree lesson

Mapping Peripheral Memory Addresses to Structures

Learn to define hardware register structures in C using volatile pointers to map directly to specific peripheral memory addresses.

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:

  1. Readability: It replaces cryptic pointer arithmetic with descriptive field names.
  2. 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.