12-队列集.md 7.3 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第12章 队列集" author: "尚硅谷研究院" date: 2026-07-17

created: 2026-07-17

队列集

用生活理解:队列集就像医院的叫号系统。你去挂号(注册队列),然后在候诊区等待。不管是哪个科室(队列/信号量)叫号,你都能听到(统一通知)。不用每个科室门口都去排队看。

Concept

Basic concept

队列集(Queue Set)是 FreeRTOS 中用于管理多个队列/信号量的数据结构。它解决了"一个任务需要等待多个队列或信号量"的问题。

  • 集中管理:将多个队列、信号量注册到同一个队列集中
  • 统一等待:任务调用 xQueueSelectFromSet() 阻塞等待任一注册成员有数据
  • 单出接口:哪个成员有数据,返回该成员的句柄;任务再对该成员执行读操作

Working principle

  1. 创建队列集(指定可容纳的成员数量)
  2. 创建队列、信号量等成员
  3. 调用 xQueueAddToSet() 注册成员
  4. 任务调用 xQueueSelectFromSet() 阻塞等待
  5. 有成员数据到达 → 返回该成员句柄 → 任务对该成员执行读/取操作

限制

  • 队列集中的成员不能独立删除(必须先移出集再删除)
  • 队列集的大小在创建时固定

Core API

Function Params Description
xQueueCreateSet() uxEventQueueLength 创建队列集
xQueueAddToSet() xQueueOrSemaphore, xQueueSet 注册成员到队列集
xQueueRemoveFromSet() xQueueOrSemaphore, xQueueSet 从队列集中移除成员
xQueueSelectFromSet() xQueueSet, xTicksToWait 等待队列集中任一成员有数据
xQueueSelectFromSetFromISR() xQueueSet 中断中查询就绪成员

Experiment

Requirements

学习使用 FreeRTOS 队列集:

  • task1:KEY1 → 往队列写入数据;KEY2 → 释放二值信号量
  • task2:从队列集中等待,根据返回的成员句柄执行对应操作

Code Implementation

File: P14/14_队列集/Core/Src/freertos_demo.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");
        }
    }
}

Key Config

/* FreeRTOSConfig.h */
#define configUSE_QUEUE_SETS    1   /* 使能队列集功能 */

Core API Reference Table

Function Parameters Return Description
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中查询就绪成员

Common Issues & Pitfalls

  1. 队列集大小需足够xQueueCreateSet(N) 的 N 必须 ≥ 注册的成员总数,否则注册可能失败。
  2. 已注册成员不可独立删除:必须先 xQueueRemoveFromSet() 再删除,否则行为未定义。
  3. 队列集不可嵌套:不能将一个队列集注册到另一个队列集中。
  4. 信号量在队列集中的使用:二值/计数信号量均可注册到队列集,但互斥信号量不可以。
  5. Select 后必须立即操作xQueueSelectFromSet() 返回后,该成员已从等待列表中移除,若不及时读取可能导致其他任务抢先读取。