2、启动ROM与引导芯片:SoC上电后的第一条指令

各位同学,今天我们来聊聊一个很有意思的话题——SoC上电后,CPU到底在干什么?

很多人觉得启动嘛,不就是按一下电源键,然后屏幕亮了。但说实话,从按下电源键到第一个像素点显示出来,中间发生的事情远比你想的要复杂。我当年第一次接触嵌入式底层时,也被这“第一条指令”给搞懵过。

2.1 上电复位:CPU的“出生时刻”

SoC上电的一瞬间,所有寄存器都是随机值。嗯,你没听错,是随机的。这时候CPU需要一个“初始状态”,也就是复位向量。

复位向量是什么?说白了,就是CPU上电后强制跳转的一个固定地址。不同的ARM Cortex-A系列,这个地址可能不同。比如:

  • Cortex-A53:复位向量通常是0x00000000或0xFFFF0000
  • Cortex-A72:可以通过硬件配置引脚选择复位地址
  • 老一点的ARM9:固定从0x00000000开始

我做过一个项目,芯片上电后死活跑不起来。查了三天,最后发现是复位引脚上的电容焊错了,导致复位时序不对。嗯,硬件工程师的锅,我们软件背了三天。

2.2 固化在ROM中的启动代码

复位向量指向的地址,通常就是芯片内部的Boot ROM。这个ROM是只读的,出厂就写死了,你改不了。它里面存的是芯片厂商写好的“第一段启动代码”。

这段代码很小,一般就几KB到几十KB。它要完成的任务却很关键:

  1. 最基本的硬件初始化:比如关闭看门狗、初始化堆栈指针
  2. 时钟系统初始化:让CPU跑在正确的频率上
  3. 检测启动模式:从哪启动?NAND?eMMC?SD卡?
  4. 加载下一级启动代码:通常是SPL或BL1

关键点:Boot ROM里的代码是芯片厂商写的,你拿不到源码。但你可以通过芯片手册知道它的行为。我建议你拿到一块新芯片时,第一件事就是看“Boot ROM”章节。

2.3 硬件初始化:让CPU“活过来”

Boot ROM里的代码,第一步是初始化最基本的硬件。注意,这里说的“基本”是真的基本——只够让CPU能跑起来,能读外部存储就行。

举个例子,我参与过一个车规级芯片的项目。Boot ROM里初始化了以下内容:

硬件模块 初始化内容 为什么需要
系统时钟 设置PLL,锁定频率 CPU需要时钟才能工作
堆栈指针 设置SP寄存器 C语言函数调用需要栈
异常向量表 设置中断入口地址 防止意外中断导致死机
内存控制器 初始化SRAM或DDR 需要内存来运行代码

这里有个坑,我必须要说。有些芯片的Boot ROM会假设外部晶振已经稳定。但实际项目中,晶振起振需要时间。我曾经遇到过一批板子,上电后10%的概率启动失败。查到最后,是晶振起振时间比芯片预期的长了那么几毫秒。解决方案?在硬件上加了RC延时电路。

2.4 启动模式检测:从哪开始?

硬件初始化完成后,Boot ROM就要决定:下一步从哪加载代码?

芯片厂商通常会提供多种启动模式。检测方式一般是通过读取芯片引脚的电平状态。比如:

  • BOOT0=0, BOOT1=0:从内部Flash启动
  • BOOT0=0, BOOT1=1:从系统存储器(Boot ROM)启动
  • BOOT0=1, BOOT1=0:从SRAM启动

但实际项目中,启动模式远不止这么简单。以我最近用的一个SoC为例,它支持:

  1. eMMC启动:从板载eMMC芯片读取
  2. SD卡启动:从外部SD卡读取
  3. USB下载模式:通过USB线从电脑下载
  4. UART串口下载:通过串口线下载
  5. SPI NOR Flash启动:从SPI接口的NOR Flash读取

个人经验:调试阶段,我习惯用SD卡启动。因为改代码方便,插拔SD卡就行。量产时再切回eMMC。但要注意,有些芯片的Boot ROM对SD卡有格式要求,比如必须是FAT32,分区表必须是MBR。我踩过这个坑,浪费了一整天。

2.5 启动设备的选择优先级

Boot ROM检测启动模式时,不是随便乱试的。它有一个固定的优先级顺序。比如某款手机SoC的优先级是这样的:

  1. 检查USB是否连接(用于下载模式)
  2. 检查eMMC是否有有效启动标志
  3. 检查SD卡是否有有效启动标志
  4. 检查UART是否有数据(用于串口下载)
  5. 如果都没有,进入死循环或报错

为什么会这样设计?你想想看,USB下载模式优先级最高,是因为开发调试时最常用。eMMC次之,因为量产设备都是从eMMC启动。SD卡再次,用于紧急恢复。

警告:千万不要在生产环境中依赖SD卡启动。SD卡插拔会导致接触不良,而且SD卡本身寿命有限。我见过一个客户,量产时用了SD卡启动,结果半年后大批量返修,全是SD卡坏了。

2.6 从Boot ROM到下一级启动

Boot ROM检测到启动设备后,会从该设备读取固定偏移位置的数据。比如:

  • 从eMMC的0x00000000偏移处读取1KB数据
  • 从SD卡的0x00000200偏移处读取(因为SD卡有MBR分区表)
  • 从SPI Flash的0x00000000偏移处读取

读取到的数据,Boot ROM会做简单的校验。比如CRC32或者简单的累加和校验。校验通过后,就把这段数据拷贝到SRAM中,然后跳转过去执行。

这段被加载的代码,就是下一级启动代码。在Android启动流程中,它通常是SPL(Secondary Program Loader)或者BL1(Boot Loader Stage 1)。

我记得有一次,客户反馈说设备偶尔启动不了。我远程调试发现,是eMMC的前1KB数据被意外擦除了。Boot ROM读不到有效数据,自然就卡住了。解决方案?在Boot ROM里加了个“如果eMMC无效,尝试从SD卡启动”的fallback逻辑。但这是芯片厂商才能改的,我们只能建议客户升级固件。

2.7 小结

好了,这一章的内容就到这里。总结一下:

  • SoC上电后,CPU从复位向量开始执行
  • 复位向量指向芯片内部的Boot ROM
  • Boot ROM做最基本的硬件初始化
  • 检测启动模式,确定从哪个设备加载代码
  • 从启动设备读取下一级启动代码并执行

下一章,我们会讲SPL和BL1的具体工作。到时候你会看到,Boot ROM只是开胃菜,真正的启动流程才刚刚开始。

嗯,今天就到这里。有问题随时问我。