FreeRTOS 中的 队列集(Queue Set) 是一种高级同步机制,允许任务同时监听多个队列或信号量的事件,并在任意一个成员有数据到达时唤醒任务。它适用于需要多路复用事件的场景,例如任务需要同时等待消息队列、信号量或其他队列集成员的事件。以下是队列集相关的 API 函数详解及使用指南。
队列集的核心特性
- 多事件监听:任务可以阻塞等待队列集中的多个队列或信号量,任一成员有事件即可唤醒任务(这个任务指的是监听任务它本身,不是其他的任务)。
- 统一管理:简化多事件监听逻辑,避免任务频繁轮询多个队列。
- 成员类型:支持队列(
QueueHandle_t
)和信号量(SemaphoreHandle_t
)。 - 限制:
- 每个队列或信号量只能加入一个队列集。
- 队列集本身不存储数据,仅标记成员是否有事件发生。
1. 创建队列集
函数原型
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
- 功能:动态分配内存并初始化一个队列集。
- 参数:
uxEventQueueLength
:队列集能同时记录的最大事件数(通常等于队列集中所有成员的队列长度总和)。
- 返回值:
- 成功:返回队列集句柄(
QueueSetHandle_t
)。 - 失败:返回
NULL
(内存不足或参数无效)。
- 成功:返回队列集句柄(
- 示例:
QueueSetHandle_t xQueueSet = xQueueCreateSet(5); // 最多记录5个事件
2. 向队列集中添加成员
函数原型
BaseType_t xQueueAddToSet(
QueueSetMemberHandle_t xQueueOrSemaphore, // 队列或信号量句柄
QueueSetHandle_t xQueueSet // 队列集句柄
);
- 功能:将队列或信号量添加到队列集中。
- 参数:
xQueueOrSemaphore
:要添加的队列或信号量句柄。xQueueSet
:目标队列集句柄。
- 返回值:
pdPASS
:添加成功。pdFAIL
:添加失败(成员已属于其他队列集,或队列集已满)。
- 示例:
QueueHandle_t xQueue1 = xQueueCreate(5, sizeof(int)); QueueHandle_t xQueue2 = xQueueCreate(5, sizeof(int)); QueueSetHandle_t xQueueSet = xQueueCreateSet(10); // 将队列加入队列集 xQueueAddToSet(xQueue1, xQueueSet); xQueueAddToSet(xQueue2, xQueueSet);
3. 从队列集中移除成员
函数原型
BaseType_t xQueueRemoveFromSet(
QueueSetMemberHandle_t xQueueOrSemaphore, // 队列或信号量句柄
QueueSetHandle_t xQueueSet // 队列集句柄
);
- 功能:从队列集中移除队列或信号量。
- 参数:同上。
- 返回值:
pdPASS
:移除成功。pdFAIL
:移除失败(成员不属于该队列集)。
4. 等待队列集中的事件
函数原型
QueueSetMemberHandle_t xQueueSelectFromSet(
QueueSetHandle_t xQueueSet,
TickType_t xTicksToWait // 阻塞时间
);
- 功能:阻塞任务,直到队列集中任意成员有事件(数据到达或信号量释放)。
- 参数:
xQueueSet
:队列集句柄。xTicksToWait
:阻塞时间(单位:系统节拍),portMAX_DELAY
表示无限等待。
- 返回值:
- 非
NULL
:触发事件的成员句柄(QueueHandle_t
或SemaphoreHandle_t
)。 NULL
:超时或队列集无效。
- 非
- 注意:返回的句柄需通过
xQueueReceive()
或xSemaphoreTake()
处理数据。
中断安全版本
QueueSetMemberHandle_t xQueueSelectFromSetFromISR(
QueueSetHandle_t xQueueSet
);
- 用于中断服务程序(ISR)中非阻塞查询队列集事件。
5. 使用流程示例
场景:任务需同时监听两个队列和一个信号量。
QueueHandle_t xQueueA, xQueueB;
SemaphoreHandle_t xSemaphore;
QueueSetHandle_t xQueueSet;
void vTaskEventMonitor(void *pvParameters) {
xQueueSet = xQueueCreateSet(10); // 总事件容量为10
// 创建队列和信号量,并加入队列集
xQueueA = xQueueCreate(5, sizeof(int));
xQueueB = xQueueCreate(5, sizeof(int));
xSemaphore = xSemaphoreCreateBinary();
xQueueAddToSet(xQueueA, xQueueSet);
xQueueAddToSet(xQueueB, xQueueSet);
xQueueAddToSet(xSemaphore, xQueueSet);
while (1) {
// 等待队列集中任意成员有事件
QueueSetMemberHandle_t xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
if (xActivatedMember == xQueueA) {
int data;
xQueueReceive(xQueueA, &data, 0); // 读取队列A数据
// 处理队列A事件...
} else if (xActivatedMember == xQueueB) {
int data;
xQueueReceive(xQueueB, &data, 0); // 读取队列B数据
// 处理队列B事件...
} else if (xActivatedMember == xSemaphore) {
xSemaphoreTake(xSemaphore, 0); // 获取信号量
// 处理信号量事件...
}
}
}
注意事项
-
事件容量限制
创建队列集时指定的uxEventQueueLength
必须 ≥ 所有成员队列的长度之和。例如:- 若队列1长度为3,队列2长度为2,信号量最大计数值为1,则
uxEventQueueLength
至少为 3+2+1=6。
- 若队列1长度为3,队列2长度为2,信号量最大计数值为1,则
-
数据读取时机
xQueueSelectFromSet()
返回触发事件的成员句柄后,必须立即调用xQueueReceive()
或xSemaphoreTake()
处理数据,否则该事件会被队列集标记为已处理,可能导致数据丢失。 -
性能影响
队列集的实现基于内部事件队列,频繁操作可能增加系统开销。在实时性要求高的场景中,需评估是否适合使用。 -
中断中使用
在 ISR 中只能使用xQueueSelectFromSetFromISR()
,且需确保不会阻塞。
适用场景
- 多事件监听:如网络任务需同时等待命令队列和数据队列。
- 统一事件分发:多个任务的事件通过队列集集中管理。
- 资源受限系统:避免为每个队列单独创建任务。
总结
队列集是 FreeRTOS 中管理多事件的高效工具,通过集中监听多个队列或信号量,简化了复杂事件处理逻辑。合理使用队列集可以提升代码可维护性,但需注意事件容量和性能开销。