一、FreeRTOS简介
ESP32内置了FreeRTOS实时操作系统内核,这是一个专为嵌入式系统设计的开源实时操作系统。它支持:
-
多任务并行处理
-
任务优先级管理
-
内存管理
-
任务间通信
-
定时器管理
二、任务创建与管理
1. 任务创建(xTaskCreate)
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称
configSTACK_DEPTH_TYPE usStackDepth, // 堆栈大小
void *pvParameters, // 任务参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t *pxCreatedTask // 任务句柄指针
);
void setup() {
Serial.begin(115200);
// 创建两个任务
xTaskCreate(
task1, // 任务函数
"Task1", // 任务名称
2048, // 堆栈大小(字节)
NULL, // 任务参数
1, // 优先级(0-24)
NULL // 任务句柄
);
xTaskCreate(task2, "Task2", 2048, NULL, 2, NULL);
}
void task1(void * parameter) {
while(1) {
Serial.println("Task1正在运行");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时1秒
}
}
void task2(void * parameter) {
while(1) {
Serial.println("Task2正在执行高优先级任务");
vTaskDelay(500 / portTICK_PERIOD_MS); // 延时500ms
}
}
void loop() {} // Arduino主循环留空
2. 关键函数解析
-
vTaskDelay():非阻塞延时 void vTaskDelay(const TickType_t xTicksToDelay); 参数使用pdMS_TO_TICKS()宏转换时间,例如: vTaskDelay(pdMS_TO_TICKS(100)); // 精确延时100ms vTaskDelete():删除任务 void vTaskDelete(TaskHandle_t xTaskToDelete);
三、任务间通信
1. 队列(Queue)
队列是先进先出(FIFO)的数据结构,用于任务间安全传递数据。
创建队列:
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength, // 队列长度
UBaseType_t uxItemSize // 单个元素大小(字节)
);
完整示例:
QueueHandle_t dataQueue;
void setup() {
Serial.begin(115200);
// 创建能存储10个int的队列
dataQueue = xQueueCreate(10, sizeof(int));
xTaskCreate(producerTask, "Producer", 2048, NULL, 1, NULL);
xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, NULL);
}
// 生产者任务
void producerTask(void * parameter) {
int counter = 0;
while(1) {
xQueueSend(dataQueue, &counter, portMAX_DELAY);
Serial.printf("生产数据:%d\n", counter);
counter++;
vTaskDelay(800 / portTICK_PERIOD_MS);
}
}
// 消费者任务
void consumerTask(void * parameter) {
int receivedData;
while(1) {
if(xQueueReceive(dataQueue, &receivedData, 1000 / portTICK_PERIOD_MS)){
Serial.printf("消费数据:%d\n", receivedData);
}
}
}
void loop() {}
2. 信号量(Semaphore)
用于任务同步和资源共享控制
二进制信号量示例:
SemaphoreHandle_t binSemaphore;
void setup() {
Serial.begin(115200);
binSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(binSemaphore); // 初始可用
xTaskCreate(taskA, "TaskA", 2048, NULL, 2, NULL);
xTaskCreate(taskB, "TaskB", 2048, NULL, 1, NULL);
}
void taskA(void * parameter) {
while(1) {
if(xSemaphoreTake(binSemaphore, portMAX_DELAY)){
Serial.println("TaskA获得信号量");
vTaskDelay(1000 / portTICK_PERIOD_MS);
xSemaphoreGive(binSemaphore);
}
}
}
void taskB(void * parameter) {
while(1) {
if(xSemaphoreTake(binSemaphore, portMAX_DELAY)){
Serial.println("TaskB获得信号量");
vTaskDelay(500 / portTICK_PERIOD_MS);
xSemaphoreGive(binSemaphore);
}
}
}
void loop() {}
四、最佳实践建议
-
堆栈大小设置原则:
-
简单任务:1-2KB
-
复杂任务(涉及字符串处理):4KB+
-
使用
uxTaskGetStackHighWaterMark()
监控堆栈使用
-
-
优先级管理:
-
0(最低)~24(最高)
-
避免"优先级反转"问题
-
-
队列使用技巧:
-
大数据建议传递指针
-
使用
xQueueSendToFront()
实现紧急消息插队 -
超时设置避免死锁
-
五、常见问题排查
-
任务无法启动
-
检查堆栈是否过小
-
确认任务函数为无限循环
-
-
队列数据丢失
-
增加队列长度
-
检查生产者速度是否过快
-
-
系统崩溃
-
使用ESP32异常解码工具
-
检查内存越界访问
-
六、综合应用案例
实现温湿度传感器数据采集与网络上传的协同工作:
#include <WiFi.h>
#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
QueueHandle_t sensorQueue;
SemaphoreHandle_t wifiSemaphore;
void setup() {
Serial.begin(115200);
dht.begin();
sensorQueue = xQueueCreate(5, sizeof(float[2]));
wifiSemaphore = xSemaphoreCreateBinary();
xTaskCreate(readSensorTask, "Sensor", 4096, NULL, 2, NULL);
xTaskCreate(uploadTask, "Upload", 4096, NULL, 1, NULL);
WiFi.begin("SSID", "password");
while(WiFi.status() != WL_CONNECTED){
vTaskDelay(500 / portTICK_PERIOD_MS);
}
xSemaphoreGive(wifiSemaphore);
}
void readSensorTask(void * parameter) {
float data[2];
while(1) {
data[0] = dht.readTemperature();
data[1] = dht.readHumidity();
if(!isnan(data[0]) && !isnan(data[1])){
xQueueSend(sensorQueue, data, 0);
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void uploadTask(void * parameter) {
float receivedData[2];
while(1) {
if(xSemaphoreTake(wifiSemaphore, 1000 / portTICK_PERIOD_MS)){
if(xQueueReceive(sensorQueue, receivedData, 0)){
// 模拟网络上传
Serial.printf("上传数据:温度%.1f℃ 湿度%.1f%%\n",
receivedData[0], receivedData[1]);
}
xSemaphoreGive(wifiSemaphore);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void loop() {}
总结
通过合理使用FreeRTOS的多任务功能,可以充分发挥ESP32的双核优势。