ESP32双核下FreeRTOS任务与WiFi协议栈抢CPU的优先级配置实测与调优

一、问题背景:为什么WiFi会和你的任务抢CPU

ESP32是双核Xtensa LX6,FreeRTOS默认在两个核心上调度任务。WiFi协议栈并不是“硬件自动完成”的,它由一组高优先级任务驱动,典型包括:

  • wifi 任务:处理协议栈事件与数据通路
  • tiT/tcpip 任务:LwIP协议栈线程
  • pp 任务:WiFi底层收发

这些任务优先级通常高于普通应用任务(常见在18~23区间,具体随IDF版本变化)。当你的任务长时间占用CPU或优先级设置不当,就会出现:

  • WiFi吞吐骤降、ping丢包
  • 任务看门狗(TWDT)触发复位
  • 音频/控制类任务抖动变大

关键结论:WiFi任务不能被“饿死”,用户任务也不能被无限抢占,调优的本质是优先级分层 + 核心隔离。

二、原理:双核调度与优先级规则

FreeRTOS在ESP32上的调度要点:

  • 每个任务有固定优先级(0~25,数值越大越高),同优先级时间片轮转
  • 任务可绑定核心(xTaskCreatePinnedToCore),不绑定则由调度器分配
  • 高优先级任务就绪即抢占同核低优先级任务
  • 双核之间互不抢占,但共享内存、锁与中断

因此推荐的分层策略:

  • 核心0:WiFi/蓝牙协议栈 + 系统任务(默认)
  • 核心1:应用主逻辑(app_main默认在核心0,需注意)
  • 用户实时任务优先级低于WiFi任务,但高于普通后台任务

三、实测配置步骤

步骤1:确认协议栈任务优先级

在menuconfig中开启Component config → FreeRTOS → Enable FreeRTOS trace,或运行时打印:

void print_tasks(void) {
    char buf[512];
    vTaskList(buf);
    printf("Name  State  Prio  Stack  Num\n%s\n", buf);
}

记录wifi、tiT任务的优先级,作为用户任务的上限参考。

步骤2:绑定核心并设置优先级

#define APP_TASK_PRIO   5
#define WIFI_SAFE_PRIO  4

void app_task(void *arg) {
    while (1) {
        // 业务逻辑
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void app_main(void) {
    xTaskCreatePinnedToCore(app_task, "app", 4096, NULL,
                            APP_TASK_PRIO, NULL, 1); // 绑定核心1
}

步骤3:对比实验

分别测试三种配置下的ping丢包率与吞吐:

  • A:用户任务优先级10,不绑定核心
  • B:用户任务优先级5,绑定核心1
  • C:用户任务优先级5,绑定核心1,且循环内加vTaskDelay(1)

实测(STA模式、TCP吞吐测试)典型结果:

  • A:丢包明显,吞吐波动大,偶发TWDT
  • B:吞吐稳定,但CPU占用高时仍有抖动
  • C:吞吐与实时性最佳,CPU占用可控

四、完整示例代码

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

static void wifi_event_handler(void *arg, esp_event_base_t base,
                               int32_t id, void *data) {
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    }
}

static void worker_task(void *arg) {
    while (1) {
        // 模拟计算型业务
        volatile int s = 0;
        for (int i = 0; i < 2000; i++) s += i;
        vTaskDelay(pdMS_TO_TICKS(5)); // 主动让出CPU
    }
}

void app_main(void) {
    nvs_flash_init();
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
                               wifi_event_handler, NULL);
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_start();

    xTaskCreatePinnedToCore(worker_task, "worker", 4096, NULL,
                            5, NULL, 1);
}

五、注意事项与避坑清单

  • 不要将用户任务优先级设到WiFi任务之上,否则协议栈饥饿导致断连
  • app_main默认运行在核心0,重计算逻辑应迁到核心1
  • 计算型循环必须插入vTaskDelay或taskYIELD,否则触发TWDT
  • 使用xTaskCreatePinnedToCore而非xTaskCreate,避免任务漂移
  • 共享数据用队列/信号量,不要用忙等自旋锁
  • 高吞吐场景可适当提高WiFi任务优先级(通过menuconfig),但需整体回归测试
  • 用vTaskGetRunTimeStats量化CPU占用,调优有据可依

六、总结

ESP32双核调优的核心是“核心隔离 + 优先级分层 + 主动让出”。把WiFi协议栈留在核心0,应用任务绑定核心1并保持较低优先级,配合合理的延时与同步机制,即可在保证WiFi稳定的同时获得良好的实时性。