Cortex-M Memory Mapand Bus Interfaces-Embedded C Training for Freshers

Cortex-M Memory Map and Bus Interfaces | STM32 Embedded

Lecture 07 — ARM Cortex-M Programming

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.

Covers source pages 55–60  |  STM32F411  |  DocID026289

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.

WHY IT MATTERS

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.

PORTABILITY

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.

ALIGNMENT

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

ARM Cortex-M4 CPU Core
32-bit address channel + 32-bit data channel
↕
▮▮▮▮▮▮▮ AHB System Bus (32-bit, up to 100 MHz on STM32F411) ▮▮▮▮▮▮▮
Flash Memory
Code region
SRAM
Data region
GPIOD
Peripheral
Timers
Peripheral
ADC
Peripheral
I2C / SPI
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)

0xFFFFFFFF
↓
0xE0100000
Vendor-specific / Implementation-defined 511 MB
0xE00FFFFF
↓
0xE0000000
Private Peripheral Bus (PPB) — NVIC, SCB, SysTick, ITM… 1 MB
0xDFFFFFFF
↓
0xA0000000
External Device (FMC / FSMC NOR, LCD) 1 GB
0x9FFFFFFF
↓
0x60000000
External RAM (FMC / FSMC SDRAM, PSRAM) 1 GB
0x5FFFFFFF
↓
0x40000000
Peripheral (GPIO, UART, SPI, I2C, ADC, Timers…) 512 MB
0x3FFFFFFF
↓
0x20000000
SRAM (on-chip data memory) 512 MB
0x1FFFFFFF
↓
0x00000000
Code (Flash, ROM, OTP, EEPROM, vector table) 512 MB

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.

MEMORY TYPES

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
IMPORTANT

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-regionStartEndSizeContent
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
Why is flash at 0x08000000, not 0x00000000?
The ARM architecture says the vector table must be readable at 0x00000000 at reset. ST solves this with a memory alias: the memory controller maps 0x00000000 to mirror either flash (BOOT0=0), system memory (BOOT0=1, BOOT1=0), or SRAM (BOOT0=1, BOOT1=1) depending on the BOOT pins. The actual flash always lives at 0x08000000 — the alias at 0x00000000 is just a window into it.
/* 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.

STM32F411

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.

SRAM LAYOUT

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

0x2001FFFF — top of SRAM
Stack (grows ↓)
Stack frames (function calls)
↕ Stack / Heap gap (free space)
Heap (malloc, grows ↑)
.bss (zero-initialised globals)
.data (initialised globals)
0x20000000 — bottom of SRAM
Bit-banding — SRAM alias region
The range 0x22000000–0x23FFFFFF is a bit-band alias of the first 1 MB of SRAM (0x20000000–0x200FFFFF). Each word in the alias maps to one bit in SRAM, enabling atomic bit read/write without disabling interrupts. STM32F411 supports bit-banding but it is rarely used in modern code (prefer atomic intrinsics instead).

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:

BusStartEndMax SpeedKey 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        */
}
Always use volatile for peripheral registers
Without 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.

EXTERNAL RAM (1 GB)

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
EXTERNAL DEVICE (1 GB)

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.

BlockBase AddressDescription
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; }
PPB access is always privileged
Unprivileged Thread mode code cannot access any address in the PPB. Attempting to do so raises a MemManage or HardFault exception. This prevents user tasks from accidentally (or maliciously) disabling the NVIC or corrupting the MPU configuration.

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:

MemoryStartEndSizeNotes
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)
Flash sector erase granularity matters
STM32F411 flash cannot be erased byte by byte — only sector by sector. Sector 0 is 16 KB, sectors 1–3 are 16 KB each, sector 4 is 64 KB, and sectors 5–7 are 128 KB each. When you write a firmware update, you must erase at least one complete sector first. This is why bootloaders reserve the first sector(s) for themselves and update code lives in later sectors.

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

Q: What happens if I try to write to the flash region from C?
Writing to flash is not like writing to SRAM. Flash must be unlocked (write 0x45670123 then 0xCDEF89AB to FLASH_KEYR register), erased sector by sector, then programmed word by word via the FLASH_CR register. Simply casting a pointer to a flash address and assigning a value will either do nothing or trigger a HardFault if the flash is locked.
Q: If SRAM is only 128 KB on STM32F411, why is the SRAM region 512 MB?
The ARM architecture reserves 512 MB for SRAM as a design choice to allow chips with larger SRAM to fit without changing the memory map layout. On STM32F411, only the first 128 KB (0x20000000–0x2001FFFF) is populated with actual SRAM. The remaining address range in the SRAM region is unmapped — reading it causes a bus fault.
Q: Can I execute code from SRAM instead of flash?
Yes. Functions can be decorated with __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.
Q: What is the difference between AHB1 and APB1 on STM32F411?
AHB1 (Advanced High-performance Bus) runs at the full CPU clock (100 MHz) and connects high-speed peripherals like GPIO, DMA, and RCC directly. APB1 (Advanced Peripheral Bus 1) is a lower-speed bus running at half the AHB clock (50 MHz), used for slower peripherals like UART, I2C, SPI, and basic timers. APB2 runs at the full AHB speed (100 MHz) for faster peripherals like USART1/6 and ADC.
Q: How do I find the exact base address of a peripheral on STM32F411?
Open the STM32F411 datasheet (DocID026289), Table 2 “Memory map and register boundary addresses”. Every peripheral is listed with its exact start and end address. For register offsets within a peripheral (e.g. GPIOA_ODR is at offset 0x14 from GPIOA_BASE), look at the peripheral’s register map in the STM32F4xx Reference Manual (RM0383).
Q: Is the Private Peripheral Bus the same on Cortex-M0 as on Cortex-M4?
The base addresses are architecturally the same (0xE000E000 for SCS, 0xE0000000 for ITM, etc.), but not all blocks are present in all cores. Cortex-M0 has a reduced NVIC and no DWT or ETM. Cortex-M4 has the full debug infrastructure including ITM, DWT, FPB, and optionally ETM. The CMSIS-Core headers handle this by only defining registers present in each specific core.

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

Leave a Reply

Your email address will not be published. Required fields are marked *