一、为什么你的应用任务会被 Wi-Fi 协议栈“饿死”?
ESP32-S3 内置双核 Xtensa LX7(Core 0 和 Core 1),默认情况下 FreeRTOS 会以对称多处理(SMP)方式调度任务,即任何任务都可能运行在任意核心上。但 Wi-Fi 协议栈(包括 TCP/IP、蓝牙等)在 ESP-IDF 中默认被固定到 Core 0,且优先级通常较高(例如 23)。
如果你的应用任务也运行在 Core 0,且优先级低于 Wi-Fi 任务,那么当 Wi-Fi 有大量数据收发时,Wi-Fi 任务会持续抢占 CPU,导致应用任务长时间得不到调度——这就是“饿死”现象。典型症状:
- 串口打印卡顿、看门狗复位
- 用户按键响应延迟超过 1 秒
- 传感器采样周期严重抖动
解决方案很简单:将应用任务固定到 Core 1,让 Wi-Fi 协议栈独占 Core 0。
二、FreeRTOS 双核调度核心原理
- xTaskCreate:创建的任务没有核心亲和性,调度器可将其分配到任意核心。
-
xTaskCreatePinnedToCore:额外指定
xCoreID参数,将任务永久绑定到指定核心(0 或 1)。 - 优先级:数值越大优先级越高。Wi-Fi 任务优先级通常为 23,应用任务建议设为 5~10。
- 空闲任务:每个核心都有一个空闲任务(优先级 0),用于清理和低功耗。
关键原则:不要让高优先级任务与 Wi-Fi 共享同一核心,否则低优先级任务永远无法运行。
三、配置步骤:将 Wi-Fi 与应用任务分离
3.1 确认 Wi-Fi 任务的核心绑定
ESP-IDF 默认将 Wi-Fi 任务绑定到 Core 0。你可以通过 menuconfig 检查:
Component config → Wi-Fi → [*] Wi-Fi Task Core Affinity → Core 0
通常保持默认即可。
3.2 创建应用任务并固定到 Core 1
使用 xTaskCreatePinnedToCore,最后一个参数传入 1(Core 1)。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "APP_TASK";
// 应用任务:模拟传感器采集与处理
void app_task(void *pvParameters) {
TickType_t last_wake = xTaskGetTickCount();
while (1) {
// 读取传感器(此处用打印模拟)
ESP_LOGI(TAG, "App task running on core %d", xPortGetCoreID());
// 模拟耗时处理
vTaskDelay(pdMS_TO_TICKS(100));
// 固定周期执行
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(500));
}
}
void app_main(void) {
// 初始化 Wi-Fi(省略具体代码,仅示意)
// wifi_init_sta();
// 创建应用任务,固定到 Core 1,优先级 5,栈 4096
xTaskCreatePinnedToCore(
app_task, // 任务函数
"app_task", // 任务名称
4096, // 栈大小(字节)
NULL, // 参数
5, // 优先级
NULL, // 任务句柄
1 // 核心 ID:1 表示 Core 1
);
ESP_LOGI(TAG, "app_main running on core %d", xPortGetCoreID());
}
3.3 验证任务核心绑定
运行后串口输出应显示:
I (1234) APP_TASK: App task running on core 1
I (1234) APP_TASK: app_main running on core 0
如果应用任务仍显示 core 0,检查是否误用了 xTaskCreate 或核心 ID 写错。
四、完整实战代码:Wi-Fi 扫描 + 应用任务并行
以下代码创建一个 Wi-Fi 扫描任务(Core 0)和一个 LED 闪烁任务(Core 1),两者互不干扰。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#define LED_GPIO 2
static const char *TAG = "DUAL_CORE";
// Wi-Fi 扫描任务(固定 Core 0)
void wifi_scan_task(void *pv) {
wifi_scan_config_t scan_cfg = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true
};
while (1) {
ESP_LOGI(TAG, "Wi-Fi scan start on core %d", xPortGetCoreID());
esp_wifi_scan_start(&scan_cfg, true);
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
ESP_LOGI(TAG, "Found %d APs", ap_count);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// LED 闪烁任务(固定 Core 1)
void led_task(void *pv) {
gpio_reset_pin(LED_GPIO);
gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(LED_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(LED_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(500));
ESP_LOGI(TAG, "LED toggle on core %d", xPortGetCoreID());
}
}
void app_main(void) {
// 初始化 NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化 Wi-Fi
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
// 创建 Wi-Fi 扫描任务,固定 Core 0
xTaskCreatePinnedToCore(wifi_scan_task, "wifi_scan", 4096, NULL, 5, NULL, 0);
// 创建 LED 任务,固定 Core 1
xTaskCreatePinnedToCore(led_task, "led_task", 2048, NULL, 5, NULL, 1);
ESP_LOGI(TAG, "app_main on core %d", xPortGetCoreID());
}
五、注意事项与避坑指南
- 核心 ID 只能为 0 或 1,传入其他值会导致断言失败。
- 不要将高优先级任务与 Wi-Fi 放在同一核心,否则低优先级任务可能永远得不到执行。
- 栈大小要充足:Wi-Fi 相关任务建议 4096 字节以上,普通任务 2048 字节。
- 使用 vTaskDelayUntil 实现精确周期,避免 vTaskDelay 累积误差。
- 调试时用 xPortGetCoreID() 打印当前核心,确认绑定生效。
-
若必须使用 xTaskCreate,可通过
xTaskCreatePinnedToCore的封装或vTaskCoreAffinitySet动态修改亲和性。 -
注意中断分配:ESP32-S3 的中断默认在 Core 0 处理,高频率中断可能影响 Wi-Fi 性能,必要时用
esp_intr_alloc指定核心。
六、总结
通过 xTaskCreatePinnedToCore 将 Wi-Fi 协议栈与应用任务物理隔离到不同核心,是解决任务饿死问题最直接有效的方法。记住:Core 0 留给 Wi-Fi/蓝牙,Core 1 跑你的应用逻辑。配合合理的优先级和栈大小,你的 ESP32-S3 将同时拥有流畅的网络通信和实时的任务响应。