2. 启动流程初探:嵌入式系统的上电启动流程、ROM Code、SPL、U-Boot的接力棒关系

嵌入式系统上电后,到底发生了什么?

很多人一上来就盯着U-Boot源码看,结果越看越懵。其实,你想想看,从按下电源到U-Boot跑起来,中间还有好几棒要交接。今天我们就聊聊这个接力赛。

2.1 上电第一件事:CPU在干嘛?

芯片一上电,CPU的PC指针会指向一个固定地址。这个地址是芯片设计时就定死的,谁也改不了。比如很多ARM芯片,上电后从0x00000000或者0xFFFF0000开始执行。

但这时候,DDR还没初始化,代码只能放在芯片内部的SRAM或者ROM里跑。SRAM很小,通常只有几十KB到几百KB。所以,第一段代码必须非常精简。

核心要点:上电后,CPU执行的第一段代码不是U-Boot,而是固化在芯片内部的ROM Code。这段代码由芯片厂商写好,用户改不了。

2.2 ROM Code:芯片出厂自带的“小管家”

ROM Code是芯片内部固化的程序,上电后自动运行。它的任务很简单:

  • 初始化最基本的硬件(时钟、看门狗、堆栈指针)
  • 检测启动介质(NAND、eMMC、SD卡、SPI Flash等)
  • 把下一段代码(通常是SPL)加载到SRAM中
  • 跳转到SPL执行

我记得有一次调试一块新板子,死活起不来。后来用示波器量启动引脚的波形,才发现ROM Code根本没检测到SD卡。原来是板子上拉电阻焊错了。嗯,这种坑,光看代码是看不出来的。

个人经验:ROM Code的启动顺序通常由芯片的Boot Pin(启动引脚)决定。比如拉高从NAND启动,拉低从SD卡启动。设计电路时,一定要留出测试点,方便调试。

2.3 SPL:U-Boot的“先遣队”

SPL全称是Secondary Program Loader,也叫MLO(MMC Loader)。它是U-Boot源码编译出来的一个小东西,体积被刻意控制在SRAM能装下的范围内。

SPL的任务是什么?说白了就两件:

  1. 初始化DDR——这是最关键的一步。没有DDR,大代码跑不了。
  2. 把完整的U-Boot从Flash加载到DDR——然后跳过去。

为什么要有SPL?因为U-Boot太大了,SRAM装不下。所以必须有个“瘦身版”先跑起来,把DDR初始化好,再把完整版请进来。

避坑指南:我曾经遇到过SPL编译出来超过SRAM大小的情况。比如某款芯片SRAM只有64KB,SPL编译出来68KB,结果死活跑不起来。解决办法是裁剪SPL功能,去掉不必要的驱动。或者换用更小的DDR初始化代码。

2.4 U-Boot:真正的“操作系统引导器”

当SPL把U-Boot加载到DDR并跳转过去后,U-Boot正式登场。这时候,它有了DDR这个大舞台,可以尽情施展。

U-Boot做的事情包括:

  • 初始化更多外设(网卡、USB、串口、I2C等)
  • 解析设备树(DTB)
  • 加载内核(zImage/Image)到DDR
  • 设置内核启动参数(bootargs)
  • 跳转到内核入口

你想想看,如果没有SPL,U-Boot自己得在SRAM里完成所有初始化,那根本不可能。所以这个接力棒设计,是嵌入式系统里非常经典的分层思想。

2.5 接力棒关系:一张图看懂

下面这张图,我画了三个阶段的交接关系。你看完应该就清楚了。

嵌入式系统启动流程:ROM Code → SPL → U-Boot 接力关系 嵌入式系统启动接力流程 ROM Code 芯片内部固化 初始化时钟/看门狗 SPL (MLO) 运行在SRAM 初始化DDR U-Boot 运行在DDR 加载内核/启动系统 加载 跳转 存储介质(NAND / eMMC / SD卡 / SPI Flash) ROM Code从这里读取SPL,SPL从这里读取U-Boot 内存空间分布 ROM (只读) SRAM (SPL运行) DDR (U-Boot运行) 容量:ROM几KB → SRAM几十KB → DDR几百MB/GB

2.6 各阶段对比表

我把三个阶段的差异整理成了一张表,方便你对照理解。

阶段 存储位置 运行空间 主要任务 能否修改
ROM Code 芯片内部ROM 芯片内部SRAM 初始化时钟、检测启动介质、加载SPL 不可修改(固化)
SPL Flash(NAND/eMMC/SD) 芯片内部SRAM 初始化DDR、加载U-Boot到DDR 可修改(编译U-Boot时生成)
U-Boot Flash(NAND/eMMC/SD) 外部DDR 初始化外设、加载内核、启动系统 可修改(编译U-Boot时生成)

2.7 实际项目中的常见问题

我在项目里遇到过几个典型问题,拿出来说说:

  • SPL加载U-Boot失败——最常见的原因是DDR初始化参数不对。比如DDR频率、时序参数配错了,导致U-Boot加载到DDR后,一运行就崩。解决办法是仔细核对芯片手册里的DDR配置表。
  • ROM Code找不到SPL——这通常是因为SPL在Flash中的存放位置不对。比如某款芯片要求SPL必须放在SD卡的第1个扇区(偏移0x200),你放错了位置,ROM Code就读不到。
  • U-Boot启动后串口没输出——我遇到过好几次,原因是SPL里初始化了串口,但U-Boot里又初始化了一次,结果把串口配置冲掉了。解决办法是在U-Boot里跳过串口初始化,或者保持SPL的配置不变。

调试技巧:如果启动卡在某个阶段,可以在SPL和U-Boot的入口处加GPIO翻转。用示波器看GPIO波形,就能知道代码跑到哪一步了。这比看串口日志更可靠,因为串口本身可能还没初始化好。

2.8 小结

嵌入式系统的启动流程,说白了就是一场接力赛。ROM Code是第一棒,SPL是第二棒,U-Boot是第三棒。每一棒都有自己的任务和限制。理解了这个接力关系,你再看U-Boot源码,就不会觉得那么乱了。