Queue Set
队列集可以传输多种数据类型的信息
队列集的基本概念
什么是队列集
队列集(Queue Set)是一种高级同步机制,允许单个任务同时监视多个队列或信号量。想象一个监控中心:
- 多个传感器:每个队列就像一个传感器,产生不同类型的数据
- 中央监控台:队列集就是监控台,可以同时显示所有传感器的状态
- 值班人员:任务就像值班人员,只需要关注监控台,不需要轮流检查每个传感器
队列集的核心价值
解决多路监听问题:在没有队列集的情况下,任务需要轮流检查多个队列:
// 低效的方式:轮流检查每个队列
void vTaskInefficient(void) {
while(1) {
if(xQueueReceive(xQueue1, &data1, 0) == pdPASS) {
process_data1(data1);
}
if(xQueueReceive(xQueue2, &data2, 0) == pdPASS) {
process_data2(data2);
}
if(xQueueReceive(xQueue3, &data3, 0) == pdPASS) {
process_data3(data3);
}
vTaskDelay(10); // 忙等待,浪费CPU
}
}
队列集的优势: - 任务可以阻塞等待任意队列有数据 - 避免忙等待,提高CPU效率 - 简化多队列监听逻辑
FreeRTOS 队列集特性
设计特点
统一监听接口:任务只需要等待队列集,不需要关心具体哪个队列
支持混合类型:可以同时包含队列和信号量
非破坏性查看:从队列集读取不会移除原始队列中的数据
容量限制:队列集本身有容量限制,需要合理设计
适用场景
多数据源处理:监控多个传感器数据流
事件驱动系统:响应来自不同来源的事件
协议处理:处理来自多个通信通道的数据
GUI事件处理:监听用户输入、定时器、网络事件等
FreeRTOS 队列集函数详解
xQueueCreateSet - 创建队列集
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
参数说明:
- uxEventQueueLength:队列集的容量,即能够同时记录的最大事件数
返回值: - 成功:队列集句柄 - 失败:NULL
容量设计原则:
// 如果有3个队列需要监控,每个队列可能同时有多个数据
// 建议容量 = 监控的队列数 × 每个队列的预期最大并发数据
QueueSetHandle_t xQueueSet = xQueueCreateSet(10); // 容量为10个事件
xQueueAddToSet - 添加成员到队列集
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
参数说明:
- xQueueOrSemaphore:要添加的队列或信号量句柄
- xQueueSet:目标队列集句柄
返回值:
- pdPASS:添加成功
- pdFAIL:添加失败
添加示例:
// 创建队列和队列集
QueueHandle_t xSensorQueue = xQueueCreate(5, sizeof(SensorData_t));
QueueHandle_t xCommandQueue = xQueueCreate(3, sizeof(Command_t));
QueueSetHandle_t xMainQueueSet = xQueueCreateSet(8);
// 添加队列到队列集
xQueueAddToSet(xSensorQueue, xMainQueueSet);
xQueueAddToSet(xCommandQueue, xMainQueueSet);
xQueueRemoveFromSet - 从队列集中移除成员
BaseType_t xQueueRemoveFromSet(QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
使用场景: - 动态重新配置系统 - 临时禁用某些数据源 - 系统关闭时清理资源
xQueueSelectFromSet - 从队列集选择就绪成员
QueueSetMemberHandle_t xQueueSelectFromSet(QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait);
参数说明:
- xQueueSet:要监听的队列集
- xTicksToWait:阻塞超时时间
返回值: - 非NULL:就绪的队列或信号量句柄 - NULL:超时没有就绪的成员
xQueueSelectFromSetFromISR - 中断安全版本
QueueSetMemberHandle_t xQueueSelectFromSetFromISR(QueueSetHandle_t xQueueSet);
中断中使用注意事项:
- 没有超时参数
- 需要检查返回值是否为NULL
- 通常配合portYIELD_FROM_ISR使用
FreeRTOS 队列集使用模式
基本使用流程
// 1. 创建队列集和成员队列
QueueSetHandle_t xQueueSet = xQueueCreateSet(10);
QueueHandle_t xQueue1 = xQueueCreate(5, sizeof(int));
QueueHandle_t xQueue2 = xQueueCreate(5, sizeof(float));
// 2. 添加队列到队列集
xQueueAddToSet(xQueue1, xQueueSet);
xQueueAddToSet(xQueue2, xQueueSet);
// 3. 任务中监听队列集
void vMonitorTask(void *pvParameters) {
QueueSetMemberHandle_t xActivatedMember;
while(1) {
// 等待任意队列有数据
xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
if(xActivatedMember == xQueue1) {
int data;
xQueueReceive(xQueue1, &data, 0);
process_queue1_data(data);
}
else if(xActivatedMember == xQueue2) {
float data;
xQueueReceive(xQueue2, &data, 0);
process_queue2_data(data);
}
}
}
信号量与队列混合监听
// 创建队列集和不同类型的成员
QueueSetHandle_t xEventSet = xQueueCreateSet(8);
QueueHandle_t xDataQueue = xQueueCreate(5, sizeof(Data_t));
SemaphoreHandle_t xTimerSemaphore = xSemaphoreCreateBinary();
SemaphoreHandle_t xButtonSemaphore = xSemaphoreCreateBinary();
// 添加所有成员到队列集
xQueueAddToSet(xDataQueue, xEventSet);
xQueueAddToSet(xTimerSemaphore, xEventSet);
xQueueAddToSet(xButtonSemaphore, xEventSet);
// 统一事件处理
void vEventHandler(void *pvParameters) {
QueueSetMemberHandle_t xEventSource;
while(1) {
xEventSource = xQueueSelectFromSet(xEventSet, portMAX_DELAY);
if(xEventSource == xDataQueue) {
Data_t data;
xQueueReceive(xDataQueue, &data, 0);
handle_data_event(data);
}
else if(xEventSource == xTimerSemaphore) {
xSemaphoreTake(xTimerSemaphore, 0);
handle_timer_event();
}
else if(xEventSource == xButtonSemaphore) {
xSemaphoreTake(xButtonSemaphore, 0);
handle_button_event();
}
}
}
动态成员管理
// 动态添加和移除队列集成员
void vDynamicQueueManagement(void) {
QueueHandle_t xTempQueue = xQueueCreate(3, sizeof(float));
// 临时添加监控
if(xQueueAddToSet(xTempQueue, xMainQueueSet) == pdPASS) {
printf("Temporary queue added to set\n");
// 监控一段时间...
vTaskDelay(10000 / portTICK_PERIOD_MS);
// 移除监控
xQueueRemoveFromSet(xTempQueue, xMainQueueSet);
}
vQueueDelete(xTempQueue);
}
CMSIS-RTOS v2 事件标志
CMSIS-RTOS v2 的等效机制
在CMSIS-RTOS v2中,队列集的功能由事件标志(Event Flags) 实现。事件标志提供类似的功能,但实现方式不同:CMSISv2提供Event_Group
RTOS 队列集 vs CMSIS-RTOS v2 事件标志机制对比
| 特性 | FreeRTOS 队列集 | CMSIS-RTOS v2 事件标志 |
|---|---|---|
| 实现基础 | 队列集合监听 | 位标志操作 |
| 数据传递 | 支持数据传输 | 仅事件通知,无数据 |
| 成员类型 | 队列、信号量混合 | 统一的事件标志位 |
| 资源开销 | 较高(需要队列集和成员队列) | 较低(单个事件标志对象) |
| 灵活性 | 动态添加移除成员 | 固定的标志位定义 |
适用场景对比
FreeRTOS队列集更适合: - 需要传输实际数据的场景 - 混合类型的同步对象(队列+信号量) - 动态变化的监听集合
CMSIS事件标志更适合: - 纯事件通知场景 - 固定的事件类型集合 - 资源受限的系统
性能考虑
FreeRTOS队列集: - 内存开销:队列集结构 + 所有成员队列 - CPU开销:选择操作需要遍历成员 - 灵活性:运行时动态配置
CMSIS事件标志: - 内存开销:单个事件标志对象 - CPU开销:位操作,效率高 - 灵活性:编译时确定事件类型
设计注意事项
FreeRTOS 队列集设计要点
容量规划:
// 错误:容量过小可能导致事件丢失
QueueSetHandle_t xSet = xQueueCreateSet(2); // 太小!
// 正确:根据并发需求设计容量
// 假设有3个队列,每个队列可能同时有2个数据
QueueSetHandle_t xSet = xQueueCreateSet(3 * 2); // 容量6
成员管理: - 一个队列只能属于一个队列集 - 添加前确保队列已创建 - 移除前确保没有任务正在等待
错误处理:
QueueSetMemberHandle_t xReadyMember = xQueueSelectFromSet(xSet, timeout);
if(xReadyMember != NULL) {
if(xReadyMember == xQueue1) {
// 处理队列1数据
if(xQueueReceive(xQueue1, &data, 0) != pdPASS) {
// 数据可能已被其他任务取走
}
}
}
CMSIS-RTOS v2 事件标志设计要点
标志位规划:
// 使用位域清晰定义事件
typedef enum {
EVT_SENSOR1_READY = (1UL << 0),
EVT_SENSOR2_READY = (1UL << 1),
EVT_USER_INPUT = (1UL << 2),
EVT_SYSTEM_ERROR = (1UL << 3),
EVT_ALL_SENSORS = EVT_SENSOR1_READY | EVT_SENSOR2_READY
} system_events_t;
等待策略:
// 等待任意传感器数据
flags = osEventFlagsWait(events, EVT_ALL_SENSORS, osFlagsWaitAny, timeout);
// 等待所有传感器数据就绪
flags = osEventFlagsWait(events, EVT_ALL_SENSORS, osFlagsWaitAll, timeout);
// 等待但不清除标志(用于监控)
flags = osEventFlagsWait(events, EVT_SYSTEM_ERROR, osFlagsWaitAny | osFlagsNoClear, timeout);
共同的最佳实践
超时设置:
- 关键任务:portMAX_DELAY / osWaitForever
- 非关键任务:合理超时,避免永久阻塞
- 监控任务:短超时,定期执行其他工作
资源清理: - 删除前确保没有任务在等待 - 按创建顺序逆序删除 - 处理删除失败的情况
错误恢复: - 检查所有API返回值 - 实现优雅降级策略 - 记录错误信息用于调试