本文记录自己使用看门狗定时器的过程,起因是系统不知名原因总是跑挂,查了很久找不到原因(排除了变量溢出、中断冲突等,各位大佬要是有相关经验可以文末留言或者私信,感谢!),因此选择了使用看门狗定时器做全局复位的补救措施,希望能给诸位学习者做一个参考。
硬件平台:ZYNQ7000系列
软件系统:FreeRTOS
本文参考博文:
FreeRTOS系统-独立看门狗监测任务执行状态
看门狗的配置参考博文
FreeRTOS系统-独立看门狗监测任务执行状态
参考技术手册:ug585-Zynq-7000
如图所示,每个 Cortex-A9 处理器都有自己的专用 32 位定时器和 32 位看门狗定时器。两个处理器共享一个全局 64 位定时器。这些定时器始终以 CPU 频率的 1/2 计时(CPU_3x2x)。
在系统级,有一个 24 位看门狗定时器和两个16 位三重定时器/计数器。系统看门狗定时器的时钟频率为 CPU 频率 (CPU_1x) 的 1/4 或 1/6,或者可以通过来自 MIO 引脚或 PL 的外部信号。两个三重定时器/计数器始终计时在 CPU 频率 (CPU_1x) 的 1/4 或 1/6 处,用于计算来自 MIO 引脚或 PL 的信号脉冲宽度。
本文使用的是SWDT也就是系统的全局看门狗定时器。
本质上是一个定时器,当看门狗超时向芯片发送复位信号,正常运行时要避免看门狗超时,可以通过周期性地重置看门狗定时器的计数器来防止看门狗超时,这种操作叫做喂狗。如果程序跑飞或者进入死循环,不执行喂狗的代码,看门狗定时器就会超时,导致系统复位。
看门狗定时器复位由看门狗定时器在启用且定时器到期时在内部生成。PS 中有三个不同的看门狗定时器:两个 ARM 内核(AWDT0 和 AWDT1)中各有一个系统级计时器 (SWDT) 和一个专用计时器。系统级定时器复位信号始终复位整个系统,而专用看门狗定时器可以仅复位容纳它的 ARM 内核,也可以复位整个系统。
看门狗的配置参考博文
//看门狗定时器
XScuWdt Watchdog;
#define WDT_DEVICE_ID XPAR_SCUWDT_0_DEVICE_ID
int watchdogConfig(XScuWdt * WdtInstancePtr, u16 DeviceId,float number)
{
int Status;
XScuWdt_Config *ConfigPtr;
u32 result;
//用来配置WDT的设备ID号,ID号在#include "xparameters.h"中可以找到。
xil_printf("start the watchdog timer successful! \r\n");
ConfigPtr = XScuWdt_LookupConfig(DeviceId);
//初始化WDT计数器
Status = XScuWdt_CfgInitialize(WdtInstancePtr, ConfigPtr,
ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//通过设置看门狗控制寄存器的WD模式位,将看门狗定时器置于看门狗模式
XScuWdt_SetWdMode(WdtInstancePtr);
//给WDT计数器装初值,这里我对此函数进行了封装。已知CPU的时钟频率为800Mhz,
//即WDT的时钟频率为400Mhz,可得倒计时1s计数器需要配置的初值为400_000_000,
//对计数器赋的初值可以直接写在number变量中(本文赋值为10s)。
result = (unsigned long)(400000000*number);
XScuWdt_LoadWdt(WdtInstancePtr,result);
//开启看门狗计数器。
XScuWdt_Start(WdtInstancePtr);
return XST_SUCCESS;
}
在多任务系统中,用看门狗的时候,我们希望可以实现看门狗监测整个系统每个任务的健康存活情况。假如系统中某个任务死掉了,能够通过看门狗进行系统复位,从而重新复位系统,以使系统重新开始运行。而每个任务都正常的时候,不要进行看门狗复位,从而不影响系统的正常执行。具体狗的复位可以根据自己实际需求设定。
参考: freeRTOS中使用看门狗的思考与实践
(1)可以在代码的执行流中插入“喂狗”。只要在各个任务中调用“喂狗”操作即可。但是这种方式不能实现监控每个任务的存活情况。因为当使用这种方式的时候,有多处地方可以进行喂狗操作,这个时候假如某个任务已经死掉了,它自身因为已经死掉不能在进行喂狗,但是其他的任务可以喂狗,整个系统依然不回被复位,也就不能知道某个任务是否出现问题。
(2)单独创建一个喂狗任务,监视各个任务的存活状态进行喂狗。
这种方式相比较于到处去调用喂狗操作更加实用,它是要在确认每个任务都正常存活的情况下才喂狗。如果某个任务因为故障不再运行了,就会导致超时不能喂狗,从而会导致系统被看门狗复位。
图片来源:FREERTOS — 独立看门狗检测任务执行状态
这种方式的实现有很多种手段,比如用事件标志组的方式就比较容易实现。
做法是:
参考文章:FreeRTOS系统-独立看门狗监测任务执行状态
需要用到的头文件
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
1)创建一个“喂狗”的独立任务;
/*
* 喂狗任务
* 在任务均已执行的情况下进行喂狗
*/
void Iwdg_task(void *pvParameters)
{
EventBits_t uxBits;
const TickType_t xTicksToWait = 2000 / portTICK_PERIOD_MS; /* 最大延迟2000ms */
Status = watchdogConfig(&Watchdog, WDT_DEVICE_ID,10);
while(1)
{
/* 等待所有任务发来事件标志 */
uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */
TASK_BIT_ALL, /* 等待TASK_BIT_ALL被设置 */
pdTRUE, /* 退出前TASK_BIT_ALL被清除,这里是TASK_BIT_ALL都被设置才表示“退出”*/
pdTRUE, /* 设置为pdTRUE表示等待TASK_BIT_ALL都被设置*/
xTicksToWait); /* 等待延迟时间 */
if((uxBits & TASK_BIT_ALL) == TASK_BIT_ALL)//判断各个任务是否执行
{
IWDG_Feed();
}
}
}
2)申请一个事件标志组,并为每个任务留一个标志位;
#define TASK_BIT_1 (0x01 << 0)
#define TASK_BIT_2 (0x01 << 1)
#define TASK_BIT_3 (0x01 << 2)
#define TASK_BIT_4 (0x01 << 3)
#define TASK_BIT_5 (0x01 << 4)
#define TASK_BIT_6 (0x01 << 5)
#define TASK_BIT_ALL ( TASK_BIT_1 |TASK_BIT_2 | TASK_BIT_3|TASK_BIT_4)
EventGroupHandle_t xCreatedEventGroup;//声明事件组
xCreatedEventGroup = xEventGroupCreate(); //创建事件组
3)在每个单独的任务中执行的时候置位属于这个任务的标志位;
void task(void *pvParameters)
{
while(1)
{
...
xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_1);//标志位置一
vTaskDelay(500);
}
}
4)在“喂狗”的独立任务中检测这些所有的任务所关联的标志位,
如果所有的标志位都置位成立,说明所有的任务都正常,进行一次喂狗操作。反之则引发一次复位。
/* 等待所有任务发来事件标志 */
uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */
TASK_BIT_ALL, /* 等待TASK_BIT_ALL被设置 */
pdTRUE, /* 退出前TASK_BIT_ALL被清除,这里是TASK_BIT_ALL都被设置才表示“退出”*/
pdTRUE, /* 设置为pdTRUE表示等待TASK_BIT_ALL都被设置*/
xTicksToWait); /* 等待延迟时间 */
if((uxBits & TASK_BIT_ALL) == TASK_BIT_ALL)//判断各个任务是否执行
{
IWDG_Feed();
}