硬件复位与CPU初始状态:复位向量、CPU上电后的默认状态、启动介质选择

各位同学,今天我们来聊聊芯片上电那一刻到底发生了什么。很多人觉得复位就是“按一下重启键”,其实没那么简单。我做了这么多年嵌入式,见过不少因为复位时序没处理好,板子死活起不来的案例。说白了,复位是整个启动链的基石,这块要是没搞明白,后面全是空中楼阁。

复位向量:CPU的第一口“奶”

CPU上电后,第一件事就是去固定的地址拿指令。这个地址,就是复位向量。不同架构的复位向量地址不一样,我整理了一张表,大家可以对照着看:

架构 复位向量地址 说明
ARM Cortex-M 0x00000000(向量表基址) 前4字节存栈顶指针,接着4字节存复位向量
ARM Cortex-A 0x00000000 或 0xFFFF0000 由硬件配置引脚决定,通常低端从0x0启动
RISC-V 0x00000000 或 0x80000000 具体由SoC设计者定,常见的是0x80000000
x86 0xFFFFFFF0 靠近4GB地址空间顶端,跳转到BIOS入口

核心要点:复位向量里存的不是代码,而是一个地址——CPU会跳转到这个地址去执行第一条指令。所以复位向量本质上是一个“指针的指针”。

我记得有一次调试一块Cortex-M4的板子,焊上去死活不跑。用示波器量复位引脚,波形正常。后来查了半天,发现是向量表里栈顶指针写错了,CPU上电后连栈都建不起来,直接卡死。嗯,从那以后我每次写启动代码,第一件事就是检查向量表的前8个字节。

CPU上电后的默认状态

CPU刚上电时,内部寄存器处于一个“半梦半醒”的状态。不同架构的默认值差异很大,但有几个共性的东西值得注意。

通用寄存器

大部分CPU的通用寄存器在上电时是随机值。别指望它们自动清零。你想想看,如果代码里没初始化就用某个寄存器做判断,那结果完全不可控。我见过一个同事,在中断服务函数里直接用了R0做累加,没赋初值,结果每次中断返回的值都不一样,查了两天才找到原因。

程序计数器(PC)

PC的初始值就是复位向量里存的那个地址。CPU从这个地址开始取指、译码、执行。这里有个坑:有些CPU在复位后PC并不是立即指向复位向量,而是先执行一段内部的微码,把缓存、总线接口初始化好,然后再跳转。这段微码对用户是透明的,但会影响复位时间。

栈指针(SP)

ARM Cortex-M系列比较贴心,上电后自动从向量表里加载栈指针。但很多其他架构的CPU,SP初始值是0或者随机值。所以启动代码的第一件事往往是手动设置栈指针,否则连函数调用都做不了。

MMU和Cache的初始状态

这两个家伙在上电时默认是关闭的。为什么?因为MMU需要页表,Cache需要填充,这些东西在复位时都不存在。CPU必须先跑一段“裸奔”代码,把内存控制器初始化好,把页表建好,然后才能打开MMU和Cache。

注意:有些SoC在复位后,Cache里可能残留上一次运行的数据。如果你不主动清Cache,直接打开它,可能会读到脏数据。我建议在启动代码里加一段Cache清理流程,哪怕芯片手册说“上电后Cache内容无效”,也清一下,求个心安。

我画了一张图,把CPU上电后的状态变化串起来,大家感受一下:

CPU上电后状态变化流程 上电复位 加载复位向量 设置初始寄存器 跳转到启动代码 初始化硬件 关键状态 • PC = 复位向量地址 • SP = 向量表加载值(Cortex-M) • MMU/Cache = 关闭 启动代码任务 • 设置栈指针 • 初始化BSS段

启动介质选择:Boot Mode Pins

一个SoC可能同时接了NOR Flash、NAND Flash、SD卡、UART等多种启动介质。那CPU怎么知道从哪个设备启动?答案就是Boot Mode Pins——一组硬件引脚,在复位时被采样,决定启动顺序。

我见过一个典型的Boot Mode配置,比如某款i.MX系列处理器:

BOOT_MODE[1:0] 启动设备 典型应用场景
00 从Fuses启动 量产产品,启动信息烧死在eFuse里
01 串行下载(USB/UART) 开发调试,烧写固件
10 内部Boot ROM 从外部Flash/SD卡启动
11 保留 ——

实战技巧:设计电路板时,我习惯把Boot Mode引脚用排针引出来,再配上跳线帽。这样开发阶段可以随时切换启动模式,不用改电路。量产时再固定焊死或者用0欧电阻选择。

这里有个容易踩的坑:Boot Mode引脚在复位完成后,可能会被复用成GPIO或其他功能。如果你在复位后还去读这些引脚的状态,拿到的值可能已经变了。我曾经在一个项目里,用Boot Mode引脚做按键检测,结果每次上电都读到错误值,后来才发现是引脚功能在启动后被切换了。

启动介质选择的内部逻辑

CPU在复位释放后的几个时钟周期内,会采样Boot Mode引脚的电平,然后根据这个值决定去哪个地址取复位向量。有些SoC更复杂,会先执行一段内部Boot ROM里的代码,这段代码再去读Boot Mode,然后从对应的设备加载第二段启动代码。这就是所谓的多级启动。

举个例子,很多手机SoC的启动流程是这样的:

  1. CPU上电,从内部Boot ROM启动
  2. Boot ROM初始化基本硬件(时钟、DDR控制器)
  3. 根据Boot Mode引脚,从eMMC或UFS读取启动镜像
  4. 验证签名,加载到DDR,跳转执行

这种设计的好处是灵活——Boot ROM是只读的,不会坏;启动介质可以随意换,只要Boot ROM支持就行。坏处是启动时间变长了,因为多了一层加载。

总结一下:硬件复位不是简单的“清零”,而是一个精心设计的状态初始化过程。复位向量决定了CPU从哪里开始跑,Boot Mode引脚决定了从哪个设备加载代码。这两者配合,构成了整个启动链的起点。

我个人习惯在设计阶段就把Boot Mode的配置画在原理图里,标注清楚每个组合对应的启动设备。这样调试的时候不用翻手册,看一眼板子就知道当前是什么启动模式。嗯,这些小习惯,关键时刻能省不少时间。