4. BootROM与一级引导

启动时间优化里,存储介质的选择是个硬骨头。很多人一上来就盯着 CPU 主频、代码效率,却忽略了数据从存储介质里搬出来到底要花多久。说白了,你代码写得再快,如果存储介质本身是个瓶颈,那启动时间照样下不来。

我个人习惯,在评估一个嵌入式系统的启动方案时,会先问三个问题:

  • 系统需要多大的存储空间?
  • 对读取速度的要求有多高?
  • 成本预算允许到什么程度?

这三个问题问完,基本就能锁定该用哪种存储介质了。

4.1 四种主流存储介质的核心差异

先看一张对比表,把 NAND Flash、eMMC、SD 卡、SPI Flash 的关键参数拉出来遛遛。

特性 SPI Flash NAND Flash eMMC SD 卡
接口类型 SPI(串行) 并行 / ONFI / Toggle MMC(8位并行) SDIO(1/4位)
典型读取速度 10~50 MB/s 50~400 MB/s 100~400 MB/s 10~100 MB/s
随机读取 差(按页读) 中等(需 ECC) 好(内置控制器) 中等
坏块管理 无(需软件处理) 需软件处理 内置 内置
启动方式 XIP(就地执行) 需加载到 RAM 需加载到 RAM 需加载到 RAM
成本(相对) 低 中 中高 低(消费级)

嗯,这里要注意一个关键点:SPI Flash 支持 XIP(eXecute In Place),也就是说 CPU 可以直接从 Flash 里取指令执行,不用先把代码搬到 RAM 里。这在启动阶段是个巨大的优势——省掉了加载时间。但代价是速度慢,而且只能跑代码,不能当大容量存储用。

核心结论:启动时间优化,优先考虑 XIP 方案(SPI Flash),如果容量或速度不够,再考虑 eMMC 或 NAND + 快速加载策略。

4.2 读取速度实测对比

光看理论参数不够,我拿实际项目里的数据说话。以下是我在某个工业控制板卡上测得的连续读取速度(使用 DMA + 双缓冲):

存储介质 接口频率 连续读取(MB/s) 4KB 随机读取(MB/s)
SPI Flash (W25Q64) 80 MHz (Quad SPI) 32 1.2
NAND Flash (MT29F2G08) 50 MHz 18 0.8
eMMC (KLMCG4JENB) HS200 (200 MHz) 180 15
SD 卡 (SanDisk Extreme) UHS-I (104 MHz) 85 6.5

看到没?eMMC 的连续读取速度是 SPI Flash 的 5 倍以上,随机读取更是差了十几倍。但为什么很多低功耗设备还是用 SPI Flash?因为启动时不需要那么高的带宽,而且 XIP 省掉了加载时间,整体启动反而更快。

我曾经在一个项目里踩过坑:选了一颗高速 NAND Flash,觉得读取速度够快,结果启动时 CPU 要先从 NAND 里把 bootloader 搬到 SRAM,这个过程花了 200ms。后来换成 SPI Flash 做 XIP,虽然读取速度慢,但省掉了搬运时间,整体启动反而快了 150ms。你想想看,有时候快不等于快。

4.3 如何根据启动时间需求选择

我一般按启动时间目标来划分选择策略:

  • 目标 < 100ms: 必须用 SPI Flash(XIP 模式),配合压缩存储 + 快速解压。代码量控制在 256KB 以内。
  • 目标 100ms ~ 500ms: 可以考虑 eMMC 或高速 NAND,但必须用 DMA + 双缓冲加载,且 bootloader 要放在 SPI Flash 里先执行。
  • 目标 > 500ms: SD 卡或普通 NAND 都可以,成本优先。但要注意 SD 卡的初始化时间比较长,我遇到过某些卡初始化就要 300ms。

我的经验:如果系统允许,把 bootloader 放在 SPI Flash 里,内核和文件系统放在 eMMC 或 NAND 里。这样既保证了快速启动,又兼顾了大容量存储。我在一个车载项目里就是这么干的,启动时间从 1.2s 降到了 0.4s。

4.4 存储介质读取优化实战技巧

选对了介质只是第一步,怎么读才是关键。以下是我总结的几个优化点:

4.4.1 使用 DMA 而非 PIO

PIO(Programmed I/O)模式下,CPU 要亲自搬数据,浪费大量时间。DMA 可以让数据搬运和 CPU 执行并行起来。我习惯在 bootloader 阶段就初始化 DMA 控制器,把读取操作全部交给 DMA。

// 示例:使用 DMA 从 SPI Flash 读取数据
void spi_flash_read_dma(uint32_t addr, uint8_t *buf, uint32_t len) {
    // 配置 SPI 为 DMA 模式
    spi_dma_enable(SPI1);
    // 设置 DMA 源地址(SPI 数据寄存器)和目的地址(内存缓冲区)
    dma_config(DMA1, DMA_CH1, (uint32_t)&SPI1->DR, (uint32_t)buf, len);
    // 发送读取命令和地址
    spi_send_cmd(SPI1, CMD_READ_DATA);
    spi_send_addr(SPI1, addr);
    // 启动 DMA 传输
    dma_start(DMA1, DMA_CH1);
    // 等待 DMA 完成
    while(!dma_is_complete(DMA1, DMA_CH1));
}

4.4.2 双缓冲(Double Buffering)

单缓冲的问题是:CPU 处理数据时,DMA 必须停下来。双缓冲可以让 DMA 往一个缓冲区写数据,同时 CPU 从另一个缓冲区读数据,两者完全流水线化。

注意:双缓冲需要两倍的 RAM 空间。在启动阶段 RAM 很宝贵,要权衡好。我一般只在加载内核镜像时用双缓冲,bootloader 本身用单缓冲就够了。

4.4.3 调整读取粒度

NAND Flash 和 eMMC 都有最小读取单元(页)。如果每次只读几个字节,效率极低。我建议:

  • NAND Flash:按页读取(通常 2KB 或 4KB)
  • eMMC:按块读取(通常 512B 或 4KB)
  • SPI Flash:按页读取(通常 256B),但可以用 Quad SPI 模式提升速度

4.5 一张图看懂存储介质选择逻辑

下面这张图是我自己画的选择决策流程,帮你快速定位该用哪种介质。

存储介质选择决策流程图 存储介质选择决策流程 启动时间目标 目标 < 100ms ? 是 SPI Flash (XIP) 代码 < 256KB 否 目标 < 500ms ? 是 eMMC / 高速 NAND DMA + 双缓冲加载 否 SD 卡 / 普通 NAND 成本优先 说明: • 所有方案都建议将 bootloader 放在 SPI Flash 中,保证最小启动时间。 • eMMC 内置控制器,坏块管理和 ECC 由硬件完成,开发成本低。 • NAND Flash 需要软件处理坏块和 ECC,会增加启动代码的复杂度。 • SD 卡初始化时间长,不适合超快速启动场景。

4.6 避坑指南

最后分享几个我踩过的坑,希望能帮你少走弯路。

我曾经犯过的错:在一个消费电子产品里,我选了 eMMC 作为唯一存储介质,觉得速度快、容量大。结果发现芯片上电后,eMMC 需要先完成内部初始化(几百毫秒),而 CPU 在这段时间里只能干等。后来我在 SPI Flash 里放了一个微型 bootloader,先让 CPU 跑起来,再并行初始化 eMMC,才把启动时间压下来。

  • 别迷信理论速度:实际读取速度受总线频率、DMA 配置、ECC 校验等因素影响,一定要实测。
  • 注意存储介质的初始化时间:eMMC 和 SD 卡上电后需要几百毫秒才能响应命令,这个时间会被计入启动时间。
  • XIP 不是万能的:SPI Flash 的随机读取性能很差,如果代码有大量跳转,XIP 反而会变慢。这种情况下,把热点代码搬到 RAM 里执行更划算。
  • 考虑未来升级:如果产品后期需要 OTA 升级,存储容量和写入速度也要提前规划。我见过一个项目因为选了太小容量的 SPI Flash,导致升级包放不下,最后只能换芯片。

好了,存储介质的选择和优化就聊到这里。记住一句话:没有最好的存储介质,只有最适合你启动时间目标的方案。