STM32 有两个看门狗,一个是独立看门狗,一个是窗口看门狗。
- 我们知道独立看门狗的工作原理就是一个递减计数器不断的往下递减计数,当减到 0 之前如果没有喂狗的话,产生复位
- 窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数,当减到一个固定值 0X40 时还不喂狗的话,产生复位,这个值叫窗口的下限, 是固定的值,不能改变。 这个是跟独立看门狗类似的地方,不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。
- 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义
RLR 是重装载寄存器,用来设置独立看门狗的计数器的值。 TR 是窗口看门狗的计数器的值,由用户独立设置, WR 是窗口看门狗的上窗口值,由用户独立设置。
窗口看门狗和独立看门狗不同的地方:
- 窗口看门狗需要在在一个有限的时间窗口中被刷新,太晚了不行,太早了也不行,这也是窗口看门狗名称的由来,而独立看门狗多早都行,只是不能太晚
- 独立看门狗由 VDD 电压域供电,在停止模式和待机模式下仍能工作
- 独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效。窗口看门狗由从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来检测应用程序非正常的过迟或过早的操作
- 独立看门狗最适合应用于那些需要看门狗作为一个在主程序之外,能够完全独立工作,并且对时间精度要求较低的场合。 窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的应用程序
- 独立看门狗没有中断,窗口看门有有一个 EWI: 提前唤醒中断 (Early wakeup interrupt)
窗口看门狗 HAL 库相关源码和定义分布在文件 stm32f1xx_hal_wwdg.c 文件和头文件 stm32f1xx_hal_wwdg.h 中。
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。
- 除非递减计数器的值在 T6 位(WWDG->CR 的第六位)变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU 复位
- 在递减计数器达到窗口配置寄存器(WWDG->CFR)数值之前,如果 7 位的递减计数器数值(在控制寄存器中)被刷新, 那么也将产生一个 MCU 复位。这表明递减计数器需要在一个有限的时间窗口中被刷新。
理解窗口看门狗框图
本节内容主要来自野火《STM32 HAL 库开发实战指南—基于F103霸道_V2》
① 窗口看门狗时钟
窗口看门狗时钟来自 PCLK1, PCLK1 最大是 36M,由 RCC 时钟控制器开启。
②计数器时钟
计数器时钟由 CK 计时器时钟经过预分频器分频得到,分频系数由配置寄存器 CFR 的位 8:7 WDGTB[1:0]配置,可以是[0,1,2,3],其中 CK 计时器时钟=PCLK1/4096,除以 4096是手册规定的,没有为什么。所以计数器的时钟 CNT_CK=PCLK1/4096/(2^WDGTB),这就可以算出计数器减一个数的时间 T= 1/CNT_CK = Tpclk1 4096 (2^WDGTB)。
③、B、C:看门狗控制寄存器、激活位、计数器
窗口看门狗的计数器是一个递减计数器,共有 7 位,其值存在控制寄存器 CR 的位 6:0,即 T[6:0],当 7 个位全部为 1 时是 0X7F,这个是最大值,当递减到 T6 位变成 0 时,即从 0x40(0100 0000) 变为 0x3F(0011 0000) 时候,会产生看门狗复位。这个在框图中也能看出来,T6 连到了或门 B 上,也就是说当 T6 变成 0 时,会产生一个复位信号。 R
0x40 是看门狗能够递减到的最小值,所以计数器的值只能是: 0X40~0X7F 之间,实际上真正用来计数的是 T[5:0]。当递减计数器递减到 0X40 的时候,还不会马上产生复位,如果使能了提前唤醒中断: CFR 位 9 EWI 置 1,则产生提前唤醒中断,如果真进入了这个中断的话,就说明程序肯定是出问题了,那么在中断服务程序里面我们就需要做最重要的工作,比如保存重要数据,或者报警等,这个中断我们也叫它死前中断。
控制寄存器 CR 的位 7 为激活位 (Activation bit),此位由软件置‘1’,但仅能由硬件在复位后清‘0’,因为复位后清零,图中 C 是一个与门,当 WDGA 位为零时,是无法产生复位信号的,所以窗口看门狗在复位后总是被禁用的。同样的,因为 WDGA 只能由硬件清零,所以窗口看门狗开启后无法通过软件关闭。在 HAL_WWDG_Init
会置位 WDGA 来开启窗口看门狗。
④窗口值
我们知道窗口看门狗必须在计数器的值在一个范围内才可以喂狗,其中下窗口的值是固定的 0X40,上窗口的值可以改变,具体的由配置寄存器 CFR 的位 6:0 W[6:0]设置。其值必须大于 0X40,如果小于或者等于 0X40 就是失去了窗口的价值,而且也不能大于计数器的值,所以必须小于 0X7F。
那窗口值具体要设置成多大?这个得根据我们需要监控的程序的运行时间来决定。如果我们要监控的程序段 A 运行的时间为 Ta,当执行完这段程序之后就要进行喂狗,如果在窗口时间内没有喂狗的话,那程序就肯定是出问题了。一般计数器的值 TR 设置成最大 0X7F,窗口值为 WR,计数器减一个数的时间为 T,那么时间:
(TR-WR)*T 应该稍微大于 Ta 即可,这样就能做到刚执行完程序段 A 之后喂狗,起到监控的作用,这样也就可以算出 WR 的值是多少。
⑤计算看门狗超时时间
窗口看门狗的超时公式如下:
Twwdg=(4096× 2^WDGTB× (T[5:0]+1)) /Fpclk1;
其中:
• Twwdg: WWDG 超时时间(单位为 ms)
• Fpclk1: APB1 的时钟频率(单位为 Khz)
• WDGTB: WWDG 的预分频系数
• T[5:0]:窗口看门狗的计数器低 6 位
这个图来自数据手册,从图我们知道看门狗超时时间: Twwdg = Tpclk1 x 4096 x 2^wdgtb x (T[5:0] + 1) ms,当 PCLK1 = 36MHZ 时, WDGTB 取不同的值时有最小和最大的超时时间,那这个最小和最大的超时时间该怎么理解,又是怎么算出来的? 讲起来有点绕,这里我稍微讲解下 WDGTB=0 时是怎么算的。递减计数器有 7 位 T[6:0] ,当位 6 变为 0 的时候就会产生复位,实际上有效的计数位是 T[5:0],而且 T6 必须先设置为 1。
- 如果T[5:0]=0 时,递减计数器再减一次,就产生复位了,那这减一的时间就等于计数器的周期=1/CNT_CK = Tpclk1 4096 (2^WDGTB) = 1/36 4096 2^0 = 113.7us,这个就是最短的超时时间
- 如果 T[5:0]全部装满为 1,即 63,当他减到 0X40 变成 0X3F 时,所需的时间就是最大的超时时间=113.72^5=113.764=7.2768ms。
A:提前喂狗产生中断
A 是一个与门,有两个输入:
- 写入 WWDG_CR,这个操作就是喂狗
- 当 T6:0 > W6:0 比较结果 =1,其中 T6:0 为计数器值,W6:0 为用户自己设置的窗口上限值
通过 HAL 库函数来配置窗口看门狗的步骤
文字主要来自正点原子《STM32F1开发指南-HAL库版本_V1.0》
1. 使能 WWDG 时钟
WWDG 不同于 IWDG, IWDG 有自己独立的 40Khz 时钟,不存在使能问题。而 WWDG使用的是 PCLK1 的时钟,需要先使能时钟。 方法是:__HAL_RCC_WWDG_CLK_ENABLE(); //使能窗口看门狗时钟
2. 设置窗口值,分频数和计数器初始值
在 HAL 库中,这三个值都是通过函数 HAL_WWDG_Init
来设置的。该函数声明如下:HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);
该函数只有一个入口参数,就是 WWDG_HandleTypeDef 结构体类型指针变量:
typedef struct
{
WWDG_TypeDef *Instance;
WWDG_InitTypeDef Init;
}WWDG_HandleTypeDef;
成员变量 Init,它是 WWDG_InitTypeDef 结构体类型,该结构体定义如下:
typedef struct
{
uint32_t Prescaler; //预分频系数
uint32_t Window; //窗口值
uint32_t Counter; //计数器值
uint32_t EWIMode ; //是否启用 WWDG 早期唤醒中断
}WWDG_InitTypeDef;
该结构体有 3 三个成员变量,分别用来设置 WWDG 的预分频系数,窗口之以及计数器值。
函数 HAL_WWDG_Init
的使用范例如下:
WWDG_HandleTypeDef WWDG_Handler; //窗口看门狗句柄
WWDG_Handler.Instance=WWDG; //窗口看门狗
WWDG_Handler.Init.Prescaler=WWDG_PRESCALER_8;//设置分频系数为 8
WWDG_Handler.Init.Window=0X5F; //设置窗口值 0X5F
WWDG_Handler.Init.Counter=0x7F; //设置计数器值 0x7F
HAL_WWDG_Init(&WWDG_Handler); //初始化 WWDG
3. 使能中断通道并配置优先级(如果开启了 WWDG 中断)
这一步相信大家已经非常熟悉了,我们这里仅仅列出两行实现代码,如下:
HAL_NVIC_SetPriority(WWDG_IRQn,2,3); //抢占优先级 2,子优先级为 3
HAL_NVIC_EnableIRQ(WWDG_IRQn); //使能窗口看门狗中断
这里大家要注意,跟串口一样,HAL 库同样为看门狗提供了 MSP 回调函数 HAL_WWDG_MspInit
,一般情况下,步骤 1 和步骤 3 的步骤,是与 MCU 相关的,我们均放在该回调函数中。
4. 编写中断服务函数
在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当窗口看门狗计数器值减到 0X3F 的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的 EWIF 位清空。
窗口看门狗中断服务函数为:void WWDG_IRQHandler(void);
如何喂狗
在 HAL 库中,喂狗函数为:HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);
WWDG 的喂狗操作实际就是往 CR 寄存器重写计数器值。
5. 重写窗口看门狗唤醒中断处理回调函数 HAL_WWDG_WakeupCallback
跟串口和外部中断一样, 首先, HAL 库定义了一个中断处理共用函数 HAL_WWDG_IRQHandler,我们在 WWDG 中断服务函数中会调用该函数。同时该函数内部,会经过一系列判断,最后调用回调函数 HAL_WWDG_WakeupCallback
,所以提前唤醒中断逻辑我们一般写在回调函数 HAL_WWDG_WakeupCallback 中。
回调函数声明为:void HAL_WWDG_EarlyWakeupCallback (WWDG_HandleTypeDef* hwwdg);