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返回值 - 实现优雅降级策略 - 记录错误信息用于调试