队列核心API实战:创建/删除/发送/接收消息
队列这东西,说白了就是RTOS里的消息快递站。你发一个数据包进去,别人从另一头取出来。我刚开始用FreeRTOS那会儿,总觉得队列不就是个数组嘛,有啥好学的?结果第一次做多任务通信,数据乱得一塌糊涂,才老老实实回来啃API。
今天咱们就把队列的五个核心操作讲透:创建、删除、发送、接收,还有阻塞与非阻塞模式。嗯,这些搞明白了,你写多任务代码心里就有底了。
队列的创建与删除
先说说创建。每个RTOS都有自己的队列创建函数,但套路都差不多。拿FreeRTOS举例:
// 创建一个能存放5个uint32_t类型数据的队列
QueueHandle_t xQueue;
xQueue = xQueueCreate(5, sizeof(uint32_t));
if (xQueue == NULL) {
// 创建失败,内存不够了
printf("队列创建失败!\n");
}
这里有两个参数:队列长度和每个数据项的大小。我个人习惯把数据项大小写成 sizeof(),而不是直接写数字。为啥?代码移植性好,你换个数据类型不用改两处。
删除队列就简单了:
vQueueDelete(xQueue);
xQueue = NULL; // 我习惯顺手置空,防止野指针
删除后,队列里还没取走的数据就全丢了。所以删除前,最好确保没有任务还在等这个队列的消息。
发送消息:阻塞与非阻塞
发送消息,核心函数就两个:
// 非阻塞发送
BaseType_t xQueueSend(xQueue, &data, 0);
// 阻塞发送(等待10个tick)
BaseType_t xQueueSend(xQueue, &data, pdMS_TO_TICKS(10));
第三个参数是等待时间。填0就是非阻塞——队列满了?直接返回失败,不等待。填个正数就是阻塞模式——队列满了,任务就挂起,等别人取走数据腾出位置,或者等到超时。
- 非阻塞:立即返回,适合对实时性要求高的场景
- 阻塞:等待直到成功或超时,适合必须把数据发出去的场景
我曾经在一个电机控制项目里,用非阻塞发送日志数据。结果队列满了,日志丢了,调试时死活找不到问题。后来改成阻塞发送,虽然偶尔会卡一下任务,但数据一条不丢。你想想看,关键数据丢了,比慢一点更可怕。
接收消息:阻塞与非阻塞
接收的套路和发送对称:
uint32_t receivedData;
// 非阻塞接收
if (xQueueReceive(xQueue, &receivedData, 0) == pdPASS) {
// 成功收到数据
} else {
// 队列为空,立即返回
}
// 阻塞接收(一直等到天荒地老)
xQueueReceive(xQueue, &receivedData, portMAX_DELAY);
注意 portMAX_DELAY 这个宏。它表示「无限等待」。我一般只在初始化阶段用这个,因为那时候任务还没开始跑,等多久都行。正常运行的任务,我建议给个超时时间,比如100ms。万一对方任务挂了,你这边也不至于死等。
知识体系总览
下面这张图,把队列的核心操作串起来了。你看一眼就明白:
实战小例子:按键消息处理
光说不练假把式。我写个简单例子,你感受一下:
// 定义队列句柄
QueueHandle_t xKeyQueue;
// 按键中断服务函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint8_t keyCode = (uint8_t)GPIO_Pin;
// 中断里用FromISR版本,非阻塞
xQueueSendFromISR(xKeyQueue, &keyCode, &xHigherPriorityTaskWoken);
// 如果唤醒了一个更高优先级的任务,就切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 按键处理任务
void vKeyTask(void *pvParameters) {
uint8_t keyValue;
while(1) {
// 阻塞等待按键消息,超时100ms
if (xQueueReceive(xKeyQueue, &keyValue, pdMS_TO_TICKS(100)) == pdPASS) {
// 处理按键
printf("按键按下: %d\n", keyValue);
} else {
// 超时了,可以做点别的事
// 比如喂狗、检查状态
}
}
}
这个模式很常见。中断负责快速收数据,任务负责慢慢处理。队列就像个缓冲池,把两者解耦了。
- 中断里只能用
FromISR后缀的API - 任务里阻塞接收,别用
portMAX_DELAY死等 - 队列大小要合理,太大浪费内存,太小容易丢数据
嗯,队列的核心API就这些。你回去拿开发板试试,先创建个队列,然后两个任务一个发一个收,看看阻塞和非阻塞的区别。实践出真知,光看文档是学不会的。