4. 处理器架构与启动模式
BootROM 里为什么要自带驱动?这个问题我当年刚接触时也困惑过。操作系统不是有驱动吗?BootROM 凑什么热闹?
其实道理很简单。你想想看,芯片上电时,DDR 还没初始化,文件系统还没挂载,整个芯片就是个「裸金属」。这时候要去读 NAND Flash 里的代码,谁来操作硬件寄存器?谁来控制时序?
只能靠 BootROM 自己。
BootROM 里的驱动,我们叫它「低级驱动」或「最小驱动」。它不追求性能,不追求功能完整,只求一件事:能把启动代码可靠地读出来。
核心原则:BootROM 驱动 = 最小功能集 + 最高可靠性 + 最小代码体积。多一行代码都可能塞不进 ROM。
4.1 为什么 BootROM 需要自带驱动
我总结下来,原因有三条:
- 没有外部依赖:BootROM 是芯片上电后第一个执行的代码。此时堆栈可能都没设好,更别提操作系统了。驱动必须自包含,不能调用任何外部函数。
- 硬件尚未就绪:PLL、时钟树、电源域可能还在配置中。BootROM 驱动需要在「半初始化」状态下工作,这对时序要求极高。
- 启动介质多样:同一颗芯片可能要从 NAND、eMMC、SPI NOR、SD 卡启动。BootROM 必须内置所有这些介质的驱动,由启动引脚选择。
我曾经在一个项目里遇到过:芯片从 NAND 启动时,前 4KB 读出来全是坏的。查了两天才发现,是 BootROM 里 NAND 控制器的时序参数配错了。从那以后,我对 BootROM 驱动的每一个寄存器配置都格外小心。
避坑指南:BootROM 驱动一旦固化进芯片,就无法修改。如果驱动有 bug,只能通过「启动补丁」或「二级 bootloader」来绕过。所以,ROM 里的驱动代码必须经过最严格的验证。
4.2 NAND Flash 控制器初始化与读取
NAND Flash 是嵌入式设备里最常见的启动介质之一。但它有个特点:天生带坏块。出厂时就可能有坏块,使用过程中还会产生新坏块。
BootROM 读 NAND 时,必须处理坏块问题。但 ROM 空间有限,不可能实现完整的坏块管理算法。所以通常的做法是:跳过坏块,只读好块。
4.2.1 NAND 控制器初始化步骤
- 时钟使能:打开 NAND 控制器的时钟门控。
- 时序配置:根据 NAND 芯片的 datasheet,设置 tRC、tWC、tRP、tWP 等时序参数。
- 引脚复用:将 GPIO 切换到 NAND 功能。
- ECC 配置:选择硬件 ECC 算法(通常是 BCH 或 RS),设置纠错能力。
- 片选使能:选中对应的 NAND 芯片。
下面是一个简化的 NAND 初始化代码示例:
void nand_init(void)
{
/* 1. 使能时钟 */
NAND_CLK_EN |= (1 << 3);
/* 2. 配置时序:假设 NAND 芯片要求 tRC = 25ns */
NAND_TIMING0 = (5 << 0); /* tRC 分频 */
NAND_TIMING1 = (3 << 8); /* tRP 分频 */
/* 3. 引脚复用 */
PINMUX_NAND |= 0xFF;
/* 4. ECC 配置:BCH-8 纠错 */
NAND_ECC_CTRL = (0x2 << 4); /* BCH 算法 */
NAND_ECC_CTRL |= (8 << 0); /* 8-bit 纠错 */
/* 5. 片选使能 */
NAND_CS_EN = 0x1;
}
个人经验:NAND 时序配置是 BootROM 里最容易出问题的地方。我建议你在配置时序时,留一点余量。比如 datasheet 要求最小 tRC 是 25ns,你就配成 30ns。虽然慢一点,但更可靠。BootROM 不差这几十纳秒。
4.2.2 NAND 读取流程
BootROM 读取 NAND 时,通常采用以下流程:
- 发送读命令(0x00 + 地址 + 0x30)
- 等待 R/B# 引脚变高(表示 NAND 准备好)
- 读取数据到内部 SRAM
- 检查 ECC 状态,如果纠错成功则继续,否则标记坏块并跳过
这里有个关键点:BootROM 通常只读前几个块。比如前 4 个块,每个块 128KB,总共 512KB。这足够存放二级 bootloader 了。
4.3 eMMC/SD 卡初始化与读取
eMMC 和 SD 卡本质上是一回事,协议几乎相同。BootROM 对它们的驱动也基本通用。
eMMC 比 NAND 友好得多——它内部自己管理坏块。BootROM 不需要关心坏块问题,直接按 LBA(逻辑块地址)读写就行。
4.3.1 eMMC 初始化流程
- 上电延时:eMMC 上电后需要等待 1ms 以上才能发命令。
- CMD0(GO_IDLE_STATE):复位卡到空闲状态。
- CMD1(SEND_OP_COND):查询卡的工作电压范围,协商工作条件。
- CMD2(ALL_SEND_CID):获取卡的 CID(唯一标识)。
- CMD3(SET_RELATIVE_ADDR):给卡分配一个相对地址(RCA)。
- CMD9(SEND_CSD):读取 CSD 寄存器,获取卡容量、块大小等信息。
- CMD7(SELECT_CARD):选中卡,进入数据传输模式。
- CMD16(SET_BLOCKLEN):设置块长度,通常是 512 字节。
这个过程看起来步骤多,但 BootROM 里实现时,每一步都有超时检测。我曾经遇到过一块 eMMC,CMD1 响应特别慢,需要循环等待 500ms 才返回。如果 BootROM 超时设得太短,就会误判为卡不存在。
注意:eMMC 的初始化时序有严格的先后顺序。CMD1 必须在 CMD0 之后,CMD2 必须在 CMD1 之后。跳步或乱序会导致卡无法识别。
4.3.2 eMMC 读取流程
初始化完成后,读取就简单了:
- 发送 CMD17(READ_SINGLE_BLOCK)或 CMD18(READ_MULTIPLE_BLOCK)
- 等待卡返回数据
- 读取数据到 SRAM
- 检查 CRC 校验
BootROM 通常使用 CMD18 连续读取多个块,这样效率更高。但要注意,eMMC 的读取速度受限于 BootROM 所在的时钟频率。BootROM 阶段,eMMC 通常跑在 400KHz 或 3.3V 模式,速度不快。
4.4 SPI NOR Flash 的 XIP 与拷贝模式
SPI NOR Flash 是三种启动介质里最简单的,但也是最灵活的。它支持两种读取模式:
| 模式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| XIP 模式 | CPU 直接从 SPI Flash 取指令执行 | 无需拷贝,启动快,节省 RAM | 执行速度受 SPI 总线速度限制 |
| 拷贝模式 | BootROM 将 Flash 内容拷贝到 SRAM 或 DDR 后再执行 | 执行速度快(RAM 中运行) | 需要 RAM 空间,拷贝耗时 |
4.4.1 XIP 模式
XIP(Execute In Place)的意思是「原地执行」。CPU 发出的取指令地址,直接映射到 SPI Flash 的地址空间。SPI 控制器负责把地址转换成 SPI 命令,从 Flash 里把指令读回来。
XIP 模式的关键在于:SPI 控制器必须支持内存映射模式。不是所有芯片都支持。
我记得有个项目,芯片支持 XIP,但 SPI Flash 的读取延迟太大,导致 CPU 频繁 stall。后来我们在 BootROM 里开了 SPI 的「预取缓存」,一次读 16 字节,才把性能提上来。
技巧:XIP 模式下,BootROM 本身就在 SPI Flash 里执行。所以 BootROM 代码必须位置无关(PIC),不能有绝对地址引用。编译时记得加 -fPIC 选项。
4.4.2 拷贝模式
拷贝模式更传统。BootROM 先初始化 SPI 控制器,然后发送 Read 命令(0x03 或 0x0B),把 Flash 内容读到内部 SRAM 里。读完后,跳转到 SRAM 执行。
拷贝模式的典型流程:
- 初始化 SPI 控制器(时钟、极性、相位)
- 发送 0x9F 命令读取 Flash ID,确认 Flash 型号
- 发送 0x05 命令读取状态寄存器,检查 Flash 是否忙
- 发送 0x03(标准读)或 0x0B(快速读)命令,从起始地址读取数据
- 将数据写入 SRAM 目标地址
- 读取完成后,跳转到 SRAM 入口点
这里有个细节:快速读(0x0B)比标准读(0x03)多一个 dummy 字节。快速读可以跑更高的时钟频率,但需要 Flash 支持。BootROM 通常会先尝试快速读,如果不支持再回退到标准读。
选择建议:如果芯片内部 SRAM 足够大(比如 256KB 以上),优先用拷贝模式。如果 SRAM 很小(比如 64KB),只能用 XIP 模式。我个人的习惯是:能拷贝就拷贝,XIP 留作备选。
4.5 三种启动介质的对比
最后,我用一张表格总结一下这三种介质的 BootROM 驱动特点:
| 特性 | NAND Flash | eMMC/SD 卡 | SPI NOR Flash |
|---|---|---|---|
| 坏块处理 | BootROM 必须处理 | 内部管理,BootROM 无需关心 | 无坏块问题 |
| ECC | 必须硬件 ECC | CRC 校验即可 | 不需要 |
| 读取速度 | 较慢(受限于 NAND 接口) | 中等(受限于初始化频率) | XIP 模式较慢,拷贝模式快 |
| 代码复杂度 | 高(坏块跳过 + ECC) | 中(协议复杂但流程固定) | 低(命令简单) |
| 典型启动大小 | 512KB ~ 2MB | 512KB ~ 4MB | 64KB ~ 512KB |
嗯,这三种介质的驱动,BootROM 里都要实现。但实际产品中,通常只会用到其中一种或两种。芯片通过启动引脚的电平组合,选择从哪个介质启动。
我曾经调试过一个板子,启动引脚被焊错了,导致芯片一直从 SPI NOR 启动,但板子上根本没焊 SPI Flash。折腾了半天才发现是硬件问题。所以,调试启动问题时,先检查启动引脚的电平,这是最快的方法。