ESP32双核下FreeRTOS任务与WiFi协议栈抢CPU导致看门狗复位的定位方法
一、问题现象与背景
在ESP32上同时跑WiFi和高负载用户任务时,常见如下现象:
- 设备运行数秒到数分钟后突然重启,串口打印
Task watchdog got triggered或Interrupt wdt timeout on CPU0/CPU1。 - 复位原因寄存器显示
RTCWDT_RTC_RESET或TG0WDT_SYS_RESET。 - 降低用户任务频率或优先级后问题消失,但无法解释根因。
ESP32是双核(PRO_CPU / APP_CPU),WiFi协议栈默认绑定在CPU0,并以较高优先级运行。若用户任务长期占用CPU0且不让出,协议栈任务得不到调度,看门狗便会复位系统。
二、原理:双核调度与看门狗
- 任务看门狗(TWDT):默认监控IDLE任务与部分系统任务。IDLE任务长时间得不到运行,TWDT触发。
- 中断看门狗(IWDT):监控中断是否长时间关闭,常见于临界区过长或关中断操作。
-
WiFi协议栈:
wifi、tiT、pp等任务优先级通常在18~23,绑定CPU0。用户任务若设为同等或更高优先级并死循环,会直接饿死协议栈。 -
双核亲和性:
xTaskCreatePinnedToCore可指定核心。把计算密集任务放到CPU1,能显著降低与协议栈的冲突。
关键结论:看门狗复位往往不是看门狗本身的问题,而是CPU被长期占用或中断被长时间关闭的征兆。
三、定位步骤
步骤1:确认复位原因
读取复位原因,区分是任务看门狗、中断看门狗还是 panic:
#include "esp_system.h"
void print_reset_reason(void)
{
esp_reset_reason_t r = esp_reset_reason();
switch (r) {
case ESP_RST_TASK_WDT: printf("TASK_WDT\n"); break;
case ESP_RST_INT_WDT: printf("INT_WDT\n"); break;
case ESP_RST_PANIC: printf("PANIC\n"); break;
case ESP_RST_POWERON: printf("POWERON\n"); break;
default: printf("OTHER:%d\n", r); break;
}
}
步骤2:开启看门狗打印与任务快照
在 menuconfig 中打开:
-
Component config -> ESP System Settings -> Panic handler behaviour -> Print registers and halt -
Component config -> FreeRTOS -> Enable FreeRTOS trace facility(可选)
复位时串口会打印各任务状态(Running/Ready/Blocked)及栈回溯,重点看哪个任务长期处于Running。
步骤3:测量各任务CPU占用
用 vTaskGetRunTimeStats 获取各任务运行时间占比,快速找出“吃CPU大户”:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void cpu_usage_report(void)
{
static char buf[1024];
vTaskGetRunTimeStats(buf);
printf("%s\n", buf);
}
需在 menuconfig 中开启 FreeRTOS -> Generate Run Time Stats,并配置高精度计时器。
步骤4:检查任务优先级与核心绑定
- 用户计算任务优先级不要高于WiFi任务(一般≤10)。
- 用
xTaskCreatePinnedToCore(..., 1)绑定到CPU1。 - 长循环中插入
vTaskDelay(1)或taskYIELD(),给同核低优先级任务让出时间。
步骤5:检查临界区与关中断
- 避免在
portENTER_CRITICAL内做耗时操作。 - 避免长时间
taskDISABLE_INTERRUPTS()。 - 使用
esp_timer或队列代替忙等。
四、完整示例:安全的双核任务结构
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_log.h"
static const char *TAG = "DUAL_CORE";
/* 计算密集任务:绑定CPU1,低优先级,主动让出 */
static void cpu_task(void *arg)
{
while (1) {
volatile uint32_t s = 0;
for (uint32_t i = 0; i < 100000; i++) s += i;
vTaskDelay(pdMS_TO_TICKS(10)); /* 关键:让出CPU */
}
}
/* 监控任务:周期性打印CPU占用 */
static void monitor_task(void *arg)
{
while (1) {
vTaskDelay(pdMS_TO_TICKS(5000));
static char buf[1024];
vTaskGetRunTimeStats(buf);
ESP_LOGI(TAG, "\n%s", buf);
}
}
void app_main(void)
{
print_reset_reason();
wifi_init_sta(); /* 用户自行实现WiFi初始化 */
xTaskCreatePinnedToCore(cpu_task, "cpu_task", 4096, NULL,
5, NULL, 1); /* CPU1,优先级5 */
xTaskCreatePinnedToCore(monitor_task, "monitor", 4096, NULL,
3, NULL, 0); /* CPU0,低优先级 */
}
五、注意事项
- 不要盲目提高看门狗超时:延长超时只是掩盖问题,根因仍是CPU饥饿。
- WiFi任务不要随意改优先级:协议栈内部有时序假设,改动易引入丢包或断连。
-
双核共享资源要加锁:跨核访问同一变量使用
portMUX_TYPE或原子操作。 -
日志本身也耗CPU:高频
ESP_LOGI会加剧抢占,定位阶段可降低日志等级。 - 区分TWDT与IWDT:前者多为任务饿死,后者多为关中断过长,排查方向不同。
- 复现时记录任务快照:在复位打印中保存各任务状态,比事后猜测更可靠。
按上述流程,通常能在10分钟内定位到是哪个任务长期占用CPU0或哪段临界区过长,从而针对性修复,而不是反复调整看门狗参数。
暂无评论,来抢沙发~