第二十四章 WWDG——窗口看门狗
目录
本章参考资料:《W55MH32参考手册》WWDG章节。
学习本章时,配合《W55MH32参考手册》WWDG章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。
1 WWDG简介
W55MH32有两个看门狗,一个是独立看门狗,一个是窗口看门狗。我们知道独立看门狗的工作原理就是一个递减计数器不断的往下递减计数, 当减到0之前如果没有喂狗的话,产生复位。窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数, 当减到一个固定值0X40时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。这个是跟独立看门狗类似的地方, 不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义。
RLR是重装载寄存器,用来设置独立看门狗的计数器的值。TR是窗口看门狗的计数器的值,由用户独立设置,WR是窗口看门狗的上窗口值,由用户独立设置。
2 WWDG功能框图剖析
WWDG功能框图如下:
2.1 窗口看门狗时钟
窗口看门狗时钟来自PCLK1,PCLK1最大是108M,由RCC时钟控制器开启。
2.2 计数器时钟
计数器时钟由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)。
2.3 计数器
窗口看门狗的计数器是一个递减计数器,共有7位,其值存在控制寄存器CR的位6:0,即T[6:0],当7个位全部为1时是0X7F, 这个是最大值,当递减到T6位变成0时,即从0X40变为0X3F时候,会产生看门狗复位。这个值0X40是看门狗能够递减到的最小值, 所以计数器的值只能是:0X40~0X7F之间,实际上真正用来计数的是T[5:0]。当递减计数器递减到0X40的时候,还不会马上产生复位, 如果使能了提前唤醒中断:CFR位9EWI置1,则产生提前唤醒中断,如果真进入了这个中断的话,就说明程序肯定是出问题了, 那么在中断服务程序里面我们就需要做最重要的工作,比如保存重要数据,或者报警等,这个中断我们也叫它死前中断。
2.4 窗口值
我们知道窗口看门狗必须在计数器的值在一个范围内才可以喂狗,其中下窗口的值是固定的0X40,上窗口的值可以改变, 具体的由配置寄存器CFR的位6:0 W[6:0]设置。其值必须大于0X40,如果小于或者等于0X40就是失去了窗口的价值,而且也不能大于计数器的值, 所以必须得小于0X7F。那窗口值具体要设置成多大?这个得根据我们需要监控的程序的运行时间来决定。如果我们要监控的程序段A运行的时间为Ta, 当执行完这段程序之后就要进行喂狗,如果在窗口时间内没有喂狗的话,那程序就肯定是出问题了。一般计数器的值TR设置成最大0X7F,窗口值为WR, 计数器减一个数的时间为T,那么时间:(TR-WR)*T应该稍微小于Ta即可,这样就能做到刚执行完程序段A之后喂狗,起到监控的作用,这样也就可以算出WR的值是多少。
2.5 计算看门狗超时时间
这个图来自数据手册,从图我们知道看门狗超时时间: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.7*2^5=113.7*64=7.2768ms。 同理,当WDGTB等于1/2/3时,代入公式即可。
3 WWDG使用方法
WWDG一般被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。比如一个程序段正常运行的时间是50ms, 在运行完这个段程序之后紧接着进行喂狗,如果在规定的时间窗口内还没有喂狗,那就说明我们监控的程序出故障了,跑飞了,那么就会产生系统复位,让程序重新运行。
4 WWDG的中断测试
4.1 代码解析
主要用于进行窗口看门狗(WWDG)的中断测试。以下是对代码各部分的详细解释:
- 头文件包含
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "delay.h"
#include "w55mh32.h"
stdlib.h、string.h 和 stdio.h 是标准 C 库头文件,分别提供通用工具函数、字符串操作函数和标准输入输出功能。
delay.h 是自定义头文件,可能用于实现延时功能。
w55mh32.h 是头文件。
2. 全局变量定义
USART_TypeDef *USART_TEST = USART1;
定义了一个指向 USART_TypeDef 类型的指针 USART_TEST,并初始化为 USART1,后续串口操作将使用 USART1。
3. 函数声明
void UART_Configuration(uint32_t bound);
uint8_t GetCmd(void);
void NVIC_Configuration(void);
UART_Configuration()函数用于配置串口通信,参数 bound 为波特率。
GetCmd()函数用于从串口接收数据。
NVIC_Configuration()函数用于配置嵌套向量中断控制器(NVIC)。
4. main()函数
int main(void)
{
RCC_ClocksTypeDef clocks;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("\n");
printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
(float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
(float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);
printf("WWDG Int Test.\n");
printf("Interrupt Feed Dog\n");
WWDG_SetPrescaler(WWDG_Prescaler_8);
WWDG_SetWindowValue(0x5F);
WWDG_Enable(0x7f);
WWDG_ClearFlag();
NVIC_Configuration();
WWDG_EnableIT();
while (1);
}
定义 RCC_ClocksTypeDef 类型的变量 clocks,用于存储系统时钟频率信息。
使能窗口看门狗(WWDG)的时钟。
调用 delay_init()函数初始化延时功能。
调用 UART_Configuration()函数配置串口通信,波特率为 115200。
调用 RCC_GetClocksFreq()函数获取系统时钟频率信息,并通过串口输出。
输出提示信息,表明进行窗口看门狗中断测试以及采用中断方式喂狗。
配置窗口看门狗的预分频器、窗口值,并使能窗口看门狗。
清除窗口看门狗的标志位。
调用 NVIC_Configuration()函数配置 NVIC。
使能窗口看门狗的中断功能。
进入无限循环,程序在此处暂停。
5. WWDG_IRQHandler()函数
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(0x7f);
WWDG_ClearFlag();
}
这是窗口看门狗的中断服务函数。当窗口看门狗产生中断时,会执行该函数。函数中:
将窗口看门狗的计数器值设置为 0x7f,即喂狗操作,防止系统复位。
清除窗口看门狗的标志位,以便能处理下一次中断。
6. NVIC_Configuration()函数
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
7. UART_Configuration()函数
void UART_Configuration(uint32_t bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART_TEST, &USART_InitStructure);
USART_Cmd(USART_TEST, ENABLE);
}
此函数用于配置 USART1 串口通信,具体步骤如下:
使能 USART1 和 GPIOA 的时钟。
配置 GPIOA 的 Pin9 为复用推挽输出模式,作为 USART1 的发送引脚。
配置 GPIOA 的 Pin10 为浮空输入模式,作为 USART1 的接收引脚。
配置 USART1 的波特率、数据位、停止位、奇偶校验位、硬件流控制和工作模式。
初始化 USART1 并使能。
8. GetCmd()函数
uint8_t GetCmd(void)
{
uint8_t tmp = 0;
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
{
tmp = USART_ReceiveData(USART1);
}
return tmp;
}
该函数用于从 USART1 接收数据。若接收缓冲区非空标志位 USART_FLAG_RXNE 被置位,则从接收缓冲区读取数据并返回。
9. SER_PutChar()函数
int SER_PutChar(int ch)
{
while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
USART_SendData(USART_TEST, (uint8_t)ch);
return ch;
}
此函数用于向 USART1 发送单个字符。等待发送完成标志位 USART_FLAG_TC 被置位后,将字符写入发送缓冲区并返回该字符。
10. fputc()函数
int fputc(int c, FILE *f)
{
if (c == '\n')
{
SER_PutChar('\r');
}
return (SER_PutChar(c));
}
这是标准库 fputc()函数的重定向实现,用于将字符输出到串口。若要输出的字符为换行符 \n,先输出回车符 \r,再输出换行符。
4.2 下载验证
该程序的主要功能是配置串口通信,输出系统时钟频率信息,同时使能窗口看门狗的中断功能,并通过中断服务函数进行喂狗操作,以防止系统复位。
如果想要了解确认这段代码是否在正确执行“喂狗”(即重置WWDG计数器),在 WWDG_IRQHandler 中断处理函数中添加串口打印语句,每次触发中断时输出调试信息。
修改代码:
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(0x7f); // 喂狗
WWDG_ClearFlag(); // 清除中断标志
printf("[WWDG] Feed dog! Counter reset to 0x7F\n"); // 添加调试输出
}
然后查看串口打印数据,查看到正在执行“喂狗”:
5 WWDG_Reset
5.1 代码解析
1. 头文件和全局变量
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "delay.h"
#include "w55mh32.h"
USART_TypeDef *USART_TEST = USART1;
包含了标准库和自定义的头文件。
USART_TEST 是一个指向 USART1 的指针,用于后续串口操作。
2. 函数声明
void UART_Configuration(uint32_t bound);
uint8_t GetCmd(void);
UART_Configuration()函数用于配置串口通信。
GetCmd()函数用于从串口接收数据。
3. main()函数
int main(void)
{
RCC_ClocksTypeDef clocks;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("\n");
printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
(float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
(float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);
printf("WWDG Reset Test.\n");
WWDG_SetPrescaler(WWDG_Prescaler_8);
WWDG_SetWindowValue(0x5F);
WWDG_Enable(0x7f);
while (1);
}
时钟和外设初始化:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE) 使能窗口看门狗(WWDG)的时钟。
delay_init() 初始化延时函数。
UART_Configuration(115200) 配置串口通信,波特率为 115200。
RCC_GetClocksFreq(&clocks) 获取系统时钟频率信息。
串口输出信息:
通过 printf()函数输出系统时钟频率信息和测试提示信息。
窗口看门狗配置:
WWDG_SetPrescaler(WWDG_Prescaler_8) 设置窗口看门狗的预分频器为 8。
WWDG_SetWindowValue(0x5F) 设置窗口值为 0x5F。
WWDG_Enable(0x7f) 使能窗口看门狗,初始计数值为 0x7f。
主循环:
while (1); 程序进入无限循环,由于没有对窗口看门狗进行喂狗操作,一段时间后窗口看门狗会触发复位。
4. UART_Configuration()函数
void UART_Configuration(uint32_t bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART_TEST, &USART_InitStructure);
USART_Cmd(USART_TEST, ENABLE);
}
使能 USART1 和 GPIOA 的时钟。
配置 PA9 为复用推挽输出,作为串口发送引脚;配置 PA10 为浮空输入,作为串口接收引脚。
配置串口的波特率、数据位、停止位、奇偶校验等参数。
初始化串口并使能。
5. GetCmd()函数
uint8_t GetCmd(void)
{
uint8_t tmp = 0;
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
{
tmp = USART_ReceiveData(USART1);
}
return tmp;
}
检查串口接收缓冲区是否有数据(USART_FLAG_RXNE 标志位)。
如果有数据,从串口接收一个字节的数据并返回。
6. SER_PutChar()和fputc()函数
int SER_PutChar(int ch)
{
while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
USART_SendData(USART_TEST, (uint8_t)ch);
return ch;
}
int fputc(int c, FILE *f)
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
if (c == '\n')
{
SER_PutChar('\r');
}
return (SER_PutChar(c));
}
SER_PutChar()函数用于通过串口发送一个字符,等待发送完成标志位(USART_FLAG_TC)置位后再发送。
fputc()函数是 printf()函数的底层实现,当输出换行符 \n 时,会先发送回车符 \r,然后再发送字符。
这段代码的主要目的是测试窗口看门狗的复位功能。程序启动后,会输出系统时钟信息和测试提示信息,然后配置并使能窗口看门狗。由于在主循环中没有对窗口看门狗进行喂狗操作,窗口看门狗会在一段时间后触发复位,重新启动程序。同时,代码还实现了串口通信功能,用于输出信息和接收数据。