资讯动态

【STM32】内存地址映射全解析

发布时间:2026/9/24 20:49:14 来源:尧图企业网站定制
STM32 内存地址映射全解析一、为什么要懂内存地址映射STM32 是 32 位 MCU地址总线 32 位理论寻址空间 2³² 4GB。这 4GB 不是随便排的而是 ARM Cortex-M 内核规定好的地图——每一块区域放什么、能干什么都有严格定义。理解这张地图的意义看懂寄存器操作库函数GPIOA-BSRR 0x0001本质是往某个地址写数据懂了映射才知道它在干嘛调试时不慌程序跑飞了看 Fault 地址、看栈指针能立刻定位是 Flash 崩了还是 RAM 爆了做 Bootloader/链接脚本自定义分区、重定向向量表都离不开地址计算面试高频考点嵌入式面试必问STM32 的内存是怎么映射的二、4GB 地址空间总览Cortex-M3/M4 把 4GB 地址空间按用途切成了 8 块每块 512MB高地址 0xFFFFFFFF ┌──────────────────────────┐ │ 系统级空间 │ 512MB → 内核私有NVIC/SCB/SysTick 0xE0000000 ├──────────────────────────┤ │ 外部设备区扩展 │ 512MB → FSMC 外接设备 0xC0000000 ├──────────────────────────┤ │ 外部 RAM 区扩展 │ 512MB → FSMC 外接 SRAM/SDRAM 0xA0000000 ├──────────────────────────┤ │ 片上外设寄存器区 │ 512MB → AHB/APB 外设寄存器 ← 重点 0x80000000 ├──────────────────────────┤ │ 外部 RAM 区扩展 │ 512MB → FSMC 外接 RAM 0x60000000 ├──────────────────────────┤ │ 外部设备区扩展 │ 512MB → FSMC 外接设备 0x40000000 ├──────────────────────────┤ │ 片上外设寄存器区 │ 512MB → GPIOA/USART1/TIM2 ← 重点 0x3FFFFFFF ├──────────────────────────┤ │ 片上 SRAM │ 512MB → 主 SRAM 备份 SRAM ← 重点 0x20000000 ├──────────────────────────┤ │ 片上 Flash │ 512MB → 主 Flash 系统存储器 ← 重点 0x00000000 └──────────────────────────┘ 低地址对 STM32F103 这种中小容量芯片来说上面一半0x80000000 以上基本没用到实际打交道的就是下面这三块Flash、SRAM、片上外设寄存器。三、片上 Flash0x08000000 起3.1 Flash 里存了什么0x08000000 ┌──────────────────────────┐ │ 中断向量表 │ ← 前几百字节 0x080000C0 ├──────────────────────────┤ │ │ │ .text 代码段 │ ← 你写的函数编译后的机器码 │ Reset_Handler │ │ main() │ │ USART1_IRQHandler() │ │ ... │ │ │ 0x0800XXXX ├──────────────────────────┤ │ .rodata 只读数据 │ ← const 常量、字符串字面量 │ const int buf[100]; │ │ printf(hello) 里的 hello │ │ │ 0x0800YYYY ├──────────────────────────┤ │ .data 段的初值 │ ← 已初始化全局变量的初值 │ int a 10; → 这里存 10│ │ 启动时拷贝到 RAM │ │ │ 0x0801FFFF └──────────────────────────┘ ← 128KB Flash 结束3.2 向量表的具体地址向量表从 Flash 最开头 0x08000000 开始每项 4 字节地址偏移内容中断号0x080000000x00初始栈顶 MSP—0x080000040x04Reset_Handler10x080000080x08NMI_Handler20x0800000C0x0CHardFault_Handler30x080000100x10MemManage_Handler40x080000140x14BusFault_Handler50x080000180x18UsageFault_Handler6............0x080000400x40WWDG_IRQHandler160x080000440x44PVD_IRQHandler17............0x080000D40xD4USART1_IRQHandler53计算公式向量表项地址 0x08000000 中断号 × 4验证 USART153 × 4 212 0xD4 → 0x080000D4 ✅3.3 Flash 页大小型号Flash 容量页大小STM32F103C864KB1KBSTM32F103CB128KB1KBSTM32F103ZE512KB2KB擦写 Flash 时必须按页擦不能按字节。所以做 IAP 时要算好页对齐。四、片上 SRAM0x20000000 起4.1 SRAM 里有什么0x2000FFFF ┌──────────────────────────┘ │ │ │ 栈 Stack │ ← 从高地址往下生长 │ ↓ │ │ 函数调用压栈、中断现场保护 │ │ │ │ 栈满了会覆盖堆/全局变量│ │ │ │ 堆 Heap │ ← 从低地址往上生长 │ ↑ │ ← malloc / free │ │ │ .bss 段 │ ← 未初始化全局变量 │ int b; │ 启动时自动清零 │ │ │ .data 段 │ ← 已初始化全局变量 │ int a 10; │ 启动时从 Flash 拷入 │ │ 0x20000000 └──────────────────────────┘4.2 .data 和 .bss 的区别段内容Flash 里有吗启动时做什么例子.data已初始化的全局变量有初值从 Flash 拷贝到 RAMint a 10;.bss未初始化的全局变量没有自动清零int b;为什么要分未初始化的变量默认都是 0不需要在 Flash 里存初值省 Flash 空间。启动代码里有一段专门做这事// 启动文件里 SystemInit 或 __main 做的事 extern uint32_t _sidata; // Flash 里 .data 的起始地址 extern uint32_t _sdata; // RAM 里 .data 的起始地址 extern uint32_t _ebss; // .bss 结束地址 extern uint32_t _sbss; // .bss 起始地址 // 拷贝 .data for (src _sidata, dst _sdata; dst _edata; ) *dst *src; // 清零 .bss for (dst _sbss; dst _ebss; ) *dst 0;4.3 栈和堆的大小在哪定的在启动文件里; 栈大小 1KB Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN3 Stack_Mem SPACE Stack_Size __initial_sp ; 堆大小 512B Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN3 __heap_base __heap_limit栈溢出的典型表现程序跑飞、HardFault、全局变量莫名其妙被改。因为栈往下长堆往上长撞在一起就出事了。五、片上外设寄存器区0x40000000 起这是你写代码最常打交道的区域——直接读写这个地址 读写硬件寄存器。5.1 总线划分0x40000000 ┌──────────────────────────┐ │ │ │ APB1 总线 │ ← 低速外设 │ USART2/3/4/5 │ │ TIM2~TIM7 │ │ I2C1/I2C2 │ │ CAN │ │ PWR/BKP │ │ │ 0x40008000 ├──────────────────────────┤ │ 保留 │ 0x40009000 ├──────────────────────────┤ │ 保留 │ 0x40010000 ├──────────────────────────┤ │ │ │ APB2 总线 │ ← 高速外设 │ GPIOA ~ GPIOG │ │ USART1 │ │ ADC1/ADC2 │ │ EXTI │ │ SPI1 │ │ TIM1 │ │ │ 0x40020000 ├──────────────────────────┤ │ │ │ AHB 总线 │ ← 最高速外设 │ DMA1/DMA2 │ │ RCC时钟控制 │ │ Flash 接口 │ │ CRC 计算单元 │ │ │ 0x50000000 └──────────────────────────┘5.2 外设基地址速查表外设基地址总线DMA10x40020000AHBRCC0x40021000AHBFlash 接口0x40022000AHBCRC0x40023000AHBGPIOA0x40010800APB2GPIOB0x40010C00APB2GPIOC0x40010000APB2USART10x40013800APB2USART20x40004400APB1USART30x40004800APB1SPI10x40013000APB2SPI20x40003800APB1TIM10x40012C00APB2TIM20x40000000APB1ADC10x40012400APB2EXTI0x40010400APB25.3 寄存器地址怎么算每个外设有一个基地址寄存器相对于基地址有偏移量。以 USART1 为例USART1 基地址 0x40013800 寄存器 偏移 实际地址 作用 ────────── ────── ────────────── ────────────── USART_SR 0x00 0x40013800 状态寄存器 USART_DR 0x04 0x40013804 数据寄存器收发 USART_BRR 0x08 0x40013808 波特率 USART_CR1 0x0C 0x4001380C 控制寄存器1 USART_CR2 0x10 0x40013810 控制寄存器2 USART_CR3 0x14 0x40013814 控制寄存器3 USART_GTPR 0x18 0x40013818 保护时间/预分频代码里直接操作// 读 USART1 状态寄存器 uint32_t sr *(volatile uint32_t *)0x40013800; // 往 USART1 写一个字节 *(volatile uint32_t *)0x40013804 0x55;库函数只是帮你封装好了// 头文件里的宏定义 #define USART1 ((USART_TypeDef *)0x40013800) // 使用 USART1-DR 0x55; // 本质还是写 0x40013804volatile 关键字必须加——告诉编译器这个地址可能被硬件改变不要优化掉读取。六、内核外设区0xE0000000 起这块区域是 Cortex-M 内核自己的私有外设和具体芯片无关外设基地址作用SysTick0xE000E010系统滴答定时器NVIC0xE000E100嵌套向量中断控制器SCB0xE000ED00系统控制块VTOR、AIRCR 等SCB 里最常用的寄存器寄存器偏移作用SCB-VTOR0x08向量表偏移寄存器SCB-AIRCR0x0C应用程序中断/复位控制SCB-SHCSR0x24系统异常控制和状态SCB-CPUID0x00内核 ID读出来知道是 M3 还是 M4七、位带区Bit-bandCortex-M3 有个很实用的特性——把一个寄存器的某一位映射到一个单独的地址直接读写那个地址就等于操作那一位不需要读-改-写。7.1 两个位带区区域位带区别名对应区别名区单独位地址SRAM0x20000000 ~ 0x200FFFFF0x22000000 ~ 0x23FFFFFF外设0x40000000 ~ 0x400FFFFF0x42000000 ~ 0x43FFFFFF7.2 地址换算公式别名地址 别名区起始 (字节偏移 × 32) (位号 × 4)为什么是 ×32因为每个字节有 8 位每位映射一个 32 位字4 字节8 × 4 32。7.3 例子操作 PB5GPIOB_BASE 0x40010C00 ODR 偏移 0x0C ODR 地址 0x40010C0C PB5 的位号 5 字节偏移 0x40010C0C - 0x40000000 0x10C0C 别名地址 0x42000000 (0x10C0C × 32) (5 × 4) 0x42000000 0x218180 0x14 0x420218194直接写*(volatile uint32_t *)0x420218194 1; // PB5 输出高 *(volatile uint32_t *)0x420218194 0; // PB5 输出低好处原子操作不会被中断打断不用读-改-写省一条指令多任务/中断安全八、BOOT 启动映射STM32 上电时CPU 总是从 0x00000000 开始取向量表。但这个地址映射到哪里由 BOOT 引脚决定BOOT0BOOT10x00000000 映射到向量表位置用途0X主 Flash0x08000000正常运行最常用10系统存储器0x1FFFF000ISP 串口烧录11SRAM0x20000000调试用少见CPU 上电PC 0x00000000 │ ↓ 芯片内部根据 BOOT 引脚重映射 ┌──────┼──────┐ ↓ ↓ ↓ 0x08000000 0x1FFFF000 0x20000000 主Flash 系统存储 SRAM所以你在芯片手册上看到向量表在 0x08000000但 CPU 实际是从 0x00000000 开始执行的——这是芯片内部做了地址重映射对程序员透明。九、VTOR运行时重定位向量表SCB-VTOR 寄存器可以在程序运行时改变向量表位置// 默认向量表在 Flash 起始 SCB-VTOR 0x08000000; // Bootloader 跳 AppApp 向量表在 Flash 偏移 16KB SCB-VTOR 0x08004000; // 把向量表搬到 SRAM运行时改中断函数 SCB-VTOR 0x20000000;注意VTOR 的地址必须对齐。对齐大小 向量表总大小向上取整到 2 的幂。至少对齐到 128 字节32 个向量 × 4 字节。十、一张图总结高地址 0xFFFFFFFF │ ┌────┴─────────┐ │ 系统级空间 │ ← NVIC(0xE000E100), SCB(0xE000ED00) ├──────────────┤ │ 外部扩展区 │ ← FSMC 外接 ├──────────────┤ │ 外设寄存器 │ ← GPIOA(0x40010800), USART1(0x40013800) │ 0x40000000 │ RCC(0x40021000), TIM2(0x40000000) ├──────────────┤ │ │ │ SRAM 64KB │ ← 栈(0x2000A000), 堆, .data/.bss │ 0x20000000 │ │ │ ├──────────────┤ │ Flash 128KB │ ← 向量表(0x08000000), 代码, .rodata │ 0x08000000 │ │ │ 低地址 0x00000000常用速查用途地址主 Flash 起始0x08000000主 SRAM 起始0x20000000AHB 外设基地址0x40000000APB2 外设基地址0x40010000APB1 外设基地址0x40000000GPIOA 基地址0x40010800GPIOB 基地址0x40010C00USART1 基地址0x40013800RCC 基地址0x40021000NVIC 基地址0xE000E100SCB 基地址0xE000ED00SysTick 基地址0xE000E010

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价