启动模式与介质:你的芯片怎么知道该从哪启动?
芯片上电后,第一件事就是找代码来执行。但问题是——代码放在哪?是焊在板子上的 NOR Flash,还是贴片 eMMC,又或者是调试时临时插的 SD 卡?
BootROM 自己不会猜。它得靠一套固定的机制来确定启动源。这套机制,就是我们今天要聊的启动模式选择与启动介质。
我早年做第一款量产产品时,就栽过跟头。板子回来死活不跑,查了两天才发现——Boot Mode 电阻虚焊,芯片一直以为自己在等 UART 下载。嗯,从那以后,我对启动模式引脚的设计检查就格外上心。
主流启动介质:各有各的脾气
不同的启动介质,在速度、容量、接口、可靠性上差异很大。选型时不能只看成本,还得看 BootROM 是否原生支持。
| 介质 | 接口类型 | 典型容量 | 读速度 | 是否支持XIP | BootROM 常见支持 |
|---|---|---|---|---|---|
| NOR Flash | SPI / Parallel | 1MB ~ 64MB | 中 | ✅ 是 | 几乎全支持 |
| NAND Flash | ONFI / Raw NAND | 128MB ~ 8GB | 高(连续读) | ❌ 否 | 需搬运代码到SRAM |
| eMMC | MMC / SDIO | 4GB ~ 128GB | 高 | ❌ 否 | 需分区引导 |
| SD 卡 | SD / SPI | 2GB ~ 32GB | 中高 | ❌ 否 | 调试阶段常用 |
| UART | UART | 无 | 极低(115200bps) | ❌ 否 | 下载模式 |
| USB | USB 2.0/3.0 | 无 | 高 | ❌ 否 | 下载/DFU模式 |
关键区别:NOR Flash 支持 XIP(Execute In Place),CPU 可以直接在 Flash 上取指运行。NAND、eMMC、SD 卡都不行——BootROM 必须先把一小段引导代码搬到内部 SRAM,再跳过去执行。这个搬运过程,就是所谓的 Boot Loader 第一阶段。
NOR Flash:最省心的启动伙伴
我个人最喜欢 NOR Flash 做启动介质。为什么?因为它简单。
- XIP 特性:CPU 复位后直接映射到地址 0x0,取复位向量,开跑。不需要搬运,不需要初始化 DRAM。
- 可靠性高:位翻转概率极低,几乎不需要 ECC。
- 接口成熟:SPI NOR 只需要 4~6 根线,PCB 布局非常友好。
但 NOR 也有短板——容量做不大,价格贵。你想想看,现在一个 16MB 的 SPI NOR 就要一两块钱,而同样价格的 NAND 能给你 1GB。所以 NOR 通常只放 BootROM 和第一级引导,真正的固件还是放在 NAND 或 eMMC 里。
实战经验:我在一个 IoT 项目里用过 W25Q128(16MB SPI NOR)同时放 Bootloader 和 Application。因为固件只有 6MB,完全够用。省掉了一颗 NAND 的成本和 ECC 校验的麻烦。如果你的固件小于 16MB,NOR 是性价比很高的选择。
NAND Flash:容量大,但坑也多
NAND 的启动流程比 NOR 复杂不少。BootROM 需要做这几件事:
- 初始化 NAND 控制器(时序参数、IO 配置)
- 读取 NAND 的 ID,确认型号
- 跳过坏块(Bad Block),找到有效的 Boot Image
- 将代码从 NAND 搬运到内部 SRAM(通常 4KB ~ 64KB)
- 校验 ECC,确保数据正确
- 跳转到 SRAM 执行
这里有个容易踩的坑——坏块管理。NAND 出厂就有坏块,而且使用过程中还会产生新的坏块。BootROM 通常只扫描前几个块(比如 Block 0 ~ Block 3),如果这些块恰好都是坏块,芯片就启动不了。
我曾经遇到过一批 NAND,Block 0 在出厂时就是坏块,但产线烧录工具没做坏块跳过,直接把 Boot Image 写到了 Block 0。结果 30% 的板子无法启动。排查后发现,BootROM 只检查 Block 0,发现是坏块就直接放弃了。解决方案是:烧录时强制写到 Block 1,并在 Block 0 写入一个跳转标记。
eMMC:现代嵌入式的主流选择
eMMC 内部集成了 NAND 和控制器,坏块管理、ECC、磨损均衡都由芯片自己搞定。对 BootROM 来说,eMMC 就像一个黑盒子——发命令,读数据,简单粗暴。
eMMC 的启动流程通常走 Boot Partition 模式:
- eMMC 内部有专门的 Boot Area Partition(通常 512KB ~ 4MB)
- BootROM 发送 CMD0 + CMD1,让 eMMC 进入 Boot Mode
- eMMC 自动从 Boot Partition 输出数据,BootROM 接收并搬运到 SRAM
- 执行完毕后,eMMC 切回正常模式,后续由 Bootloader 访问 User Partition
我建议你在设计 eMMC 启动时,一定要确认 Boot Partition 是否使能。有些厂商的 eMMC 默认 Boot Partition 是关闭的,需要第一次烧录时通过专用命令开启。否则板子回来怎么都起不来。
UART 与 USB:调试阶段的救命稻草
这两种介质不是用来跑正式固件的,而是用于 工厂烧录 和 开发调试。
UART 下载模式:
- BootROM 初始化 UART,等待主机发送特定握手信号
- 通常使用 XMODEM / YMODEM 协议传输固件
- 速度慢(115200 bps 是常态),但胜在简单可靠
- 适合小容量 Bootloader 的烧录
USB 下载模式:
- BootROM 初始化 USB 设备控制器,枚举为 DFU(Device Firmware Update)设备
- 主机通过 USB 批量传输数据,速度可达 MB/s 级别
- 适合大容量固件的首次烧录
我的习惯:量产产品一定会保留 UART 下载模式作为 fallback。万一 eMMC 里的固件刷坏了,至少还能通过 UART 救回来。USB DFU 虽然快,但需要主机装驱动,产线环境不一定方便。
启动模式选择:硬件引脚 vs 寄存器
芯片怎么知道该从哪个介质启动?两种方式:
1. Boot Mode Pin(硬件引脚)
芯片复位时,采样特定 GPIO 的电平状态,组合成启动介质编号。
// 典型 Boot Mode 引脚映射(以某 ARM SoC 为例)
// BOOT[2:0] 引脚电平决定启动介质
// 000 - NOR Flash (SPI)
// 001 - NAND Flash
// 010 - eMMC
// 011 - SD Card
// 100 - UART Download
// 101 - USB DFU
// 110 - Reserved
// 111 - Reserved
硬件引脚的好处是——不需要任何软件干预,上电瞬间就决定。缺点是占用了 GPIO,而且一旦 PCB 打样完成,启动顺序就固定了。
2. Boot Mode 寄存器(软件配置)
有些芯片支持在 BootROM 执行初期,通过读取 OTP(One-Time Programmable)或 EFUSE 中的值来决定启动介质。这种方式更灵活,但需要提前烧录配置。
更常见的是 混合模式:硬件引脚决定第一优先级,如果该介质启动失败,BootROM 回退到寄存器配置的次选介质。
避坑指南:硬件引脚采样是在复位释放瞬间完成的。如果引脚上有较大的 RC 延迟(比如接了上拉电阻和电容),电平可能还没稳定,BootROM 就读到了错误的值。我建议 Boot Mode 引脚不要加滤波电容,上拉/下拉电阻用 4.7kΩ ~ 10kΩ 即可。
启动流程全景图
下面这张图展示了从芯片上电到 BootROM 完成介质选择、加载代码、跳转执行的完整流程:
实际项目中的启动策略
讲完理论,说说我在实际项目中怎么用这些知识。
案例:一款工业网关产品
- 主芯片:某 ARM Cortex-A7 双核
- 启动介质:SPI NOR (16MB) + eMMC (8GB)
- 启动策略:
- Boot Mode Pin 设为 NOR Flash 启动
- NOR 中存放 Bootloader(U-Boot SPL)
- U-Boot SPL 初始化 DRAM,然后从 eMMC 加载完整的 U-Boot 和 Linux 内核
- 如果 eMMC 读取失败,回退到 NOR 中的救援系统(一个迷你的 busybox 环境)
这个方案的好处是:NOR 只放关键代码,容量需求小;eMMC 放系统和应用,容量大、成本低。而且万一 eMMC 坏了,还能通过 NOR 里的救援系统远程修复。
注意:多级启动时,每一级的代码大小都要严格控制。比如 SPL 通常限制在 64KB 以内,因为内部 SRAM 就那么大。我曾经见过一个团队把 SPL 写到了 80KB,结果链接时才发现放不下,只能砍功能重写。
小结
启动介质和模式选择,是 BootROM 设计的第一个关键决策。选对了,后续开发顺风顺水;选错了,可能整个项目都要推倒重来。
我的建议是:
- 小容量固件(<16MB):优先考虑 SPI NOR,省心省力
- 大容量固件(>128MB):eMMC 是主流,注意 Boot Partition 配置
- 成本敏感:NAND 可以选,但一定要做好坏块管理和 ECC
- 调试阶段:保留 UART 或 USB 下载模式作为 fallback
- Boot Mode Pin:不要加滤波电容,电阻值选 4.7kΩ ~ 10kΩ