ESP32双核FreeRTOS任务看门狗与中断看门狗同时触发的定位方法

1. 原理讲解:TWDT与IWDT的差异与同时触发的含义

ESP32的FreeRTOS提供两种看门狗:

  • 任务看门狗(TWDT):监控用户任务是否按时喂狗。每个任务可单独注册,超时后触发panic或打印任务状态。
  • 中断看门狗(IWDT):监控中断是否长时间关闭(如临界区过长、中断嵌套过深),超时后直接复位。

两者同时触发通常意味着:某个高优先级任务或中断服务程序长时间占用CPU,导致低优先级任务无法喂TWDT,同时中断被长时间屏蔽触发IWDT。常见场景:

  • 在临界区(portENTER_CRITICAL)内执行耗时操作(如Flash写、长循环)。
  • 高优先级任务死循环且未让出CPU,低优先级喂狗任务饿死。
  • 中断服务程序(ISR)中调用阻塞API或执行过长。
  • 双核间共享资源竞争导致自旋锁长时间持有。

2. 配置步骤:开启看门狗与日志

在menuconfig中配置:

  • Component config → ESP System Settings → Interrupt watchdog:使能IWDT,超时时间设为300ms(默认)。
  • Component config → FreeRTOS → Task watchdog:使能TWDT,超时时间设为5s。
  • Component config → ESP System Settings → Panic handler behaviour:选择Print registers and halt或GDB stub,便于捕获现场。
  • 开启Core Dump到Flash或UART,用于事后分析。

代码中初始化TWDT并添加当前任务:

#include "esp_task_wdt.h"

void app_main(void)
{
    // 初始化TWDT,超时5秒
    esp_task_wdt_init(5, true); // true: 超时触发panic
    // 将当前任务加入TWDT监控
    esp_task_wdt_add(NULL);

    while (1) {
        // 业务逻辑
        vTaskDelay(pdMS_TO_TICKS(1000));
        esp_task_wdt_reset(); // 喂狗
    }
}

3. 定位方法:联合分析看门狗日志与任务状态

当两者同时触发时,串口会打印类似信息:

E (12345) task_wdt: Task watchdog got triggered.
E (12345) task_wdt: Tasks currently running:
E (12345) task_wdt: CPU 0: IDLE
E (12345) task_wdt: CPU 1: main
E (12345) esp_int_wdt: Interrupt wdt timeout on CPU0

关键信息解读:

  • Tasks currently running列出触发时各CPU正在运行的任务。若某CPU显示非IDLE且非喂狗任务,说明该任务可能死循环。
  • Interrupt wdt timeout on CPUx指出哪个CPU的中断被长时间关闭。
  • 结合Core Dump中的调用栈,定位到具体函数。

定位流程:

  1. 复现问题,抓取完整串口日志。
  2. 查看TWDT打印的“当前运行任务”,确认哪个任务霸占CPU。
  3. 查看IWDT的CPU编号,与TWDT信息交叉验证。
  4. 若Core Dump可用,使用idf.py coredump-info解析调用栈。
  5. 检查代码中该任务或ISR是否包含长临界区、死循环或阻塞调用。

4. 完整代码示例:模拟同时触发并捕获

以下代码故意在临界区内执行长循环,同时让喂狗任务无法运行,模拟双看门狗触发:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "esp_int_wdt.h"

static void feed_task(void *arg)
{
    esp_task_wdt_add(NULL);
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        esp_task_wdt_reset();
        printf("Feed task alive\n");
    }
}

static void hog_task(void *arg)
{
    // 高优先级任务,长时间关闭中断
    while (1) {
        portENTER_CRITICAL(&spinlock); // 关闭中断
        // 模拟耗时操作,超过IWDT超时
        for (volatile int i = 0; i < 100000000; i++);
        portEXIT_CRITICAL(&spinlock);
        vTaskDelay(1); // 短暂让出,但不足以喂狗
    }
}

void app_main(void)
{
    esp_task_wdt_init(5, true);
    xTaskCreatePinnedToCore(feed_task, "feed", 2048, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(hog_task, "hog", 2048, NULL, 5, NULL, 1);
}

运行后,串口将先后打印IWDT超时和TWDT超时,通过日志可看到CPU1上hog任务正在运行,且CPU1中断被关闭。

5. 注意事项

  • 避免在临界区内调用可能阻塞的API,如vTaskDelay、printf(可能触发锁)。
  • 喂狗任务优先级不宜过低,否则易被高优先级任务饿死;建议单独创建一个中等优先级任务专门喂狗。
  • 双核场景下,每个核上的喂狗任务需分别注册,因为TWDT按任务监控,不区分核心。
  • IWDT超时时间通常较短(300ms),任何超过该时间的临界区都需拆分。
  • 使用Core Dump时确保Flash分区足够,并在menuconfig中配置正确的分区表。
  • 调试时可将TWDT超时设为较大值,先定位IWDT问题,再逐步收紧。

通过上述方法,可系统性地定位ESP32双核下双看门狗同时触发的根因,避免盲目修改代码。