2. 嵌入式启动流程基础
嵌入式系统上电后,到底发生了什么?
很多人觉得Bootloader就是一段代码,跑起来就完事了。其实不然。从你按下电源键,到操作系统开始运行,中间有一条完整的链路。这条链路里,每个环节都有它的设计逻辑。
我个人习惯把这条链路分成三段:硬件初始化 → 引导加载 → 操作系统启动。今天我们就顺着这条线,把每个节点掰开来看。
2.1 上电那一刻:ROM Code 在做什么?
芯片上电后,CPU会从一个固定的地址开始执行。这个地址里放的是什么?是芯片出厂时固化的ROM Code(也叫BootROM)。
ROM Code是芯片厂商写死的,你改不了。它的任务很简单:
- 初始化最基本的硬件(时钟、堆栈、异常向量表)
- 检测启动介质(NAND、eMMC、SD卡、UART等)
- 把下一级代码(通常是SPL)加载到内部SRAM
- 跳转到SPL入口
关键点:ROM Code通常只有几KB到几十KB。它不能做复杂的事情,比如文件系统解析、ECC校验——这些是后面的事。
我记得有一次调试一块新板子,死活起不来。量了电源、时钟都没问题。最后用JTAG挂上去一看,ROM Code在检测启动介质时卡住了——因为板子上没接任何存储芯片。嗯,这个坑踩得值。
2.2 SPL 与 TPL:两级加载的由来
为什么需要SPL(Secondary Program Loader)和TPL(Tertiary Program Loader)?
说白了,就是存储空间不够。
很多SoC的内部SRAM只有几十KB到几百KB。而一个完整的U-Boot(或者别的Bootloader)可能有几百KB甚至几MB。你不可能一次性把它全塞进SRAM里。
所以就有了分级加载:
| 阶段 | 典型大小 | 运行位置 | 主要任务 |
|---|---|---|---|
| ROM Code | 4KB - 32KB | 内部ROM | 初始化时钟、检测启动介质、加载SPL |
| SPL | 16KB - 128KB | 内部SRAM | 初始化DDR、加载TPL或U-Boot |
| TPL | 32KB - 256KB | 内部SRAM或DDR | 更复杂的硬件初始化、加载U-Boot |
| U-Boot | 200KB - 1MB+ | DDR | 文件系统、网络、启动内核 |
你想想看,SPL的任务其实很纯粹:把DDR初始化好。因为只有DDR初始化了,才有足够的内存去加载完整的Bootloader。
我的经验:SPL的调试是最痛苦的。因为这时候DDR还没起来,你没法用printk,没法用断点。我一般会在SPL里加GPIO翻转,用示波器看波形来定位问题。土办法,但管用。
2.3 启动介质选择机制
ROM Code怎么知道从哪个设备启动?
这取决于芯片的启动引脚(Boot Mode Pins)。芯片上电时,会采样这些引脚的电平,然后决定从哪个介质加载。
常见的启动介质有:
- NAND Flash:成本低、容量大,但需要ECC和坏块管理
- eMMC:内置控制器,使用方便,但贵一些
- SD/MMC卡:可插拔,适合开发和量产
- SPI NOR Flash:启动快、可靠性高,但容量小
- UART/USB:用于工厂烧录或调试
ROM Code会按照优先级依次尝试这些介质。比如先看eMMC有没有有效镜像,没有就试SD卡,再没有就进USB下载模式。
注意:不同芯片的启动顺序不一样。有的芯片支持动态切换,有的只能按固定顺序。我曾经遇到一个项目,因为没仔细看芯片手册,默认以为eMMC优先级最高,结果ROM Code一直从空的SD卡启动,卡了三天。
2.4 完整启动链路图
下面这张图,是我自己画的一个典型嵌入式系统启动链路。你可以对照着看,每一步对应什么代码在运行。
2.5 避坑指南:启动流程中的常见问题
做Bootloader移植,启动流程这块最容易出问题。我列几个我踩过的坑:
- DDR时序配置错误:SPL初始化DDR时,时序参数必须和板子上的DDR颗粒匹配。差一个cycle,系统就起不来。我建议先用芯片厂商提供的DDR训练工具生成参数,再手动微调。
- 启动介质顺序搞反:有些芯片的启动顺序不是你想的那样。比如某款i.MX芯片,默认先从SD卡启动,而不是eMMC。不看手册就默认,容易翻车。
- SPL大小超限:内部SRAM就那么大,SPL编译出来超了,ROM Code加载不完。我一般会在链接脚本里加一个SECTION检查,编译完自动报错。
- ECC校验没开:NAND Flash有坏块,ROM Code读数据时如果没开ECC,读到错误数据就直接挂了。这个坑在量产时特别容易暴露。
一个小技巧:在SPL里加一个LED闪烁函数。每完成一个初始化步骤,就闪一下。这样即使串口没起来,你也能通过LED状态判断卡在哪一步。我在好几个项目里都用这招,屡试不爽。
2.6 小结
启动流程说白了就是从ROM Code到SPL再到U-Boot的接力赛。每一棒都有自己的任务,跑完就交接。理解了这个链路,你就能在出问题时快速定位——是ROM Code没找到介质?还是SPL初始化DDR失败?还是U-Boot加载内核时崩了?