2. 系统复位类型:冷启动、热启动、看门狗复位、电源管理复位

做嵌入式开发这么多年,我见过不少工程师一上来就调驱动、改内核,结果系统一复位就翻车。说白了,你连系统是怎么「醒过来」的都没搞清楚,怎么优化启动速度?

复位类型这事,看着简单,坑却不少。我刚开始带项目时,就因为在热启动场景下没处理好内存状态,导致产线上一批设备间歇性死机。嗯,从那以后,我对复位类型的理解就深刻多了。

2.1 四种复位类型概述

嵌入式系统里,复位不是只有「按一下重启键」这么简单。根据复位源和硬件行为,我习惯把它们分成四类:

复位类型 触发源 硬件行为 典型场景
冷启动 上电复位(POR) 全芯片复位,所有寄存器恢复默认值 设备首次上电、电池更换后
热启动 外部复位引脚、软件复位指令 CPU核心复位,部分外设状态保留 用户按复位键、系统升级后重启
看门狗复位 WDT超时 强制复位,通常保留复位原因寄存器 系统死锁、任务超时
电源管理复位 电压跌落、睡眠唤醒、电源域切换 部分电源域复位,保留低功耗状态信息 低功耗模式唤醒、电压不稳

核心观点:不同的复位类型,Bootloader 和内核的恢复路径完全不同。你如果一刀切地处理,要么启动慢,要么直接挂掉。

2.2 冷启动:一切从零开始

冷启动是最「干净」的复位。硬件上电瞬间,POR 电路拉低复位信号,等电源稳定后再释放。这时候,CPU、内存控制器、外设寄存器全部回到默认状态。

我个人习惯把冷启动叫做「裸奔启动」——因为没有任何上下文可以依赖。U-Boot 必须从头初始化时钟、DDR、存储控制器,然后加载内核。

冷启动的典型流程:

  1. POR 释放,CPU 从复位向量取第一条指令
  2. 执行固化 ROM 代码(BootROM),加载 SPL 或 U-Boot
  3. U-Boot 初始化硬件(时钟、DDR、MMU 等)
  4. 加载并启动内核

注意:冷启动时 DDR 需要完整的 training 流程。我曾经在某款 i.MX 平台上遇到过,DDR training 参数没适配好,冷启动成功率只有 70%。后来加了温度补偿校准才搞定。

2.3 热启动:保留状态的艺术

热启动就有点意思了。CPU 核心复位了,但 DDR 里的数据可能还在——前提是你没断电。很多工程师以为热启动就是「快速重启」,其实没那么简单。

你想想看,热启动时 U-Boot 要不要重新初始化 DDR?如果 DDR 已经初始化过了,再初始化一遍不仅浪费时间,还可能破坏之前保留的数据。

我建议的做法是:

  • 在 U-Boot 里检测复位原因寄存器,判断是热启动还是冷启动
  • 如果是热启动,跳过 DDR training,直接使用保留的配置
  • 保留一个「启动标志」在 DDR 的保留区域,用于判断内存状态是否有效
/* 伪代码:U-Boot 中判断复位类型 */
u32 reset_cause = readl(RST_CAUSE_REG);

if (reset_cause == RST_CAUSE_WARM) {
    /* 热启动:跳过 DDR 初始化 */
    if (check_ddr_magic(DDR_MAGIC_ADDR)) {
        printf("Warm reset: skipping DDR init\n");
        goto skip_ddr;
    }
}
/* 冷启动或 DDR 状态无效:完整初始化 */
ddr_init();
write_ddr_magic(DDR_MAGIC_ADDR);

skip_ddr:
/* 继续后续启动流程 */

实战技巧:DDR 保留区域的 magic number 一定要选一个不容易被误判的值。我习惯用 0xDEADBEEF 或者 0xCAFEBABE,并且在写入时加上 CRC 校验。

2.4 看门狗复位:最棘手的场景

看门狗复位,说白了就是系统「死透了」之后被强行拉回来。但问题在于——你根本不知道死之前发生了什么。

我在项目中遇到过最头疼的情况:看门狗复位后,某个外设还卡在异常状态,U-Boot 初始化时读寄存器返回全 F,直接导致系统卡死在 probe 阶段。

看门狗复位的几个关键点:

  • 复位原因寄存器:一定要在启动早期读取并保存,因为后续初始化可能会把它覆盖掉
  • 外设状态清理:看门狗复位不会清理所有外设,需要手动复位异常设备
  • 日志记录:把复位原因写到持久化存储(如 EEPROM、RTC 备份寄存器),方便事后分析

避坑指南:我曾经遇到一个案例,看门狗复位后 GPIO 输出状态被锁死,导致电机驱动芯片过流。后来我们在 U-Boot 的 early stage 里加了 GPIO 强制复位,才彻底解决。

2.5 电源管理复位:低功耗的暗坑

电源管理复位,在低功耗设备里特别常见。系统进入睡眠模式后,某个电源域被关掉,唤醒时又得重新初始化。但有些电源域是保持的——比如 RTC、备份寄存器。

这里有个容易踩的坑:

  • 有些 SoC 在电源管理复位后,DDR 进入自刷新模式,数据还在
  • 但有些 SoC 会把 DDR 完全断电,数据全丢
  • 你必须在芯片手册里确认清楚,不能想当然

我个人的处理策略:

  1. 在 U-Boot 里读取 PMIC 状态寄存器,判断是唤醒还是冷启动
  2. 如果是唤醒,检查 DDR 自刷新标志,决定是否重新初始化
  3. 保留低功耗模式下的上下文信息(如唤醒源、唤醒时间)

2.6 复位类型判断与启动路径选择

说了这么多,最终要落地到代码里。我画了一张流程图,帮你理清思路:

复位类型判断与启动路径选择 系统复位 读取复位原因寄存器 冷启动(POR) 热启动 看门狗复位 电源管理复位 处理: 完整 DDR training 所有外设重新初始化 从头加载内核 处理: 跳过 DDR training 检查 DDR magic 快速启动内核 处理: 强制复位异常外设 记录复位原因 检查文件系统完整性 处理: 检查 DDR 自刷新状态 恢复电源域上下文 快速唤醒系统 进入 U-Boot 主流程 不同复位路径 启动时间差异可达 50% 以上

我的建议:在 U-Boot 的 board_init_f 阶段就完成复位类型判断,然后根据类型选择不同的初始化路径。这样能最大化启动速度,同时保证稳定性。

2.7 总结:复位类型决定了你的优化策略

四种复位类型,对应四种不同的启动策略。你不能用冷启动的流程去处理热启动,也不能忽视看门狗复位后的外设异常。

最后分享一个我自己的经验:在项目初期,就把复位类型判断逻辑写进 U-Boot 的公共代码里,而不是每个板子单独实现。这样后期维护起来省心很多——你想想看,如果每个项目都重写一遍,出错的概率得有多大?