--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第12章 队列集" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 队列集 > **用生活理解**:队列集就像医院的叫号系统。你去挂号(注册队列),然后在候诊区等待。不管是哪个科室(队列/信号量)叫号,你都能听到(统一通知)。不用每个科室门口都去排队看。 ## 概念介绍 ### 基本概念 队列集(Queue Set)是 FreeRTOS 中用于管理多个队列/信号量的数据结构。它解决了"一个任务需要等待多个队列或信号量"的问题。 - **集中管理**:将多个队列、信号量注册到同一个队列集中 - **统一等待**:任务调用 `xQueueSelectFromSet()` 阻塞等待任一注册成员有数据 - **单出接口**:哪个成员有数据,返回该成员的句柄;任务再对该成员执行读操作 ### 工作原理 1. 创建队列集(指定可容纳的成员数量) 2. 创建队列、信号量等成员 3. 调用 `xQueueAddToSet()` 注册成员 4. 任务调用 `xQueueSelectFromSet()` 阻塞等待 5. 有成员数据到达 → 返回该成员句柄 → 任务对该成员执行读/取操作 **限制**: - 队列集中的成员不能独立删除(必须先移出集再删除) - 队列集的大小在创建时固定 ### 核心API | 函数 | 参数 | 功能简述 | |----------|--------|-------------| | `xQueueCreateSet()` | `uxEventQueueLength` | 创建队列集 | | `xQueueAddToSet()` | `xQueueOrSemaphore`, `xQueueSet` | 注册成员到队列集 | | `xQueueRemoveFromSet()` | `xQueueOrSemaphore`, `xQueueSet` | 从队列集中移除成员 | | `xQueueSelectFromSet()` | `xQueueSet`, `xTicksToWait` | 等待队列集中任一成员有数据 | | `xQueueSelectFromSetFromISR()` | `xQueueSet` | 中断中查询就绪成员 | ## 实验 ### 需求描述 学习使用 FreeRTOS 队列集: - **task1**:KEY1 → 往队列写入数据;KEY2 → 释放二值信号量 - **task2**:从队列集中等待,根据返回的成员句柄执行对应操作 ### 代码实现 **代码来源:**03_代码/14_队列集/Core/Src/freertos_demo.c ```c #include "queue.h" /* 队列操作头文件 */ #include "semphr.h" /* 信号量操作头文件 */ /* 任务配置 */ #define START_TASK_PRIORITY 1 #define START_TASK_STACK_DEPTH 128 TaskHandle_t start_task_handler; void Start_Task(void *pvParameters); #define TASK1_PRIORITY 2 #define TASK1_STACK_DEPTH 128 TaskHandle_t task1_handler; void Task1(void *pvParameters); #define TASK2_PRIORITY 3 #define TASK2_STACK_DEPTH 128 TaskHandle_t task2_handler; void Task2(void *pvParameters); QueueSetHandle_t queueset_handle; /* 队列集句柄 */ QueueHandle_t queue_handle; /* 普通队列句柄 */ QueueHandle_t semphr_handle; /* 二值信号量句柄 */ /* FreeRTOS入口函数 */ void FreeRTOS_Start(void) { xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* 启动任务:创建队列集、队列、信号量并注册 */ void Start_Task(void *pvParameters) { taskENTER_CRITICAL(); /* 创建队列集,可容纳2个成员 */ queueset_handle = xQueueCreateSet(2); if (queueset_handle != NULL) { printf("队列集创建成功\r\n"); } /* 创建普通队列:长度1,元素1字节 */ queue_handle = xQueueCreate(1, sizeof(uint8_t)); /* 创建二值信号量 */ semphr_handle = xSemaphoreCreateBinary(); /* 将队列和信号量注册到队列集 */ xQueueAddToSet(queue_handle, queueset_handle); xQueueAddToSet(semphr_handle, queueset_handle); xTaskCreate((TaskFunction_t)Task1, (char *)"Task1", (configSTACK_DEPTH_TYPE)TASK1_STACK_DEPTH, (void *)NULL, (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handler); xTaskCreate((TaskFunction_t)Task2, (char *)"Task2", (configSTACK_DEPTH_TYPE)TASK2_STACK_DEPTH, (void *)NULL, (UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handler); vTaskDelete(NULL); taskEXIT_CRITICAL(); } /* task1:KEY1发队列,KEY2释放信号量 */ void Task1(void *pvParameters) { uint8_t key = 0; BaseType_t err = 0; while (1) { key = Key_Detect(); if (key == KEY1_PRESS) { /* 往队列写入数据 */ err = xQueueSend(queue_handle, &key, portMAX_DELAY); if (err == pdPASS) { printf("往队列queue_handle写入数据成功\r\n"); } } else if (key == KEY2_PRESS) { /* 释放二值信号量 */ err = xSemaphoreGive(semphr_handle); if (err == pdPASS) { printf("释放信号量成功\r\n"); } } vTaskDelay(10); } } /* task2:从队列集中等待任一成员就绪 */ void Task2(void *pvParameters) { QueueSetMemberHandle_t member_handle; uint8_t key; while (1) { /* 阻塞等待队列集中任一成员有数据,永久等待 */ member_handle = xQueueSelectFromSet(queueset_handle, portMAX_DELAY); if (member_handle == queue_handle) { /* 是队列成员就绪 */ xQueueReceive(member_handle, &key, portMAX_DELAY); printf("获取到的队列数据=%d\r\n", key); } else if (member_handle == semphr_handle) { /* 是信号量成员就绪 */ xSemaphoreTake(member_handle, portMAX_DELAY); printf("获取信号量成功\r\n"); } } } ``` ### 关键配置 ```c /* FreeRTOSConfig.h */ #define configUSE_QUEUE_SETS 1 /* 使能队列集功能 */ ``` ## 核心函数速查表 | 函数 | 参数 | 返回值 | 功能简述 | |----------|-----------|--------|-------------| | `QueueSetHandle_t xQueueCreateSet(uxEventQueueLength)` | 事件队列长度 | NULL=失败 | 创建队列集 | | `BaseType_t xQueueAddToSet(xQueueOrSemaphore, xQueueSet)` | 成员句柄, 队列集句柄 | pdPASS/pdFAIL | 注册成员 | | `BaseType_t xQueueRemoveFromSet(xQueueOrSemaphore, xQueueSet)` | 同上 | pdPASS/pdFAIL | 移除成员 | | `QueueSetMemberHandle_t xQueueSelectFromSet(xQueueSet, xTicksToWait)` | 队列集句柄, 超时 | NULL=超时 | 等待就绪成员 | | `QueueSetMemberHandle_t xQueueSelectFromSetFromISR(xQueueSet)` | 队列集句柄 | NULL=无就绪 | ISR中查询就绪成员 | ## 常见问题与避坑 1. **队列集大小需足够**:`xQueueCreateSet(N)` 的 N 必须 ≥ 注册的成员总数,否则注册可能失败。 2. **已注册成员不可独立删除**:必须先 `xQueueRemoveFromSet()` 再删除,否则行为未定义。 3. **队列集不可嵌套**:不能将一个队列集注册到另一个队列集中。 4. **信号量在队列集中的使用**:二值/计数信号量均可注册到队列集,但互斥信号量不可以。 5. **Select 后必须立即操作**:`xQueueSelectFromSet()` 返回后,该成员已从等待列表中移除,若不及时读取可能导致其他任务抢先读取。