ESP32学习-FreeRTOS队列使用指南与实战
FreeRTOS 缓存队列关键概念
FreeRTOS 的缓存队列(Queue)是一种用于任务间通信的机制,允许任务或中断服务例程(ISR)以 FIFO(先进先出)的方式发送和接收数据。缓存队列可以传输固定大小的数据块,支持多任务并发操作,并通过阻塞机制实现同步。
关键特性:
- 线程安全:队列操作是原子的,无需额外保护。
- 阻塞与非阻塞:任务可以阻塞等待队列空间或数据,或立即返回。
- 多任务支持:多个任务可以同时读写队列。
- 中断安全:提供带
FromISR
后缀的 API 供中断上下文使用。
缓存队列使用模板
创建队列
使用 xQueueCreate()
或 xQueueCreateStatic()
创建队列。动态创建示例:
QueueHandle_t xQueue = xQueueCreate(uxQueueLength, // 队列长度(最大可存数据项数)uxItemSize // 每个数据项的大小(字节)
);
参数说明:
uxQueueLength
:队列容量,即队列可存储的最大数据项数量。uxItemSize
:每个数据项的大小(如传输int
则设为sizeof(int)
)。
发送数据到队列
使用 xQueueSend()
或 xQueueSendFromISR()
(中断中发送):
BaseType_t xStatus = xQueueSend(xQueue, // 队列句柄pvItemToQueue, // 指向待发送数据的指针xTicksToWait // 阻塞超时时间(portMAX_DELAY 表示无限等待)
);
参数说明:
pvItemToQueue
:指向待发送数据的指针(数据会被复制到队列)。xTicksToWait
:若队列满时,任务阻塞的 tick 数。0 表示不阻塞,立即返回。
从队列接收数据
使用 xQueueReceive()
或 xQueueReceiveFromISR()
(中断中接收):
BaseType_t xStatus = xQueueReceive(xQueue, // 队列句柄pvBuffer, // 接收数据的缓冲区指针xTicksToWait // 阻塞超时时间
);
参数说明:
pvBuffer
:指向接收缓冲区的指针(队列数据会被复制到此)。xTicksToWait
:若队列为空时,任务阻塞的 tick 数。
其他常用函数
uxQueueMessagesWaiting()
:获取队列中当前数据项数量。xQueueReset()
:重置队列为空状态。vQueueDelete()
:删除动态创建的队列。
完整示例代码
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>// 定义队列参数
#define QUEUE_LENGTH 5 // 队列最多容纳 5 个元素
#define ITEM_SIZE sizeof(int) // 每个元素大小为一个 int// 发送任务:每秒向队列发送一个整数
void vSenderTask(void *pvParameters) {QueueHandle_t xQueue = (QueueHandle_t)pvParameters; // 获取队列句柄int valueToSend = 42;while (1) {// 向队列发送数据,如果队列已满则阻塞等待xQueueSend(xQueue, &valueToSend, portMAX_DELAY);printf("Sent: %d\n", valueToSend);// 延时 1 秒(1000 毫秒)vTaskDelay(pdMS_TO_TICKS(1000));}
}// 接收任务:阻塞等待从队列接收数据
void vReceiverTask(void *pvParameters) {QueueHandle_t xQueue = (QueueHandle_t)pvParameters; // 获取队列句柄int receivedValue;while (1) {// 从队列接收数据(如果队列为空,则阻塞等待)if (xQueueReceive(xQueue, &receivedValue, portMAX_DELAY) == pdPASS) {// 成功接收到数据,打印处理printf("Received: %d\n", receivedValue);}}
}// 系统主入口函数
void main() {// 创建队列:最大容纳 QUEUE_LENGTH 个 int 类型数据QueueHandle_t xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);// 创建发送任务,优先级为 1,传入队列句柄xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, xQueue, 1, NULL);// 创建接收任务,优先级也为 1,传入队列句柄xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, xQueue, 1, NULL);// 启动 FreeRTOS 调度器(之后永不返回)vTaskStartScheduler();
}
关键注意事项
- 数据复制:队列通过内存拷贝传输数据,需确保发送的数据指针有效。
- 中断上下文:在中断中必须使用
FromISR
函数,且不可阻塞。 - 优先级反转:高优先级任务长时间阻塞可能导致低优先级任务无法释放队列资源。
问题
1.为什么main函数不会直接退出吗?
vTaskStartScheduler()这个函数会启动 FreeRTOS 的调度器,从此进入多任务运行状态:
- 控制权会交由调度器管理;
- main() 不会继续执行;
- 实际上 vTaskStartScheduler() 永不返回(除非发生错误);
所以,只要调度器启动成功,main() 的最后一行之后的代码永远不会执行。