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的任务是什么?说白了就两件:
- 初始化DDR——这是最关键的一步。没有DDR,大代码跑不了。
- 把完整的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 接力棒关系:一张图看懂
下面这张图,我画了三个阶段的交接关系。你看完应该就清楚了。
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源码,就不会觉得那么乱了。