19. WWDG—窗口看门狗¶
本章参考资料:《dm00327659-stm32mp157-advanced-armbased-32bit-mpus-stmicroelectronics》。
19.1. WWDG简介¶
STM32MP157总的有三个看门狗,两个独立看门狗IWDG,一个窗口看门狗WWDG。独立看门狗只供A7使用。 而窗口看门狗只供M4使用。独立看门狗的工作原理就是一个递减计数器不断的往下递减计数, 当减到0之前如果没有刷新递减计数器的值(即俗称的喂狗)的话,产生复位。窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数, 当减到一个固定值0X40时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。这个是跟独立看门狗类似的地方, 不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义
RLR是重装载寄存器,用来设置独立看门狗的计数器的值。TR是窗口看门狗的计数器的值,由用户独立设置,WR是窗口看门狗的上窗口值,由用户独立设置。
19.2. WWDG功能框图剖析¶
19.2.1. 窗口看门狗时钟¶
窗口看门狗时钟来自PCLK1,PCLK1最大是104.5M,由RCC时钟控制器开启。
19.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)。
19.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,则产生提前唤醒中断,如果真进入了这个中断的话,就说明程序肯定是出问题了, 那么在中断服务程序里面我们就需要做最重要的工作,比如保存重要数据,或者报警等,这个中断我们也叫它死前中断。
19.2.4. 窗口值¶
我们知道窗口看门狗必须在计数器的值在一个范围内才可以喂狗,其中下窗口的值是固定的0X40,上窗口的值可以改变, 具体的由配置寄存器CFR的位6:0 W[6:0]设置。其值必须大于0X40,如果小于或者等于0X40就是失去了窗口的价值,而且也不能大于计数器的值, 所以必须得小于0X7F。那窗口值具体要设置成多大?这个得根据我们需要监控的程序的运行时间来决定。如果我们要监控的程序段A运行的时间为Ta, 当执行完这段程序之后就要进行喂狗,如果在窗口时间内没有喂狗的话,那程序就肯定是出问题了。一般计数器的值TR设置成最大0X7F,窗口值为WR, 计数器减一个数的时间为T,那么时间:(TR-WR)*T应该稍微大于Ta即可,这样就能做到刚执行完程序段A之后喂狗,起到监控的作用,这样也就可以算出WR的值是多少。
19.2.5. 计算看门狗超时时间¶
这个图来自数据手册,从图我们知道看门狗超时时间:Twwdg = Tpclk1 x 4096 x 2^wdgtb x (T[5:0] + 1) ms, 当PCLK1 = 48MHZ时,WDGTB取不同的值时有最小和最大的超时时间,那这个最小和最大的超时时间该怎么理解,又是怎么算出来的? 讲起来有点绕,这里我稍微讲解下WDGTB=3时是怎么算的。 递减计数器有7位T[6:0] ,当位6变为0的时候就会产生复位,实际上有效的计数位是T[5:0], 而且T[6]必须先设置为1。如果T[5:0]=0时,递减计数器再减一次,就产生复位了, 那这减一的时间就等于计数器的周期=1/CNT_CK = Tpclk1 * 4096 * (2^WDGTB) = 1/48000 * 4096 *2^3 =682.67us, 这个就是最短的超时时间。如果T[5:0]全部装满为1,即63,当他减到0X40变成0X3F时, 所需的时间就是最大的超时时间=218.49*2^5=218.49*64=43.69ms。 同理,当WDGTB等于0/1/2时,代入公式即可。
19.3. 怎么用WWDG¶
WWDG一般被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。 比如一个程序段正常运行的时间是100ms,在运行完这个段程序之后紧接着进行喂狗,如果在规定的时间窗口内还没有喂狗, 那就说明我们监控的程序出故障了,跑飞了,那么就会产生系统复位,让程序重新运行。
19.4. 软件设计¶
本次实验设计这样的一个场景,某函数执行时间为100ms至200ms之间,若函数提前退出或执行时间过长则背离了正常的运行 序列产生了软件故障从而应产生系统复位,让程序重新运行。
在STM32CubeIDE上搜WWDG,选择WWDG1勾选给Cortex-M4并勾选选择激活,选择时钟的分频为128,此时 窗口看门狗的实际时钟约为104.5MHz/4096/128≈199.3Hz,每当计一个数时的时间约为5ms,而当递减计数器减到0x40(十进制数64)时 若开启了提前唤醒中断则会进入中断并复位,而我们假设某函数的运行时间为100-200ms, 因此递减计数器在这段时间时钟节拍为20-40个,故最大的计数器的值TR为64+40=104,上窗口的窗口值WR=64+20=84; 将我们计算得到的数值填入外设的参数配置区,并使能提前唤醒中断。
在NVIC中开启窗口看门狗中断
串口看门狗配置到此结束。
19.4.1. 定时器初始化结构体¶
窗口看门狗结构体WWDG_HandleTypeDef用于管理窗口看门狗外设的资源。
1 2 3 4 5 6 | typedef struct
{
WWDG_TypeDef *Instance; /*!< 外设寄存器基地址 */
WWDG_InitTypeDef Init; /*!< 窗口看门狗初始化结构体 */
} WWDG_HandleTypeDef;
|
(1) Instance: 窗口看门狗寄存器基地址指针,所有参数都是指定基地址后才能正确写入寄存器。
(2) Init: 窗口看门狗初始化结构体,下面会详细讲解每一个成员。
1 2 3 4 5 6 7 8 | typedef struct
{
uint32_t Prescaler; /*!< 预分频器值 */
uint32_t Window; /*!< 上窗口值 */
uint32_t Counter; /*!< 计数器的值 */
uint32_t EWIMode ; /*!< 提前唤醒中断 */
} WWDG_InitTypeDef;
|
(1) Prescaler:窗口看门狗预分频器设置,时钟源经该预分频器才是定时器时钟。可设置范围为0至65535,实现1至65536分频。
(2) Window:上窗口的值。具体的由配置寄存器CFR的位6:0 W[6:0]设置。其值必须大于0X40,如果小于或者等于0X40就是失去了窗口的价值,而且也不能大于计数器的值,所以必须得小于0X7F
(3) Counter:计数器的值,每经过一个窗口看门狗时钟节拍时,递减1。
(4) EWIMode:提前唤醒中断,当选择设能提前唤醒中断时,当计数器到0x40时将会触发提前唤醒中断。
19.4.2. 窗口看门狗初始化函数¶
MX_WWDG1_Init函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void MX_WWDG1_Init(void)
{
hwwdg1.Instance = WWDG1; //设置WWDG基地址
hwwdg1.Init.Prescaler = WWDG_PRESCALER_128; //窗口看门狗预分频器设置
hwwdg1.Init.Window = 84; //上窗口的值
hwwdg1.Init.Counter = 104; //计数器的值
hwwdg1.Init.EWIMode = WWDG_EWI_ENABLE; //提前唤醒中断
if (HAL_WWDG_Init(&hwwdg1) != HAL_OK) //初始化WWDG
{
Error_Handler();
}
}
|
在MX_WWDG1_Init函数中配置窗口看门狗的配置信息,并初始化相关配置。
HAL_WWDG_MspInit函数
1 2 3 4 5 6 7 8 9 10 11 12 | void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
{
if(wwdgHandle->Instance==WWDG1)
{
__HAL_RCC_WWDG1_CLK_ENABLE();
HAL_NVIC_SetPriority(WWDG1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(WWDG1_IRQn);
}
}
|
在HAL_WWDG_MspInit函数中主要使能窗口看门狗时钟并设置相关中断。
看门狗中断函数
1 2 3 4 | void WWDG1_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&hwwdg1);
}
|
在递减计数器减到0X40的时候将产生提前唤醒中断,这个中断我们称它为死前中断或者叫遗嘱中断, 在中断函数里面我们应该处理最重要的事情,而且必须得快,因为递减计数器再减一次,就会产生系统复位; 只有超过时间不喂狗时才当计数器的值为0x40时才会产生中断,若是提前喂狗则不会产生中断。
19.4.3. 添加用户代码¶
我们将添加两个关于窗口看门狗相关的文件,bsp_wwdg.h和bsp_wwdg.c,主要用来编写中断回调函数的内容。
由于添加的内容相当简单,添加的内容如下
1 2 3 4 5 6 7 8 | void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
if( hwwdg->Instance == WWDG1)
{
printf("系统即将复位\n");
}
}
|
只在提前唤醒中断的回调函数中,打印了一句话,在实际的应用中读者根据自己的需求编写相关代码内容。
main_task.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include "main_task.h"
void Main_Config(void) //配置函数
{
LED_GPIO_Config();
}
//此函数的运行的时间为delay ms
void Wwdg_TestTask(uint16_t delay)
{
HAL_Delay(delay); //模拟需要执行特定时间的任务
}
void Main_Task(void) //主要的任务函数
{
printf("WWDG test\n");
while(1)
{
Wwdg_TestTask(90); //模拟一个需要某个时间的函数
HAL_WWDG_Refresh(&hwwdg1); //喂狗
LED1_TOGGLE; //LED翻转
}
}
|
第10-13行,定义了Wwdg_TestTask函数,用于模拟任务执行需要的时间,
第15-26行,在进入while循环前,先打印了“WWDG test”,用于判断上电的第一次打印, 在调用Wwdg_TestTask函数后进行喂狗,并进行翻转LED1,若传入Wwdg_TestTask的产生不在100-200之间将发生复位,这里的100-200是 个粗略值,由于计算出来的一个时钟节拍约为5ms并不是个准确值再加上其他代码也需要少许的时间,可能会存在1~2的误差。在测试的时候尽量 不使用如99,100,199,200此类边缘值。
19.4.4. 主函数¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int main(void)
{
HAL_Init();
if(IS_ENGINEERING_BOOT_MODE())
{
SystemClock_Config();
}
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_WWDG1_Init();
Main_Config();
while (1)
{
Main_Task();
}
}
|
主函数的内容和之前一样,仅添加了Main_Config与Main_Task函数。
19.4.5. 下载验证¶
当我们设置Wwdg_TestTask的参数在100-200之间(粗略值)时,可发现LED1不断闪烁,串口也仅打印了一次“WWDG test”, 系统正常工作; 当设置Wwdg_TestTask的参数低于100时,可发现LED1不再闪烁并且串口不断打印“WWDG test”值,系统不断复位; 当设置Wwdg_TestTask的参数高于200时,除了LED1不再闪烁和串口不断打印“WWDG test”值外,串口还会打印“系统即将复位”。 系统进入中断后再复位。