第9章 STM32F4窗口看门狗实验
第九章 STM32F4窗口看门狗简介
1. WWDG简介
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。除非递减计数器的值在 T6 位(WWDG->CR 的第六位)变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU 复位。在递减计数器达到窗口配置寄存器(WWDG->CFR)数值之前,如果 7 位的递减计数器数值(在控制寄存器中)被刷新, 那么也将产生一个 MCU 复位。这表明递减计数器需要在一个有限的时间窗口中被刷新。
- 功能:定期重载计数器,以防止系统发生故障。若未及时重载,WWDG会复位系统。
- 窗口模式:在特定的时间窗口内重载计数器,确保系统在正常状态下运行。
T[6:0]就是 WWDG_CR 的低七位, W[6:0]即是 WWDG->CFR 的低七位。 T[6:0]就是窗口看门狗的计数器,而 W[6:0]则是窗口看门狗的上窗口,下窗口值是固定的(0X40)。当窗口看门狗的计数器在上窗口值之外被刷新,或者低于下窗口值都会产生复位。
上窗口值(W[6:0]) 是由用户自己设定的,根据实际要求来设计窗口值,但是一定要确保窗口值大于 0X40,否则窗口就不存在了。
窗口看门狗的超时公式如下:
根据上面的公式,假设 Fpclk1=42Mhz,那么可以得到最小-最大超时时间表如表
2. WWDG寄存器介绍
2.1 控制寄存器WWFG_CR
寄存器位域说明
- T[6:0]: 计数器初始值(Counter value)
- 范围:0x00到0x7F,表示WWDG计数器的初始值。
- W[6:0]: 窗口值(Window value)
- 范围:0x00到0x7F,决定了重载计数器的时间窗口。
WWDG_CR寄存器结构
typedef struct
{
__IO uint32_t T;
__IO uint32_t W;
} WWDG_TypeDef;
#define WWDG_CR_T_SHIFT 0
#define WWDG_CR_W_SHIFT 11
寄存器设置示例
#include "stm32f4xx_hal.h"
void WWDG_Config(void) {
// 启用WWDG时钟
__HAL_RCC_WWDG_CLK_ENABLE();
// 设置计数器和窗口值
WWDG->CR |= (0x7F << WWDG_CR_T_SHIFT); // 设置计数器初始值为127
WWDG->CR |= (0x50 << WWDG_CR_W_SHIFT); // 设置窗口值为80
}
2.2 配置寄存器WWDF_CFR
该位中的 EWI 是提前唤醒中断,也就是在快要产生复位的前一段时间(T[6:0]=0X40) 来提醒我们,需要进行喂狗了,否则将复位!因此,我们一般用该位来设置中断,当窗口看门狗的计数器值减到 0X40 的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向 WWDG_CR 重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后, 必须在不大于 1 个窗口看门狗计数周期的时间(在 PCLK1 频率为 42M 且 WDGTB 为 0 的条件下,该时间为 97.52us)内重新写 WWDG_CR,否则,看门狗将产生复位!
2.3 状态寄存器WWDG_S
该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位 0 有效,其他都是保留位。当计数器值达到 40h 时,此位由硬件置 1。它必须通过软件写 0 来清除。对此位写 1 无效。 即使中断未被使能,在计数器的值达到 0X40 的时候, 此位也会被置1。
以下是一个示例,展示如何读取WWDG_SR
寄存器的状态:
#include "stm32f4xx_hal.h"
void Check_WWDG_Status(void) {
// 检查WWDG状态寄存器
if (WWDG->SR & WWDG_SR_EWIF) {
// 早期警告已触发,可以采取相应措施
// 例如:重载WWDG计数器
WWDG->CR |= (0x7F); // 重载计数器至最大值
}
}
在介绍完了窗口看门狗的寄存器之后,我们介绍要如何启用 STM32F4 的窗口看门狗。这里我们介绍 HAL 中用中断的方式来喂狗的方法
3. WWDG使用基本步骤
3.1 使能WWDG时钟
WWDG 不同于 IWDG, IWDG 有自己独立的 32Khz 时钟,不存在使能问题。而 WWDG使用的是 PCLK1 的时钟,需要先使能时钟。方法是:
__HAL_RCC_WWDG_CLK_ENABLE(); // 使能窗口看门狗时钟
3.2 设置窗口值,分频数和计数器初始值
在 HAL 库中,这三个值都是通过函数 HAL_WWDG_Init 来设置的。该函数声明如下:
HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);
该函数只有一个入口参数,就是 WWDG_HandleTypeDef 结构体类型指针变量。这里我们来看看 WWDG_HandleTypeDef 结构体定义:
typedef struct
{
WWDG_TypeDef *Instance;
WWDG_InitTypeDef Init;
HAL_LockTypeDef Lock;
__IO HAL_WWDG_StateTypeDef State;
}WWDG_HandleTypeDef;
该结构体和前面我们讲解的 WWDG_HandleTypeDef 类似,这里我们就主要讲解成员变量Init,它是 WWDG_InitTypeDef 结构体类型,该结构体定义如下:
typedef struct
{
uint32_t Prescaler; // 预分频系数
uint32_t Window; // 窗口值
uint32_t Counter; // 计数器值
}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.3 开启WWDG
HAL库中开启WWDG的函数有两个:
HAL_StatusTypeDef HAL_WWDG_Start(WWDG_HandleTypeDef *hwwdg);
HAL_StatusTypeDef HAL_WWDG_Start_IT(WWDG_HandleTypeDef *hwwdg);
函数 HAL_WWDG_Start 仅仅只是用来开启 WWDG,而函数 HAL_WWDG_Start_IT 除了启动 WWDG,还同时启动 WWDG 中断。
3.4 使能中断通道并配置优先级(如果开启了WWDG中断)
这一步相信大家已经非常熟悉了,我们这里仅仅列出两行实现代码,如下:
HAL_NVIC_SetPriority(WWDG_IRQn,2,3); // 抢占优先级 2,子优先级为 3
HAL_NVIC_EnableIRQ(WWDG_IRQn); // 使能窗口看门狗中断
这里大家要注意,跟串口一样,HAL 库同样为看门狗提供了MSP回调函 数HAL_WWDG_MspInit,一般情况下,步骤 1 和步骤 4 的步骤,是与 MCU 相关的,我们均放在该回调函数中。
3.5 编写中断服务函数
在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当窗口看门狗计数器值减到 0X3F 的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的 EWIF 位清空。
窗口看门狗中断服务函数为:
void WWDG_IRQHandler(void);
在 HAL 库中,喂狗函数为:
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg, uint32_t cnt);
WWDG 的喂狗操作实际就是往 CR 寄存器重写计数器值,这里的第二个入口函数就是重写的计数器的值。
3.6 重写窗口看门狗唤醒中断处理回调函数HAL_WWDG_WakeupCallback
跟串口和外部中断一样, 首先, HAL 库定义了一个中断处理共用函数HAL_WWDG_IRQHandler,我们在 WWDG 中断服务函数中会调用该函数。同时该函数内部,会经过一系列判断,最后调用回调函数 HAL_WWDG_WakeupCallback,所以提前唤醒中断逻辑我们一般些在回调函数 HAL_WWDG_WakeupCallback 中。 回调函数声明为:
void HAL_WWDG_WakeupCallback(WWDG_HandleTypeDef* hwwdg);
完成了以上 6 个步骤之后,我们就可以使用 STM32F4 的窗口看门狗了。这一章的实验,我们将通过 DS0 来指示 STM32F4 是否被复位了,如果被复位了就会点亮 300ms。 DS1 用来指示中断喂狗,每次中断喂狗翻转一次。
#include "stm32f4xx_hal.h"
// 延时函数
void Delay(uint32_t delay) {
HAL_Delay(delay);
}
// 初始化GPIO
void GPIO_Init(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// DS0 初始化
GPIO_InitStruct.Pin = GPIO_PIN_0; // PA0
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// DS1 初始化
GPIO_InitStruct.Pin = GPIO_PIN_1; // PA1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// WWDG 中断服务程序
void WWDG_IRQHandler(void)
{
// 清除早期警告标志
if(WWDG->SR & WWDG_SR_EWIF)
{
WWDG->SR = 0; // 清除标志
// 翻转DS1
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // PA1
}
}
// 主程序
int main(void)
{
HAL_Init();
SystemClock_Config(); // 配置系统时钟
GPIO_Init();
// 检查复位状态
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IOSRST))
{
// 如果被复位,点亮DS0
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // PA0
Delay(300); // 延时300ms
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 关闭DS0
}
// WWDG 配置
__HAL_RCC_WWDG_CLK_ENABLE();
WWDG->CFR = 0x7F; // 设置窗口值
WWDG->CR |= WWDG_CR_WDGA; // 启用WWDG
while (1)
{
// 主循环不断重载WWDG计数器
WWDG->CR = 0x7F; // 重载计数器
// 可以添加其他功能
}
}
2024.9.30 第一次修订,后期不再维护