单片机上电到 main 之间发生了什么?
你按下开发板的复位键,程序从 main 开始跑——但 main 之前发生了什么?其实 CPU 先要经历一段"没人教过你"的隐秘旅程:从地址 0 取栈指针、跳复位向量、初始化数据段、配置时钟树……走完才轮到你写的 main。今天讲透这段旅程。
一、上电瞬间:CPU 只认两个地址
ARM Cortex-M 内核复位后,硬件做的第一件事非常"死板":从地址 0 取初始栈指针(SP),从地址 4 取复位向量(Reset_Handler 的地址)。
地址 0x00000000: 0x20001000 ← 初始 SP(指向 RAM 顶部)
地址 0x00000004: Reset_Handler ← 程序入口地址
注意:入口不是 main,而是 Reset_Handler——这就是很多人困惑的"为什么 main 不是真正的入口"。这些内容由链接脚本和启动汇编文件安排(在 Cortex-M 的启动文件 startup_xxx.s 里)。
二、Reset_Handler:给 C 语言铺路
Reset_Handler 是汇编写的,做三件 C 语言"默认需要但硬件不提供"的事:
- 复制 .data 段:已初始化的全局变量(如
int x = 5)存在 Flash,但运行时要在 RAM——启动代码把它们从 Flash 搬到 RAM - 清零 .bss 段:未初始化的全局变量(如
int arr[100])在 C 标准里必须初始化为 0——启动代码把这块 RAM 清零 - 调用 SystemInit:配置时钟树(PLL 倍频)、使能外设时钟——这一步决定了芯片跑多快
做完这些,才调用 main()。
三、代码演示:模拟启动全流程
class MCU:
def __init__(self, flash=None):
# Flash 前两个字: 初始 SP 和复位向量
self.flash = flash or {0x0000: "SP=0x20001000", 0x0004: "Reset_Handler"}
self.sp = None; self.pc = None
def power_on(self):
print("[上电] 从地址 0 取初始 SP, 从地址 4 取复位向量")
self.sp = self.flash[0x0000].split("=")[1]
self.pc = self.flash[0x0004]
print(f" SP = {self.sp}, PC = {self.pc}")
self.reset_handler()
def reset_handler(self):
print("[Reset_Handler] 复制 .data 段到 RAM, 清零 .bss 段")
self.system_init()
def system_init(self):
print("[SystemInit] 配置时钟树 (PLL), 使能外设时钟")
self.main()
def main(self):
print("[main] 用户代码开始执行 ✓")
MCU().power_on()
运行输出:
[上电] 从地址 0 取初始 SP, 从地址 4 取复位向量
SP = 0x20001000, PC = Reset_Handler
[Reset_Handler] 复制 .data 段到 RAM, 清零 .bss 段
[SystemInit] 配置时钟树 (PLL), 使能外设时钟
[main] 用户代码开始执行 ✓
上电 → 取 SP/PC → Reset_Handler(数据段初始化)→ SystemInit(时钟)→ main——这就是每一颗单片机开机时都在走的固定路线。
四、为什么要懂启动流程?
- 调试"程序跑不起来":卡在启动阶段(时钟没配对、Flash 等待周期不对)时,main 里的断点根本打不到
- 优化启动时间:.bss 清零和 .data 复制在大 RAM 芯片上耗时明显,可优化
- 写 Bootloader:需要自己控制向量表偏移(
SCB->VTOR)和跳转逻辑 - 理解链接脚本:
section怎么布局、栈顶在哪,全在这里体现
五、避坑清单
- 入口不是 main:复位向量指向 Reset_Handler,main 是被它调用的
- 栈顶地址要合法:初始 SP 必须指向 RAM 范围内且对齐,否则一入栈就 HardFault
- .bss 不清零是隐患:某些情况下编译器依赖"全局变量为 0",跳过后会出现诡异 bug
- 时钟没配就跑外设 = 白等:SystemInit 之前,外设时钟可能是关闭状态
- 向量表可偏移:带 Bootloader 时,应用固件要把向量表搬到自己的起始地址并设置 VTOR
六、想系统学 ARM Cortex-M?
本文精选自 ima 知识号【Kruptos】《ARM Cortex-M 技术详解》订阅库(第 011 期复位与启动从地址 0 到 Reset_Handler、第 058 期启动代码与 SystemInit 等 100 期系统教程,从内核架构、寄存器、异常模型到外设驱动与工程实战,每期配可运行代码)。
📚 完整系列 100 期 + 配套代码,已在 ima 知识号发布
本文只是系列的一个切片。完整系列(100 期系统教程 + 每期可运行代码)在 ima 知识号【Kruptos】持续更新中:
- 🗂 68+ 技术知识库:信号与系统、SDR 软件无线电、数字信号处理、操作系统、AI Agent、大模型微调……几乎覆盖全部软硬件技术栈
- 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
- ✅ 全部免费订阅,后续更新自动推送
🔍 订阅方式:打开 ima(腾讯智能工作台)→ 搜索「Kruptos」→ 一键订阅。或在 ima 内直接搜索《ARM Cortex-M 技术详解》等知识库名称。
💬 你调试过启动阶段的问题吗?卡在哪一步?评论区聊聊——想看 Bootloader 还是链接脚本,点赞高的安排。
作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研)|原创内容,转载注明出处

752

被折叠的 条评论
为什么被折叠?



