ESP-IDF 双核任务:传感器采集 + OLED 显示
目标
利用 ESP32 双核架构:Core 0 专用于 OLED 显示刷新,Core 1 处理传感器采集和 WiFi 上报。通过 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;
void display_task(void *pvParameters) {
sensor_data_t data;
TickType_t last_wake = xTaskGetTickCount();
ESP_LOGI(TAG, "[Core %d] 显示任务启动", xPortGetCoreID());
while (1) {
if (xQueueReceive(sensor_queue, &data, pdMS_TO_TICKS(50)) == pdTRUE) {
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());
}
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(50));
}
}
void sensor_task(void *pvParameters) {
sensor_data_t data;
uint32_t seq = 0;
ESP_LOGI(TAG, "[Core %d] 传感器任务启动", xPortGetCoreID());
while (1) {
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++;
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);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
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));
}
}
void app_main(void) {
ESP_LOGI(TAG, "ESP32 双核任务演示");
sensor_queue = xQueueCreate(16, sizeof(sensor_data_t));
assert(sensor_queue != NULL);
BaseType_t ret;
ret = xTaskCreatePinnedToCore(
sensor_task,
"sensor",
4096,
NULL,
3,
NULL,
1
);
assert(ret == pdPASS);
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, "所有任务已创建完成!");
}
运行步骤
. ~/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 避免累积误差
ESP-IDF Hello World:WiFi 扫描仪
目标
在 ESP32 上运行 ESP-IDF FreeRTOS 任务,扫描周围 WiFi 网络并输出信号强度。
完整代码
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "nvs_flash.h"
static const char *TAG = "wifi_scan";
void wifi_scan_task(void *pvParameters) {
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());
while (1) {
ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, true));
ESP_LOGI(TAG, "=== WiFi 扫描完成 ===");
uint16_t ap_count = 0;
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_LOGI(TAG, "发现 %d 个 WiFi 网络", ap_count);
if (ap_count > 0) {
wifi_ap_record_t *ap_records = calloc(ap_count, sizeof(wifi_ap_record_t));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_records));
int show = ap_count > 10 ? 10 : ap_count;
for (int i = 0; i < show; i++) {
ESP_LOGI(TAG,
"SSID: %-20s | RSSI: %4d dBm | CH: %2d | %s",
ap_records[i].ssid,
ap_records[i].rssi,
ap_records[i].primary,
ap_records[i].authmode == WIFI_AUTH_OPEN ? "开放" : "加密");
}
free(ap_records);
}
esp_wifi_clear_ap_list();
ESP_LOGI(TAG, "5 秒后重新扫描...\n");
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void app_main(void) {
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);
ESP_LOGI(TAG, "ESP32 WiFi 扫描仪启动");
xTaskCreate(wifi_scan_task, "wifi_scan", 4096, NULL, 5, NULL);
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(wifi_scanner)
运行步骤
. ~/esp/esp-idf/export.sh
idf.py set-target esp32
idf.py menuconfig
idf.py build flash monitor
预期输出
I (456) wifi_scan: ESP32 WiFi 扫描仪启动
I (1234) wifi_scan: === WiFi 扫描完成 ===
I (1235) wifi_scan: 发现 12 个 WiFi 网络
I (1236) wifi_scan: SSID: MyHome-5G | RSSI: -42 dBm | CH: 11 | 加密
I (1237) wifi_scan: SSID: Starbucks WiFi | RSSI: -58 dBm | CH: 6 | 开放
...
I (1238) wifi_scan: 5 秒后重新扫描...
关键点
app_main 是 ESP-IDF 的入口函数(类似 main)
- 必须先初始化 NVS,WiFi 库依赖它存储配置
- FreeRTOS 任务栈大小 4096 是典型值,复杂任务需增大
esp_wifi_scan_start(NULL, true) 的 true 表示阻塞等待扫描完成