0
0
Power-electronicsHow-ToBeginner · 3 min read

How to Create Register Map Using Struct in Embedded C

In embedded C, you create a register map by defining a struct that matches the hardware register layout, then use a pointer to that struct at the register's base address. This lets you access hardware registers with named fields instead of raw addresses.
📐

Syntax

Define a struct with members representing each hardware register or bit field. Then declare a pointer to this struct and assign it the base address of the hardware registers.

Each member corresponds to a register or part of a register, allowing easy access by name.

c
typedef struct {
    volatile uint32_t REG1;  // Register 1
    volatile uint32_t REG2;  // Register 2
    volatile uint32_t REG3;  // Register 3
} RegisterMap_t;

#define REGISTER_BASE_ADDRESS ((uintptr_t)0x40000000)

RegisterMap_t * const REGISTERS = (RegisterMap_t *)REGISTER_BASE_ADDRESS;
💻

Example

This example shows how to define a register map struct, assign it to a hardware base address, and read/write registers using the struct fields.

c
#include <stdint.h>
#include <stdio.h>

// Define the register map struct
typedef struct {
    volatile uint32_t CTRL;   // Control register
    volatile uint32_t STATUS; // Status register
    volatile uint32_t DATA;   // Data register
} RegisterMap_t;

// Simulated hardware base address (for example only)
uint32_t simulated_hardware[3] = {0};

// Pointer to the register map at the simulated hardware address
RegisterMap_t * const REG = (RegisterMap_t *)simulated_hardware;

int main() {
    // Write to control register
    REG->CTRL = 0x1;
    // Write data
    REG->DATA = 0xABCD1234;
    // Read status (simulated as zero)
    uint32_t status = REG->STATUS;

    printf("CTRL = 0x%08X\n", REG->CTRL);
    printf("DATA = 0x%08X\n", REG->DATA);
    printf("STATUS = 0x%08X\n", status);

    return 0;
}
Output
CTRL = 0x00000001 DATA = 0xABCD1234 STATUS = 0x00000000
⚠️

Common Pitfalls

  • Not using volatile: Hardware registers can change outside program control, so volatile prevents compiler optimizations that break access.
  • Incorrect struct alignment or padding: The struct layout must exactly match the hardware register layout; otherwise, access will be wrong.
  • Wrong base address: The pointer must be set to the correct hardware base address.
  • Accessing registers as normal variables: Always use pointers and volatile to ensure proper hardware access.
c
/* Wrong: Missing volatile and wrong pointer type */
typedef struct {
    uint32_t REG1;
    uint32_t REG2;
} WrongMap_t;

#define BASE_ADDR 0x40000000
WrongMap_t *map = (WrongMap_t *)BASE_ADDR;

/* Correct: Use volatile and const pointer */
typedef struct {
    volatile uint32_t REG1;
    volatile uint32_t REG2;
} CorrectMap_t;

#define BASE_ADDR_CORRECT 0x40000000
CorrectMap_t * const map_correct = (CorrectMap_t *)BASE_ADDR_CORRECT;
📊

Quick Reference

  • Use typedef struct to define register layout.
  • Mark register fields as volatile to prevent optimization.
  • Assign a pointer to the hardware base address cast to the struct type.
  • Access registers via pointer fields, e.g., REG->CTRL.
  • Ensure struct matches hardware layout exactly (order, size, padding).

Key Takeaways

Define a struct matching the hardware register layout with volatile members.
Use a pointer to the struct at the hardware base address for register access.
Always mark hardware registers as volatile to avoid compiler optimization issues.
Ensure struct alignment and order exactly match the hardware specification.
Access registers using the struct pointer fields for clear and safe code.