
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[3.2.1、创建队列集 xQueueCreateSet](#3.2.1、创建队列集 xQueueCreateSet)
[3.2.2、把队列加入队列集 xQueueAddToSet](#3.2.2、把队列加入队列集 xQueueAddToSet)
[3.2.3、读取队列集 xQueueSelectFromSet](#3.2.3、读取队列集 xQueueSelectFromSet)
前言
在 FreeRTOS 多任务系统中,任务之间不能直接互相访问变量 ,否则会出现数据竞争、死机、打印乱码等问题。队列就是 FreeRTOS 提供的最基础、最核心、最通用的任务间通信工具,掌握队列,信号量、互斥锁、消息邮箱都能一通百通。
一、队列概述
队列(Queue) 是一种用于任务间通信的数据结构。本质上,它像一个 "带锁的管道" 或 "安全的消息邮箱",允许一个任务发送数据、另一个任务接收数据,且不会发生数据冲突。队列是线程安全的。
队列可以包含若干个数据:队列中有若干项,这被称为长度。每个数据大小固定。
创建队列时就要指定长度、数据大小。
数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读。也可以强制写队列头部:覆盖头部数据。
多任务访问 : 队列不属于某个任务,任何任务和中断都可以向队列写入/读取消息。

队列中,数据的读写本质就是环形缓冲区,在此基础上增加了互斥措施、阻塞 - 唤醒机制。
如果队列不传输数据,仅调整 "数据个数"(只传递状态) ,它就是信号量(semaphore)。
如果信号量中限定 "数据个数" 的最大值为 1,它就是互斥量(mutex)。
队列通常用作环形****FIFO(先进先出) 缓冲区,其中数据被写入队列的末尾(尾部) ,此时尾部就变成头部了,并从队列的前端(头部)删除。
二、队列函数
2.1、创建队列
队列的创建有两种方法:动态分配内存、静态分配内存。
2.1.1、动态分配内存
这是最常用的方式,内存由 FreeRTOS 内核自动分配,用完自动回收,省心省力。
函数原型:
cpp
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength, // 队列长度:最多存多少个数据
UBaseType_t uxItemSize // 每个数据的大小(字节)
);
参数说明:
| 参数 | 说明 |
|---|---|
uxQueueLength |
队列的最大容量,比如填 10,就表示最多能存 10 条消息 |
uxItemSize |
单条消息的大小,比如存 uint32_t 就是 4,存自定义结构体就是 sizeof(MyStruct) |
返回值
非 NULL:创建成功,返回队列句柄,后续操作都用这个句柄。
NULL:创建失败,通常是堆内存不足。
2.1.2、静态分配
如果你做的是高可靠性项目,不想依赖堆内存,就可以用静态方式,自己提前定义好内存缓冲区。
cpp
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer, // 数据存储缓冲区
StaticQueue_t *pxQueueBuffer // 队列控制块
);
参数说明
| 参数 | 说明 |
|---|---|
uxQueueLength / uxItemSize |
和动态创建的参数含义完全一样 |
pucQueueStorageBuffer |
你自己定义的 uint8_t 数组,大小必须是 uxQueueLength * uxItemSize |
pxQueueBuffer |
必须是一个 StaticQueue_t 类型的结构体变量,用来保存队列的元数据 |
2.2、队列的写入函数
创建好队列后,我们就可以往里面发数据了。FreeRTOS 提供了一套 xQueueSend 系列函数,功能非常丰富。
| 函数 | 描述 |
|---|---|
xQueueSend() / xQueueSendToBack() |
往队列的尾部写入消息(标准 FIFO) |
xQueueSendToFront() |
往队列的头部写入消息(插队) |
xQueueOverwrite() |
覆写队列消息(仅用于队列长度为 1 的情况) |
xQueueSendFromISR() / xQueueSendToBackFromISR() |
在中断服务函数中往队列尾部写入消息 |
xQueueSendToFrontFromISR() |
在中断中往队列头部写入消息 |
xQueueOverwriteFromISR() |
在中断中覆写队列消息(仅用于队列长度为 1 的情况) |
2.3、队列的读取(接收)函数
发送了数据,自然就要读取。FreeRTOS 也提供了一套读取 API,核心是 xQueueReceive。
| 函数 | 描述 |
|---|---|
xQueueReceive() |
从队列头部读取消息,并删除消息 |
xQueuePeek() |
从队列头部读取消息,但不删除消息(数据还在队列里) |
xQueueReceiveFromISR() |
在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() |
在中断中从队列头部读取消息,不删除 |
cpp
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
);
| 参数 | 说明 |
|---|---|
xQueue |
队列句柄,要读哪个队列 |
pvBuffer |
数据接收缓冲区,队列的数据会被复制到这里(大小在创建队列时已指定) |
xTicksToWait |
阻塞超时时间:队列为空时,任务最多等待多久 |
| 返回值 | pdPASS:成功读取;pdFALSE:超时失败 |
三、队列集
普通队列只能传递同一种数据类型,而且一个任务只能阻塞等待一个队列。如果你的任务需要同时监听多个队列(比如同时接收按键、串口、传感器数据),就需要用到队列集(Queue Set)。
3.1、队列集使用步骤
启用队列集:在 FreeRTOSConfig.h 中,将 configUSE_QUEUE_SETS 配置为 1。
创建队列集:xQueueCreateSet()
创建队列 / 信号量
将队列 / 信号量加入队列集:xQueueAddToSet()
发送数据 / 释放信号量
从队列集中读取句柄:xQueueSelectFromSet()
3.2、队列集核心API函数
3.2.1、创建队列集 xQueueCreateSet
cpp
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
uxEventQueueLength:队列集的长度,等于要加入的队列 / 信号量的总数。
返回值:队列集句柄,失败返回 NULL。
3.2.2、把队列加入队列集 xQueueAddToSet
cpp
BaseType_t xQueueAddToSet(
QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet
);
xQueueOrSemaphore:要加入的队列或信号量句柄
xQueueSet:队列集句柄
返回值:pdTRUE 成功,pdFALSE 失败
3.2.3、读取队列集 xQueueSelectFromSet
cpp
QueueSetMemberHandle_t xQueueSelectFromSet(
QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait
);
xQueueSet:队列集句柄
xTicksToWait:阻塞超时时间
返回值:有数据的队列 / 信号量句柄,失败返回 NULL
3.3、示例
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define QUEUE1_LENGTH 5
#define QUEUE2_LENGTH 5
#define ITEM_SIZE sizeof(uint32_t)
QueueHandle_t g_Queue1, g_Queue2;
QueueSetHandle_t g_QueueSet;
// 生产者任务1:往Queue1发数据
void vProducer1(void *pvParameters)
{
uint32_t count = 0;
while(1)
{
count++;
xQueueSend(g_Queue1, &count, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 生产者任务2:往Queue2发数据
void vProducer2(void *pvParameters)
{
uint32_t count = 100;
while(1)
{
count++;
xQueueSend(g_Queue2, &count, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(700));
}
}
// 消费者任务:监听队列集,处理两个队列的数据
void vConsumer(void *pvParameters)
{
QueueSetMemberHandle_t xReceivedQueue;
uint32_t data;
while(1)
{
// 阻塞等待队列集里有事件
xReceivedQueue = xQueueSelectFromSet(g_QueueSet, portMAX_DELAY);
if (xReceivedQueue == g_Queue1)
{
xQueueReceive(g_Queue1, &data, 0);
// 处理Queue1的数据
}
else if (xReceivedQueue == g_Queue2)
{
xQueueReceive(g_Queue2, &data, 0);
// 处理Queue2的数据
}
}
}
void app_main()
{
// 1. 创建两个队列
g_Queue1 = xQueueCreate(QUEUE1_LENGTH, ITEM_SIZE);
g_Queue2 = xQueueCreate(QUEUE2_LENGTH, ITEM_SIZE);
// 2. 创建队列集,长度为2(要监听2个队列)
g_QueueSet = xQueueCreateSet(2);
// 3. 将队列加入队列集
xQueueAddToSet(g_Queue1, g_QueueSet);
xQueueAddToSet(g_Queue2, g_QueueSet);
// 4. 创建任务
xTaskCreate(vProducer1, "Producer1", 1024, NULL, 2, NULL);
xTaskCreate(vProducer2, "Producer2", 1024, NULL, 2, NULL);
xTaskCreate(vConsumer, "Consumer", 1024, NULL, 3, NULL);
vTaskStartScheduler();
}
总结
本期博客主要对FreeRTOS中的队列进行了详解。