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 传输 |
2.2 启动设备选择机制:芯片怎么知道从哪启动?
芯片没有“眼睛”,它怎么知道用户想从 NAND 还是 SPI Flash 启动?答案就在硬件引脚上——Boot Pin / Strapping。
芯片上电复位时,会采样一组专用引脚的电平状态。这些引脚通常在上电瞬间被锁存,然后 BootROM 根据锁存值决定启动流程。说白了,就是一组“跳线”告诉芯片该走哪条路。
// 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 晶振),然后等待它稳定。这个等待时间不能太短,否则时钟还没稳定就开始跑,逻辑会乱套。
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;
}
2.3.3 DDR 初始化:让代码有地方待
BootROM 通常需要把用户代码从慢速存储(比如 SPI Flash)拷贝到 DDR 中运行。所以 DDR 必须在加载前完成初始化。DDR 初始化是 BootROM 里最复杂、最敏感的部分。
DDR 初始化流程大致如下:
- 复位 DDR 控制器和 PHY——确保它们处于已知状态。
- 配置 DDR 时序参数——包括 CAS 延迟、行地址选通时间、刷新周期等。这些参数必须和 DDR 芯片的数据手册严格匹配。
- 执行 DDR 训练——包括写均衡(Write Leveling)、读均衡(Read DQS Gating)、眼图训练等。这一步是为了补偿 PCB 走线延迟和芯片工艺偏差。
- 验证 DDR 读写——写一个已知模式,读回来对比,确保 DDR 工作正常。
// 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 交出控制权的完整路径。
这张图把整个流程串起来了。你注意看,从“芯片上电复位”到“加载用户代码”,中间每一步都不能跳过。尤其是 DDR 初始化,它是最容易出问题的地方。我建议你在实际项目中,把 DDR 训练日志打印出来,这样调试时能快速定位是时序问题还是硬件问题。
2.5 小结
启动模式和硬件初始化,是 BootROM 的“基本功”。说白了,就是芯片上电后先搞清楚“我从哪启动”、“我的时钟多快”、“我的内存在哪”。这三个问题搞定了,才能去加载你的应用程序。
我个人习惯在项目初期,先花一天时间把 Boot Pin 的硬件设计 review 一遍,再花一天把 DDR 训练参数调好。这两件事做好了,后面启动流程基本不会出大问题。嗯,磨刀不误砍柴工。