2. 计算机启动流程概览

芯片上电后,第一件事是什么?不是跑你的应用程序,而是决定“我从哪里启动”。这个决策过程,就是 BootROM 的核心职责之一。今天咱们就聊聊常见的启动模式、芯片怎么选启动设备,以及最小硬件初始化到底要做哪些事。

2.1 常见的启动模式:各有各的脾气

不同的存储介质,有不同的接口协议和时序要求。BootROM 必须能识别并驱动它们。我按接口类型把它们分成了几类,你对比着看会更清楚。

启动模式 接口类型 典型速度 BootROM 关注点
NAND Flash 并行 NAND 接口 ~50 MB/s 坏块管理、ECC 校验、页读取时序
NOR Flash 并行 NOR 接口 ~100 MB/s XIP(原地执行)、地址映射、读时序
eMMC MMC/SD 协议 ~400 MB/s 初始化序列、分区切换、HS200/HS400 模式
SPI Flash SPI 总线 ~50 MB/s 命令集、四线/双线模式、Dual/Quad 读取
UART 串口 ~115200 bps 波特率协商、XMODEM/YMODEM 协议
USB USB 2.0/3.0 ~480 Mbps 设备枚举、DFU 协议、Bulk 传输
个人经验: 我早期做的一个项目,选了 NAND 启动。结果发现 BootROM 里的 ECC 算法和 NAND 芯片要求的算法不匹配,导致启动成功率只有 70%。后来不得不改硬件 strapping 切换到 SPI Flash 启动。所以选启动介质时,一定要先确认 BootROM 支持哪种 ECC 强度。

2.2 启动设备选择机制:芯片怎么知道从哪启动?

芯片没有“眼睛”,它怎么知道用户想从 NAND 还是 SPI Flash 启动?答案就在硬件引脚上——Boot Pin / Strapping。

芯片上电复位时,会采样一组专用引脚的电平状态。这些引脚通常在上电瞬间被锁存,然后 BootROM 根据锁存值决定启动流程。说白了,就是一组“跳线”告诉芯片该走哪条路。

典型 Boot Pin 配置示例(以某 ARM SoC 为例):
// Boot Mode 选择引脚
// BOOT[2:0] = 000 -> NAND Flash
// BOOT[2:0] = 001 -> NOR Flash
// BOOT[2:0] = 010 -> eMMC
// BOOT[2:0] = 011 -> SPI Flash
// BOOT[2:0] = 100 -> UART
// BOOT[2:0] = 101 -> USB
// BOOT[2:0] = 110 -> 保留
// BOOT[2:0] = 111 -> 内部 ROM 测试模式

这里有个坑:Boot Pin 的采样时序。我记得有一次,客户反馈说板子 10 次上电有 3 次启动失败。排查到最后,发现是 Boot Pin 的上拉电阻值选得太大,导致上电瞬间电平还没稳定就被锁存了。嗯,硬件设计上,Boot Pin 的 RC 常数一定要小于芯片复位释放时间。

2.3 最小硬件初始化流程:BootROM 的“热身运动”

BootROM 在加载用户代码之前,必须先让芯片“活起来”。这个最小初始化流程,我习惯把它分成三步:时钟 → PLL → DDR。顺序不能乱,因为后面的依赖前面的。

2.3.1 时钟初始化:一切的基础

芯片内部所有逻辑都需要时钟。BootROM 第一步通常是使能内部振荡器(比如 24MHz 晶振),然后等待它稳定。这个等待时间不能太短,否则时钟还没稳定就开始跑,逻辑会乱套。

注意: 有些芯片支持“旁路模式”——直接用外部时钟输入,跳过内部振荡器。但 BootROM 一般不会默认走这条路,因为外部时钟可能还没准备好。我见过一个案例,工程师为了省成本没接晶振,想用外部有源时钟,结果 BootROM 死等内部振荡器稳定,直接卡死。所以,除非芯片手册明确支持,否则别省晶振。

2.3.2 PLL 配置:把时钟提上去

基础时钟有了,但频率太低(比如 24MHz),跑 BootROM 太慢。BootROM 会配置 PLL(锁相环)把时钟倍频到几百 MHz。这里的关键是:PLL 锁定时间。配置完 PLL 后,必须等待它锁定,才能切换系统时钟。

// 伪代码:PLL 初始化流程
void pll_init(void) {
    // 1. 选择 PLL 参考时钟源(内部振荡器)
    PLL_REF_SEL = INTERNAL_OSC;
    
    // 2. 配置倍频系数和分频系数
    PLL_M = 25;  // 倍频 25 倍
    PLL_N = 1;   // 分频 1 倍
    // 输出频率 = 24MHz * 25 / 1 = 600MHz
    
    // 3. 使能 PLL
    PLL_EN = 1;
    
    // 4. 等待 PLL 锁定(轮询锁定标志)
    while (!PLL_LOCKED);
    
    // 5. 切换系统时钟到 PLL 输出
    SYS_CLK_SEL = PLL_OUTPUT;
}
避坑指南: 我曾经在调试一个芯片时,发现 PLL 锁定标志永远不置位。查了半天,原来是 PLL 的供电电压没达到要求。BootROM 里没有电压检测逻辑,所以硬件设计上一定要保证 PLL 供电先稳定。这个坑,硬件工程师和固件工程师容易互相甩锅,建议提前对齐。

2.3.3 DDR 初始化:让代码有地方待

BootROM 通常需要把用户代码从慢速存储(比如 SPI Flash)拷贝到 DDR 中运行。所以 DDR 必须在加载前完成初始化。DDR 初始化是 BootROM 里最复杂、最敏感的部分。

DDR 初始化流程大致如下:

  1. 复位 DDR 控制器和 PHY——确保它们处于已知状态。
  2. 配置 DDR 时序参数——包括 CAS 延迟、行地址选通时间、刷新周期等。这些参数必须和 DDR 芯片的数据手册严格匹配。
  3. 执行 DDR 训练——包括写均衡(Write Leveling)、读均衡(Read DQS Gating)、眼图训练等。这一步是为了补偿 PCB 走线延迟和芯片工艺偏差。
  4. 验证 DDR 读写——写一个已知模式,读回来对比,确保 DDR 工作正常。
DDR 参数配置示例(DDR3-1600):
// DDR3 时序参数结构体
typedef struct {
    uint8_t  cas_latency;      // CL = 11
    uint8_t  write_recovery;   // WR = 12
    uint8_t  row_cycle_time;   // tRC = 48.75ns
    uint8_t  row_active_time;  // tRAS = 35ns
    uint16_t refresh_interval; // tREFI = 7.8us
    uint8_t  mode_register;    // MR0 = 0x1840
} ddr_timing_t;

这里我要特别强调:DDR 训练参数不能硬编码。不同批次的芯片、不同温度的 PCB,训练结果可能不一样。好的 BootROM 会在每次上电时重新训练,而不是用上一次保存的参数。我见过一个产品,为了省启动时间,把训练参数存到 Flash 里,结果冬天低温时 DDR 频繁报错。嗯,省时间不能省可靠性。

2.4 启动模式与初始化流程总览

说了这么多,咱们用一张图把整个流程串起来。这张图展示了从芯片上电到 BootROM 交出控制权的完整路径。

BootROM 启动模式与硬件初始化流程 BootROM 启动模式与硬件初始化流程 芯片上电复位 Boot Pin 采样(锁存启动模式) 根据 Boot Pin 值选择启动模式 时钟初始化(使能振荡器) PLL 配置(倍频到目标频率) DDR 初始化(训练 + 验证) 从启动设备加载用户代码到 DDR 关键说明 • Boot Pin 采样必须在复位释放前完成 • PLL 锁定时间因芯片而异 • DDR 训练参数不可硬编码 常见启动模式 • NAND / NOR / eMMC • SPI Flash / UART / USB • 优先级由 Boot Pin 决定

这张图把整个流程串起来了。你注意看,从“芯片上电复位”到“加载用户代码”,中间每一步都不能跳过。尤其是 DDR 初始化,它是最容易出问题的地方。我建议你在实际项目中,把 DDR 训练日志打印出来,这样调试时能快速定位是时序问题还是硬件问题。

2.5 小结

启动模式和硬件初始化,是 BootROM 的“基本功”。说白了,就是芯片上电后先搞清楚“我从哪启动”、“我的时钟多快”、“我的内存在哪”。这三个问题搞定了,才能去加载你的应用程序。

我个人习惯在项目初期,先花一天时间把 Boot Pin 的硬件设计 review 一遍,再花一天把 DDR 训练参数调好。这两件事做好了,后面启动流程基本不会出大问题。嗯,磨刀不误砍柴工。