FreeRTOS 在 ESP32 中的地位
FreeRTOS 是全球使用最广泛的嵌入式实时操作系统(RTOS),由 Richard Barry 创建,现由 Amazon 维护(AWS FreeRTOS)。ESP-IDF 将其深度集成:你编写的 app_main() 函数就是 FreeRTOS 的一个任务;Wi-Fi 协议栈、蓝牙驱动都运行在各自的 FreeRTOS 任务中。
ESP-IDF 使用修改版 FreeRTOS(称为 ESP-FreeRTOS)支持双核对称多处理(SMP):可以将任务绑定到 Core 0 或 Core 1,或允许调度器自动分配。标准 FreeRTOS 的所有 API 都可用,但调度器在两个核上分别运行,需要注意跨核同步。
任务(Task)基础
关键概念
任务创建示例
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "TASK";
void sensor_task(void *arg)
{
ESP_LOGI(TAG, "传感器任务运行在 Core %d", xPortGetCoreID());
while (1) {
/* 读取传感器 */
ESP_LOGI(TAG, "读取传感器...");
vTaskDelay(pdMS_TO_TICKS(2000)); // 2秒延迟(任务进入 Blocked 状态)
}
}
void network_task(void *arg)
{
ESP_LOGI(TAG, "网络任务运行在 Core %d", xPortGetCoreID());
while (1) {
/* 发送 MQTT 数据 */
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void app_main(void)
{
/* 普通创建(调度器自动分配核心)*/
xTaskCreate(
sensor_task, // 任务函数
"sensor", // 任务名(调试用)
4096, // 栈大小(字节)
NULL, // 传递给任务的参数
5, // 优先级
NULL // 任务句柄(可选)
);
/* 固定到 Core 1(APP_CPU),避免被 Wi-Fi 打断 */
xTaskCreatePinnedToCore(
network_task,
"network",
8192,
NULL,
8,
NULL,
1 // Core 1 = APP_CPU
);
/* 查看任务状态(调试)*/
char buf[512];
vTaskList(buf); // 需在 menuconfig 中启用 configUSE_TRACE_FACILITY
ESP_LOGI(TAG, "任务列表:\n%s", buf);
}
同步原语
队列(Queue)
#include "freertos/queue.h"
typedef struct {
float temperature;
float humidity;
} sensor_data_t;
static QueueHandle_t sensor_queue;
void sensor_task(void *arg)
{
sensor_data_t data;
while (1) {
data.temperature = 25.0f + (esp_random() % 100) / 10.0f;
data.humidity = 60.0f + (esp_random() % 20);
/* 发送到队列,等待最多 100ms */
xQueueSend(sensor_queue, &data, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void upload_task(void *arg)
{
sensor_data_t data;
while (1) {
/* 从队列接收,永久等待 */
if (xQueueReceive(sensor_queue, &data, portMAX_DELAY)) {
ESP_LOGI("UPLOAD", "温度:%.1f 湿度:%.1f",
data.temperature, data.humidity);
/* 发布到 MQTT... */
}
}
}
void app_main(void)
{
sensor_queue = xQueueCreate(10, sizeof(sensor_data_t));
xTaskCreate(sensor_task, "sensor", 4096, NULL, 5, NULL);
xTaskCreate(upload_task, "upload", 8192, NULL, 6, NULL);
}
互斥锁(Mutex)
#include "freertos/semphr.h"
static SemaphoreHandle_t display_mutex;
static char shared_buffer[128]; // 多任务共享的显示缓冲区
static void task_a(void *arg)
{
while (1) {
if (xSemaphoreTake(display_mutex, pdMS_TO_TICKS(200)) == pdTRUE) {
snprintf(shared_buffer, sizeof(shared_buffer), "Task A: %lu", xTaskGetTickCount());
/* 更新显示... */
xSemaphoreGive(display_mutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void)
{
display_mutex = xSemaphoreCreateMutex();
xTaskCreate(task_a, "task_a", 2048, NULL, 5, NULL);
}
事件组(Event Group)
#include "freertos/event_groups.h"
#define EVT_WIFI_READY BIT0
#define EVT_SENSOR_READY BIT1
#define EVT_ALL_READY (EVT_WIFI_READY | EVT_SENSOR_READY)
static EventGroupHandle_t system_events;
void wifi_init_task(void *arg) {
/* ... Wi-Fi 初始化 ... */
xEventGroupSetBits(system_events, EVT_WIFI_READY);
vTaskDelete(NULL);
}
void sensor_init_task(void *arg) {
/* ... 传感器初始化 ... */
xEventGroupSetBits(system_events, EVT_SENSOR_READY);
vTaskDelete(NULL);
}
void main_task(void *arg) {
/* 等待 Wi-Fi 和传感器都就绪(AND 等待) */
xEventGroupWaitBits(system_events, EVT_ALL_READY,
pdTRUE, pdTRUE, portMAX_DELAY);
ESP_LOGI("MAIN", "系统就绪,开始上报数据");
while (1) { /* ... */ }
}
看门狗(Watchdog)
ESP32 实现了两级看门狗机制,防止程序卡死:
内存诊断
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
void print_memory_info(void)
{
ESP_LOGI("MEM", "内部 DRAM 堆:");
ESP_LOGI("MEM", " 总计: %d 字节",
heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI("MEM", " 空闲: %d 字节",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI("MEM", " 最小曾经空闲: %d 字节",
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
/* 当前任务剩余栈空间 */
ESP_LOGI("MEM", "当前任务栈剩余: %d 字节",
(int)uxTaskGetStackHighWaterMark(NULL) * sizeof(StackType_t));
}
在 menuconfig 中启用 FreeRTOS → Check for stack overflow(选 Method 2),当任务栈溢出时会调用 vApplicationStackOverflowHook() 而不是直接崩溃,便于调试。开发阶段建议开启,发布时可关闭节省性能。
ESP32 双核任务绑定
ESP32 有两个 Xtensa LX6 内核(Core 0 = PRO_CPU,Core 1 = APP_CPU)。Wi-Fi/BT 协议栈默认运行在 Core 0,用户任务默认由两个核心负载均衡调度。可以手动将任务绑定到特定核心,避免与协议栈争用 Core 0:
/* xTaskCreatePinnedToCore:绑定任务到指定核心 */
/* 参数:函数, 名称, 栈大小, 参数, 优先级, 句柄, 核心ID */
/* 数据上报任务绑定到 Core 0(和 Wi-Fi 同核,网络操作不用跨核切换)*/
xTaskCreatePinnedToCore(
mqtt_report_task, /* 任务函数 */
"mqtt_report", /* 任务名称,用于调试 */
4096, /* 栈大小(字节),Wi-Fi 操作需要更大的栈 */
NULL, /* 参数 */
5, /* 优先级(0-24,数字越大越高,configMAX_PRIORITIES-1 为最高)*/
NULL, /* 任务句柄(不需要则 NULL)*/
0 /* 核心 ID:0=PRO_CPU, 1=APP_CPU */
);
/* 传感器读取任务绑定到 Core 1(独占核心,实时性更好)*/
xTaskCreatePinnedToCore(sensor_read_task, "sensor", 2048, NULL, 10, NULL, 1);
/* 不绑定核心(tskNO_AFFINITY):任何空闲核心都可以运行 */
xTaskCreate(background_task, "bg", 2048, NULL, 1, NULL);
/* 等价于 xTaskCreatePinnedToCore(..., tskNO_AFFINITY) */
标准 FreeRTOS 优先级 0 最低,数字越大越高。ESP32 的 FreeRTOS 端口使用相同规则,最高优先级由 configMAX_PRIORITIES 决定(默认 25,即 0-24)。注意:ESP-IDF 内置任务(Wi-Fi、BT、idle)使用了多个优先级层级,用户任务通常在 1-19 之间选择。不要使用优先级 24(esp_timer 任务)和 23(Wi-Fi 任务),否则会干扰系统功能。
EventGroup 事件组:多条件同步
EventGroup 适合"等待多个事件同时发生"的场景,如等待 Wi-Fi 连接完成 + MQTT 连接完成后才开始上报数据:
#include "freertos/event_groups.h"
/* 定义事件位(每个位代表一个事件,最多 24 位)*/
#define EVT_WIFI_CONNECTED (1 << 0) /* bit 0:Wi-Fi 已连接 */
#define EVT_MQTT_CONNECTED (1 << 1) /* bit 1:MQTT 已连接 */
#define EVT_SENSOR_READY (1 << 2) /* bit 2:传感器初始化完成 */
#define EVT_ALL_READY (EVT_WIFI_CONNECTED | EVT_MQTT_CONNECTED | EVT_SENSOR_READY)
EventGroupHandle_t app_events;
void init_task(void) {
app_events = xEventGroupCreate();
}
/* Wi-Fi 连接回调中设置事件位 */
void on_wifi_connected(void) {
xEventGroupSetBits(app_events, EVT_WIFI_CONNECTED);
ESP_LOGI("EVT", "Wi-Fi 连接事件已设置");
}
/* 传感器初始化完成时设置 */
void on_sensor_ready(void) {
xEventGroupSetBits(app_events, EVT_SENSOR_READY);
}
/* 上报任务:等待所有事件位同时置 1 才开始工作 */
void report_task(void *pvParam) {
ESP_LOGI("REPORT", "等待系统就绪...");
/* xEventGroupWaitBits 参数:
* 事件组句柄
* 等待的位集合(AND:全部满足;OR:任一满足)
* pdTRUE:等到后自动清除等待的位
* pdTRUE:AND 模式(所有指定位都要为 1)
* pdFALSE:OR 模式(任一位为 1 即可)
* 超时(portMAX_DELAY = 永久等待) */
EventBits_t bits = xEventGroupWaitBits(
app_events,
EVT_ALL_READY, /* 等待全部三个位 */
pdFALSE, /* 不自动清除 */
pdTRUE, /* AND 模式:全部满足 */
pdMS_TO_TICKS(30000) /* 30秒超时 */
);
if ((bits & EVT_ALL_READY) == EVT_ALL_READY) {
ESP_LOGI("REPORT", "系统就绪!开始上报");
while (1) {
/* 正常上报逻辑 */
vTaskDelay(pdMS_TO_TICKS(10000));
}
} else {
ESP_LOGE("REPORT", "等待超时!检查网络和传感器");
}
vTaskDelete(NULL);
}
ESP32 上的 FreeRTOS 是嵌入式实时操作系统,提供抢占式多任务调度。核心同步原语:Queue(任务间数据传递)、Semaphore(计数/信号)、Mutex(互斥保护共享资源)、EventGroup(多事件同步)。ESP32 双核特性可通过 xTaskCreatePinnedToCore 利用,将 Wi-Fi/MQTT 任务绑定 Core 0,时序敏感任务绑定 Core 1。ISR 中必须使用 FromISR 版本 API(xQueueSendFromISR、xSemaphoreGiveFromISR),并在退出时检查 xHigherPriorityTaskWoken 以决定是否触发任务切换。看门狗(TWDT)防止任务死循环,开发阶段建议开启栈溢出检测。