看门狗机制

lcysts
24
2025-10-19

看门狗定义

看门狗其实就是一个定时器,从功能上说它可以让微控制器在程序发生意外(程序进入死循环或跑飞)的时候,能够自动重启单片机,让系统回到正常状态。

毕竟对于计算机系统来说,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的任务如果希望看门狗看管任务,也可以使用如下流程启用看门狗机制,并且在任务中主动喂狗

  1. 头文件中引入 #include "esp_task_wdt.h"

  2. 在任务中调用esp_task_wdt_add(NULL),申请看门狗看管当前任务

  3. 在任务执行过程中,定时调用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()
{
}

动物装饰