(1)实验平台:普中STM32F103朱雀、玄武开发板
上一章我们介绍了独立看门狗 IWDG, 这一章我们来学习下窗口看门狗(以下简称 WWDG) 。 本章要实现的功能是: 使用窗口看门狗的中断来喂狗, 通过 D1、D2 指示灯提示程序运行状态。 学习本章可以参考《STM32F10x 中文参考手册》 -18窗口看门狗(WWDG) 章节。 本章分为如下几部分内容:
24.1 WWDG 介绍
24.1.1 WWDG 简介
24.1.2 WWDG 结构框图
24.2 WWDG 配置步骤
24.3 硬件设计
24.4 软件设计
24.4.1 WWDG 初始化函数
24.4.2 窗口看门狗中断函数
24.4.3 主函数
24.5 实验现象
课后作业
24.1 WWDG 介绍
24.1.1 WWDG 简介
上一章我们已经介绍了 IWDG, 知道它的工作原理就是一个 12 位递减计数器不断递减计数, 当减到 0 之前还未进行喂狗的话, 产生一个 MCU 复位。 窗口看门狗 WWDG 其实和独立看门狗类似, 它是一个 7 位递减计数器不断的往下递减计数,当减到一个固定值 0X40 时还不喂狗的话, 产生一个 MCU 复位, 这个值叫窗口的下限, 是固定的值, 不能改变。 这个和独立看门狗是类似的, 不同的是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位, 这个值叫窗口的上限, 上限值由用户独立设置。 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以刷新(喂狗) , 这也是窗口看门狗中“窗口” 两个字的含义。 窗口看门狗时序图如下图所示:
从图中可以看到, T[6:0]是窗口控制寄存器(WWDG_CR) 的低 7 位, W[6:0]是窗口配置寄存器(WWDG_CFR) 低 7 位。 T[6:0]就是窗口看门狗的计数器值, 而W[6:0]是窗口看门狗的上窗口, 下窗口是固定值 0X40。 当窗口看门狗的计数器在上窗口值之外或者低于下窗口值被刷新都会产生复位。
上窗口值(W[6:0]) 是由用户自己设定的, 根据实际要求来设计窗口值, 但是一定要确保窗口值大于 0X40, 否则窗口就不存在了。
窗口看门狗 WWDG 通常被用来监测, 由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。
24.1.2 WWDG 结构框图
要更好的理解窗口看门狗, 就需要了解它内部的结构, 如下图所示:
我们把 WWDG 结构框图分成 5 个子模块, 按照顺序依次进行简单介绍。
(1) 标号 1: WWDG 时钟
窗口看门狗(WWDG)的时钟来自 PCLK1, 即挂接在 APB1 总线上, 由 RCC 时钟控制器开启。 APB1 时钟最大为 36M。
(2) 标号 2: WDG 预分频器器
PCLK1 时钟并不是直接提供给窗口看门狗计数器时钟, 而是通过一个 WDG 预分频器分频后输入给计数器时钟。 我们可以操作配置寄存器 WWDG_CFR 的位 8:7 WDGTB[1:0]来设置分频因子, 分频因子可以为 0、 1、 2、 3。
分频后的计数器时钟为: CK_CNT= PCLK1/4096/(2^WDGTB), 除以 4096 是中文参考手册内公式规定, 没有为什么。 PCLK1 等于 APB1 时钟, WDGTB 为分频因子(0-3) , 2^WDGTB 大小就是 1、 2、 4、 8, 与库函数中的分频参数对应。 每经过一个计数器时钟, 计数器就减 1。
(3) 标号 3: 计数器
窗口看门狗的计数器是一个 7 位的递减计数器, 计数最大值为 0X7F, 其值存放在控制寄存器 WWDG_CR 中的 6:0 位, 即 T[6:0]。 当递减到 T6 位变成 0 时,即从 0X40 变为 0X3F 时候, 会产生看门狗复位。 这个值 0X40 是窗口看门狗能够递减到的最小值, 所以计数器的值只能在 0X40~0X7F 之间, 实际上用来计数的是 T[5:0]。 当递减计数器递减到 0X40 的时候, 还不会马上产生复位, 如果使能了提前唤醒中断, 窗口配置寄存器(WWDG_CFR) 位 9 EWI 置 1, 则产生提前唤醒中断, 也就是在快产生复位的前一段时间提醒我们, 需要进行喂狗了, 否则将复位。 我们通常都是在提前唤醒中断内向 WWDG_CR 重新写入计数器的值,来达到喂狗的目的。 需要注意的是: 在进入中断后, 必须在不大于 1 个窗口看门狗计数周期的时间(在 PCLK1 频率为 36M 且 WDGTB 为 0 的条件下, 该时间为 113us) 内重新写 WWDG_CR, 否则, 看门狗将产生复位!
如果不使用提前唤醒中断来喂狗, 我们要会计算窗口看门狗的超时时间, 计算公式如下:
Twwdg=(4096× 2^WDGTB× (T[5:0]+1)) /PCLK1;
其中:
Twwdg 为窗口看门狗的超时时间, 单位为 ms。
PCLK1 为 APB1 的时钟频率, 最大 36MHz。
WDGTB 为窗口看门狗的预分频系数。
T[5:0]为窗口看门狗的计数器低 6 位。
根据上面的公式, 假设 PCLK1=54Mhz, 那么可以得到最小-最大超时时间表,如下:
(4) 标号 4: 看门狗配置寄存器
我们知道窗口看门狗必须在窗口范围内进行喂狗才不会产生复位, 窗口中的下窗口是一个固定值 0X40, 上窗口值可以改变, 具体的由配置寄存器 WWDG_CFR的位 W[6:0]设置。 其值必须大于 0X40, 如果小于或者等于 0X40 就是失去了窗口的意义, 而且也不能大于计数器的最大值 0X7F。 窗口值具体要设置成多大,这个得根据我们需要监控的程序的运行时间来决定。 假如我们要监控的程序段 A运行的时间为 Ta, 当执行完这段程序之后就要进行喂狗, 如果在窗口时间内没有喂狗的话, 那程序就肯定是出问题了。 一般计数器的值 TR 设置成最大 0X7F,窗口值为 WW, 计数器减一个数的时间为 T, 那么时间: (TR-WW)*T 应该稍微大于 Ta 即可, 这样就能做到刚执行完程序段 A 之后喂狗, 起到监控的作用, 这样也就可以算出 WW 的值是多少。
(5) 标号 5: 系统复位信号
当计数器值超过配置寄存器内的上窗口设置值或者低于下窗口值, 并且WDGA 位置 1, 即开启窗口看门狗, 将产生一个系统复位信号, 促使系统复位。
由于篇幅限制, 本章并没有对相关寄存器进行介绍, 大家可以参考《STM32F10x 中文参考手册》 -18 窗口看门狗(WWDG) 章节, 里面有详细的讲解。如果看不懂的可以暂时放下, 因为我们使用的是库函数开发。
24.2 WWDG 配置步骤
接下来我们介绍下如何使用库函数对 WWDG 进行配置。 这个也是在编写程序中必须要了解的。 具体步骤如下: ( WWDG 相关库函数在 stm32f10x_wwdg.c 和stm32f10x_wwdg.h 文件中)
(1) 使能 WWDG 时钟
WWDG 不同于 IWDG, IWDG 有自己独立的 LSI 时钟, 所以不存在使能问题, 而WWDG 使用的是 APB1 时钟, 需要先使能时钟。 在库函数中实现函数如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);(2) 设置 WWDG 窗口值、 分频数
设置 WWDG 窗口值函数为:
void WWDG_SetWindowValue(uint8_t WindowValue);窗口值最大值为 0X7F, 最小不能低于 0X40, 否则就失去了窗口的意义。
设置 WWDG 分频数函数为:
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);分频系数可以为 WWDG_Prescaler_1、 WWDG_Prescaler_2、 WWDG_Prescaler_4、WWDG_Prescaler_8。
(3) 开启 WWDG 中断并分组
通常对窗口看门狗进行喂狗是在提前唤醒中断内操作, 所以需要打开 WWDG的中断功能, 并且配置对应的中断通道及分组。
中断分组及通道选择是在 NVIC_Init 初始化内完成, 这个在前面章节中都介绍过, 这里我们看下使能 WWDG 中断, 库函数如下:
WWDG_EnableIT();(4) 设置计数器初始值并使能 WWDG
库函数中提供了一个同时设置计数器初始值和使能 WWDG 的函数, 如下:
void WWDG_Enable(uint8_t Counter);注意计数器最大值不能大于 OX7F。
库函数还提供了一个独立设置计数器值的函数, 如下:
void WWDG_SetCounter(uint8_t Counter);(5) 编写 WWDG 中断服务函数
最后我们还需要编写一个 WWDG 中断服务函数, 通过中断函数进行喂狗。 WWDG中断服务函数名在 STM32F1 启动文件内就有, WWDG 中断函数名如下:
WWDG_IRQHandler在中断内要进行喂狗, 可以直接调用 WWDG_SetCounter()函数, 给它传递一个窗口值即可, 特别注意, 在中断内喂狗一定要快, 否则当看门狗计数器值减到0X3F 时将产生复位。 然后清除 WWDG 中断状态标志位 EWIF, 函数如下:
WWDG_ClearFlag(); //清除窗口看门狗状态标志通过以上几步配置后, 我们就可以正常使用窗口看门狗了, 我们需要在中断内快速喂狗, 否则系统即会重新启动。
24.3 硬件设计
由于 WWDG 是 STM32F1 内部资源, 因此本硬件电路非常简单, 只有 D1、 D2 指示灯连接, 对应的电路图在前面章节中都有介绍, 这里就不多说。 D1 指示灯用来提示系统是否被复位, D2 指示灯用来作为喂狗提示, 每进入中断喂狗 D2 指示灯状态翻转一次。
24.4 软件设计
本章所要实现的功能是: 系统开启时 DS0 指示灯点亮 500ms 时间, 然后熄灭。DS1 指示灯不断闪烁表示正在喂狗。 如果喂狗超时将重启系统, DS0 指示灯点亮500ms, 然后熄灭, 继续喂狗。 程序框架如下:
(1) 初始化 WWDG(使能 WWDG 时钟, 设置窗口及分频值, 使能中断等)
(2) 编写窗口看门狗中断函数
(3) 编写主函数
在前面介绍 WWDG 配置步骤时, 就已经讲解如何开启 WWDG、 设置窗口及分频值等。 下面我们打开“\4--实验程序\1--基础实验\16-窗口看门狗实验” 工程,在 APP 工程组中可以看到添加了 wwdg.c 文件, 在 StdPeriph_Driver 工程组中添加了 stm32f10x_wwdg.c 库文件。 窗口看门狗操作的库函数都放在stm32f10x_wwdg.c 和 stm32f10x_wwdg.h 文件中, 所以使用到窗口看门狗就必须加入 stm32f10x_wwdg.c 文件, 同时还要包含对应的头文件路径。
24.4.1 WWDG 初始化函数
要使用 WWDG, 我们必须先对它进行配置。 WWDG 初始化代码如下:
/******************************************************************************* * 函 数 名 : WWDG_Init * 函数功能 : WWDG初始化 窗口值是:0x5f 定时器计数值是:0x7f 预分频值是:8 窗口看门狗进入中断的频率计算公式是:PCLK1/(4096*2^pre) * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void WWDG_Init(void) { NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE); //开启窗口看门狗的时钟 WWDG_SetWindowValue(0x5f);//设置窗口值 WWDG_SetPrescaler(WWDG_Prescaler_8);//设置分频值 NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;//窗口中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器 WWDG_Enable(0x7f); //使能窗口看门狗并初始化计数器值 WWDG_ClearFlag(); //清除窗口看门狗状态标志(这一句必须加上,否则进入不了中断) WWDG_EnableIT(); //开启中断 }在 WWDG_Init()函数中, 首先使能 WWDG 时钟, 设置 WWDG 窗口值为 0X5F, 分频值为 WWDG_Prescaler_8, 然后设置中断分组并开启中断, 最后设置计数器值为 0X7F 并使能 WWDG。 这一过程在前面步骤介绍中已经提了。
24.4.2 窗口看门狗中断函数
初始化 WWDG 后, 中断就已经开启了, 当窗口看门狗计数器递减到 0X40 时,就会产生一次提前唤醒中断, 具体代码如下:
/******************************************************************************* * 函 数 名 : WWDG_IRQHandler * 函数功能 : WWDG中断函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void WWDG_IRQHandler(void) { WWDG_SetCounter(0x7f); //重新赋值 WWDG_ClearFlag(); //清除窗口看门狗状态标志 LED2=!LED2; }在中断内必须快速进行喂狗, 也就是重新对窗口看门狗计数器赋值。 然后清除中断状态标志, 这里我们使用一个 DS1 指示灯来提示喂狗, 如果喂狗 DS1 指示灯状态翻转一次。
24.4.3 主函数
编写好窗口看门狗初始化和中断函数后, 接下来就可以编写主函数了, 代码如下:
#include "system.h" #include "SysTick.h" #include "led.h" #include "usart.h" #include "wwdg.h" /******************************************************************************* * 函 数 名 : main * 函数功能 : 主函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ int main() { SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(115200); LED1=0; delay_ms(500); WWDG_Init(); while(1) { LED1=1; } }主函数实现的功能很简单, 首先调用之前编写好的硬件初始化函数, 包括SysTick 系统时钟, 中断分组, LED 初始化等。 然后让 DS0 指示灯点亮 500ms,再调用我们前面编写的 WWDG 初始化函数, 最后进入 while 循环, 关闭 DS0 指示灯。 在主函数内并没有看到喂狗操作, 这是因为我们使用窗口看门狗中断进行喂狗, 当计数器递减到 0X40 时, 进入中断喂狗, DS1 指示灯状态翻转一次, 如果喂狗失败, 将使系统复位, 那么 DS1 指示灯又会点亮 500ms 后熄灭。
24.5 实验现象
将工程程序编译后下载到开发板内, 可以看到DS0指示灯点亮500ms后熄灭。然后 DS1 指示灯不断闪烁, 表示正在喂狗。
课后作业
(1) 让窗口看门狗在中断内喂狗超时, 观察现象。 (温馨提示: 只需要在喂狗前加上一个短暂延时即可)