4. 启动模式与引脚配置
大家好,我是你们的嵌入式系统讲师。今天我们来聊聊BootROM启动流程中一个非常关键的环节——启动设备探测与加载。
说实话,很多工程师在写应用层代码时,很少会关心BootROM是怎么找到启动设备的。但一旦你开始做底层启动、做bring-up,你就会发现——BootROM的启动设备探测算法,是整个系统能否正常启动的第一步。这一步错了,后面全是白搭。
4.1 启动设备探测算法:BootROM如何识别存储介质?
BootROM在芯片上电后,首先要做的事情就是:找到哪个设备里存了可启动的镜像。它怎么找?靠的就是设备探测算法。
不同的存储介质,探测方式完全不同。我挑两个最常见的来讲:SPI Flash 和 eMMC。
4.1.1 SPI Flash探测:读ID号
SPI Flash的探测,说白了就是发一个命令,让Flash芯片报上名来。这个命令叫 RDID(Read ID),命令码是 0x9F。
BootROM会这样做:
- 拉低片选(CS),选中SPI设备
- 发送
0x9F命令 - 等待Flash返回3个字节:Manufacturer ID + Device ID
- 拉高片选,结束通信
拿到ID后,BootROM会查一张内部表。比如:
| Manufacturer ID | Device ID | 对应型号 | 容量 |
|---|---|---|---|
| 0xEF | 0x4018 | W25Q128JV | 16MB |
| 0xC8 | 0x4015 | GD25Q64B | 8MB |
| 0x1C | 0x3816 | EN25QH128 | 16MB |
4.1.2 eMMC探测:CMD0 + 初始化序列
eMMC的探测比SPI复杂得多。它走的是MMC协议,BootROM需要发送一系列命令来完成初始化。
核心流程是这样的:
- 发送
CMD0(GO_IDLE_STATE),让eMMC进入空闲状态 - 发送
CMD1(SEND_OP_COND),获取OCR寄存器,判断工作电压 - 发送
CMD2(ALL_SEND_CID),获取设备CID(唯一标识) - 发送
CMD3(SET_RELATIVE_ADDR),给设备分配一个相对地址(RCA) - 发送
CMD9(SEND_CSD),获取CSD寄存器,了解设备容量、块大小等
嗯,这里要注意:CMD0是必须成功的。如果CMD0没响应,BootROM就会认为这个eMMC不存在,直接跳过。
4.2 启动头(Boot Header)解析:镜像的身份证
BootROM找到了存储设备,接下来要读数据了。但它不能随便读——它需要先找到启动头(Boot Header)。
启动头是什么?说白了,就是一段固定格式的数据,放在镜像的最开头。它告诉BootROM:
- 这个镜像是什么格式的
- 它有多大
- 它应该被加载到哪个地址
- 它的校验值是多少
一个典型的Boot Header结构如下:
typedef struct {
uint32_t magic; // 魔数,比如 0xAA55AA55
uint32_t image_size; // 镜像总大小(字节)
uint32_t load_addr; // 加载地址
uint32_t entry_point; // 入口地址
uint32_t checksum; // 校验和
uint32_t version; // 版本号
uint8_t reserved[16]; // 保留字段
} boot_header_t;
BootROM读到这个结构后,会做几件事:
- 验证魔数:如果魔数不对,说明这不是一个合法的启动镜像,直接跳过
- 检查版本:有些BootROM只支持特定版本的Header格式
- 记录加载地址:后面要把镜像拷贝到内存的这个位置
- 记录入口地址:跳转时要跳到这里
4.3 启动镜像完整性校验:CRC / Checksum
BootROM把镜像读到内存后,不能直接跳过去执行。它得先确认:这个镜像有没有被损坏?
怎么确认?靠校验。常用的校验方式有两种:
4.3.1 简单校验和(Checksum)
就是把镜像的所有字节加起来,取低32位。实现简单,但碰撞概率高。适合对安全性要求不高的场景。
uint32_t calculate_checksum(uint8_t *data, uint32_t len) {
uint32_t sum = 0;
for (uint32_t i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}
4.3.2 CRC校验
CRC(循环冗余校验)比Checksum可靠得多。BootROM里常用的是CRC32。它用多项式除法来计算,能检测出大部分常见的错误。
uint32_t calculate_crc32(uint8_t *data, uint32_t len) {
// 这里省略标准CRC32实现,实际项目中用查表法效率更高
// 我建议直接用硬件CRC模块,速度比软件快10倍以上
return crc32_hardware(data, len);
}
4.4 加载地址与跳转逻辑:最后一步
校验通过后,BootROM要做最后两件事:加载 和 跳转。
4.4.1 加载地址
BootROM从Boot Header里读到了 load_addr,然后它会把整个镜像从存储设备拷贝到内存的这个地址。
这里有个坑:加载地址必须是内存的有效地址。如果加载地址指向了未初始化的DDR区域,或者指向了只读区域,那系统就会崩溃。
4.4.2 跳转逻辑
拷贝完成后,BootROM会跳转到 entry_point 地址执行。跳转前,它通常会做几件事:
- 关闭中断(防止跳转过程中被打断)
- 设置栈指针(SP)
- 清空一些状态寄存器
- 最后,用一条跳转指令(比如
BX或BL)跳过去
// 伪代码:BootROM跳转逻辑
void boot_jump(uint32_t entry_point) {
__disable_irq(); // 关中断
__set_MSP(entry_point); // 设置主栈指针(有些架构需要)
// 清空一些关键寄存器
// ...
// 跳转
((void (*)(void))entry_point)();
}
跳转之后,BootROM的使命就完成了。控制权交给了下一级启动程序(比如U-Boot SPL或裸机程序)。
4.5 本章知识体系总览
为了让你更直观地理解整个流程,我画了一张流程图:
这张图把整个流程串起来了。你想想看,从芯片上电到最终跳转到用户程序,BootROM做了多少事?每一步都不能出错。
好了,这一章的内容就到这里。启动设备探测与加载,是BootROM最核心的功能之一。理解了它,你就掌握了底层启动的钥匙。
可可精品资料库