第2章 启动流程分析:MCU上电后到底在干什么?

很多刚入行的朋友问我:MCU一上电,程序是怎么跑起来的?

说实话,这个问题看似简单,但真要在项目里踩过坑,才会明白启动流程有多重要。我记得有一次调试一块新板子,焊好芯片、烧录程序、上电——结果啥反应没有。折腾了两天,最后发现是复位向量表地址写错了。嗯,从那以后,我每次做新项目都会先把启动流程捋一遍。

2.1 MCU上电启动流程

MCU上电后,硬件会自动执行一系列固定动作。这些动作不需要你写任何代码,是芯片出厂就定死的。

核心要点:MCU启动 = 硬件自动完成 + 软件接力执行。硬件部分你改不了,但软件部分必须自己配好。

我把启动流程拆成5个关键步骤:

  1. 上电复位(POR)——电源电压达到稳定阈值,芯片内部复位电路释放复位信号。
  2. 硬件初始化——CPU内核、系统时钟、存储器接口等基本模块完成自检和配置。
  3. 读取复位向量——CPU从固定地址(通常是0x0000 0000或0x0800 0000)取出栈指针和复位入口地址。
  4. 跳转至复位向量——PC指针指向复位处理函数(Reset_Handler)。
  5. 软件初始化——在Reset_Handler里完成C运行时环境初始化(BSS清零、DATA段拷贝、堆栈配置),最后跳转到main()。

说白了,前4步是硬件干的活,第5步才是你写的代码开始执行的地方。

2.2 向量表与复位向量

向量表是MCU启动的核心数据结构。它本质上是一个地址数组,每个条目对应一个中断或异常的处理函数入口。

以ARM Cortex-M系列为例,向量表默认放在起始地址(0x0000 0000)。前两个条目特别重要:

偏移地址 内容 说明
0x0000 0000 栈顶指针(MSP初始值) 上电后SP寄存器自动加载此值
0x0000 0004 复位向量(Reset_Handler地址) 上电后PC寄存器跳转至此地址
0x0000 0008 NMI异常入口 不可屏蔽中断
0x0000 000C HardFault异常入口 硬件错误处理
... ... ...

我曾经踩过的坑:有一次做IAP升级,我把向量表重定位到了0x0801 0000,但忘记更新SCB->VTOR寄存器。结果一触发中断,CPU还是去0x0800 0000找向量表,直接跑飞。记住:重定位向量表后,一定要手动设置VTOR!

在启动文件(startup_xxx.s)里,向量表通常这样定义:

__Vectors       DCD     __initial_sp          ; 栈顶指针
                DCD     Reset_Handler         ; 复位向量
                DCD     NMI_Handler           ; NMI
                DCD     HardFault_Handler     ; Hard Fault
                DCD     MemManage_Handler     ; MPU异常
                DCD     BusFault_Handler      ; 总线异常
                DCD     UsageFault_Handler    ; 用法异常
                ; ... 其他中断向量

复位向量指向的Reset_Handler函数,负责完成C运行环境的初始化。我习惯在Reset_Handler里做三件事:

  • 调用SystemInit()配置系统时钟
  • 执行__main()或__libc_init_array()完成BSS清零和DATA段拷贝
  • 最后跳转到main()

2.3 启动模式选择

大多数MCU支持多种启动模式。说白了,就是告诉芯片:上电后从哪个存储器里取向量表。

以STM32为例,通过BOOT0和BOOT1引脚的电平组合来选择启动模式:

BOOT0 BOOT1 启动模式 说明
0 X Flash(主闪存) 正常用户程序运行模式
1 0 System Memory(系统存储器) 内置Bootloader,用于串口/USB下载
1 1 SRAM(内部RAM) 调试或临时运行程序

我的建议:产品量产时,BOOT0一定要拉低(Flash模式)。如果需要在产线烧录,可以用串口或CAN进入System Memory模式。我个人习惯在PCB上预留BOOT0的测试点,方便产线操作。

三种模式的应用场景:

  • Flash模式——正常产品运行,程序固化在内部Flash中。
  • System Memory模式——芯片出厂自带的Bootloader,支持UART、USB、CAN等外设下载。我经常用它来救砖。
  • SRAM模式——调试阶段用,程序下载到RAM里运行,掉电即失。适合快速验证。

你想想看,如果芯片Flash里的程序把启动向量写坏了,怎么办?

这时候把BOOT0拉高、BOOT1拉低,从System Memory启动,就能用串口重新烧录。这就是芯片厂商留的后门。

2.4 启动流程全景图

下面这张图把整个启动流程串起来了。我建议你把它存下来,调试时对照着看。

MCU上电启动流程全景图 ① 上电复位(POR) ② 硬件初始化 ③ 读取向量表 ④ 跳转Reset_Handler ⑤ 软件初始化 → main() 硬件自动完成 • 电源稳定后释放复位 • CPU内核自检 • 从固定地址取向量 • 加载SP和PC 软件接力执行 • SystemInit()配时钟 • BSS清零/DATA拷贝 • 跳转main() 启动模式选择:Flash / System Memory / SRAM 由BOOT引脚电平决定向量表映射地址

2.5 实战中的注意事项

避坑指南:

  • 向量表对齐——Cortex-M要求向量表按512字节对齐(0x200的倍数)。我曾经因为对齐不对,导致中断响应错乱。
  • 栈指针初始化——启动文件里定义的__initial_sp必须指向有效的RAM区域。如果栈顶地址超出RAM范围,上电直接HardFault。
  • SystemInit()不能少——有些芯片的SystemInit()里会配置Flash等待周期。如果跳过,Flash读取速度跟不上CPU频率,程序跑飞。

调试小技巧:上电后程序没跑?先检查PC寄存器的值。如果PC=0xFFFFFFxx,说明取向量失败,大概率是向量表地址不对或Flash没内容。如果PC指向了奇怪的值,检查栈指针是否在RAM范围内。

嗯,启动流程这部分内容,说白了就是搞清楚芯片上电后谁先干活、谁后干活。硬件干完活,把接力棒交给你写的启动代码,最后才跑到main()。这个顺序搞清楚了,后面做Bootloader、IAP升级、调试启动异常,心里就有底了。