进阶:双核 FreeRTOS 任务与核间通信

知识库
知识库文档
/tech-stacks/esp-idf/examples/进阶:双核 FreeRTOS 任务与核间通信.md

文档

ESP-IDF 双核任务:传感器采集 + OLED 显示

目标

利用 ESP32 双核架构:Core 0 专用于 OLED 显示刷新,Core 1 处理传感器采集和 WiFi 上报。通过 FreeRTOS 队列实现核心间通信。

完整代码

/* dual_core_demo.c — ESP32 双核任务示例
 * Core 0: OLED 显示任务(刷新周期 50ms)
 * Core 1: 传感器采集 + WiFi 上报(采集周期 1000ms)
 * 核间通信:FreeRTOS 队列
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "nvs_flash.h"

static const char *TAG = "dual_core";

/* ── 传感器数据结构(队列元素) ── */
typedef struct {
    int64_t timestamp_us;
    float   temperature;
    float   humidity;
    float   pressure;
    uint32_t sequence;
} sensor_data_t;

/* ── 队列句柄 ── */
static QueueHandle_t sensor_queue;

/* ═══════════════════════════════════════════
 * Core 0 任务:OLED 显示
 * ═══════════════════════════════════════════ */
void display_task(void *pvParameters) {
    sensor_data_t data;
    TickType_t last_wake = xTaskGetTickCount();

    ESP_LOGI(TAG, "[Core %d] 显示任务启动", xPortGetCoreID());

    while (1) {
        /* 非阻塞接收队列(50ms 超时) */
        if (xQueueReceive(sensor_queue, &data, pdMS_TO_TICKS(50)) == pdTRUE) {
            /* 实际项目:将这些数据写入 OLED */
            ESP_LOGI(TAG, "[Core %d] 🖥️ 显示更新 | "
                          "T=%.1f°C H=%.1f%% P=%.1fhPa | 序列=%lu",
                          xPortGetCoreID(),
                          data.temperature, data.humidity,
                          data.pressure, data.sequence);
        } else {
            /* 无新数据——刷新静态屏保 */
            ESP_LOGD(TAG, "[Core %d] 屏保刷新", xPortGetCoreID());
        }

        /* 精确 50ms 周期 */
        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(50));
    }
}

/* ═══════════════════════════════════════════
 * Core 1 任务:传感器采集与 WiFi
 * ═══════════════════════════════════════════ */
void sensor_task(void *pvParameters) {
    sensor_data_t data;
    uint32_t seq = 0;

    ESP_LOGI(TAG, "[Core %d] 传感器任务启动", xPortGetCoreID());

    while (1) {
        /* 模拟温度:25 + 正弦漂移 + 噪声 */
        float base_temp = 25.0f;
        float drift = 4.0f * sinf((float)seq * 0.3f);
        float noise = ((float)(esp_random() % 100) - 50.0f) / 100.0f;

        data.timestamp_us = esp_timer_get_time();
        data.temperature  = base_temp + drift + noise;
        data.humidity     = 55.0f + 10.0f * cosf((float)seq * 0.2f);
        data.pressure     = 1013.25f + noise * 2.0f;
        data.sequence     = seq++;

        /* 发送到 Core 0 的显示队列 */
        if (xQueueSend(sensor_queue, &data, pdMS_TO_TICKS(100)) != pdTRUE) {
            ESP_LOGW(TAG, "队列满!丢弃数据 seq=%lu", data.sequence);
        } else {
            ESP_LOGI(TAG, "[Core %d] 📡 采集 seq=%lu | T=%.1f°C H=%.1f%%",
                     xPortGetCoreID(), data.sequence,
                     data.temperature, data.humidity);
        }

        /* WiFi 上报逻辑(实际工程) */
        // send_to_mqtt(&data);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

/* ═══════════════════════════════════════════
 * 监控任务:统计 CPU 使用率
 * ═══════════════════════════════════════════ */
void monitor_task(void *pvParameters) {
    char buf[512];
    while (1) {
        vTaskList(buf);
        ESP_LOGI(TAG, "\n=== 任务列表 ===\n%s", buf);

        ESP_LOGI(TAG, "队列消息数: %d / 队列空余: %d",
                 uxQueueMessagesWaiting(sensor_queue),
                 uxQueueSpacesAvailable(sensor_queue));

        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

/* ═══════════════════════════════════════════
 * app_main
 * ═══════════════════════════════════════════ */
void app_main(void) {
    ESP_LOGI(TAG, "ESP32 双核任务演示");

    /* 创建队列(容量 16 条) */
    sensor_queue = xQueueCreate(16, sizeof(sensor_data_t));
    assert(sensor_queue != NULL);

    /* ── 任务创建(显式指定核心) ── */
    BaseType_t ret;

    /* Core 1: 传感器任务(优先级 3) */
    ret = xTaskCreatePinnedToCore(
        sensor_task,     // 函数
        "sensor",        // 名称
        4096,            // 栈大小(字)
        NULL,            // 参数
        3,               // 优先级
        NULL,            // 任务句柄(不需要)
        1                // 绑定 Core 1
    );
    assert(ret == pdPASS);

    /* Core 0: 显示任务(优先级 2,PRO 协议栈使用 Core 0) */
    ret = xTaskCreatePinnedToCore(
        display_task, "display", 4096, NULL, 2, NULL, 0);
    assert(ret == pdPASS);

    /* 任意核心:监控任务 */
    ret = xTaskCreate(
        monitor_task, "monitor", 4096, NULL, 1, NULL);
    assert(ret == pdPASS);

    ESP_LOGI(TAG, "所有任务已创建完成!");
    /* app_main 返回后被自动删除,不占用 Stack */
}

运行步骤

. ~/esp/esp-idf/export.sh
idf.py set-target esp32
idf.py build flash monitor

预期输出

I (xxx) dual_core: ESP32 双核任务演示
I (xxx) dual_core: [Core 0] 显示任务启动
I (xxx) dual_core: [Core 1] 传感器任务启动
I (xxx) dual_core: 所有任务已创建完成!
I (xxx) dual_core: [Core 1] 📡 采集 seq=0 | T=25.0°C H=54.5%
I (xxx) dual_core: [Core 0] 🖥️ 显示更新 | T=25.0°C H=54.5% P=1013.2hPa | 序列=0
...
I (xxx) dual_core:
=== 任务列表 ===
sensor           R   3   4096    xxx   1
display          B   2   4096    xxx   0
monitor          B   1   4096    xxx   1

关键点

  • xTaskCreatePinnedToCore 的最后一个参数 0/1 决定在哪个核心上运行
  • Core 0 默认运行 PRO CPU(WiFi/BT 协议栈),显示任务与其共享
  • Core 1 运行 APP CPU,适合计算密集型传感器处理
  • FreeRTOS 队列是核间通信的首选方式(线程安全、无锁)
  • 固定周期任务用 vTaskDelayUntil 避免累积误差

信息

路径
/tech-stacks/esp-idf/examples/进阶:双核 FreeRTOS 任务与核间通信.md
更新时间
2026/5/31