【FreeRTOS队列】
队列是 FreeRTOS 提供的一种异步通信缓冲区,可以理解为一个 “线程安全的消息管道”,设置队列的目的是为了实现任务间的安全通信、资源管理、任务调度与协调、中断处理与任务交互,以及提高系统的可靠性和可维护性。任务 / 中断可以向队列发送(写入)数据;任务可以从队列接收(读取)数据;队列内置互斥保护,多个任务同时访问也不会出现数据错乱。
队列核心概念
1. 定义
队列是 FreeRTOS 提供的一种异步通信缓冲区,可以理解为一个 “线程安全的消息管道”,设置队列的目的是为了实现任务间的安全通信、资源管理、任务调度与协调、中断处理与任务交互,以及提高系统的可靠性和可维护性。
- 任务 / 中断可以向队列发送(写入) 数据;
- 任务可以从队列接收(读取) 数据;
- 队列内置互斥保护,多个任务同时访问也不会出现数据错乱。
2. 关键特性
| 特性 | 说明 |
|---|---|
| 数据拷贝(而非指针) | 默认情况下,队列会把数据完整拷贝到队列缓冲区(而非传递指针),保证数据安全性(即使发送方数据销毁,接收方仍能拿到完整数据);若传递大数据,建议发送数据指针(减少拷贝开销)。 |
| FIFO 默认规则 |
队列可以保存有限数量的固定大小的数据项。把队列能够保存的数据项的最大数量称为队列的“长度”。在创建队列时,会设置其长度以及每个数据项的大小 数据默认 “先进先出”(先写入的先读取),也可配置为 “后进先出”(LIFO)。 |
| 多向通信 | 支持 “多发一收”“一发多收”“多发多收”,是通用的通信方式。 |
| 阻塞机制 |
发送 / 接收时可设置超时时间: - 发送:队列满时,任务可阻塞等待队列有空位; - 接收:队列空时,任务可阻塞等待队列有数据; 阻塞期间任务进入 “阻塞态”,不占用 CPU 资源。 队列可以被分组成集,允许任务进入阻塞状态,以等待队列集中的任意一个队列的数据变得可用。 |
| 独立于任务 | 队列不属于任何任务,只要持有队列句柄,任务 / 中断均可访问。 |
3. 核心参数
创建队列时需指定两个关键参数:
- 队列长度:队列最多能存放的数据项数量(比如长度为 5 的队列,最多存 5 个数据项);
- 项大小:每个数据项的字节数(比如传递
int类型,项大小为sizeof(int);传递结构体,项大小为sizeof(结构体名))。
队列的函数
所有队列 API 均需包含头文件 #include "queue.h",核心 API 分为任务级(普通任务中调用)和中断级(中断服务函数中调用,后缀带 FromISR)。
创建函数
/* 动态创建 */
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
| 参数名称 | 参数说明 |
|---|---|
| uxQueueLength | 正在创建的队列可以容纳的数据项的最大数量。 |
| uxItemSize | 存储在队列中的每个数据项的大小,以字节为单位。 |
| 返回值 | 如果返回NULL,表示无法创建队列,因为没有足够的可用堆内存供FreeRTOS分配队列数据结构和存储区域。如果返回非NULL值,则表示队列已成功创建,应将返回值作为已创建队列的句柄存储起来。 |
xQueueCreate () API函数的用法:
struct AMessage
{
char ucMessageID;
char ucData[ 20 ];
};
void vATask( void *pvParameters )
{
QueueHandle_t xQueue1, xQueue2;
/* 创建一个队列,能够存储10个unsigned long类型的值。 */
xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );
if( xQueue1 == NULL )
{
/* 队列创建失败,不能使用。 */
}
/* 创建一个队列,能够存储10个指向AMessage结构的指针。
这些指针将被排队,因为它们是相对较大的结构体。 */
xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );
if( xQueue2 == NULL )
{
/* 队列创建失败,不能使用。 */
}
/* ... 任务的其余代码。 */
}
/* 静态创建 */
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer );
| 参数名称 | 参数说明 |
|---|---|
| uxQueueLength | 正在创建的队列可以容纳的数据项的最大数量。 |
| uxItemSize | 存储在队列中的每个数据项的大小,以字节为单位。 |
| pucQueueStorageBuffer | 如果 uxItemSize 非零,则 pucQueueStorageBuffer必须指向一个至少足够大的 uint8_t 数组,以容纳队列中可以同时存在的最大项数,即 (uxQueueLength * uxItemSize) 个字节。 如果 uxItemSize 为零,则 pucQueueStorageBuffer 可以为 NULL。 |
| pxQueueBuffer | 必须指向 StaticQueue_t 类型的变量, 该变量将用于保存队列的数据结构体。 |
| 返回值 | 如果队列创建成功,则返回所创建队列的句柄。如果pxQueueBuffer 为 NULL,则返回 NULL。 |
xQueueCreateStatic () API函数的用法:
/* 队列将被创建以容纳最多10个uint64_t类型的变量。 */
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint64_t )
/* 用于保存队列数据结构的变量。 */
static StaticQueue_t xStaticQueue;
/* 作为队列的存储区域使用的数组。这必须至少有
uxQueueLength * uxItemSize 字节的大小。 */
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
void vATask( void *pvParameters )
{
QueueHandle_t xQueue;
/* 创建一个能够包含10个uint64_t值的队列。 */
xQueue = xQueueCreateStatic( QUEUE_LENGTH,
ITEM_SIZE,
ucQueueStorageArea,
&xStaticQueue );
/* 因为队列创建成功,所以xQueue不应该是NULL。 */
configASSERT( xQueue );
}
| 对比维度 | xQueueCreate() |
xQueueCreateStatic() |
|---|---|---|
| 内存分配方式 | 动态分配:从 FreeRTOS 堆中自动分配队列所需内存(包括队列控制块、数据存储区) | 静态分配:需用户手动提供队列控制块、数据存储区的内存(内存需提前定义,如全局数组) |
| 参数要求 | 仅需传入 “队列长度”“数据项大小” 两个参数 | 除 “队列长度”“数据项大小” 外,还需传入 “队列控制块内存地址”“数据存储区内存地址” |
| 返回值逻辑 | 成功返回队列句柄,堆内存不足时返回NULL |
只要用户提供的内存有效,返回队列句柄;若内存地址无效,返回NULL(通常由用户确保内存合法性) |
| 使用前置条件 | 需开启 FreeRTOS 动态内存配置(configSUPPORT_DYNAMIC_ALLOCATION = 1) |
需开启 FreeRTOS 静态内存配置(configSUPPORT_STATIC_ALLOCATION = 1) |
| 内存管理特性 | 无需用户管理内存,但可能产生堆内存碎片 | 无内存碎片问题,但需用户提前规划内存空间,灵活性较低 |
数据发送(任务级)
| API 函数 | 作用 |
|---|---|
xQueueSend() / xQueueSendToBack() |
向队列尾部发送数据(FIFO 模式,最常用) |
xQueueSendToFront() |
向队列头部发送数据(LIFO 模式) |
函数原型(以 xQueueSend 为例)
BaseType_t xQueueSend(
QueueHandle_t xQueue, // 队列句柄
const void *pvItemToQueue, // 要发送的数据地址
TickType_t xTicksToWait // 阻塞超时时间(tick数)
);
| 参数名称 | 参数说明 |
|---|---|
| xQueue | 正在将数据发送(写入)到队列,这个参数是该队列的句柄。队列句柄从用于创建队列的 xQueueCreate() 或 xQueueCreateStatic() 调用中返回。 |
| pvItemToQueue | 指向要被复制到队列中的数据的指针。队列能容纳的每个数据项的大小是在创建队列时设置的,pvItemToQueue指向的数据将复制到队列的存储区。 |
| xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,使用宏 pdMS_TO_TICKS() 把以毫秒为单位的时间转换为以滴答为单位的时间。如果 INCLUDE_vTaskSuspend 设置为 “1” , 则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
| 返回值 | 如果发送成功,则返回pdPASS。如果队列已满,则返回errQUEUE_FULL。 |
数据发送(中断级)
中断中必须使用带 FromISR 后缀的 API,避免阻塞(中断中不能阻塞):
BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken // 标记是否需要触发任务切换
);
pxHigherPriorityTaskWoken:若发送数据后唤醒了更高优先级的任务,该变量会被设为pdTRUE,中断结束前需调用portYIELD_FROM_ISR()触发调度;- 超时时间固定为 0(中断中不允许等待)。
数据接收(任务级)
注意:不要在中断服务程序中调用 xQueueReceive() API函数。可以采用中断安全的 xQueueReceiveFromISR() API函数。
函数xQueueReceive()
BaseType_t xQueueReceive(
QueueHandle_t xQueue, // 队列句柄
void *pvBuffer, // 接收数据的缓冲区地址
TickType_t xTicksToWait // 阻塞超时时间
);
| 参数名称 | 参数说明 |
|---|---|
| xQueue | 正在从队列中接收(读取)数据的队列句柄。 |
| pvBuffer | 指向接收到的数据项的缓冲区的指针。队列能容纳的每个数据项的大小是在创建队列时设置的,pvBuffer指向的缓冲区必须足够大以容纳队列中存储的每个数据项。 |
| xTicksToWait | 如果在调用时队列为空,则任务应阻塞等待项目接收的最长时间。如果队列为空,将 xTicksToWait设置为 0 将导致函数立即返回。时间是以滴答周期为单位定义的,使用宏 pdMS_TO_TICKS() 把以毫秒为单位的时间转换为以滴答为单位的时间。如果 INCLUDE_vTaskSuspend 设置为 “1”,则将阻塞时间指定为 portMAX_DELAY会导致任务无限期地阻塞(没有超时)。 |
| 返回值 | 如果从队列中成功接收项目,则返回pdPASS。如果在调用时队列为空,返回pdFAIL |
查询函数
用于查询队列中当前数据项的数量。 注意:不要在中断服务程序中调uxQueueMessagesWaiting() API函数。应该使用中断安全版本的 uxQueueMessagesWaitingFromISR() API函数来替代。
uxQueueMessagesWaiting() API函数的原型如下所示:
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
| 参数名称 | 参数说明 |
|---|---|
| xQueue | 要查询的队列的句柄。 |
| 返回值 | 被查询队列当前的数据项的数量。如果返回0,则队列为空。 |
uxQueueSpacesAvailable()查询队列剩余可用空间数量参数同上。
xQueueReset()用于重置队列状态的核心操作函数,作用是将队列恢复到创建时的初始状态,常用于队列复用、异常处理后清空队列等场景。
队列集函数
函数xQueueCreateSet()
队列集由句柄引用,这些句柄是QueueSetHandle_t类型的变量。xQueueCreateSet() API 函数用于创建队列集,并返回QueueSetHandle_t类型的变量,该变量引用了函数创建的队列集。xQueueCreateSet() API 函数的原型如下所示:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength);
xQueueCreateSet() API函数的参数与返回值说明如下:
| 参数名称 | 参数说明 |
|---|---|
| uxEventQueueLength | 当作为队列集成员的队列接收到数据时,接收队列的句柄会被发送到队列集。uxEventQueueLength定义了正在创建的队列集可以容纳的所有队列集成员的数据项的总和。队列集容纳数据项的最大数量就是队列集中每个队列的长度之和。例如,如果队列集中有3个空队列,并且每个队列的长度为5,那么在队列集中的所有队列都满之前,队列集中的队列总共可以接收15个数据项(3个队列各乘以5个数据项)。在这个例子中,uxEventQueueLength必须设置为15,以保证队列集能够接收发送到它的每个数据项。 |
| 返回值 | 如果返回NULL,则无法创建队列集,因为没有足够的堆内存供FreeRTOS来分配队列集的数据结构和存储区域。如果返回非NULL值,则队列集已成功创建,并且返回的值是所创建队列集的句柄。 |
下面代码示例展示了xQueueCreateSet() API函数的用法:
/* 定义将要添加到队列集合中的队列的长度。 */
#define QUEUE_LENGTH_1 10
#define QUEUE_LENGTH_2 10
/* 一个有效的长度为1二进制信号量。 */
#define BINARY_SEMAPHORE_LENGTH 1
/* 分别定义队列1和队列2所持有的项的大小。
这里使用的值仅用于演示目的。 */
#define ITEM_SIZE_QUEUE_1 sizeof( uint32_t )
#define ITEM_SIZE_QUEUE_2 sizeof( something_else_t )
/* 将要添加到队列集合中的2个队列和二进制信号量的总长度。 */
#define COMBINED_LENGTH ( QUEUE_LENGTH_1 +
QUEUE_LENGTH_2 +
BINARY_SEMAPHORE_LENGTH )
void vAFunction( void )
{
static QueueSetHandle_t xQueueSet;
QueueHandle_t xQueue1, xQueue2, xSemaphore;
QueueSetMemberHandle_t xActivatedMember;
uint32_t xReceivedFromQueue1;
something_else_t xReceivedFromQueue2;
/* 创建一个足够大的队列集合,以容纳要添加到集合中的每个
队列和信号量的每个空间的事件。 */
xQueueSet = xQueueCreateSet( COMBINED_LENGTH );
/* 创建将包含在集合中的队列和信号量。 */
xQueue1 = xQueueCreate( QUEUE_LENGTH_1, ITEM_SIZE_QUEUE_1 );
xQueue2 = xQueueCreate( QUEUE_LENGTH_2, ITEM_SIZE_QUEUE_2 );
/* 创建要添加到集合中的信号量。 */
xSemaphore = xSemaphoreCreateBinary();
/* 检查是否已创建所有内容。 */
configASSERT( xQueueSet );
configASSERT( xQueue1 );
configASSERT( xQueue2 );
configASSERT( xSemaphore );
/* 将队列和信号量添加到集合中。从这些队列和信号量中读取操作只能在调用
xQueueSelectFromSet() 后进行,该函数将从这一点开始返回队列或信号量的句柄。 */
xQueueAddToSet( xQueue1, xQueueSet );
xQueueAddToSet( xQueue2, xQueueSet );
xQueueAddToSet( xSemaphore, xQueueSet );
for( ;; )
{
/* 阻塞等待,直到从已添加到集合的队列或信号量中有内容可用。不要阻塞超过200毫秒。 */
xActivatedMember = xQueueSelectFromSet( xQueueSet,200 / portTICK_PERIOD_MS );
/* 选择了哪个集合成员?接收/获取操作可以使用零阻塞时间,因为它们保证会通过,因为
除非有内容可用,否则 xQueueSelectFromSet() 不会返回句柄。 */
if( xActivatedMember == xQueue1 )
{
xQueueReceive( xActivatedMember, &xReceivedFromQueue1, 0 );
vProcessValueFromQueue1( xReceivedFromQueue1 );
}
else if( xActivatedMember == xQueue2 )
{
xQueueReceive( xActivatedMember, &xReceivedFromQueue2, 0 );
vProcessValueFromQueue2( &xReceivedFromQueue2 );
}
else if( xActivatedMember == xSemaphore )
{
/* 获取信号量以确保它可以再次被“释放”。 */
xSemaphoreTake( xActivatedMember, 0 );
vProcessEventNotifiedBySemaphore();
break;
}
else
{
/* 200毫秒的阻塞时间已过,但没有一个 RTOS 队列或信号量准备好进行处理。 */
}
}
}
函数xQueueAddToSet()
xQueueAddToSet() API函数将一个队列或信号量添加到队列集中。信号量的相关内容将在教程的后续部分进行描述。xQueueAddToSet () API 函数的原型如下所示:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );
xQueueAddToSet () API函数的参数与返回值说明如下所示:
| 参数名称 | 参数说明 |
|---|---|
| xQueue或Semaphore | 添加到队列集中的队列或信号量的句柄。队列句柄和信号量句柄都可以强制转换为QueueSetMemberHandle_t类型。 |
| xQueueSet | 将队列或信号量加入队列集,这个参数是队列集的句柄。 |
| 返回值 | 有两种可能的返回值:pdPASS-表示队列集已成功创建。pdFAIL-表示队列或信号量无法添加到队列集合中。队列和二进制信号量只有在它们为空时,才能添加到队列集中。计数信号量只有在它们的计数为0时,才能添加到队列集中。向队列集添加成员时,一次只能是队列或信号量。 |
函数xQueueSelectFromSet()
当需要同时等待多个队列 / 信号量的事件(如:多个队列的 “数据可读”、多个信号量的 “可用”)时,无需创建多个阻塞任务,只需通过一个队列集合管理这些对象,调用 xQueueSelectFromSet() 即可阻塞等待,直到任一对象触发事件或超时。
QueueSetMemberHandle_t xQueueSelectFromSet(
QueueSetHandle_t xQueueSet, // 队列集合句柄
const BaseType_t xWaitForMessage, // 等待类型:读/写
const TickType_t xTicksToWait // 阻塞超时时间(时钟节拍数)
);
| 参数 | 说明 |
|---|---|
xQueueSet |
已创建的「队列集合」句柄(通过 xQueueCreateSet() 创建),不可为 NULL。 |
xWaitForMessage |
等待类型:- pdTRUE:等待队列 / 信号量「可读」(接收数据 / 获取信号量);- pdFALSE:等待队列 / 信号量「可写」(发送数据 / 释放信号量)。 |
xTicksToWait |
阻塞等待的最大时钟节拍数:- 0:不阻塞,立即返回;- portMAX_DELAY:永久阻塞(直到有事件触发);- 其他值:对应实际时间(需结合 configTICK_RATE_HZ 换算,如 100ms = 100 * configTICK_RATE_HZ / 1000)。 |
| 返回值类型 | 说明 |
|---|---|
QueueSetMemberHandle_t |
成功:返回触发事件的「队列 / 信号量句柄」(需强制转换为对应类型,如 QueueHandle_t 或 SemaphoreHandle_t);失败:返回 NULL(原因:超时未触发任何事件,或参数非法)。 |
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)