看门狗定义
看门狗其实就是一个定时器,从功能上说它可以让微控制器在程序发生意外(程序进入死循环或跑飞)的时候,能够自动重启单片机,让系统回到正常状态。
毕竟对于计算机系统来说,99%的问题都可以通过重启解决~ esp32单片机也深谙此道。
看门狗分类
esp32上看门狗一共有两种类型,第一种为中断看门狗,第二种为任务看门狗
中断看门狗(IWDT)
中断看门狗,简称IWDT(interrupt watch dof timer),它是用于监控有没有哪一个中断处理程序执行时间过长(默认时间为800ms),如果时间过长,直接重启esp32
任务看门狗(TWDT)
任务看门狗,简称TWDT(task watch dog timer),它用来看管freertos中的任务,被它看管的任务,如果长时间(超时时间为5秒)得不到CPU资源,没机会运行,它将重启esp32
看门狗机制
中断看门狗默认开启,所以我们在编写中断处理函数时,一定要让函数逻辑尽可能简单
对于任务看门狗,其机制如下:
如上图所示,esp32有两个CPU,在每一个CPU上,都有一个预先创建的任务,这个任务的优先级非常低(为0),并且它的作用有如下几个:
当esp32处于空闲状态时,自动降低功耗,增加续航时间;
当自己CPU下有任务被删除时,主动清理相关内存资源;
重置看门狗定时器
默认情况下,esp32为0号CPU上的空闲任务添加了看门狗监管逻辑,如果CPU0上的空闲任务,在5秒钟之内获取不到CPU资源,没有机会运行,它将触发看门狗重启机制,导致esp32重启。
喂狗的定义
我们说过,看门狗其实本质上就是一个定时器,只有定时器超时才会触发重启逻辑,所以我们只需要保证在超时时间范围内,去重置定时器,就能保证不被重启,这个重置定时器的动作,有一个很高雅的名字,就叫“喂狗”。
对于中断看门狗来说,要避免esp32不被重启,需要尽可能降低中断处理函数的复杂度,让中断函数尽快运行结束。对于之前的中断需求,可以换一种方式来实现
每次只在中断处理函数中对计数器进行累加(这个操作非常轻量快速)
定期通过串口打印计数器的逻辑可以放到loop函数中
修改后的代码如下:
#include <Arduino.h>
volatile int count = 0; // 按钮计数器
long last_press_time = 0; // 上一次按下按钮的时间,用于按钮消抖
void handle()
{
// 两次按下的时间要大于500ms才认为是一次有效的操作
if (millis() - last_press_time > 500)
{
count++; // 计数器加一
last_press_time = millis(); // 记录本次按钮按下的时间
}
}
void setup()
{
Serial.begin(115200);
// 开启23号引脚的中断
attachInterrupt(digitalPinToInterrupt(23), handle, RISING);
}
void loop()
{
Serial.println(count); // 把当前计数值通过串口打印出来,每秒打印一次
delay(1000);
}
中断看门狗的超时时间
中断看门狗超时时间是300ms

暴力方案:禁用看门狗
这种方案很暴力,相当于饮鸩止渴,极不推荐,看门狗存在的意义是保证系统不死机不卡顿,如果暴力破坏机制,可能导致意想不到的问题发生
当然了,还是给大家介绍如何暴力取消看门狗,调用disableCore0WDT()函数取消即可,完整代码如下:
#include <Arduino.h>
volatile uint64_t count = 0; // 创建一个计数器
void cal(void *ptr)
{
while (1)
{
count++;
}
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
disableCore0WDT(); // 禁用CPU0上的看门狗机制
// 任务优先级为1,分配到CPU0
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 1, NULL, 0);
}
void loop()
{
}可选方案:降低任务优先级IDLE任务的任务优先级为0,我们可以降低cal任务的优先级也为0,这种情况下,IDLE任务与cal任务将公平竞争CPU资源,IDLE也就有机会重置看门狗计时器,避免被重启。
// 任务优先级降低为0,分配到CPU0
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 0, NULL, 0);推荐方案:主动让CPU
这种方案在实战中比较推荐,原理就是在cal任务中,每隔一段时间主动让出CPU,让IDLE任务有机会执行,有机会重置看门狗计时器。
主动让出CPU最简单的方式就是调用vTaskDelay函数,主动休眠一段时间,另外上一节课在任务循环中,使用Serial.println()函数输出数据,其实也是一种让出CPU资源的方式,因为Serial.println()属于IO操作,IO操作时不需要占用CPU,但不推荐该方式,毕竟没人会因为要让出CPU而故意进行串口输出~
#include <Arduino.h>
volatile uint64_t count = 0; // 创建一个计数器
void cal(void *ptr)
{
while (1)
{
count++;
vTaskDelay(1); // 主动休眠1ms,让出CPU给IDLE任务
}
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
// 任务优先级为1,分配到CPU0
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 1, NULL, 0);
}
void loop()
{
}CPU1空闲任务启用看门狗
对于CPU1来说,看门狗默认不启用,如果要对CPU1上的空闲任务也启用看门狗机制,则可以使用如下函数启用
enableCore1WDT()函数
该函数会启用CPU1上的Idle task的看门狗机制,函数没有任何参数,直接调用即可。
#include <Arduino.h>
volatile uint64_t count = 0; // 创建一个计数器
void cal(void *ptr)
{
while (1)
{
count++;
}
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
enableCore1WDT();
// 将任务创建在CPU1上,并且上一行启用了CPU1上的空闲任务看门狗,这个程序将会触发任务看门狗
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 1, NULL, 1);
}
void loop()
{
}上述代码将会出发CPU1上的任务看门狗机制

规避CPU1上空闲任务看门狗机制触发的代码如下:
#include <Arduino.h>
volatile uint64_t count = 0; // 创建一个计数器
void cal(void *ptr)
{
while (1)
{
count++;
vTaskDelay(1); // 主动退让CPU资源
}
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
enableCore1WDT();
// 将任务创建在CPU1上,并且上一行启用了CPU1上的空闲任务看门狗,这个程序将会触发任务看门狗
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 1, NULL, 1);
}
void loop()
{
vTaskDelay(1); // 主动退让CPU资源
}第9行和第24行分别在cal任务和loopTask任务中对CPU进行让步,确保CPU1上的空闲任务有机会重置定时器
其他任务使用看门狗
其他非Idle task的任务如果希望看门狗看管任务,也可以使用如下流程启用看门狗机制,并且在任务中主动喂狗
头文件中引入 #include "esp_task_wdt.h"
在任务中调用esp_task_wdt_add(NULL),申请看门狗看管当前任务
在任务执行过程中,定时调用esp_task_wdt_reset()函数喂狗(重置定时器)
#include <Arduino.h>
#include "esp_task_wdt.h"
volatile uint64_t count = 0; // 创建一个计数器
void cal(void *ptr)
{
esp_task_wdt_add(NULL); // 申请看门狗看护本任务
while (1)
{
count++;
esp_task_wdt_reset(); // 看门狗喂狗操作
}
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
// 创建一个任务,优先级为1,绑定到1号CPU(1号CPU空闲任务默认不启用看门狗机制)
xTaskCreatePinnedToCore(cal, "cal", 2048, nullptr, 1, NULL, 1);
}
void loop()
{
}