在上一章《PWR电源控制》中,我们学习了 STM32 的低功耗模式,知道了如何通过待机、停机、睡眠等方式让芯片在不需要全速运行时降低功耗。但低功耗也带来了一个新的问题:一旦程序在运行过程中跑飞、死循环或者受到干扰而卡住,系统可能长时间无法自行恢复,此时就需要一个"自动报警并复位"的机制来兜底。
这一章我们就来学习 STM32 的看门狗(WDG,Watchdog)。看门狗本质上是一个递减计数器,程序正常运行时需要定期"喂狗"重装载计数器;一旦程序异常导致未能按时喂狗,计数器减到阈值就会触发复位,让系统从异常状态中自动恢复。STM32 提供了两种看门狗:由独立 LSI 时钟驱动的独立看门狗(IWDG) ,以及由 APB1 时钟驱动、带窗口约束的窗口看门狗(WWDG)。
本文将从待机模式讲起,依次介绍 IWDG 与 WWDG 的框图、键寄存器、超时时间计算方式,并通过两个完整实例演示它们的开启流程与代码实现,最后对两者进行对比总结,帮助你在实际项目中选对、用好看门狗。
如果觉得有收获可以为我的文章点点小赞,这将激励我创作更好的内容!谢谢!
一、WDG看门狗
1.1 待机模式
执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行
整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
1.2 IWDG框图

1.3 IWDG键寄存器
键寄存器本质上是控制寄存器,用于控制硬件电路的工作
在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率

1.4 IWDG超时时间
超时时间:T IWDG = T LSI × PR预分频系数 × (RL + 1)
其中:T LSI = 1 / F LSI

1.5 WWDG框图

1.6 WWDG工作特性
递减计数器T6:0的值小于0x40时,WWDG产生复位
递减计数器T6:0在窗口W6:0外被重新装载时,WWDG产生复位
递减计数器T6:0等于0x40时可以产生早期唤醒中断(EWI),用于重装载计数器以避免WWDG复位
定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位
1.7 WWDG工作特性

1.8 WWDG超时时间
超时时间:
T WWDG = T PCLK1 × 4096 × WDGTB预分频系数 × (T5:0 + 1)
窗口时间:
T WIN = T PCLK1 × 4096 × WDGTB预分频系数 × (T5:0 - W5:0)
其中:T PCLK1 = 1 / F PCLK1

1.9 IWDG和WWDG对比

1.10 实例一 14.1.1 独立看门狗
独立看门狗的开启流程
第一步,开启时钟 , 只有LSI时钟开启,独立看门狗才能运行 ,所以初始化独立看门狗之前,LSI必须得开启;
第二步,要先往IWDG的键寄存器写入键值0x5555,解除写保护;
第三步,写入预分频器和重装寄存器的重装值;
第四步,往IWDG的键寄存器写入键值0xCCCC,启动独立看门狗;
最后,记得在主循环里,不断往IWDG的键寄存器写入键值0xAAAA,不断喂狗。
可能用到的 IWDG 库函数:
//写使能控制
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
//写预分频器
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
//写重装值
void IWDG_SetReload(uint16_t Reload);
//重新装载寄存器(喂狗)
void IWDG_ReloadCounter(void);
//启动独立看门狗
void IWDG_Enable(void);
//获取标志位状态
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);
可能用到的 RCC 库函数:
//RCC_GetFlagStatus查看标志位,查看是看门狗导致的复位还是上电或复位键导致的复位
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
//RCC_ClearFlag清除标志位
void RCC_ClearFlag(void);
main.c ↓
-----------------------------
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"IWDG TEST");
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET )
{
OLED_ShowString(2,1,"IWDGRST");
Delay_ms(500);
OLED_ShowString(2,1," ");
Delay_ms(100);
RCC_ClearFlag();
}
else
{
OLED_ShowString(3,1,"RST");
Delay_ms(500);
OLED_ShowString(3,1," ");
Delay_ms(100);
}
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_16);
IWDG_SetReload( 2500 -1 ); //喂狗时间应该小于1000ms
IWDG_ReloadCounter();
IWDG_Enable();
while(1)
{
Key_GetNum();
IWDG_ReloadCounter();
OLED_ShowString(4,1,"FEED");
Delay_ms(200);
OLED_ShowString(4,1," ");
Delay_ms(600);
}
}
-----------------------------

1.11 实例二 14.2.1 窗口看门狗
窗口看门狗的开启流程
第一步,开启窗口看门狗APB1的时钟;
第二步,配置预分频和窗口值等寄存器,窗口看门狗的各个寄存器是没有写保护的;
第三步,写入控制寄存器CR,设置看门狗使能位、计数器溢出标志位和计数器有效位;
最后,在运行过程中,我们不断向计数器写入重装值,进行喂狗操作。
可能用到的 WWDG 库函数:
//恢复缺省
void WWDG_DeInit(void);
//写入预分频器
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
//写入窗口值
void WWDG_SetWindowValue(uint8_t WindowValue);
//使能中断
void WWDG_EnableIT(void);
//写入计数器
void WWDG_SetCounter(uint8_t Counter);
//使能窗口看门狗
void WWDG_Enable(uint8_t Counter);
//获取标志位、清除标志位
FlagStatus WWDG_GetFlagStatus(void);
void WWDG_ClearFlag(void);
main.c ↓
-----------------------------
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"WWDG TEST");
if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET )
{
OLED_ShowString(2,1,"WWDGRST");
Delay_ms(500);
OLED_ShowString(2,1," ");
Delay_ms(100);
RCC_ClearFlag();
}
else
{
OLED_ShowString(3,1,"RST");
Delay_ms(500);
OLED_ShowString(3,1," ");
Delay_ms(100);
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
WWDG_SetPrescaler(WWDG_Prescaler_8);
WWDG_SetWindowValue(0x40 | 21); //有20ms时间喂狗,正确喂狗时间为30~50ms
WWDG_Enable( 0x40 | (55-1) ); //50ms
while(1)
{
Key_GetNum();
OLED_ShowString(4,1,"FEED");
Delay_ms(20);
OLED_ShowString(4,1," ");
Delay_ms(20);
WWDG_SetCounter( 0x40 | (55-1) );
}
}
-----------------------------

二、总结
本文围绕 STM32 的看门狗(WDG)展开,先介绍了待机模式的基本概念,再分别讲解了独立看门狗(IWDG)和窗口看门狗(WWDG)的框图、寄存器、超时时间计算方式,最后通过两个实例演示了它们的开启流程与代码实现。核心要点可以归纳为以下几点:
- 看门狗的作用:看门狗本质上是一个递减计数器,程序正常运行时需要定期"喂狗"重装载计数器;一旦程序跑飞或死循环导致未能按时喂狗,计数器减到阈值就会触发复位,让系统从异常中自动恢复。
- IWDG 的特点:由独立的 LSI 时钟驱动,即使主时钟故障也能继续工作,可靠性高;超时时间由 TLSI、预分频系数 PR 和重装值 RL 共同决定,即 TIWDG = TLSI × PR × (RL + 1)。
- WWDG 的特点:由 APB1 时钟(PCLK1)驱动,除了要求"不能喂太晚",还要求"不能喂太早",必须在窗口时间内喂狗,因此不仅能检测程序卡死,还能检测程序跑飞后异常快速执行的情况,适合对时序要求更严格的场景。
- 两者的选择:IWDG 独立时钟、配置简单、超时时间较长,适合作为系统级兜底复位;WWDG 精度更高、带窗口约束,适合监控对时间敏感的周期性任务。
- 使用要点:IWDG 需要先写 0x5555 解除写保护,再配置预分频和重装值,写 0xCCCC 启动,主循环中写 0xAAAA 喂狗;WWDG 则需先开启 APB1 时钟,配置预分频与窗口值,再写 CR 使能,运行中向计数器写入重装值完成喂狗。
- 调试技巧 :可通过
RCC_GetFlagStatus判断复位来源(看门狗复位还是上电/复位键复位),并用RCC_ClearFlag清除标志位,便于定位问题。
总的来说,看门狗是提高嵌入式系统可靠性的重要手段。实际项目中应根据系统的时钟结构、任务周期和可靠性要求合理选择 IWDG 或 WWDG,并注意喂狗时间要留有余量,避免因喂狗过晚或过早导致误复位。
都看到这了点点赞吧!求求了!