18、看门狗与低功耗模式:启动阶段的特殊处理
看门狗和低功耗模式,这两个东西在启动阶段特别容易出问题。我见过不少工程师,芯片刚上电就跑飞了,或者进低功耗就醒不过来——说白了,都是启动阶段的初始化没处理好。
今天咱们就聊聊,在系统刚启动那会儿,怎么伺候好看门狗,怎么安全地进入和退出低功耗模式。
18.1 看门狗:启动阶段的“定时炸弹”
看门狗(Watchdog Timer)是个好东西,能防止程序跑飞。但它在启动阶段,反而可能成为一颗定时炸弹。
为什么会这样?因为很多芯片上电后,看门狗默认就是开启的。你还没来得及喂狗,它就先把你复位了。
我个人习惯的做法是:在复位向量指向的第一条指令,就立刻禁用看门狗。比如在 ARM Cortex-M 上,启动文件里通常这样写:
; 启动文件 startup.s 片段
Reset_Handler:
; 第一步:关掉看门狗
LDR R0, =IWDG_BASE_ADDR
LDR R1, [R0, #IWDG_KR_OFFSET]
MOV R2, #0xCCCC ; 解锁键值
STR R2, [R0, #IWDG_KR_OFFSET]
MOV R2, #0x0000 ; 禁用看门狗
STR R2, [R0, #IWDG_KR_OFFSET]
; 然后才初始化时钟、堆栈等
BL SystemInit
BL main
嗯,这里要注意:不同厂家的看门狗,解锁和禁用的方式完全不同。有的需要写特定序列,有的需要写寄存器。我建议你仔细看芯片手册,别想当然。
18.2 低功耗模式:启动时的“沉睡陷阱”
低功耗模式,说白了就是让芯片睡觉。但启动阶段,芯片刚醒来,很多外设还没准备好,这时候如果贸然操作,很容易出问题。
我在项目中遇到过一件事:某次做物联网终端,芯片从深度睡眠中唤醒后,直接去读传感器数据,结果读回来全是0。查了半天才发现,是 ADC 还没稳定。
启动阶段处理低功耗模式,核心就三点:
- 判断唤醒源:是谁把芯片叫醒的?定时器?外部中断?还是复位?
- 恢复时钟和外设:低功耗模式下,很多时钟和外设都被关了,需要重新初始化。
- 清理唤醒标志:不清除的话,下次可能误判。
看个例子,这是我从深度睡眠中唤醒后的处理流程:
void SystemWakeupHandler(void)
{
uint32_t wakeup_source;
// 1. 读取唤醒源寄存器
wakeup_source = RTC->ISR & RTC_ISR_WUTF;
// 2. 恢复系统时钟
SystemClock_Config(); // 重新配置PLL、HSE等
// 3. 重新初始化外设
UART_Init();
I2C_Init();
ADC_Init();
// 4. 等待外设稳定
Delay_ms(10); // 给ADC、晶振一点时间
// 5. 清除唤醒标志
RTC->ISR &= ~RTC_ISR_WUTF;
// 6. 根据唤醒源做不同处理
if (wakeup_source & RTC_ISR_WUTF) {
// 定时唤醒:采集数据
Sensor_Read();
} else {
// 外部中断唤醒:处理事件
Event_Process();
}
}
18.3 看门狗与低功耗的“相爱相杀”
看门狗和低功耗模式,有时候会互相打架。你想想看:芯片进入低功耗后,CPU 停了,看门狗还在跑吗?
这取决于芯片设计。有些芯片的看门狗在低功耗模式下会自动暂停,有些则不会。如果看门狗还在跑,而你又没喂狗,那它就会在芯片睡着的时候,把芯片复位。
我曾经踩过这个坑:一个电池供电的设备,每隔几分钟唤醒一次采集数据。结果发现设备总是莫名其妙重启。查了三天,才发现是看门狗在睡眠期间超时了。
解决方案有两种:
| 方案 | 做法 | 适用场景 |
|---|---|---|
| 方案一:睡眠前禁用看门狗 | 进入低功耗前关掉看门狗,唤醒后再重新开启 | 睡眠时间不确定,或芯片不支持看门狗自动暂停 |
| 方案二:使用独立看门狗(IWDG) | IWDG 使用独立时钟,不受系统时钟影响,可在睡眠中继续运行 | 需要高可靠性,防止睡眠期间死机 |
我个人更倾向于方案二。因为方案一有个风险:如果你忘了在唤醒后重新开启看门狗,那系统就处于无保护状态了。而 IWDG 只要配置好,它自己会一直跑,你只需要在唤醒后及时喂狗就行。
18.4 启动阶段的“黄金窗口”
启动阶段,从复位到 main 函数执行,这段时间我称之为“黄金窗口”。
为什么叫黄金窗口?因为这段时间里,系统还没完全初始化,很多中断还没开启,外设还没配置。你可以在此时做一些特殊操作,比如:
- 检查复位原因(是上电复位、看门狗复位,还是低功耗唤醒?)
- 根据复位原因,决定是否跳过某些初始化步骤
- 记录复位次数,用于故障诊断
看个实际例子:
void CheckResetCause(void)
{
uint32_t reset_cause = RCC->CSR;
if (reset_cause & RCC_CSR_WDGRSTF) {
// 看门狗复位:记录错误,进入安全模式
ErrorLog_Write("Watchdog reset occurred");
EnterSafeMode();
} else if (reset_cause & RCC_CSR_SFTRSTF) {
// 软件复位:正常启动
NormalBoot();
} else if (reset_cause & RCC_CSR_PORRSTF) {
// 上电复位:完整初始化
FullInit();
} else if (reset_cause & RCC_CSR_PINRSTF) {
// 外部复位引脚:可能是低功耗唤醒
WakeupFromSleep();
}
// 清除复位标志,避免下次误判
RCC->CSR |= RCC_CSR_RMVF;
}
18.5 知识体系总览
说了这么多,咱们用一张图来总结一下启动阶段看门狗和低功耗模式的处理逻辑:
这张图把整个流程串起来了。你想想看,从复位开始,先关看门狗,再判断唤醒源,然后根据情况做不同处理,最后才进入 main 函数。每一步都有它的道理。
- 看门狗在启动阶段要优先处理,别让它成为绊脚石
- 低功耗唤醒后,不要假设任何外设还活着,全部重新初始化
- 看门狗和低功耗要协调好,避免睡眠期间被复位
- 利用启动阶段的“黄金窗口”,做复位原因分析和故障诊断
嗯,这些经验都是我用实际项目换来的。希望你能少走一些弯路。