FreeRTOS(六)之队列:任务间通信的基石

前言

任务之间怎么传数据?答案是队列。它是 FreeRTOS 最核心的通信机制------信号量、互斥量底层都是队列实现的。

目录

前言

一、队列是什么

二、传输数据的两种方法

三、队列常规操作

创建队列

写队列

读队列

四、队列的阻塞访问

五、示例:队列的基本使用

[六、其他常用队列 API](#六、其他常用队列 API)

覆盖与偷看的区别

七、邮箱(覆盖模式)

八、队列集

九、总结

一、队列是什么

队列是任务间传递数据的桥梁:一个任务往队列里写数据,另一个任务从队列里读数据,双方不用关心对方什么时候处理。

cpp 复制代码
任务A ──写入──> [队列缓冲区] ──读出──> 任务B

队列的特性:

  • 先进先出(FIFO):先写入的先被读出;

  • 数据拷贝 :写入时数据被复制进队列,读出时再复制出来------队列里存的是数据的副本

  • 有长度限制:队列满了再写会失败(或阻塞),空了再读也会失败(或阻塞)。

二、传输数据的两种方法

FreeRTOS 队列传输数据有两种方式:

方式 做法 适用
直接传数据 把数据本体拷进队列 小数据(结构体、数值)
传指针 只把数据的地址拷进队列 大块数据(数组、字符串、缓冲区)

⚠️ 传指针要注意:指针指向的内存必须长期有效(全局/静态/堆内存),不能传函数局部变量------函数返回后内存就没了,队列里存的是悬空指针。

三、队列常规操作

使用队列的标准流程:创建 → 写 → 读 → 删除

创建队列

cpp 复制代码
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,  // 队列长度
                           UBaseType_t uxItemSize);    // 每个元素大小(字节)
QueueHandle_t xQueue;   // 队列句柄
xQueue = xQueueCreate(5, sizeof(int));   // 长度为 5、元素为 int 的队列

写队列

cpp 复制代码
BaseType_t xQueueSend(QueueHandle_t xQueue,   // 队列句柄
                      const void *pvItemToQueue, // 要发送的数据指针
                      TickType_t xTicksToWait);  // 阻塞等待时间(0=不等待)

读队列

cpp 复制代码
BaseType_t xQueueReceive(QueueHandle_t xQueue,
                         void *pvBuffer,         // 接收缓冲区
                         TickType_t xTicksToWait);

四、队列的阻塞访问

队列操作支持阻塞------这是它最强大的地方:

  • 写队列时队列满 → 任务进入阻塞态,等队列有空位(最多等 xTicksToWait 时间);

  • 读队列时队列空 → 任务进入阻塞态,等有数据(最多等 xTicksToWait 时间);

  • 超时还没等到 → 返回 pdFALSE(失败)。

cpp 复制代码
int num;
// 阻塞等待 1000ms 内的数据,没有就返回 pdFALSE
if (xQueueReceive(xQueue, &num, pdMS_TO_TICKS(1000)) == pdPASS)
{
    printf("Received: %d\n", num);
}

阻塞期间任务不占 CPU------调度器会去运行其他就绪任务,这就是 RTOS 高效的来源。

五、示例:队列的基本使用

本示例创建 1 个队列、2 个发送任务、1 个接收任务:发送任务优先级 1,分别往队列写入 100、200;接收任务优先级 2,读队列并打印。

cpp 复制代码
QueueHandle_t xQueue;
​
void vSenderTask(void *pvParameters)   // 两个发送任务共用
{
    int num = *(int *)pvParameters;
    while (1)
    {
        xQueueSend(xQueue, &num, portMAX_DELAY);   // 无限等待
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}
​
void vReceiverTask(void *pvParameters)
{
    int num;
    while (1)
    {
        xQueueReceive(xQueue, &num, portMAX_DELAY);
        printf("Received: %d\n", num);
    }
}
​
int main(void)
{
    static int num1 = 100, num2 = 200;
    xQueue = xQueueCreate(5, sizeof(int));
​
    xTaskCreate(vSenderTask, "sender1", 128, &num1, 1, NULL);
    xTaskCreate(vSenderTask, "sender2", 128, &num2, 1, NULL);
    xTaskCreate(vReceiverTask, "receiver", 128, NULL, 2, NULL);
    vTaskStartScheduler();
}

运行结果:接收任务(优先级 2)先运行,阻塞等数据;发送任务写入 100、200,接收任务依次打印。两个发送任务轮流发送,接收任务按 FIFO 读出。

六、其他常用队列 API

函数 功能 说明
xQueuePeek() 偷看 :读出但不移除数据 多任务共享读同一数据时用
xQueueOverwrite() 覆盖:直接覆盖队首数据 仅限队列长度为 1 时使用,不会阻塞
xQueueReset() 复位队列 清空所有数据
uxQueueMessagesWaiting() 查询队列中有多少数据 调试用
uxQueueSpacesAvailable() 查询队列剩余空间 调试用
vQueueDelete() 删除队列 释放内存
xQueueSendFromISR() 在中断里写队列 中断中使用,详见后文

覆盖与偷看的区别

  • 覆盖(xQueueOverwrite) :队列长度必须为 1,队列满时新数据直接覆盖旧数据------不会阻塞,适合"只保留最新值"的场景(如最新的传感器读数);

  • 偷看(xQueuePeek) :从队列复制出数据但不移除,数据留给后面的人读。队列空时偷看会阻塞;一旦有数据,以后每次偷看都成功。

七、邮箱(覆盖模式)

队列长度为 1 + 覆盖写入 = 邮箱:只存最新的一份数据,新数据来了直接顶掉旧的。常用于"最新状态通知"(如最新温度、最新配置)。

八、队列集

队列集(Queue Set):把多个队列"打包"成一个集合,一个任务可以同时等待多个队列:

cpp 复制代码
QueueSetHandle_t xQueueSet = xQueueCreateSet(10);   // 创建队列集
xQueueAddToSet(xQueue1, xQueueSet);                  // 把队列加入集合
xQueueAddToSet(xQueue2, xQueueSet);
xQueueSetMemberHandle_t xMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
// 返回哪个队列有数据,再针对该队列读取

适用场景:一个任务要监听多个数据源(串口、按键、传感器),用队列集就不用开多个任务。

九、总结

  • 队列 = 任务间传数据的先进先出缓冲区,数据拷贝进出;

  • 阻塞访问是核心:满/空时任务自动阻塞,不浪费 CPU;

  • 小数据直接传,大块数据传指针(注意指针生命周期);

  • 队列长度 1 + 覆盖 = 邮箱(存最新值);

  • 队列集让一个任务同时等多个队列。

相关推荐
桃蹊、1 天前
FreeRTOS(二)之堆和栈:内存基础入门
freertos·内存管理··
桃蹊、1 天前
FreeRTOS(四)之任务管理(上):创建、删除与任务参数
freertos·任务·task·创建任务
论迹复利3 天前
PendSV与RISC-V trap——上下文切换的两种设计哲学
内核·freertos·risc-v
捷瑞电子工坊6 天前
FreeRTOS中断管理:从原理到实践(标准库版)
嵌入式·freertos·实时操作系统·任务通知·临界区·中断管理·二值信号量
捷瑞电子工坊7 天前
FreeRTOS 内存管理详解:从 heap_1 到 heap_5 的选择与实践
freertos·内存管理·嵌入式开发·内存碎片·嵌入式实时操作系统·动态内存分配
qq_401700417 天前
FreeRTOS队列集--允许任务同时在多个队列或信号量上阻塞等待读取操作
freertos
hsjiasb8 天前
FreeRTOS学习(三十九)——最终总结
学习·学习笔记·freertos
林森lsjs8 天前
队列 FIFO 原理 & 手撕两种队列实现(链表 + 循环数组)—数据结构陆
java·开发语言·数据结构·队列
捷瑞电子工坊8 天前
FreeRTOS事件组:多任务同步的利器
stm32·嵌入式·freertos·实时操作系统·标准外设库·事件组·任务同步