实际上,在 main() 出现之前,单片机已经默默做了很多“脏活累活”。我们可以把这个过程想象成工厂开工前的准备流程
1. 启动过程全景图
当单片机掉电后再上电,或者你按下 Reset(复位) 键时,芯片内部会发生以下连锁反应:
sequenceDiagram
participant HW as 硬件 (CPU)
participant Flash as Flash (0x08000000)
participant Startup as 启动文件 (.s)
participant System as SystemInit 函数
participant Main as main() 函数
HW->>Flash: 1. 读取栈顶指针 (MSP)
HW->>Flash: 2. 读取复位向量 (Reset Vector)
Flash->>Startup: 3. 跳转到复位程序入口
Startup->>System: 4. 执行时钟初始化
System->>Startup: 初始化完成
Startup->>Main: 5. 跳转到用户代码
2. 详细步骤拆解
第一步:找“地图”和“办公桌”
当复位信号产生,CPU 会雷打不动地去 Flash 的起始地址(0x0800 0000) 寻找两块最重要的数据:
- 栈顶指针 (MSP):告诉 CPU 办公桌(SRAM)的顶端在哪里,方便以后存放临时变量。
- 复位向量 (Reset Vector):这其实是一个地址,指向“第一行程序”在 Flash 里的位置。
第二步:进入汇编世界(启动文件)
你可能在工程里见过一个叫 startup_stm32xxxx.s 的文件,这就是启动文件。它是用汇编语言写的。
- 为什么用汇编? 因为此时 C 语言的环境(比如变量、堆栈)还没搭好,只能用最原始的汇编指令。
- 这个文件的任务是:定义中断向量表。它告诉 CPU:如果发生了复位去哪、发生了中断去哪。
第三步:初始化“心跳”(SystemInit)
在进入 main 之前,启动文件会先调用一个 C 函数叫 SystemInit()。
- 任务:配置外部晶振,把芯片的频率从低速(刚启动时通常很慢)提升到正常工作频率(比如 72MHz)。
- 这就是为什么你的程序一跑起来就是全速的。
第四步:搬运数据(数据初始化)
前面 [[ZZZ-02-Flash和SRAM]] 中说到:
- 代码在 Flash。
- 变量在 SRAM。 如果你写了
int a = 10;,这个“10”在断电时是存在 Flash 里的。 在这一步,启动代码会把 Flash 里的初始值“搬”到 SRAM 的变量地址里。这样等你进入main的时候,变量a已经变成10了。
第五步:跳转到 main
所有环境都搭好了,“办公桌”清理干净了,“时钟”也准了。启动文件最后执行一条跳转指令: BL main 至此,你熟悉的 C 语言世界大幕开启!
| 阶段 | 发生位置 | 主要任务 |
|---|---|---|
| 硬件阶段 | CPU 内核 | 获取栈顶地址和首条指令地址 |
| 软件阶段 (汇编) | startup_xxx.s |
配置中断向量表,初始化堆栈 |
| 系统初始化 | system_stm32xxx.c |
设置系统时钟 (RCC) |
| 数据搬运 | 库代码 | 将 Flash 中的变量初始值拷贝到 SRAM |
| 应用阶段 | main.c |
执行你写的代码 |
为什么要了解这个
- 调试死机:如果你的程序还没进
main就挂了,你得知道要去启动文件或者SystemInit里找原因。 - 理解中断:中断向量表就在启动文件里。如果你想深入底层,这是必经之路。
- 节省空间:理解了数据搬运,你就会明白为什么全局变量如果不初始化会占空间,以及如何优化内存。