STM32 上电启动
Todo
本笔记仍在学习中,暂时只整理已经学到的启动过程。
先看结论
STM32 复位后并不是直接跳进 main(),而是先从向量表取得初始栈顶和复位入口,再由启动文件完成运行时初始化,最后才进入用户代码。排查“程序不进 main()”时,应严格按这个顺序定位:供电/复位 → 向量表 → Reset_Handler → .data/.bss → 时钟 → main()。
阅读导航
- 启动链路:先理解复位后 CPU 实际执行的步骤。
- 排查方法:根据停留位置快速缩小范围。
- 启动文件与链接脚本:明确每一步由哪个文件负责。
- 后续方向:再进入 Bootloader、升级和多应用管理。
1. 启动链路
上电或复位
↓
读取向量表第 0 项(初始栈顶地址)
↓
将该值装入 MSP
↓
读取向量表第 1 项(复位向量)
↓
将复位向量装入 PC,进入 Reset_Handler
↓
执行启动文件 startup_<device>.s/.c
├── 复制 .data 段到 SRAM
├── 清零 .bss 段
├── 调用 SystemInit()
│ ├── 配置时钟系统
│ ├── 可能配置 Flash 预取
│ └── 完成其他底层初始化
└── 调用 __libc_init_array()
└── 执行 C++ 全局对象构造函数
↓
进入 main()
↓
用户程序开始执行
Note
.data、.bss、SystemInit() 和运行库初始化的具体先后顺序可能因芯片启动文件及工具链而不同,后续结合实际工程继续确认。
2. 启动阶段各文件的职责
| 阶段 | 主要文件/符号 | 要确认的事实 |
|---|---|---|
| 向量表 | startup_<device>.s、.isr_vector |
第 0 项是初始 MSP,第 1 项是 Reset_Handler 地址 |
| 运行时初始化 | Reset_Handler |
.data 从 Flash 复制到 SRAM,.bss 清零 |
| 时钟与底层硬件 | SystemInit() |
时钟树、Flash 等待周期和芯片级底层配置 |
| C/C++ 运行库 | __libc_init_array()(若使用) |
C++ 全局对象构造、运行库初始化 |
| 用户程序 | main() |
HAL、外设和业务逻辑从这里开始 |
启动文件、链接脚本和芯片型号必须匹配。更换 Flash 容量、启动地址或 Bootloader 后,要同步检查向量表位置和链接脚本,不能只替换一个 .s 文件。
3. 启动异常的排查顺序
3.1 先排除硬件与复位
- 测量 VDD、VDDA 和 GND,确认电源在允许范围内且没有明显跌落。
- 检查
NRST是否被外部电路持续拉低;确认上拉、电容和复位芯片连接正确。 - 检查 BOOT0/BOOT 配置,排除芯片进入系统 Bootloader 而不是用户 Flash 的情况。
3.2 再确认向量表和 Reset_Handler
使用调试器读取 Flash 起始地址(常见为 0x08000000,具体以芯片为准):
地址 + 0x00:初始 MSP,应落在有效 SRAM 范围
地址 + 0x04:Reset_Handler,应落在有效代码区且最低位为 1(Thumb 状态)
若这两项明显异常,优先检查链接脚本、烧录地址、芯片型号和镜像是否完整,不要先改 main()。
3.3 在启动函数设置断点
建议按以下顺序下断点或插入一次性标记:
Reset_Handler入口;.data复制和.bss清零之后;SystemInit()返回处;main()第一行。
每到一个断点就记录 PC、SP 和时钟状态。这样可以区分“没有执行到”和“执行后又 HardFault”。
3.4 进入 main() 后再查外设
若已经进入 main(),但应用无输出,再检查 HAL 初始化、时钟频率、串口引脚复用和中断向量;不要把外设故障误判为启动文件故障。
4. 学习这部分的作用
问题排查
- 系统无法启动时定位问题
- 理解复位后的系统状态
- 调试启动阶段的错误
系统优化
- 优化启动时间
- 减少功耗
- 自定义初始化流程
深入理解
- 掌握 ARM Cortex-M 的工作机制
- 理解编译器和链接器的作用
- 了解硬件和软件的交互
5. 后续内容
- Bootloader
- 固件升级
- 多应用程序管理