Cortex-M Memory Map
and Bus Interfaces
Every address a Cortex-M processor can generate falls somewhere in a 4 GB space. This lecture shows exactly how that space is divided, what lives where, and how the AHB/APB bus hierarchy connects the CPU to flash, SRAM, and peripherals on STM32F411.
- What is a Memory Map?
- The 32-bit Address Bus — 4 GB Space
- The AHB System Bus Diagram
- Complete ARM Cortex-M Memory Map
- Code Region (0x00000000 – 0x1FFFFFFF)
- SRAM Region (0x20000000 – 0x3FFFFFFF)
- Peripheral Region (0x40000000 – 0x5FFFFFFF)
- External RAM and Device Regions
- Private Peripheral Bus (0xE0000000+)
- STM32F411 Specific Memory Layout
- Accessing Regions in C Code
- FAQ
1. What is a Memory Map?
A microprocessor’s memory map is a blueprint that assigns every byte of its addressable space to a purpose. When the processor puts an address on its address bus, the hardware determines which device responds — flash memory, SRAM, a peripheral register, or the system control space.
The mapping is fixed by the processor architecture and the chip vendor. ARM defines the top-level regions (code, SRAM, peripheral, system) and each chip vendor (ST, NXP, TI) then places their specific flash, SRAM, and peripherals within those regions.
Writing to hardware registers
Every GPIO pin, timer, UART, and ADC is controlled by a register at a specific address. Knowing the memory map tells you which address to write to enable a peripheral.
Same regions, different chips
All Cortex-M chips share the same region boundaries. Code that uses the private peripheral bus (NVIC, SCB, SysTick) at 0xE000E000 works on any Cortex-M without change — only peripheral addresses differ.
Access alignment rules
Most Cortex-M peripherals require word-aligned (4-byte) access. Unaligned access to peripheral registers causes a HardFault. The memory map helps you understand which regions allow byte/halfword access.
2. The 32-bit Address Bus — 4 GB Space
The Cortex-M processor has a 32-bit address bus. A 32-bit address can represent 2³² = 4,294,967,296 unique byte addresses, which equals exactly 4 GB of addressable space.
/* The full 32-bit address space in one equation:
2^32 = 4,294,967,296 bytes = 4 GB
Addresses range from:
0x00000000 (lowest) to 0xFFFFFFFF (highest)
┌───────────────────────────────────────────┐
│ 0x00000000 → 0xFFFFFFFF │
│ Code SRAM Peripheral System ... │
└───────────────────────────────────────────┘ */
/* Example: accessing a register at a known address */
#define GPIOA_MODER (*(volatile uint32_t *)0x40020000U)
void set_pa5_output(void) {
GPIOA_MODER &= ~(3U << 10); /* clear bits 11:10 */
GPIOA_MODER |= (1U << 10); /* set mode = output */
}
Even though the processor can address 4 GB, no real STM32 chip has 4 GB of actual memory. Most of the 4 GB space is simply not populated — reading from an unpopulated address causes a HardFault (bus fault). The memory map defines exactly which addresses are populated.
3. The AHB System Bus Diagram
The Cortex-M CPU core connects to all memories and peripherals through a bus called the AHB (Advanced High-performance Bus). AHB is part of ARM’s AMBA (Advanced Microcontroller Bus Architecture) specification.
AHB System Bus — How the CPU Reaches Everything
32-bit address channel + 32-bit data channel
Code region
Data region
Peripheral
Peripheral
Peripheral
Peripheral
Every device connected to the bus responds to a specific range of addresses. The CPU puts the target address on the address channel; only the device mapped to that address responds.
For slower peripherals (UART, I2C, SPI, basic timers), the AHB connects through an APB (Advanced Peripheral Bus) bridge that runs at a lower clock frequency (50 MHz for APB1, 100 MHz for APB2 on STM32F411). The bridge handles clock domain crossing so the fast CPU does not have to wait for slow peripherals.
4. Complete ARM Cortex-M Memory Map
The ARM architecture defines the following fixed regions within the 4 GB space. Every Cortex-M chip in the world — STM32, LPC, Kinetis, SAM — uses these exact boundaries.
ARM Cortex-M Fixed Memory Map (4 GB)
↓
0xE0100000
↓
0xE0000000
↓
0xA0000000
↓
0x60000000
↓
0x40000000
↓
0x20000000
↓
0x00000000
Diagram: higher addresses at top, lower addresses at bottom. All boundaries are fixed by the ARM architecture.
| Region | Start | End | Size | Purpose |
|---|---|---|---|---|
| Code | 0x00000000 |
0x1FFFFFFF |
512 MB | Program memory: flash, ROM, OTP, vector table base |
| SRAM | 0x20000000 |
0x3FFFFFFF |
512 MB | On-chip data RAM: stack, heap, .data, .bss |
| Peripheral | 0x40000000 |
0x5FFFFFFF |
512 MB | Memory-mapped peripheral registers (GPIO, UART, SPI…) |
| External RAM | 0x60000000 |
0x9FFFFFFF |
1 GB | FMC/FSMC: external SDRAM, PSRAM, NAND flash |
| External Device | 0xA0000000 |
0xDFFFFFFF |
1 GB | FMC: NOR flash, LCD parallel interface |
| PPB | 0xE0000000 |
0xE00FFFFF |
1 MB | Private Peripheral Bus: NVIC, SCB, SysTick, ITM, DWT, FPB |
| Vendor-specific | 0xE0100000 |
0xFFFFFFFF |
511 MB | Chip-vendor extensions (ETM, TPIU, ROM table on ST devices) |
5. Code Region (0x00000000 – 0x1FFFFFFF)
The Code region is where the processor fetches instructions from. ARM architecture designates this 512 MB region for program memory and mandates that the vector table must be accessible here at reset.
What goes in the Code region
- Embedded flash — most common on STM32 (rewritable NOR flash)
- ROM — factory-programmed bootloader code
- OTP (One Time Programmable) — permanently write once
- EEPROM — byte-rewritable non-volatile storage
- System memory — ST-supplied bootloader at 0x1FFF0000
Three dedicated bus interfaces
The Cortex-M4 core has three separate interfaces into the Code region:
• ICode — instruction fetch bus
• DCode — literal pool / data read bus
• System — general data access
ICode and DCode can operate simultaneously, allowing the core to fetch an instruction
while also reading a constant from flash — this is how the pipeline stays full.
STM32F411 Code region layout
| Sub-region | Start | End | Size | Content |
|---|---|---|---|---|
| User flash | 0x08000000 |
0x0807FFFF |
512 KB | Your application code, vector table, .rodata |
| System memory (bootloader) | 0x1FFF0000 |
0x1FFF77FF |
30 KB | ST factory USB/UART DFU bootloader (read-only) |
| OTP area | 0x1FFF7800 |
0x1FFF7A0F |
528 B | One-time programmable bytes (device ID, calibration) |
| Option bytes | 0x1FFFC000 |
0x1FFFC00F |
16 B | Flash read protection, brownout level, BOOT config |
/* Verify: the vector table at 0x08000000 mirrors to 0x00000000 */
volatile uint32_t *flash_vt = (uint32_t *)0x08000000U;
volatile uint32_t *alias_vt = (uint32_t *)0x00000000U;
/* These should print the same value (initial MSP) */
printf("Flash[0]: 0x%08X\n", (unsigned)flash_vt[0]);
printf("Alias[0]: 0x%08X\n", (unsigned)alias_vt[0]);
6. SRAM Region (0x20000000 – 0x3FFFFFFF)
SRAM (Static Random Access Memory) is the processor’s working memory — fast, volatile storage that loses content when power is removed. This is where the stack, heap, and all runtime data live.
128 KB on-chip SRAM
The STM32F411 has 128 KB SRAM at 0x20000000 to 0x2001FFFF.
The rest of the 512 MB SRAM region (0x20020000 to 0x3FFFFFFF) is unpopulated —
accessing it causes a bus fault.
What lives in SRAM
- Stack — grows downward from top of SRAM (0x2001FFFF+1)
- Heap — grows upward from end of .bss
- .bss — uninitialised globals (zeroed at startup)
- .data — initialised globals (copied from flash)
STM32F411 SRAM Layout at Runtime
Stack (grows ↓)
0x20000000 — bottom of SRAM
7. Peripheral Region (0x40000000 – 0x5FFFFFFF)
This 512 MB region is where chip vendors place the memory-mapped registers of all on-chip peripherals. Writing to an address in this region does not write to memory — it writes to a hardware register that controls physical hardware.
On STM32F411, the peripheral region is subdivided into APB1 bus, APB2 bus, AHB1 bus, and AHB2 bus sub-regions:
| Bus | Start | End | Max Speed | Key Peripherals |
|---|---|---|---|---|
| APB1 | 0x40000000 |
0x40007FFF |
50 MHz | TIM2–5, TIM12–14, USART2/3, I2C1–3, SPI2/3, WWDG, RTC, DAC |
| APB2 | 0x40010000 |
0x40016BFF |
100 MHz | TIM1, TIM9–11, USART1/6, SPI1/4/5, ADC1, SDIO, SYSCFG, EXTI |
| AHB1 | 0x40020000 |
0x4007FFFF |
100 MHz | GPIOA–H, CRC, RCC, Flash interface, DMA1, DMA2 |
| AHB2 | 0x50000000 |
0x5003FFFF |
100 MHz | USB OTG FS |
/* Peripheral register access via C macros — STM32F411 */
/* RCC (Reset and Clock Control) — enables peripheral clocks */
#define RCC_BASE 0x40023800U
#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x30U))
/* GPIOA base address */
#define GPIOA_BASE 0x40020000U
#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00U))
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x14U))
/* Example: toggle LED on PA5 */
void init_led(void)
{
RCC_AHB1ENR |= (1U << 0); /* enable GPIOA clock */
GPIOA_MODER &= ~(3U << 10); /* PA5: clear mode bits */
GPIOA_MODER |= (1U << 10); /* PA5: output mode */
}
void toggle_led(void)
{
GPIOA_ODR ^= (1U << 5); /* toggle bit 5 */
}
volatile, GCC may cache the register value in a CPU register and
never re-read it from the actual hardware address. This causes code that “looks correct”
to silently fail — the register never gets the new value. Every peripheral register
pointer must be cast as volatile uint32_t *.
8. External RAM and Device Regions
The 2 GB region from 0x60000000 to 0xDFFFFFFF is reserved for external memory connected through the FMC (Flexible Memory Controller) or its older variant FSMC. Not all STM32 chips have FMC — the STM32F411 does not include FMC.
0x60000000 – 0x9FFFFFFF
- SDRAM, SRAM, PSRAM via FMC
- Used when on-chip SRAM is insufficient
- Typical on STM32F4 Discovery (8 MB SDRAM)
- Access speed depends on FMC timing config
0xA0000000 – 0xDFFFFFFF
- NOR flash, NAND flash via FMC
- LCD parallel interface (8080/6800 mode)
- Execute-in-place (XIP) from external NOR flash
- Not available on STM32F411 (no FMC)
9. Private Peripheral Bus (0xE0000000 – 0xE00FFFFF)
The Private Peripheral Bus (PPB) is a special 1 MB region that is the same on every single Cortex-M processor ever made. It gives access to the ARM-defined system control and debug registers. These addresses are hard-coded in the ARM architecture — they are not configurable by chip vendors.
| Block | Base Address | Description |
|---|---|---|
| ITM | 0xE0000000 |
Instrumentation Trace Macrocell — printf over SWO |
| DWT | 0xE0001000 |
Data Watchpoint and Trace — cycle counter, watchpoints |
| FPB | 0xE0002000 |
Flash Patch and Breakpoint — hardware breakpoints |
| SCS (NVIC, SCB, SysTick, MPU) | 0xE000E000 |
System Control Space — interrupt control, fault configuration |
| ETM | 0xE0041000 |
Embedded Trace Macrocell — instruction trace (if present) |
| TPIU | 0xE0040000 |
Trace Port Interface Unit — SWO output configuration |
| ROM Table | 0xE00FF000 |
Identifies debug components present on this chip |
/* PPB registers are always at these addresses on any Cortex-M */
/* NVIC Interrupt Set Enable Register 0 (enables IRQs 0–31) */
#define NVIC_ISER0 (*(volatile uint32_t *)0xE000E100U)
/* SysTick Control and Status Register */
#define SYST_CSR (*(volatile uint32_t *)0xE000E010U)
/* SCB Application Interrupt and Reset Control Register */
#define SCB_AIRCR (*(volatile uint32_t *)0xE000ED0CU)
/* DWT Cycle Counter */
#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004U)
#define DWT_CTRL (*(volatile uint32_t *)0xE0001000U)
/* Enable DWT cycle counter for timing measurements */
void dwt_enable(void)
{
/* CoreDebug DEMCR: enable DWT */
*(volatile uint32_t *)0xE000EDFC |= (1U << 24);
DWT_CYCCNT = 0;
DWT_CTRL |= 1U; /* CYCCNTENA */
}
uint32_t get_cycles(void) { return DWT_CYCCNT; }
10. STM32F411 Specific Memory Layout
The STM32F411xC/xE datasheet (DocID026289) defines exactly how the ARM memory regions are populated for this specific chip. Key facts:
| Memory | Start | End | Size | Notes |
|---|---|---|---|---|
| Flash (user area) | 0x08000000 |
0x0807FFFF |
512 KB | Divided into 8 sectors (16/16/16/16/64/128/128/128 KB) |
| SRAM | 0x20000000 |
0x2001FFFF |
128 KB | Single block; stack top = 0x20020000 |
| APB1 peripherals | 0x40000000 |
0x40007FFF |
32 KB | TIM2–5, USART2, SPI2/3, I2C1–3, PWR, RTC |
| APB2 peripherals | 0x40010000 |
0x40016BFF |
~27 KB | TIM1, ADC1, USART1/6, SPI1/4/5, SYSCFG, EXTI |
| AHB1 peripherals | 0x40020000 |
0x40023FFF |
~16 KB | GPIOA–H, CRC, RCC, Flash interface |
| DMA1, DMA2 | 0x40026000 |
0x40027FFF |
8 KB | DMA controllers on AHB1 |
| USB OTG FS | 0x50000000 |
0x5003FFFF |
256 KB | AHB2, includes 1.25 KB USB FIFO SRAM |
| System bootloader | 0x1FFF0000 |
0x1FFF77FF |
30 KB | ST DFU bootloader (read-only, factory programmed) |
11. Accessing Memory Regions in C Code
Now that you know what lives where, here are practical patterns for accessing each region from C.
#include <stdint.h>
/* ---- 1. Reading from Code region (flash constants) ---- */
/* The linker places const data in flash automatically */
const uint32_t lookup_table[4] = {0, 1, 4, 9}; /* lives in flash */
/* ---- 2. Writing/reading SRAM ---- */
static uint8_t dma_buffer[256]; /* in SRAM (.bss) */
/* ---- 3. Peripheral access — always volatile ---- */
#define RCC_BASE 0x40023800UL
#define GPIOA_BASE 0x40020000UL
/* Enable GPIOA clock and configure PA5 output */
void gpio_init(void)
{
volatile uint32_t *rcc_ahb1enr = (uint32_t *)(RCC_BASE + 0x30);
volatile uint32_t *gpioa_moder = (uint32_t *)(GPIOA_BASE + 0x00);
volatile uint32_t *gpioa_odr = (uint32_t *)(GPIOA_BASE + 0x14);
*rcc_ahb1enr |= (1U << 0); /* GPIOAEN */
*gpioa_moder &= ~(3U << 10);
*gpioa_moder |= (1U << 10); /* PA5 output */
*gpioa_odr |= (1U << 5); /* PA5 high */
}
/* ---- 4. PPB access — NVIC and SysTick ---- */
#define NVIC_ISER0 (*(volatile uint32_t *)0xE000E100U)
#define SYST_CSR (*(volatile uint32_t *)0xE000E010U)
#define SYST_RVR (*(volatile uint32_t *)0xE000E014U)
/* Configure SysTick to fire every 1 ms at 100 MHz */
void systick_init(void)
{
SYST_RVR = 100000U - 1U; /* reload value: 100 MHz / 1 kHz - 1 */
SYST_CSR = 0x7U; /* CLKSOURCE=1, TICKINT=1, ENABLE=1 */
}
/* ---- 5. Reading chip unique device ID (in Code region OTP area) ---- */
#define UID_BASE 0x1FFF7A10U /* STM32F411 unique ID base */
void read_uid(uint32_t uid[3])
{
volatile uint32_t *p = (volatile uint32_t *)UID_BASE;
uid[0] = p[0];
uid[1] = p[1];
uid[2] = p[2];
}
12. FAQ
__attribute__((section(".RamFunc")))
and the linker script will copy them from flash to SRAM at startup, then execute them
from SRAM. This is useful for: (1) functions that reprogram flash (must run from SRAM
since flash is busy), and (2) latency-critical ISRs that need guaranteed
zero-wait-state execution.
Next: SRAM and Peripheral Region Details
The next lecture goes deeper into the SRAM and Peripheral regions — exploring the full peripheral register structure, bit-field manipulation patterns, and how to read the reference manual to find any register address on any STM32 chip.

2 Comments