3. 硬件与启动介质基础

硬件初始化,说白了就是给芯片“通电叫醒”的过程。很多工程师把精力全放在软件算法上,结果一测启动时间——好家伙,光硬件初始化就占了 60%。我早年做一款工业相机时就吃过这个亏,后来花了整整两周专门优化硬件启动流程,才把启动时间从 3.2 秒压到 0.8 秒。

这一章,我就把那些实战中磨出来的技巧摊开讲。时钟怎么配最快?DDR 能不能跳过自检?外设时钟门控怎么玩出花来?咱们一个一个来。

3.1 时钟配置:别让 PLL 等你

时钟系统是芯片的心脏。但很多人没意识到——PLL 锁定是需要时间的。少则几十微秒,多则几百微秒。你想想看,如果每个外设都等 PLL 锁定完再配,那时间全耗在等待上了。

核心思路:先让 CPU 跑起来,再慢慢配其他时钟。别一上来就把所有 PLL 全开了。

3.1.1 时钟树的分阶段使能策略

我习惯把时钟初始化拆成三个阶段:

  1. 紧急阶段(~10 µs):只用内部 RC 振荡器,让 CPU 先跑起来。这时候频率低点没关系,能执行代码就行。
  2. 快速阶段(~50 µs):只使能 CPU 和内存控制器需要的 PLL。其他外设的时钟先关着。
  3. 后台阶段(~200 µs):等系统进入主循环后,再逐个使能外设时钟。这时候启动时间已经计完了。

我在一个 ARM Cortex-M7 项目里试过,把 PLL 初始化从串行改成这种分阶段策略,光这一步就省了 180 µs。

3.1.2 绕过不必要的 PLL 重配

很多芯片上电后默认就有个内部振荡器在跑。如果你不需要高精度时钟,完全可以跳过 PLL 配置,直接用内部时钟。我见过有人明明只需要 48 MHz,非要去配一个 400 MHz 的 PLL 再分频下来——这不是脱裤子放屁吗?

小技巧:如果芯片支持,可以在 BootROM 阶段就把时钟配置好,这样你的代码一启动就能直接用高频时钟。ST 的某些系列就支持这种“预配置时钟”模式。

3.1.3 时钟门控的批量操作

别一个外设一个外设地开时钟。大多数芯片都支持“批量使能”寄存器——一次写操作就能打开一组外设的时钟。我建议你把所有需要的外设时钟整理成一个位掩码,一次写入。

// 错误做法:逐个使能
RCC->AHB1ENR |= (1 << 0);  // GPIOA
RCC->AHB1ENR |= (1 << 1);  // GPIOB
RCC->AHB1ENR |= (1 << 2);  // GPIOC
// 浪费了 3 次总线写操作

// 正确做法:批量使能
RCC->AHB1ENR |= (1 << 0) | (1 << 1) | (1 << 2);
// 一次搞定

别小看这几条指令。在低速启动阶段,每次总线访问都要几十个时钟周期。积少成多,能省下不少时间。

3.2 DDR 初始化:跳过那些不必要的自检

DDR 初始化是硬件初始化里最耗时的环节之一。少则几百微秒,多则几毫秒。为什么?因为 DDR 控制器要做校准、训练、ZQ 校准……一套流程走下来,时间就上去了。

注意:跳过 DDR 自检有风险。如果你用的是消费级 DDR,或者工作温度范围很宽,建议至少做一次完整的初始化。只有在硬件设计成熟、环境可控的情况下,才考虑优化。

3.2.1 使用预训练参数

大多数 DDR 控制器都支持“软件训练”模式。你可以先在开发阶段跑一次完整的训练流程,把训练好的参数(如 DQS 延迟、ODT 配置、驱动强度)保存下来。量产时直接加载这些参数,跳过训练过程。

我在一个 i.MX6 项目里用过这招。完整训练要 1.2 ms,加载预训练参数只要 50 µs——省了 95% 的时间。

3.2.2 精简 ZQ 校准

ZQ 校准是 DDR 初始化里最耗时的步骤之一。很多芯片默认做“长校准”(long ZQ calibration),一次就要 512 个时钟周期。实际上,如果你用的是同一批次的 DDR 颗粒,完全可以改成“短校准”(short ZQ calibration),只要 64 个时钟周期。

校准类型 耗时(@400 MHz DDR) 适用场景
长校准(Long ZQ) ~1.28 µs 首次上电、温度变化大
短校准(Short ZQ) ~0.16 µs 量产批次稳定、环境可控
跳过校准 0 µs 使用预训练参数时

3.2.3 内存初始化只刷必要区域

DDR 上电后需要初始化——说白了就是把所有内存单元写一遍。但你真的需要初始化全部内存吗?

我建议只初始化以下区域:

  • CPU 的栈指针区域
  • 中断向量表所在区域
  • 数据段(.data)和 BSS 段

其他区域等用到的时候再初始化。有些芯片支持“按页初始化”,可以精确控制刷哪些地址。

避坑指南:我曾经在一个项目里跳过了 DDR 全部初始化,结果发现 ECC 校验会报错。后来才知道,ECC 要求所有内存单元必须有初始值。如果你开了 ECC,千万别跳过初始化。

3.3 外设时钟门控:该关的关,该开的开

时钟门控(Clock Gating)是芯片设计里用来省电的机制。但在启动优化里,它是个双刃剑——关掉不用的外设能省时间,但开错顺序会死机。

3.3.1 只使能必要的外设

很多工程师习惯“把所有外设时钟全开了再说”。这其实是个坏习惯。你想想看,一个芯片可能有几十个外设,每个外设的时钟使能都要写寄存器。全开了不仅浪费时间,还可能造成意外的电源冲突。

我建议的做法是:

  1. 列一个清单,只开启动阶段必须用的外设(如 UART、定时器、GPIO)
  2. 其他外设等用到的时候再开
  3. 如果芯片支持“自动门控”,打开这个功能,让硬件自己管理时钟

3.3.2 利用硬件自动门控

现代芯片基本都支持硬件自动门控。你只需要在初始化时配置好时钟源和分频系数,然后打开自动门控位。之后外设不工作时,硬件会自动关闭时钟;需要时再自动打开。

这招在启动阶段特别有用——你不需要在代码里手动管理每个外设的时钟开关,省了代码执行时间,也省了功耗。

3.3.3 时钟门控的依赖关系

有些外设之间有时钟依赖关系。比如 SPI 可能需要 DMA 的时钟先打开。如果你搞反了顺序,轻则外设不工作,重则总线挂死。

我一般会画一张时钟依赖图,贴在工位上。每次配新板子的时候先看一眼,省得踩坑。

我曾经踩过的坑:有一次做音频项目,我先开了 I2S 的时钟,再开 PLL 的时钟。结果 I2S 直接输出噪声——因为 PLL 还没锁定时,I2S 的时钟源是乱的。后来改成先开 PLL,等锁定后再开 I2S,问题就解决了。

3.4 实战案例:一个完整的硬件初始化优化流程

说了这么多理论,咱们来看个实际案例。这是一个基于 Cortex-M7 的工业控制器,原始启动时间 2.1 秒,其中硬件初始化占了 1.4 秒。

优化步骤:

  1. 时钟部分:改成三阶段使能,省了 180 µs
  2. DDR 部分:使用预训练参数 + 短 ZQ 校准,省了 1.1 ms
  3. 外设时钟:只开 UART 和定时器,其他外设延迟使能,省了 50 µs
  4. 批量操作:所有寄存器写操作改成 32 位批量写入,省了 20 µs

最终硬件初始化时间从 1.4 秒降到了 0.15 秒。整个启动时间从 2.1 秒降到了 0.8 秒。

关键点:硬件初始化优化的核心不是“让代码跑得更快”,而是“让硬件少干活”。能跳过的步骤就跳过,能并行的步骤就别串行,能批量操作的就别逐个操作。

3.5 知识体系总览

下面这张图总结了本章的核心逻辑。你可以把它当作优化时的检查清单。

硬件初始化优化知识体系 时钟配置 DDR 初始化 外设时钟门控 分阶段使能策略 跳过不必要的 PLL 重配 批量操作时钟门控 使用预训练参数 精简 ZQ 校准 只初始化必要区域 只使能必要外设 利用硬件自动门控 理清时钟依赖关系 目标:硬件初始化时间 < 200 µs 核心原则:能跳过的步骤就跳过,能并行的步骤就别串行 能批量操作的就别逐个操作,能延迟的就别急着做

嗯,这一章的内容就到这儿。时钟、DDR、门控——这三个点抓好了,硬件初始化这块基本就稳了。记住,优化的本质不是让代码跑得更快,而是让硬件少干没必要的活。