跳转到内容
新建笔记

STM32 内存映射、链接布局与启动过程

“STM32 的内存划分”至少包含两个层次:芯片把 Flash、SRAM 和外设放在哪些地址;工具链又把代码、变量、堆和栈放进哪些区域。启动代码负责把链接布局变成 C/C++ 程序能够使用的运行状态。把这三件事连起来,才能解释变量地址、启动失败和内存越界。

本页主要使用 STM32F103/Cortex-M3,再用 F407、H743 说明跨系列差异。容量和脚本数值必须与实际料号一致。

1. 架构地址空间不等于芯片实际容量

跳转到“1. 架构地址空间不等于芯片实际容量”

Cortex-M3 可寻址空间为 2322^{32} 字节,即4 GiB;这不是芯片含有4 GiB内存。架构为区域规定访问类型,芯片只在其中实现部分存储器和外设。

地址范围Cortex-M3 区域STM32F103 中应怎样理解
0x00000000–0x1FFFFFFFCode,512 MiB包括启动视图、主Flash及系统存储器等;不是512 MiB的启动镜像。
0x20000000–0x3FFFFFFFSRAM,512 MiB实际片上SRAM从0x20000000开始,容量按型号;其余地址不是任意可用RAM。
0x40000000–0x5FFFFFFFPeripheral,512 MiB映射外设寄存器,访问具有设备语义。
0x60000000–0x9FFFFFFFExternal RAM,1 GiB有相应控制器的芯片可映射外部存储器;板上也必须实际连接。
0xA0000000–0xDFFFFFFFExternal device,1 GiB不是全部保留;例如F103相应型号的FSMC寄存器从0xA0000000开始。
0xE0000000–0xE00FFFFFPPB,1 MiBNVIC、SysTick、SCB及调试相关映射。
0xE0100000–0xFFFFFFFF厂商定义/保留范围不能把它与上面的1 MiB合称全部PPB。

访问没有实现的地址可能产生总线错误;访问已经实现、但不属于当前对象的 SRAM,反而可能悄悄破坏其他数据。越界不保证立即 HardFault。PM0056 内存模型

2. F103 中常用的实际地址

跳转到“2. F103 中常用的实际地址”
对象地址/条件
主 Flash0x08000000起;例如F103C8标称64 KiB,容量必须按具体订货型号。
SRAM0x20000000起;例如F103C8为20 KiB,不将其他密度的64/96 KiB套给它。
System memoryST出厂ROM引导程序;部分中/高密度F103从0x1FFFF000开始,XL从0x1FFFE000开始,按AN2606分组。
Flash容量寄存器F103相关表中位于0x1FFFF7E0;读宽和单位按文档。
96位UIDF103相关表中从0x1FFFF7E8开始。
Option bytes0x1FFFF800附近的专用配置区,需要专门擦写流程;不是普通只读UID。
GPIOA / USART1 / RCCF103示例分别为0x40010800、0x40013800、0x40021000;程序优先使用对应CMSIS设备头文件。

APB1、APB2、AHB描述总线和外设连接。典型F103中GPIO/AFIO/USART1等位于APB2,USART2/I²C/PWR/BKP等位于APB1,DMA/RCC等在AHB相关路径;实际时钟由分频和芯片上限共同决定,不能把总线名称直接翻译为固定的快慢比例。

Flash擦除页大小、编程粒度和单双Bank随容量等级变化。F103常见中密度1 KiB页、高密度2 KiB页,其基本编程操作按手册使用半字等规定粒度;不要把F4的扇区规则套到F1。将一个Flash操作函数放到RAM,也不能自动保证其调用函数、常量、向量表和中断处理都不再访问受影响Flash。RM0008

BOOT0/BOOT1选择主Flash、System memory或SRAM启动,详细接线见启动引脚。主Flash和System memory可在启动空间出现别名,同时保留各自正常地址;这不代表整个512 MiB Code区被重映射。

RM0008对 SRAM启动特别说明:芯片使用特殊机制取得启动信息,SRAM仍只在0x20000000处访问;应用初始化要把向量表移到SRAM。不能简单画成所有0x00000000访问都变成SRAM访问。

复位后的核心从启动视图取得两项:

偏移0x00:初始MSP(主栈指针)
偏移0x04:Reset_Handler入口向量

异常向量的最低位用于表示Thumb状态;入口必须符合Cortex-M约定,不应把函数向量最低位清零后随意跳转。初始MSP必须落在设计允许的RAM栈范围并满足对齐要求。

VTOR只选择向量表,不替你完成应用交接

跳转到“VTOR只选择向量表,不替你完成应用交接”

SCB->VTOR决定异常处理使用哪张向量表,不会搬运整个程序,也不会把当前执行流变成新应用。PM0056对本Cortex-M3实现规定最小 128个word,即512字节对齐;ST F1模板也要求相应基址/偏移为0x200的倍数,不能误写为128字节。向量数量等进一步要求按具体实现核对。

Bootloader交接应用还需约定MSP、Thumb入口、中断屏蔽/挂起、SysTick、DMA、外设和时钟状态,必要时处理屏障及执行上下文。只给VTOR=APP_BASE; jump();会隐藏关键前提,本页不把它当完整通用跳转函数。

4. 节、段、运行地址与加载地址

跳转到“4. 节、段、运行地址与加载地址”

编译器生成输入节(section),链接器把它们放入输出节,并形成可执行文件的装载信息。日常口语常把这些都叫“段”,但调试ELF时应区分section与program segment。

常见输出节/区域通常用途Flash/SRAM关系
.isr_vector初始栈顶和异常入口通常在Flash开头,也可按设计放入RAM。
.text机器指令常在Flash直接执行;特定函数可安排到RAM。
.rodata只读常量、字符串、表格常放Flash,但优化和链接脚本决定实际归属。
.data有初值的可写静态存储对象运行时在RAM;初始字节通常储存在Flash。
.bss零初始化的静态存储对象占RAM,启动时清零,通常不在镜像中存同等数量零字节。
.init_array等C/C++运行时初始化入口启动代码按运行库约定调用。
heap / stack动态分配区和调用栈按链接器、运行库/RTOS配置分配RAM,并非必有同名ELF节。

VMA是运行地址;LMA是初始内容的加载地址。.data正是两者可能不同的典型例子:

Flash:代码 | 常量 | data初值(LMA)
│ Reset_Handler复制
▼
RAM: data运行区(VMA) | bss清零区 | 其他缓冲/堆/栈

如果只把.data的VMA放在RAM,却没有安排Flash中的初始镜像或启动复制,那么带初值变量不一定正确;反过来,RAM段间很大的地址空洞也可能导致错误的裸二进制尺寸。

#include <stdint.h>
uint32_t counter = 7u;
uint32_t samples[16];
const uint32_t calibration_table[4] = {10u, 20u, 30u, 40u};
int main(void)
{
for (;;) {
/* 在调试器中观察地址和初始内容,业务代码另行实现。 */
}
}

在保留这些对象的普通链接设置中,counter通常在.data,samples在.bss,常量表在.rodata。开启优化/垃圾回收后,未被使用的对象可能消失,因此应看本次构建的map/ELF,不能只凭C声明推断最终地址。

GNU工具链可检查:

终端窗口
arm-none-eabi-size firmware.elf
arm-none-eabi-objdump -h firmware.elf
arm-none-eabi-nm -n firmware.elf
arm-none-eabi-readelf -l firmware.elf

objdump -h查看节的大小、VMA/LMA,nm查符号,readelf -l看program headers,链接时生成的map进一步回答对象由哪个目标文件引入。

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

这仅是F103C8容量例子的区域声明。完整脚本还要有SECTIONS、向量保留、对齐、.data的AT>加载位置、启动符号、栈/堆预留和超界检查,不能把这六行当可直接替换工程的完整链接脚本。

5. 从复位到main:实际启动文件决定顺序

跳转到“5. 从复位到main:实际启动文件决定顺序”

芯片复位后先使用规定的复位时钟状态;F103以内部HSI为初始系统时钟来源。BOOT采样、核心取MSP/入口与软件初始化属于不同步骤。

本次核对的ST官方GNU startup_stm32f103xb.s顺序为:

硬件取初始MSP/Reset_Handler
→ Reset_Handler调用SystemInit
→ 从_sidata复制初值到_sdata…_edata
→ 将_sbss…_ebss清零
→ __libc_init_array
→ main

这一模板中 SystemInit在.data/.bss准备前调用。因此这种早期初始化不能任意依赖已经初始化的全局可写对象。不同编译器、年代和工程模板可能采用不同顺序,应打开实际startup和system文件核对。

当前所核对的F1 SystemInit模板主要建立系统初态并按配置处理向量/外部存储等,并不意味着它一定开启HSE、配置PLL到72 MHz。许多Cube生成项目在main中的SystemClock_Config完成目标时钟配置;旧库可能有其他组织方式。

C++非局部对象的初始化由相应运行时处理;函数内静态对象可能在第一次执行到声明时才构造,不能说“所有静态对象都在main前构造”。没有正常退出流程的固件也不能假定会执行全部析构。

启动耗时不固定为1–5 ms。电源/复位释放、晶振启动、PLL、数据量、C++构造和应用初始化都会影响它。用GPIO标记并测量各阶段时,应说明GPIO本身何时已经可访问;RCC_CSR的复位标志应在按规定清除前读取。

常见裸机布局把低地址留给.data/.bss、高地址作为向下增长的栈,中间留堆。但“堆向上、栈从RAM顶部向下”是布局方案,不能覆盖所有内存池、多区域RTOS和自定义运行库。

  • Cortex-M3有MSP和PSP两个栈指针寄存器。处理异常时使用MSP;线程模式可按配置用MSP或PSP。
  • 单个CPU不是为每个任务提供一只永久独立的PSP寄存器。RTOS在任务切换时保存/恢复每个任务的栈指针和上下文。
  • 任务控制块(TCB)保存任务管理信息,不等于任务栈本身。
  • FreeRTOS heap_4管理一块堆数组,heap_5可管理多个不连续区域;静态创建任务可由应用提供TCB和栈,不经过RTOS堆。
  • C库malloc和RTOS分配器可能是不同区域与接口,不应仅调一个heap_size就认为两者都被限制。

根据调用深度、中断嵌套、局部大数组和库函数开销估算栈,再结合水位/溢出检测测试。水位是已观察路径的证据,不是所有异常路径一定安全的证明。中断中能否分配取决于分配器和系统约束,普通阻塞堆接口不应直接搬入ISR。

7. 位带、外设访问与DMA

跳转到“7. 位带、外设访问与DMA”

F103/Cortex-M3架构的SRAM和外设位带区分别从0x20000000、0x40000000开始,各覆盖1 MiB;对应别名区从0x22000000、0x42000000开始。只有底层实际实现、且允许这样访问的对象才有意义。

若目标字节相对位带区起点的偏移为 dd,该字节内位号 b∈[0,7]b\in[0,7],其32位别名地址为

Aalias=Aalias base+32d+4b.A_{\mathrm{alias}}=A_{\mathrm{alias\ base}}+32d+4b.

例如SRAM起点字节的第3位,对应0x2200000C。这是按位访问机制,不等于任意多寄存器操作都成为原子事务。GPIO常可直接使用BSRR;对读清零、写1清零等特殊寄存器应按外设语义操作。

volatile限制编译器对相应访问的某些优化,不提供多任务锁、复合操作原子性或缓存一致性。DMA缓冲还应满足控制器可达性、对齐、生命周期与同步要求。F103 Cortex-M3没有H7 Cortex-M7那套数据缓存问题,不能反过来把F1经验推广到H7。

8. 跨系列比较:不要搬错地址

跳转到“8. 跨系列比较:不要搬错地址”
明确器件例子区域起始地址注意点
STM32F407CCM RAM0x10000000核心耦合区域,DMA访问能力与普通SRAM不同。
STM32H743ITCM RAM0x00000000面向指令的TCM;不是F407的CCM地址。
STM32H743DTCM RAM0x20000000数据TCM,主设备访问能力需查互联表。
STM32H743AXI SRAM0x24000000D1域AXI路径。
STM32H743D2 SRAM1等0x30000000起各块起点/容量分别查表,不视作无限连续区域。
STM32H743SRAM40x38000000D3域。
STM32H743备份SRAM0x38800000仍位于该RAM地址范围,保持条件单独核对。

F103有备份寄存器,不能默认存在电池保持的普通备份SRAM。F407的备份SRAM位置和H743也不同;“备份”是功能,不决定它一定落在某一固定地址大区。RM0090、RM0433

普通Linux/Windows进程依靠MMU、内核和装载器建立虚拟地址空间,可使用动态库与文件映射;F103没有这种分页式进程虚拟内存,常见固件按芯片地址静态链接,再由启动代码准备运行数据。

两者都涉及代码、常量、静态对象、堆和栈,但负责人不同。不能从“没有桌面进程装载器”推导出F103绝不可能实现自定义模块/资源加载;那需要软件自己设计格式、定位和权限,不能直接套用桌面mmap/动态链接的假设。

10. 定位问题的顺序与验证范围

跳转到“10. 定位问题的顺序与验证范围”

查变量先看map/ELF的符号和节,再看芯片地址范围、DMA可达性与初始化。查启动先看BOOT与复位、电源,再看向量的MSP/Thumb入口、链接地址、启动复制/清零和时钟建立。查随机崩溃还要考虑栈、越界、并发与外设副作用,不能只凭一个地址就判断“RAM坏了”。

本页整合3篇完整原文,保留桌面对照、位带、链接布局、RTOS、跨系列比较和启动排查。官方启动源、各器件手册和FreeRTOS分配器已核对;示例对象另用Arm GNU工具链生成ELF检查节、符号、LMA/VMA和初值,未在实物板上执行或完成整套固件启动验收。