第六章:6.1 ESP32教学:多任务处理与FreeRTOS实战
一、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 DHT22DHT 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的双核优势。