文档
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避免累积误差