什么是看门狗
看门狗定时器是一种电子定时器,用于检测嵌入式系统中的错误并从中恢复。 看门狗定时器的基本原理很简单但很有效。 在特定时间段内,系统必须通知(喂狗)看门狗它仍在运行。 如果看门狗未收到此通知,则它假定存在故障并将系统置于已知状态。 通常,看门狗将重置(复位)处理器。 但是,对于更复杂的系统,看门狗可能必须触发一系列操作才能将系统置于已知的安全状态,例如电梯,检测到系统故障就不能简单的复位,不然后果很严重,而是在看门狗中断触发后让系统处于一个安全的状态,比如停止运动,并打开安全保护装置。
基本的看门狗保护
平时常用的一种方式是创建一个任务,在任务里周期性的喂狗。这种方式在这个喂狗任务故障或者系统完全崩溃(如hardfault)导致喂狗任务无法正常执行时,可以发挥作用。
现在我们处于RTOS下,情况就有点复杂了,某个或多个任务失效了,并不会导致整个系统崩溃。这些情况可能是:
- 某个任务里进入了死循环,但是仍能正常调度其它任务;
- 两个或多个任务因资源问题进入了死锁的状态;
- 某个低优先级任务因为高优先级任务一直抢占了CPU而得不到运行;
可见,这种方式在RTOS环境下能发挥的效果有限!!!
提高鲁棒性
前面提到的失效原因,都是因为看门狗没能检测到每一个任务的运行状态。所以,优化的办法的就是让它能检测到每个任务的运行。一个方法就是其它任务周期性的给喂狗任务发送通知,喂狗任务如果收到所有任务的通知就进行一次喂狗。这样,如果某个任务故障了,那将无法正常的发送喂狗通知,喂狗条件无法得到满足,因此一段时间后看门狗定时器将会超时。
下面是FreeRTOS下的简单实现方式:
#define TASK_1_BIT (1UL << 0UL)
#define TASK_2_BIT (1UL << 1UL)
/* 看门狗任务设为最高优先级,这样在收到低优先级的任务发来喂狗通知后能及时喂狗 */
void task_watchdog(void * pvParameters)
{
uint32_t ulNotifiedValue;
while (1)
{
/* 等待喂狗通知 */
xTaskNotifyWait( 0x00, 0x00, &ulNotifiedValue, portMAX_DELAY );
/* 判断是否收到了所有其他任务的通知 */
if( (ulNotifiedValue & (TASK_1_BIT | TASK_2_BIT)) == (TASK_1_BIT | TASK_2_BIT) )
{
/* 收到则喂狗 */
feed_dog();
/* 清除通知,等待一下次喂狗 */
ulTaskNotifyValueClear(NULL, UINT_MAX);
}
}
}
void task_1(void * pvParameters)
{
while (1)
{
/* 通知喂狗 */
xTaskNotify( xTaskWatchdogHandle, TASK_1_BIT, eSetBits );
/* 努力干活 */
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void task_2(void * pvParameters)
{
uint32_t ulNotifiedValue;
BaseType_t result;
while (1)
{
/* 类似这种等待不能永久等待,而是有个超时时间,后面根据判断是否收到消息再做处理 */
/* 官方文档有句话是这么说的:NOTE! Real applications should not block indefinitely,
but instead time out occasionally in order to handle error conditions
that may prevent the interrupt from sending any more notifications. */
result = xTaskNotifyWait( 0x00, ULONG_MAX, &ulNotifiedValue, 2000 / portTICK_PERIOD_MS );
if (pdTRUE == result)
{
/* 努力干活 */
}
/* 通知喂狗 */
xTaskNotify( xTaskWatchdogHandle, TASK_2_BIT, eSetBits );
}
}
更高级的使用策略
本人没看懂,下面是一个现成的解决方案,有条件的可以研究
网友评论