Electronics Design AU
Firmware

How Does the Memory Map Work in an Embedded Microcontroller?

Last updated 29 June 2026 · 8 min read

Direct Answer

A microcontroller's memory map is the layout of its full 32-bit address space (0x00000000–0xFFFFFFFF), dividing it into regions for flash (executable code and constants), SRAM (read-write data and stack), memory-mapped peripheral registers (hardware control and status registers accessed as C pointers), and a private peripheral bus for the CPU's own debug and system control hardware. On a Cortex-M MCU like the STM32F4, flash typically starts at 0x08000000, SRAM at 0x20000000, and APB/AHB peripherals at 0x40000000. The linker script's ORIGIN and LENGTH values map directly to these hardware addresses.

Detailed Explanation

A 32-bit microcontroller has a 32-bit address bus, giving it a theoretical address space of 4 GB (0x00000000 to 0xFFFFFFFF). Almost none of that space corresponds to physical memory — most of the addresses are reserved or unmapped. The memory map defines which address ranges correspond to which physical resources: flash, RAM, peripheral hardware, and debug infrastructure.

Understanding the memory map is essential for three things: writing correct volatile pointer code for hardware registers, configuring linker scripts with the right ORIGIN and LENGTH values, and using a debugger to inspect live register and memory state.

The Standard Cortex-M Address Map

ARM defines a standard address map that all Cortex-M processors follow (per the ARM Cortex-M Architecture Reference Manual):

RegionAddress rangeDescription
Code0x00000000–0x1FFFFFFFFlash, ROM, boot alias
SRAM0x20000000–0x3FFFFFFFOn-chip RAM (bit-banding capable)
Peripheral0x40000000–0x5FFFFFFFMemory-mapped peripheral registers
External RAM0x60000000–0x9FFFFFFFExternal SRAM/SDRAM via FSMC/FMC
External device0xA0000000–0xDFFFFFFFExternal device (LCD, NAND, NOR)
Private Peripheral Bus0xE0000000–0xFFFFFFFFCPU-internal: NVIC, SysTick, DWT, ITM, debug

Chip vendors place their specific flash, RAM, and peripherals within these ARM-defined ranges. The exact addresses vary by manufacturer and MCU family.

STM32F4 Memory Map Example

For the STM32F405/407 family (from ST RM0090):

ResourceStart addressSize
Flash memory0x08000000Up to 1 MB (family dependent)
SRAM10x20000000112 KB
SRAM20x2001C00016 KB
CCM (Core Coupled Memory)0x1000000064 KB
APB1 peripherals (timers, I2C, SPI, UART)0x40000000
APB2 peripherals (ADC, SPI1, USART1, EXTI)0x40010000
AHB1 (GPIO, DMA, CRC, flash interface)0x40020000
AHB2 (USB OTG FS, camera, crypto)0x50000000

Flash lives in the Code region (0x08000000), not at 0x00000000. The reason is the boot alias mechanism — see the FAQ below for why the Cortex-M boots from 0x00000000 but flash is physically at 0x08000000.

Memory-Mapped Peripheral Registers

Every hardware peripheral on a microcontroller — UART, SPI, I2C, ADC, DMA, GPIO — is controlled through a set of registers that appear at fixed addresses in the peripheral address space. From C's perspective, they are just addresses where reads and writes have hardware side effects.

The STM32 HAL and CMSIS device headers define these as C structures:

/* CMSIS-style definition for STM32 GPIOA */
typedef struct {
    volatile uint32_t MODER;    /* offset 0x00 — pin mode register */
    volatile uint32_t OTYPER;   /* offset 0x04 — output type */
    volatile uint32_t OSPEEDR;  /* offset 0x08 — output speed */
    volatile uint32_t PUPDR;    /* offset 0x0C — pull-up/pull-down */
    volatile uint32_t IDR;      /* offset 0x10 — input data register */
    volatile uint32_t ODR;      /* offset 0x14 — output data register */
    /* ... more registers ... */
} GPIO_TypeDef;

/* GPIOA starts at 0x40020000 in the AHB1 peripheral bus */
#define GPIOA ((GPIO_TypeDef *) 0x40020000UL)

A write to GPIOA->ODR = 0x0001; compiles to a single store instruction at address 0x40020014 — the hardware GPIO peripheral sees this store and changes the pin output state.

Why volatile Is Non-Negotiable for Hardware Registers

The C compiler is allowed to assume that memory is stable — if you read the same address twice with no intervening write in your code, it is allowed to use the value from the first read and skip the second. For normal variables in RAM, this optimisation is correct. For hardware registers, it is catastrophically wrong.

Consider polling a UART receive register:

/* Wrong — without volatile, the compiler may only read USART2->SR once */
while (!(USART2->SR & USART_SR_RXNE));
uint8_t byte = USART2->DR;

/* Correct — volatile forces a re-read of SR on every loop iteration */
while (!(USART2->SR & USART_SR_RXNE));  /* SR is declared volatile */
uint8_t byte = USART2->DR;

The RXNE bit in SR changes from 0 to 1 when a byte is received — this change is caused by hardware, not by software. Without volatile, a sufficiently aggressive optimiser reads SR once, sees RXNE as 0, and either loops forever with a cached 0 or eliminates the loop entirely. The CMSIS device headers declare all peripheral registers as volatile uint32_t, which is why HAL-generated code works correctly — but hand-written register access code must apply volatile explicitly.

The Vector Table at the Start of Flash

The first bytes of the code region (wherever flash is mapped at boot) are not executable instructions — they are the interrupt vector table: an array of 32-bit addresses. On Cortex-M the first entry is the initial stack pointer value; the second is the address of the Reset Handler (the first code executed after reset); subsequent entries are the addresses of interrupt service routines.

The linker script's KEEP(*(.isr_vector)) ensures the vector table is placed at the very start of the flash binary, at the flash ORIGIN address. On STM32, this is 0x08000000. The reset handler address stored in the second vector table entry is the first code the processor executes — it loads the stack pointer, calls SystemInit(), copies .data to RAM, and zeros .bss before calling main().

After a bootloader hands control to an application, the application writes its own vector table address to SCB->VTOR (the Vector Table Offset Register at 0xE000ED08, in the Private Peripheral Bus region). This tells the NVIC where to find the interrupt service routine addresses for the running application rather than the bootloader.

How the Linker Script Maps to the Memory Map

The MEMORY block in the linker script directly encodes the physical memory map. For STM32F405:

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

These values come from the reference manual's memory map table — not from the Cortex-M architecture specification (which only defines the broad region boundaries). Every STM32 variant in the same package family has the same base addresses but different LENGTH values; always confirm against the specific device's datasheet. For products with over-the-air firmware updates, the available flash space is divided into primary and secondary application slots — understanding the flash address layout is a prerequisite before configuring the partition table.

Inspecting the Memory Map in a Debugger

A JTAG/SWD debugger allows direct inspection of the memory map at runtime. In STM32CubeIDE or VS Code with Cortex-Debug, the "Memory" view lets you read any address — navigate to 0x40020000 to read raw GPIO register values, 0xE000ED04 to read the NVIC pending interrupt status, or 0x08000000 to inspect the vector table entries and confirm the reset handler address. This is invaluable when a peripheral is not behaving as expected and the question is whether the register contains what the firmware intended to write.

For firmware projects requiring correct peripheral register configuration, linker setup, or hardware-bring-up, Zeus Design's embedded firmware team handles the full hardware-software interface from linker script to driver layer.

Design Considerations

  • Align DMA buffers to their region's requirements. On STM32F4, DMA cannot access CCM RAM (0x10000000) — place all DMA transmit/receive buffers in SRAM1/SRAM2. The linker script can enforce this by putting DMA buffers in a named section assigned to the correct RAM region.
  • Reserve CCM RAM for time-critical data. CCM RAM has zero-wait-state access from the Cortex-M4 data bus but is inaccessible to DMA. It is ideal for RTOS task stacks, interrupt handler data, and lookup tables that are read frequently by the CPU. Check your specific device's reference manual — not all STM32 families have CCM.
  • Unaligned accesses in the peripheral region may fault. Peripheral registers are almost always 32-bit aligned; accessing them as 8-bit or 16-bit pointers may produce a hardware fault on strictly-aligned Cortex-M3/M4 implementations. Read and write full 32-bit words and use bit-masking in C rather than byte-width pointer casts.

Common Mistakes

  • Using addresses from the wrong reference. The Cortex-M architecture defines broad region boundaries (SRAM starts somewhere in 0x20000000–0x3FFFFFFF); vendor chips place flash and RAM at specific addresses within those regions. Always use the specific device's reference manual, not the architecture overview.
  • Reading peripheral registers without volatile. Without volatile, the optimiser may cache register values in CPU registers. Symptoms: polling loops that spin forever, status flags that appear never to change, or data registers that always read the same value. Always declare peripheral register structures with volatile.
  • Forgetting the CCM RAM DMA restriction. A working firmware that is ported to use CCM RAM for its buffers (to free up SRAM1) will silently break any DMA transfers using those buffers — the DMA controller will read and write unrelated memory locations. The firmware compiles without errors.
  • Hardcoding addresses instead of using CMSIS headers. Writing *(uint32_t *)0x40020014 = 1; instead of GPIOA->ODR = 1; is error-prone and non-portable. CMSIS device headers are generated from vendor SVD files and are the authoritative source for peripheral register addresses — use them.

Frequently Asked Questions

Why does STM32 flash start at 0x08000000 if the Cortex-M boots from 0x00000000?
The Cortex-M processor always fetches its initial stack pointer and reset handler from address 0x00000000 on reset. STM32 handles the offset by using a boot alias: depending on the BOOT0 pin state and option bytes, the hardware remaps either flash (0x08000000) or system memory (the factory bootloader) to appear at 0x00000000 for the first few cycles of execution. Once the CPU is running, normal code accesses flash through its physical address at 0x08000000. The linker script uses 0x08000000 because that is the physical flash address used during normal operation.
What makes a peripheral register different from a normal variable?
A peripheral register is a hardware register — a flip-flop array inside the peripheral — that appears at a fixed address in the MCU's address space. Writing to that address changes the hardware state; reading it returns the current hardware state, which may change between reads without any software action (e.g. a UART receive data register changes value when a byte arrives). This is fundamentally different from a RAM variable, where reads are stable unless software writes it. The C compiler assumes memory is stable; without the volatile keyword, it may cache a peripheral register's value in a CPU register and never re-read it, producing firmware that misses received data or never sees a status flag change.
What is the difference between SRAM1, SRAM2, and CCM RAM on the STM32F4?
STM32F4 devices typically have three RAM regions: SRAM1 (typically 112 KB, starting at 0x20000000) and SRAM2 (typically 16 KB, at 0x2001C000), which together form a contiguous 128 KB block accessible by both the CPU and the DMA controller; and CCM RAM (Core Coupled Memory, typically 64 KB, at 0x10000000), which is connected directly to the data bus of the Cortex-M4 core and offers zero-wait-state CPU access but is not accessible by the DMA controller. Stack, task control blocks, and time-critical data structures benefit from CCM RAM; DMA receive/transmit buffers must stay in SRAM1/SRAM2.

References

Related Questions

Related Forum Discussions