文章目录
- 一、STM32F4 窗口看门狗简介
- 二、硬件设计
- 三、软件设计
- 四、实验现象
- 五、STM32CubeMX 配置 WWDG
在本章中,我们将使用窗口看门狗的 中断功能来喂狗,通过
DS0
和
DS1
提示程序的运行状态。
一、STM32F4 窗口看门狗简介
窗口看门狗(WWDG
)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。除非递减计数器的值在 T6
位(WWDG->CR
的第六位)变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU
复位。在递减计数器达到窗口配置寄存器(WWDG->CFR
)数值之前,如果 7 位的递减计数器数值(在控制寄存器中)被刷新, 那么也将产生一个 MCU
复位。这表明递减计数器需要在一个有限的时间窗口中被刷新。窗口看门狗工作示意图如下:
图中,T[6:0]
就是 WWDG_CR
的低七位,W[6:0]即是窗口配置寄存器WWDG->CFR
的低七位。T[6:0]
就是窗口看门狗的计数器,而 W[6:0]
则是窗口看门狗的上窗口,下窗口值是固定的(0X40
)。当窗口看门狗的计数器在上窗口值之外被刷新,或者低于下窗口值都会产生复位。
上窗口值(W[6:0]
)是由用户自己设定的,根据实际要求来设计窗口值,但是一定要确保窗口值大于 0X40
,否则窗口就不存在了。
窗口看门狗的超时公式如下:
T
w
w
d
g
=
(
4096
×
2
W
D
G
T
B
×
(
T
[
5
:
0
]
+
1
)
)
/
F
p
c
l
k
1
Twwdg=(4096×2^{WDGTB}×(T[5:0]+1)) /Fpclk1
Twwdg=(4096×2WDGTB×(T[5:0]+1))/Fpclk1
其中:
Twwdg
:WWDG
超时时间(单位为ms
)Fpclk1
:APB1
的时钟频率(单位为Khz
)WDGTB
:WWDG
的预分频系数T[5:0]
:窗口看门狗的计数器低 6 位
根据上面的公式,假设 Fpclk1=45Mhz
,那么可以得到最小-最大超时时间表如表所示:
接下来,我们介绍窗口看门狗的 3 个寄存器:
-
控制寄存器
WWDG_CR
该寄存器的各位描述如图所示:
可以看出,这里我们的WWDG_CR
只有低八位有效,T[6:0]
用来存储看门狗的计数器值,随时更新的,每个窗口看门狗计数周期(4096×2^ WDGTB
)减 1。当该计数器的值从0X40
变为0X3F
的时候,将产生看门狗复位。WDGA
位则是看门狗的激活位,该位由软件置 1,以启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。 -
配置寄存器
WWDG_CFR
该寄存器的各位描述如图所示:
该位中的EWI
是提前唤醒中断,也就是在快要产生复位的前一段时间(T[6:0]=0X40
)来提醒我们,需要进行喂狗了,否则将复位!因此,我们一般用该位来设置中断,当窗口看门狗的计数器值减到0X40
的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向WWDG_CR
重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于 1 个窗口看门狗计数周期的时间(在PCLK1
频率为42M
且WDGTB
为 0 的条件下,该时间为97.52us
)内重新写WWDG_CR
,否则,看门狗将产生复位! -
状态寄存器
WWDG_SR
该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位 0 有效,其他都是保留位。当计数器值达到40h
时,此位由硬件置 1。它必须通过软件写 0 来清除。对此位写 1 无效。即使中断未被使能,在计数器的值达到0X40
的时候,此位也会被置 1。
下面介绍如何启用 STM32F4
的窗口看门狗。这里我们介绍 HAL
中用中断的方式来喂狗的方法,窗口看门狗 HAL
库相关源码和定义分布在文件 stm32f4xx_hal_wwdg.c
文件和头文件 stm32f4xx_hal_wwdg.h
中。步骤如下:
-
使能 WWDG 时钟
WWDG
不同于IWDG
,IWDG
有自己独立的32Khz
时钟,不存在使能问题。而WWDG
使用的是PCLK1
的时钟,需要先使能时钟。方法是__HAL_RCC_WWDG_CLK_ENABLE(); //使能窗口看门狗时钟
-
设置窗口值,分频数和计数器初始值
在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;
- 成员变量
Init
,它是WWDG_InitTypeDef
结构体类型,该结构体定义如下:
该结构体有 3 三个成员变量,分别用来设置typedef struct { uint32_t Prescaler; //预分频系数 uint32_t Window; //窗口值 uint32_t Counter; //计数器值 }WWDG_InitTypeDef;
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
- 成员变量
-
开启 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
中断。 -
使能中断通道并配置优先级(如果开启了 WWDG 中断)
HAL_NVIC_SetPriority(WWDG_IRQn,2,3); //抢占优先级 2,子优先级为 3 HAL_NVIC_EnableIRQ(WWDG_IRQn); //使能窗口看门狗中断
这里大家要注意, 跟串口一样,
HAL
库同样为看门狗提供了MSP
回调函数HAL_WWDG_MspInit
,一般情况下,步骤 1 和步骤 4 的步骤,是与MCU
相关的,我们均放在该回调函数中。 -
编写中断服务函数
编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则,当窗口看门狗计数器值减到0X3F
的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的EWIF
位清空。
窗口看门狗中断服务函数为:void WWDG_IRQHandler(void);
在
HAL
库中,喂狗函数为:HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg, uint32_t cnt);
WWDG
的喂狗操作实际就是往CR
寄存器重写计数器值,这里的第二个入口函数就是重写的计数器的值。 -
重写窗口看门狗唤醒中断处理回调函数 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
用来指示中断喂狗,每次中断喂狗翻转一次。
二、硬件设计
本实验用到的硬件资源有:
- 指示灯
DS0
和DS1
- 窗口看门狗
其中指示灯前面介绍过了,窗口看门狗属于 STM32F429
的内部资源,只需要软件设置好即可正常工作。我们通过DS0
和DS1
来指示STM32F429
的复位情况和窗口看门狗的喂狗情况。
三、软件设计
我们直接复制“独立看门狗实验”的工程模板,将复制过来的模板文件夹重新命名为“6-窗口看门狗实验”。在HARDWARE->WWDG
文件夹下面新建wwdg.c
文件以及头文件 wwdg.h
。
打开wwdg.c
文件,代码如下:
#include "wwdg.h"
#include "led.h"
WWDG_HandleTypeDef WWDG_Handler; //窗口看门狗句柄
//保存WWDG计数器的设置值,默认为最大
u8 WWDG_CNT=0X7F;
//初始化窗口看门狗
//tr :T[6:0],计数器值
//wr :W[6:0],窗口值
//fprer:分频系数(WDGTB),仅最低2位有效
//Fwwdg=PCLK1/(4096*2^fprer). 一般PCLK1=45Mhz
void WWDG_Init(u8 tr,u8 wr,u32 fprer)
{
WWDG_Handler.Instance=WWDG;
WWDG_Handler.Init.Prescaler=fprer; //设置分频系数
WWDG_Handler.Init.Window=wr; //设置窗口值
WWDG_Handler.Init.Counter=tr; //设置计数器值
HAL_WWDG_Init(&WWDG_Handler); //初始化WWDG
HAL_WWDG_Start_IT(&WWDG_Handler); //开启窗口看门狗,并开启WWDG中断
}
//WWDG底层驱动,时钟配置,中断配置
//此函数会被HAL_WWDG_Init()调用
//hwwdg:窗口看门狗句柄
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
__HAL_RCC_WWDG_CLK_ENABLE(); //使能窗口看门狗时钟
HAL_NVIC_SetPriority(WWDG_IRQn,2,3); //抢占优先级2,子优先级为3
HAL_NVIC_EnableIRQ(WWDG_IRQn); //使能窗口看门狗中断
}
//窗口看门狗中断服务函数
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&WWDG_Handler);//调用WWDG共用中断处理函数
}
//中断服务函数处理过程
//此函数会被HAL_WWDG_IRQHandler()调用
void HAL_WWDG_WakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
HAL_WWDG_Refresh(&WWDG_Handler,WWDG_CNT);//更新窗口看门狗值
LED1=!LED1;
}
其头文件wwdg.h
为
#ifndef __WWDG_H
#define __WWDG_H
#include "sys.h"
void WWDG_Init(u8 tr,u8 wr,u32 fprer);
#endif
wwdg.c
文件一共包含四个函数。第一个函数 WWDG_Init()
实现的是前面讲解的步骤 1 和步骤 3,主要作用是调用函数 HAL_WWDG_Init
设置 WWDG
的分频系数,窗口值和计数器初始值,同时还调用HAL_WWDG_Start_IT
函数开启看门狗和使能看门狗中断。包括看门狗计数器的值和看门狗比较值等。第二个函数 HAL_WWDG_MspInit
是 WWDG
的 MSP
回调函数,该函数主要作用是使能 WWDG
时钟,以及设置 NVIC
,实现的是前面讲解的步骤 2 和 4。第三个函数 WWDG_IRQHandler
也就是中断服务函数,该函数在前面步骤 5 有讲解,一般情况下,在该函数内部会调用中断共用处理函数 HAL_WWDG_IRQHandler
。第四个函数HAL_WWDG_WakeupCallback
是提前唤醒中断回调函数,该函数内部我们主要编写了喂狗操作以及LED1
翻转。注意到这里有个全局变量WWDG_CNT
,该变量用来保存最初设置WWDG_CR
计数器的值。在后续的中断服务函数里面,就又通过 HAL_WWDG_Refresh
函数把该数值放回到 WWDG_CR
上。
主函数main.c
代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "wwdg.h"
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化USART
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
LED0=0; //点亮LED0
delay_ms(300); //延时300ms再初始化看门狗,LED0的变化"可见"
WWDG_Init(0X7F,0X5F,WWDG_PRESCALER_8); //计数器值为7F,窗口寄存器为5F,分频数为8
while(1)
{
LED0=1; //熄灭LED灯
}
}
该函数通过 LED0(DS0)
来指示是否正在初始化。而 LED1(DS1)
用来指示是否发生了中断。我们先让 LED0
亮 300ms,然后关闭以用于判断是否有复位发生了。在初始化 WWDG
之后,我们回到死循环,关闭 LED1
,并等待看门狗中断的触发/复位。
四、实验现象
使用 USB
线将开发板和电脑连接成功后(电脑能识别开发板上 CH340
串口),把编译后产生的.hex
文件烧入到芯片内。可以看到 DS0
亮一下之后熄灭,紧接着 DS1
开始不停的闪烁。
五、STM32CubeMX 配置 WWDG
首先我们看看使能 WWDG
的方法,勾选上 Activated
选项即可使能 WWDG
。操作方法如下图所示:
接下来配置 WWDG
的三个参数,进入 Configuration->WWDG
界面,如下图所示:
第一个参数是配置分频系数,这里我们配置为 8。第二个参数是配置窗口值,第三个参数是配置计数器初始值。配置好之后,因为我们要开启提前唤醒中断,所以这里我们要使能中断并配置 NVIC
中断优先级。进入 Configuration->NVIC
界面。配置方法如下图所示:
配置完成之后直接生成工程源码。在 main.c
文件中生成的 MX_WWDG_Init
函数和本实验的 WWDG_Init
函数实现功能类似。
/* WWDG init function */
void MX_WWDG_Init(void)
{
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
hwwdg.Init.Window = 0x5F;
hwwdg.Init.Counter = 0x7F;
HAL_WWDG_Init(&hwwdg);
}
在 stm32f4xx_it.c
中生成的中断服务函数和我们实验一致。
/**
* @brief This function handles Window watchdog interrupt.
*/
void WWDG_IRQHandler(void)
{
/* USER CODE BEGIN WWDG_IRQn 0 */
/* USER CODE END WWDG_IRQn 0 */
HAL_WWDG_IRQHandler(&hwwdg);
/* USER CODE BEGIN WWDG_IRQn 1 */
/* USER CODE END WWDG_IRQn 1 */
}
在 stm32f4xx_hal_msp.c
文件中生成的 MSP 回调函数内容核我们实验内容一致。
void HAL_WWDG_MspInit(WWDG_HandleTypeDef* hwwdg)
{
if(hwwdg->Instance==WWDG)
{
/* USER CODE BEGIN WWDG_MspInit 0 */
/* USER CODE END WWDG_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_WWDG_CLK_ENABLE();
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3);
HAL_NVIC_EnableIRQ(WWDG_IRQn);
/* USER CODE BEGIN WWDG_MspInit 1 */
/* USER CODE END WWDG_MspInit 1 */
}
}