MCU上电后的动作
配套视频 :
p04_mcu_startup_1080p30.mp4(1920×1080@30,7 分 12 秒,10 章)【俺系一坨硅的作品】【芯片拆解室】 按下复位后,芯片内部发生了什么?
【MCU拆解室--MCU的启动过程-哔哩哔哩】 https://b23.tv/oaufs54
参考芯片 :STM32F103C8T6(Cortex-M3,72 MHz,64 KB Flash,20 KB SRAM)
本文定位:对照阅读版。地址、段大小、等待周期都来自本集真实 ELF / map / 数据手册。
阅读指南
| 章节 | 对应视频 | 时间码 | 一句话 |
|---|---|---|---|
| 0 | --- | --- | 三个问题 |
| 1 | L1 上电 | 00:00 | 复位后 1 毫秒里的五拍 |
| 2 | L2 三块地皮 | 00:57 | 手册 p11/p34 与地址空间 |
| 3 | L3 时钟心跳 | 01:32 | HSI→HSE→PLL 72 MHz |
| 4 | L4 链接脚本 | 02:04 | ENTRY/MEMORY/SECTIONS 三件事 |
| 5 | L5 启动文件 | 03:48 | 向量表 → Reset_Handler → main |
| 6 | L6 取指执行 | 04:18 | Thumb 16/32 位与流水线 |
| 7 | L7 栈 | 04:45 | push/sub/pop 与 1540 字节 |
| 8 | L8 中断 | 05:24 | 硬件自动压 8 个寄存器 |
| 9 | L9 总线 | 06:05 | 等待周期与 ramfunc |
| 10 | L10 总览 | 06:53 | 十拍路径 |
第 0 章 三个问题
- 复位之后,CPU 怎么知道第一条指令在哪?
- 谁把
.data从 Flash 搬进 RAM?谁把.bss清零? - 中断发生时,现场是谁保存的?
第 1 章 上电后的 1 毫秒

图 1-1 STM32F103 最小系统板:3.3 V、晶振、复位键(视频 00:15)
复位后,硬件只做两件事(读向量表前两项):
| 拍 | 读哪里 | 装进 | 值(本工程) |
|---|---|---|---|
| 1 | [0x0000_0000] |
SP | 0x2000_5000 |
| 2 | [0x0000_0004] |
PC | 0x0800_086C(Reset_Handler) |
然后启动代码接手:
SystemInit:配时钟(HSI 8 MHz → PLL 72 MHz);- 把
.data从 Flash 拷到 SRAM; - 把
.bss从0x2000_0060清到0x2000_0224; bl main→ PC =0x0800_0958。

图 1-2 五拍:读栈顶 / 读复位向量 / 搬 data / 清 bss / 跳 main(视频 00:40)
为什么先设 SP 再跳? 因为 C 函数一进去就要用栈;没有 SP 就没有栈,任何
push都会写飞。
第 2 章 三块地皮与地址空间

图 2-1 RM0008/DS 框图:内核 72 MHz、Flash 64 KB、SRAM 20 KB、BusMatrix(视频 01:15)
| 区域 | 起始 | 大小 | 权限 |
|---|---|---|---|
| Flash | 0x0800_0000 |
64 KB | 读/执行 |
| SRAM | 0x2000_0000 |
20 KB | 读/写/执行 |
| 外设 | 0x4000_0000 |
--- | 读/写 |
| 内核外设(NVIC/SysTick) | 0xE000_0000 |
--- | 读/写 |
地址就是门牌号:CPU 不区分"内存"和"外设",总线译码器按地址分发。
本集与 P02 的差别 :P02 用 L552(512 KB/256 KB)讲概念;从本集起统一到 F103(64 KB/20 KB)。所以本集所有地址都以 F103 为准(
_estack = 0x2000_5000)。
第 3 章 时钟:芯片的心跳

图 3-1 HSI/HSE → PLL → SYSCLK → AHB/APB(视频 01:45)
| 步骤 | 时钟 | 频率 |
|---|---|---|
| 上电默认 | HSI(内部 RC) | 8 MHz |
| 外部晶振稳定 | HSE | 8 MHz |
| PLL ×9 | SYSCLK | 72 MHz |
| AHB 分频 | HCLK | 72 MHz |
| APB1 分频 /2 | PCLK1 | 36 MHz |
| APB2 分频 /1 | PCLK2 | 72 MHz |
为什么要先 HSI 再 HSE? 上电时外部晶振还没起振(需要几毫秒),先用内部 RC 保证芯片能跑,等 HSE 稳定再切换。
时钟决定了"一拍"有多长;后面第 9 章的等待周期、第 8 章的 1 ms 中断,都以它为前提。
第 4 章 链接脚本:三块骨架

图 4-1 ENTRY / MEMORY / SECTIONS;权限字母 rx / rwx;通配符;AT>FLASH(视频 02:45)
4.1 ENTRY 与 MEMORY
ld
ENTRY(Reset_Handler)
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
(rx):可读可执行,不可写 → 所以变量不能住在 Flash;(rwx):可读可写可执行 → RAM。
4.2 SECTIONS 与权限的含义
ld
.isr_vector : { KEEP(*(.isr_vector)) } >FLASH
.text : { *(.text .text.*) } >FLASH
.rodata : { *(.rodata .rodata.*) } >FLASH
.data : { *(.data) } >RAM AT> FLASH
.bss : { *(.bss) } >RAM
._user_heap_stack : { ... } >RAM
- 括号里不是文件名,是通配符 :把所有
.o的同名段收进来; .data的AT> FLASH:运行地址在 RAM,装载地址在 Flash(LMA/VMA);.bss不占 Flash,启动时清零。
4.3 对着真实固件看
| 段 | 地址 | 大小 |
|---|---|---|
.isr_vector |
0x0800_0000 |
236 B |
.text |
0x0800_00EC |
3,268 B |
.rodata |
0x0800_0DB0 |
16 B |
.data |
LMA 0x0800_0DC8 / VMA 0x2000_0000 |
96 B |
.bss |
0x2000_0060 |
452 B |
._user_heap_stack |
0x2000_0224 |
1,540 B |
_estack |
0x2000_5000 |
--- |
_estack钉死在 SRAM 顶端:栈从这里往下长。
第 5 章 启动文件:逐行看

图 5-1 向量表前三项(小端)+ 复位那一拍 SP/PC 同时点亮(视频 04:00)
asm
__Vectors:
.word 0x20005000 ; [0] 初始 SP
.word Reset_Handler ; [4] 复位入口
.word NMI_Handler ; [8]
...
.word SysTick_Handler ; [0x3C] 第 15 项
Reset_Handler 的真实流程:
asm
Reset_Handler:
bl SystemInit ; 配时钟
ldr r0, =_sdata
ldr r1, =_sidata ; Flash 里的 .data 模板
ldr r2, =_edata
CopyDataInit:
ldr r3, [r1], #4
str r3, [r0], #4
cmp r0, r2
bcc CopyDataInit ; 搬 .data
ldr r0, =_sbss
ldr r1, =_ebss
movs r3, #0
FillZerobss:
str r3, [r0], #4
cmp r0, r1
bcc FillZerobss ; 清 .bss
bl main ; 进 main
注意
ldr r3,[r1],#4:带后索引的 ldr------读完自动把指针加 4,是拷贝循环的经典写法。
第 6 章 取指与执行

图 6-1 PC 指向 Flash,指令经 AHB 取回;0x4803 是一条 16 位 Thumb 指令(视频 04:30)
- 每条指令先按 PC 从 Flash 读;Cortex-M3 是三级流水线(取指/译码/执行);
- Thumb-2:大部分指令 16 位,复杂指令 32 位;
0x4803=ldr r0, [pc, #12](16 位)。
取指要经过 AHB、Flash;Flash 有等待周期(第 9 章),这就是"流水线气泡"的来源。
第 7 章 栈:1540 字节的家底

图 7-1 C/汇编/栈三栏对照:push 压帧、sub 腾空间、pop 返回(视频 05:05)
asm
; 函数入口
push {r4, lr} ; 保存现场
sub sp, #8 ; 局部变量(int s)
...
pop {r4, pc} ; 恢复并返回
- 上电 SP =
0x2000_5000; - 链接脚本没有显式分配栈,栈和堆共享剩下的 1,540 字节 (
0x2000_0224向上到0x2000_5000); - 撞上就 HardFault(或踩坏 .bss)。
工程建议 :用
-Wl,--print-memory-usage和 map 文件确认余量;中断嵌套、递归、大局部数组都会加深栈。
第 8 章 中断:硬件替你做的那部分

图 8-1 SysTick 打断主循环,硬件自动压 R0--R3/R12/LR/PC/xPSR(视频 05:45)
SysTick 每 1 ms 打断一次:
- 硬件自动把 8 个寄存器 压栈:
R0 R1 R2 R3 R12 LR PC xPSR; - 从向量表第 15 项(偏移
0x3C)取SysTick_Handler,装进 PC; - 返回时硬件自动弹栈(
EXC_RETURN决定用 MSP 还是 PSP)。
重要边界 :自动压栈是 Cortex-M 的硬件特性;AVR、多数 RISC-V 需要软件保存现场。面试常考。
被中断的现场 (真实反汇编):主循环 0x0800_0978 ldr r2,[r4] → 097A cmp → 097C bne;压栈时 PC 记的是 0x0800_097C,返回后从这里继续。
第 9 章 总线:等待周期与 ramfunc

图 9-1 HCLK 扫描九拍、Flash 一次读要 3 拍(2 个等待周期)(视频 06:20)
- AHB 72 MHz;CPU 取指/数据都走它;
- Flash 跟不上 72 MHz :一次读要 2 个等待周期(共 3 拍);
- 对策一:打开预取缓冲(
FLASH->ACR的 PRFTBE),减少气泡; - 对策二:把最热的函数搬进 RAM(ramfunc)。
ramfunc 三步:
c
__attribute__((section(".ramfunc"))) void hot(void) { ... }
ld
.ramfunc : { *(.ramfunc .ramfunc.*) } >RAM AT> FLASH
asm
; 启动文件里已有的 CopyDataInit 稍作修改即可搬 .ramfunc
代价:占 RAM、启动多一次拷贝、只适合最热的函数。
第 10 章 总览:十拍路径
- 上电/复位 → 读
[0]设 SP; - 读
[4]跳 Reset_Handler; - SystemInit 配时钟;
- 搬
.data; - 清
.bss; bl main;- 取指(Flash → AHB → CPU);
- 函数用栈(push/sub/pop);
- 中断自动压栈;
- 总线等待周期与 ramfunc 优化。
自测(10 题)
- 复位后前两条"指令"读的是什么?
- 为什么链接脚本里 Flash 没有
w? .data的 LMA 和 VMA 分别在哪?_estack是多少?为什么是这个值?- SysTick 中断硬件压了哪 8 个寄存器?
- 为什么中断返回能"自动"弹栈?
- Flash 等待周期是多少?影响什么?
- ramfunc 的三步与代价?
- 栈和堆共享多少字节?撞上会怎样?
- 为什么先 HSI 再 HSE?
参考答案
- 向量表第 0 项(SP 初值)和第 1 项(复位入口)。
- Flash 只读/可执行,不可写;变量必须在 RAM。
- LMA 在 Flash(0x0800_0DC8),VMA 在 RAM(0x2000_0000)。
0x2000_5000(20 KB SRAM 顶端),栈从这里向下长。- R0 R1 R2 R3 R12 LR PC xPSR。
EXC_RETURN特殊值告知硬件从哪个栈恢复。- 2 个等待周期(一次读 3 拍);影响取指/数据吞吐。
section(".ramfunc")+ 链接脚本>RAM AT>FLASH+ 启动拷贝;代价是 RAM 占用与启动拷贝。- 1,540 字节;撞上 HardFault 或踩坏 .bss。
- 上电时外部晶振未稳定,先用内部 RC 保证可运行。