问题现象与背景

在ESP32双核(Core 0运行WiFi/蓝牙协议栈,Core 1运行应用任务)开发中,常遇到设备随机重启,串口打印Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU0)或Stack canary watchpoint triggered。这类复位往往与任务栈溢出或WiFi协议栈抢占栈资源有关。

  • 典型场景:高频率调用esp_wifi_*API、在WiFi事件回调中执行耗时操作、任务栈设置过小(如2048字节)。
  • 根本原因:FreeRTOS任务栈与WiFi协议栈共享同一内存区域(默认协议栈任务栈约3-4KB),当应用任务栈溢出时可能覆盖协议栈数据,或协议栈内部中断看门狗因任务阻塞而超时。

原理讲解:双核栈竞争机制

1. 栈空间分配

ESP32的FreeRTOS为每个任务分配独立栈,但WiFi协议栈运行在Core 0的wifi任务中(优先级较高)。若应用任务也创建在Core 0且优先级接近,可能因调度延迟导致协议栈任务无法及时喂狗。

2. 看门狗触发条件

  • 任务看门狗(TWDT):监控指定任务是否在超时内调用esp_task_wdt_reset()。
  • 中断看门狗(IWDT):监控中断是否长时间关闭。WiFi协议栈内部有中断处理,若应用任务长时间关中断(如临界区过长),会触发IWDT。

3. 栈溢出检测

FreeRTOS提供configCHECK_FOR_STACK_OVERFLOW(设为2时启用栈哨兵),溢出时触发vApplicationStackOverflowHook。但WiFi协议栈的栈溢出可能直接导致硬件异常。

定位方法与配置步骤

步骤1:启用栈溢出检测与看门狗调试

在menuconfig中配置:

  • Component config → FreeRTOS → Enable stack overflow checking 设为2
  • Component config → ESP System Settings → Panic handler behaviour 设为Print registers and halt
  • Component config → ESP System Settings → Enable Core Dump 设为Flash

步骤2:监控任务栈水位

使用uxTaskGetStackHighWaterMark()获取任务剩余栈空间,定期打印。

步骤3:检查WiFi协议栈任务状态

调用esp_wifi_get_ps()和esp_get_free_heap_size()辅助判断。

步骤4:使用Core Dump分析复位现场

通过idf.py coredump-info解析Flash中的Core Dump,查看崩溃时的PC指针和栈回溯。

完整代码示例

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_task_wdt.h"
#include "esp_log.h"

#define TAG "STACK_DEBUG"
#define TASK_STACK_SIZE 4096  // 建议不小于4096

// 栈溢出钩子函数
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
    ESP_LOGE(TAG, "Stack overflow in task: %s", pcTaskName);
    // 打印当前任务栈水位
    UBaseType_t watermark = uxTaskGetStackHighWaterMark(xTask);
    ESP_LOGE(TAG, "High water mark: %u words", watermark);
    abort();
}

// 应用任务:模拟WiFi操作
void app_task(void *pvParameters) {
    // 订阅任务看门狗
    esp_task_wdt_add(NULL);
    
    while (1) {
        // 模拟调用WiFi API(可能阻塞)
        wifi_ap_record_t ap_info;
        esp_wifi_sta_get_ap_info(&ap_info);
        
        // 打印栈水位
        UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL);
        ESP_LOGI(TAG, "Task stack watermark: %u words", watermark);
        
        // 喂狗
        esp_task_wdt_reset();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main() {
    // 初始化WiFi(省略详细配置)
    esp_wifi_init(NULL);
    
    // 创建任务在Core 1(避免与WiFi协议栈同核竞争)
    xTaskCreatePinnedToCore(app_task, "app_task", TASK_STACK_SIZE, NULL, 5, NULL, 1);
    
    // 启用任务看门狗(超时5秒)
    esp_task_wdt_init(5, true);
}

注意事项

  • 任务栈大小:调用WiFi API的任务栈建议≥4096字节,若使用TLS/HTTPS需≥8192字节。
  • 核绑定:将应用任务固定到Core 1(xTaskCreatePinnedToCore最后一个参数为1),避免与Core 0的WiFi任务抢栈。
  • 临界区:避免在WiFi事件回调中执行长临界区或阻塞操作,回调应快速返回。
  • 看门狗喂狗:若任务可能长时间阻塞,应增加看门狗超时或使用esp_task_wdt_reset()前确保任务确实在运行。
  • Core Dump分析:确保Flash分区表包含coredump分区,否则无法保存崩溃信息。

通过以上方法,可系统性地定位ESP32双核下因栈竞争导致的看门狗复位问题,提升系统稳定性。