嵌入式进阶——MCU上电后的动作

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 章 三个问题

  1. 复位之后,CPU 怎么知道第一条指令在哪?
  2. 谁把 .data 从 Flash 搬进 RAM?谁把 .bss 清零?
  3. 中断发生时,现场是谁保存的?

第 1 章 上电后的 1 毫秒

图 1-1 STM32F103 最小系统板:3.3 V、晶振、复位键(视频 00:15)

复位后,硬件只做两件事(读向量表前两项):

拍 读哪里 装进 值(本工程)
1 [0x0000_0000] SP 0x2000_5000
2 [0x0000_0004] PC 0x0800_086C(Reset_Handler)

然后启动代码接手:

  1. SystemInit:配时钟(HSI 8 MHz → PLL 72 MHz);
  2. 把 .data 从 Flash 拷到 SRAM;
  3. 把 .bss 从 0x2000_0060 清到 0x2000_0224;
  4. 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 打断一次:

  1. 硬件自动把 8 个寄存器 压栈:R0 R1 R2 R3 R12 LR PC xPSR;
  2. 从向量表第 15 项(偏移 0x3C)取 SysTick_Handler,装进 PC;
  3. 返回时硬件自动弹栈(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 章 总览:十拍路径

  1. 上电/复位 → 读 [0] 设 SP;
  2. 读 [4] 跳 Reset_Handler;
  3. SystemInit 配时钟;
  4. 搬 .data;
  5. 清 .bss;
  6. bl main;
  7. 取指(Flash → AHB → CPU);
  8. 函数用栈(push/sub/pop);
  9. 中断自动压栈;
  10. 总线等待周期与 ramfunc 优化。

自测(10 题)

  1. 复位后前两条"指令"读的是什么?
  2. 为什么链接脚本里 Flash 没有 w?
  3. .data 的 LMA 和 VMA 分别在哪?
  4. _estack 是多少?为什么是这个值?
  5. SysTick 中断硬件压了哪 8 个寄存器?
  6. 为什么中断返回能"自动"弹栈?
  7. Flash 等待周期是多少?影响什么?
  8. ramfunc 的三步与代价?
  9. 栈和堆共享多少字节?撞上会怎样?
  10. 为什么先 HSI 再 HSE?

参考答案

  1. 向量表第 0 项(SP 初值)和第 1 项(复位入口)。
  2. Flash 只读/可执行,不可写;变量必须在 RAM。
  3. LMA 在 Flash(0x0800_0DC8),VMA 在 RAM(0x2000_0000)。
  4. 0x2000_5000(20 KB SRAM 顶端),栈从这里向下长。
  5. R0 R1 R2 R3 R12 LR PC xPSR。
  6. EXC_RETURN 特殊值告知硬件从哪个栈恢复。
  7. 2 个等待周期(一次读 3 拍);影响取指/数据吞吐。
  8. section(".ramfunc") + 链接脚本 >RAM AT>FLASH + 启动拷贝;代价是 RAM 占用与启动拷贝。
  9. 1,540 字节;撞上 HardFault 或踩坏 .bss。
  10. 上电时外部晶振未稳定,先用内部 RC 保证可运行。
相关推荐
车软学堂3 小时前
从测试转开发,到底要补什么
网络·人工智能·python·功能测试·单片机·车载系统·自动化
LCG米3 小时前
STM32 ADC多通道DMA定时器触发实测:TRGO触发链、采样率精确控制与乒乓缓冲落地
stm32·单片机·嵌入式·adc
iCxhust3 小时前
SEGGER 的 J-Scope
单片机·嵌入式硬件
bkspiderx5 小时前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信
路上有只喵5 小时前
认识元器件 · 单片机(MCU/MPU/SOC) · 实战篇2
单片机·嵌入式硬件
单片机仿真设计5 小时前
Proteus仿真51单片机的智能温控风扇调速系统设计(仿真图+程序)
单片机·嵌入式硬件·proteus
辣知6 小时前
辣知·化智93 被历史尘封的鬻子
学习
youcans_7 小时前
【嵌入式软件AI编程】22. STM32 基础实验:GPIO外部中断
stm32·单片机·嵌入式硬件·ai编程·claude
未来_攻城狮7 小时前
STM32F407VGT6 芯片详解:高性能 Cortex-M4 MCU 入门指南
stm32·单片机·嵌入式硬件