1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
第十九章 窗口门狗(WWDG)实验
本章我们学习如何使用STM32F1的另外一个看门狗,窗口看门狗(以下简称WWDG)。我们将使用窗口看门狗的中断功能来喂狗,通过LED0和LED1提示程序的运行状态。
本章分为如下几个小节:
19.1 WWDG简介
19.2 硬件设计
19.3 程序设计
19.4 下载验证
19.1 WWDG简介
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。窗口看门狗跟独立看门狗一样,也是一个递减计数器,不同的是它们的复位条件不一样。窗口看门狗产生复位信号有两个条件:
1)当递减计数器的数值从0x40减到0x3F时(T6位跳变到0)。
2)当喂狗的时候如果计数器的值大于W[6:0]时,此数值在WWDG_CFR寄存器定义。
上述的两个条件详细解释是,当计数器的值减到0x40时还不喂狗的话,到下一个计数就会产生复位,这个值称为窗口的下限值,是固定的值,不能改变。这个跟独立看门狗类似,不同的是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值W[6:0]由用户设置。窗口看门狗计数器的上限值和下限值就是窗口的含义,喂狗也必须在窗口之内,否则就会复位。
19.1.1 WWDG框图
下面先来认识WWDG的逻辑结构,可以帮助我们快速认识窗口看门狗的工作原理,如图19.1.1.1所示。
图19.1.1.1 WWDG逻辑框图
WWDG有一个来自RCC的PCLK1输入时钟,经过一个4096的分频器(4096分频在设计时已经设定死了,图中并没有给出来,但我们可以通过查看寄存器WWDG_CFR的WDGTB位的描述知道),再经过一个分频系数可选(1、2、4、8)的可编程预分频器提供时钟给一个7位递减计数器,这里有两个输出信号。
结合寄存器分析窗口看门狗的上限值和下限值。W[6:0] 是WWDG_CFR寄存器的低7位,用于与递减计数器T[6:0]比较的窗口值,也就是我们说的上限值,由用户设置。0x40就是下限值,递减计数器达到这个值就会产生复位。T6位就是WWDG_CR寄存器的位6,即递减计数器T[6:0]的最高位。他们的关系可以用图19.1.1.2来说明:
图19.1.1.2 窗口看门狗工作示意图
图19.1.1.2可以看出,递减计数器的值递减过程中,当T[6:0]>W[6:0]是不允许刷新T[6:0]的值,即不允许喂狗,否则会产生复位。只有在W[6:0]<T[6:0]< 0x3F这个时间可以喂狗,这就是喂狗的窗口时间。当T[6:0]=0x3F,即T6位为0这一刻,也会产生复位。
上限值W[6:0]是由用户自己设置,但是一定要确保大于0x40,否则就不存在上图的窗口了,下限值0x40是固定的,不可修改。
知道了窗口看门狗的工作原理,下面学习如何计算窗口看门狗的超时公式:
TWWDG =(4096×2^WDGTB×(T[5:0] + 1))/ FPCLK1
其中:
TWWDG:WWDG超时时间(单位为ms)
FPCLK1: APB1的时钟频率(单位为 Khz)
2^WDGTB:是WWDG_CFR寄存器设置的预分频系数值
T[5:0]:窗口看门狗的计数器低6位的值
根据以上公式,假设 FPCLK1=36Mhz,那么可以得到最小-最大超时时间表如下表所示:
19.1.2 WWDG寄存器
WWDG只有3个寄存器,具体如下:
控制寄存器(WWDG_CR)
窗口看门狗的控制寄存器描述如图19.1.2.1所示:
图19.1.2.1 WWDG_CR寄存器
该寄存器只有低八位有效,其中T[6:0]用来存储看门狗的计数器的值,随时更新的,每隔(4096×2^ WDGTB[2:0])PCLK个周期减1。当该计数器的值从0x40变为0x3F的时候,将产生看门狗复位。
WDGA位则是看门狗的激活位,该位由软件置1,启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。
配置寄存器(WWDG_CFR)
配置寄存器描述如图19.1.2.2所示:
图19.1.2.2 WWDG_CFR寄存器
该寄存器中的EWI位是提前唤醒中断,如果该位置1,当递减计数器等于0x40时产生提前唤醒中断,我们就可以及时喂狗以避免WWDG复位。因此,我们一般都会用该位来设置中断,当窗口看门狗的计数器值减到0X40的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向WWDG_CR重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于1个窗口看门狗计数周期的时间(在pclk1频率为36M且WDGTB为0的条件下,该时间为113us)内重新写WWDG_CR,否则,看门狗将产生复位!
状态寄存器(WWDG_SR)
该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位0有效,其他都是保留位。当计数器值达到0x40时,此位由硬件置1。它必须通过软件写0来清除。对此位写1无效。即使中断未被使能,在计数器的值达到0x40的时候,此位也会被置1。
19.2 硬件设计
- 例程功能
先点亮LED0延时300ms后,初始化窗口看门狗,进入死循环,关闭LED0。然后等待窗口看门狗中断的到来,在中断里面,喂狗,并执行LED1的翻转操作。我们将通过LED0来指示STM32F1是否被复位了,如果被复位了就会点亮300ms。LED1用来指示中断喂狗,每次中断喂狗翻转一次。 - 硬件资源
1)LED灯
LED0 – PB5
LED1 – PE5
2)窗口看门狗 - 原理图
窗口看门狗属于STM32F103的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LED1来指示STM32F103的复位情况和窗口看门狗的喂狗情况。
19.3 程序设计
19.3.1 WWDG的HAL库驱动
WWDG在HAL库中的驱动代码在stm32f1xx_hal_wwdg.c文件(及其头文件)中。 - HAL_WWDG_Init函数
IWDG的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);
函数描述:
用于初始化WWDG。
函数形参:
形参1是WWDG句柄,WWDG_HandleTypeDef结构体类型,其定义如下:
typedef struct
{
WWDG_TypeDef Instance; / WWDG寄存器基地址 /
WWDG_InitTypeDef Init; / WWDG初始化参数 */
}WWDG_HandleTypeDef;
1)Instance:指向WWDG寄存器基地址。
2)Init:WWDG初始化结构体,用于配置计数器的相关参数。
WWDG_InitTypeDef这个结构体类型定义如下:
typedef struct
{
uint32_t Prescaler; /* 预分频系数 */
uint32_t Window; /* 窗口值 */
uint32_t Counter; /* 计数器值 */
uint32_t EWIMode; /* 提前唤醒中断使能 */
} WWDG_InitTypeDef;
1)Prescaler:预分频系数,WWDG_PRESCALER_1\ WWDG_PRESCALER_2\
WWDG_PRESCALER_4\ WWDG_PRESCALER_8四个值,分别表示1\2\4\8分频。
2)Window:窗口值,即上限值。
3)Counter:计数器值,用于保存要设置计数器的值。
4)EWIMode:提前唤醒中断使能。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
2. HAL_WWDG_Refresh函数
HAL_WWDG_Refresh函数是窗口看门狗的喂狗函数。其声明如下:
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);
函数描述:
该函数实际就是往CR寄存器重写Counte这个预先保存的计数器值。
函数形参:
形参1是WWDG_HandleTypeDef结构体指针类型的WWDG句柄。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
窗口看门狗配置步骤
1) 使能WWDG时钟
WWDG不同于IWDG,IWDG有自己独立的40Khz时钟。而WWDG使用的是PCLK1的时钟,需要先使能时钟。方法是:
__HAL_RCC_WWDG_CLK_ENABLE();
2) 设置窗口值,分频数和计数器初始值
在HAL库中,这三个值都是通过函数HAL_WWDG_Init来设置的,详见本例程源码。
3) 开启WWDG
通过设置WWDG_CR寄存器的WDGA(bit7)位为1来实现开启窗口看门狗,同样是在HAL_WWDG_Init函数里面实现。
4) 使能中断通道并配置优先级(如果开启了WWDG中断)
WWDG的中断也是通过:HAL_NVIC_EnableIRQ函数使能,通过HAL_NVIC_SetPriority函数设置优先级。
HAL库同样为看门狗提供了MSP回调函数HAL_WWDG_MspInit,一般情况下,步骤1和步骤4的步骤,我们均放在该回调函数中。
5) 编写中断服务函数
在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当窗口看门狗计数器值减到0X3F的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的EWIF位清空。
窗口看门狗中断服务函数为:WWDG_IRQHandler,喂狗函数为:HAL_WWDG_Refresh。
6) 重写窗口看门狗唤醒中断处理回调函数HAL_WWDG_EarlyWakeupCallback
HAL库定义了一个WWDG中断处理共用函数HAL_WWDG_IRQHandler,我们在WWDG中断服务函数中会调用该函数。同时该函数会调用回调函数HAL_WWDG_EarlyWakeupCallback,提前唤醒中断逻辑(喂狗、闪灯)我们写在回调函数HAL_WWDG_EarlyWakeupCallback中。
19.3.2 程序流程图
本实验利用窗口看门狗的特性,配置一个合适的窗口时间,并开启了提前唤醒中断,如果程序未在合适的时间喂狗,则会触发窗口看门狗中断。我们在进入中断后需要第一时间喂狗,否则系统将会复位,并用LED1的翻转来表示一次未及时喂狗的事件。
下面的流程图表示了main函数必要的编程步骤。
图19.3.2.1 窗口看门狗实验程序流程图
19.3.3 程序解析
- WWDG驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。窗口看门狗(WWDG)驱动源码包括两个文件:wdg.c和wdg.h。
wdg.h头文件只有函数的声明,就不解释了。下面我们直接解析wdg.c的程序,先看WWDG的初始化函数,其定义如下:
/**
* @brief 初始化窗口看门狗
* @param tr: T[6:0],计数器值
* @param tw: W[6:0],窗口值
* @param fprer: 分频系数(WDGTB),范围:WWDG_PRESCALER_1~WWDG_PRESCALER_8,
* 表示2^WDGTB分频, Fwwdg=PCLK1/(4096*2^fprer). 一般PCLK1=36Mhz
* @retval 无
*/
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
{
wwdg_handler.Instance = WWDG;
wwdg_handler.Init.Prescaler = fprer; /* 设置分频系数 */
wwdg_handler.Init.Window = wr; /* 设置窗口值 */
wwdg_handler.Init.Counter = tr; /* 设置计数器值 */
wwdg_handler.Init.EWIMode = WWDG_EWI_ENABLE; /* 使能窗口看门狗提前唤醒中断 */
HAL_WWDG_Init(&wwdg_handler); /* 初始化WWDG */
}
WWDG_Init是独立看门狗初始化函数,主要设置预分频数、窗口值和计数器的值,以及选择是否使能窗口看门狗提前唤醒中断。
因为用到中断,我们用HAL_WWDG_MspInit函数来编写窗口看门狗中断的初始化代码。当然大家也可以HAL_WWDG_MspInit函数的代码放到wwdg_init函数里面。这个初始化框架就是HAL库的特点。
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
__HAL_RCC_WWDG1_CLK_ENABLE(); /* 使能窗口看门狗时钟 */
HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3); /* 抢占优先级2,子优先级为3 */
HAL_NVIC_EnableIRQ(WWDG_IRQn); /* 使能窗口看门狗中断 */
}
HAL_WWDG_MspInit函数会被HAL_WWDG_Init函数调用。该函数使能窗口看门狗的时钟,并设置窗口看门狗中断的抢占优先级为2,响应优先级为3。
/**
* @brief 窗口看门狗中断服务程序
* @param 无
* @retval 无
*/
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&g_wwdg_handle);
}
WWDG_IRQHandler函数是窗口看门狗中断服务函数,而这个函数实际上就是调用HAL库的中断处理函数HAL_WWDG_IRQHandler。逻辑程序在下面的这个回调函数中:
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
HAL_WWDG_Refresh(&g_wwdg_handle);/* 更新窗口看门狗值 */
LED1_TOGGLE(); /* LED1闪烁 */
}
在回调函数内部调用HAL_WWDG_Refresh函数喂狗,并翻转LED1。
2. main.c代码
在main.c里面编写如下代码:
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
LED0(0); /* 点亮LED0 红灯 */
delay_ms(300); /* 延时300ms再初始化看门狗,LED0的变化"可见" */
/* 计数器值为7f,窗口寄存器为5f,分频数为8 */
wwdg_init(0X7F, 0X5F, WWDG_PRESCALER_8);
while (1)
{
LED0(1); /* 关闭红灯 */
}
}
在main函数里,先初始化系统和用户的外设代码,然后先点亮LED0,延时300ms后,初始化窗口看门狗,进入死循环,关闭LED0。
调用wwdg_init(0X7F,0X5F,WWDG_PRESCALER_8)这个语句,就设置计数器值为7f,窗口寄存器为5f,分频数为8,然后可由前面的公式得到窗口上限时间Twwdg=4096×8×(0x7F-0x5F)/36MHz=29.12ms,窗口下限时间Twwdg=4096×8×(0x7F-0x3F)/36MHz=58.25ms,即喂狗的窗口区间为29.12~58.25ms。我们在程序的其它地方没有喂狗,所以程序会在58.25ms左右进入死前中断,我们在中断中喂狗一次,并翻转LED1。
19.4 下载验证
下载代码后,可以看到LED0亮了一下就熄灭,紧接着LED1开始不停的闪烁。可以接入示波器测试得每秒钟闪烁17次左右,说明程序在中断不停的喂狗,和我们预期的一致。