FreeRTOS队列详解:从原理到实战
1.1 前课准备
1.1.1 课程内容
了解 FreeRTOS 的队列,掌握 FreeRTOS 队列的使用场景,学会如何利用队列实现任务间的通信,熟悉队列的 API。
1.1.2 课程资料
《FreeRTOS 中文实战教程》第 2 章。
1.2 FreeRTOS 队列概述
1.2.1 使用背景
在实际的应用中,常常会遇到一个任务或者中断服务函数需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候,两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息,就会涉及到“资源管理”的问题。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。
1.2.2 队列介绍
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息。队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列是用来传递消息的,所以也称为消息队列。

1.2.3 数据存储
队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。
往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据则是把队列中的数据拷贝删除。(读出一个字节数据,队列中的数据就少一个)
1.2.4 多任务操作队列
队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。
1.2.5 读阻塞
当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,所以对单个队列而言,可能存在多个任务处于阻塞状态,以等待数据有效。这种情况下,一旦队列数据有效,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
1.2.6 写阻塞
同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态,以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

1.3 队列的 API
1.3.1 创建队列
函数原型
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize
);
函数功能
创建一个队列深度为 uxQueueLength、数据大小为 uxItemSize 的队列。
函数参数
- uxQueueLength:队列深度,保存最大的数据单元数目。
- uxItemSize:每个数据单元的大小,单位是字节。
函数说明
xQueueGenericCreate(
( uxQueueLength ), // 队列深度
( uxItemSize ), // 每个数据单元大小
( queueQUEUE_TYPE_BASE ) // 队列类型
)
函数返回值
- NULL:表示没有足够的堆空间分配给队列而导致创建失败。
- 非 NULL:创建成功,返回队列的句柄。
示例
定义队列的句柄变量:
QueueHandle_t Queue_t;
创建队列:
Queue_t = xQueueCreate(10, 5);
1.3.2 写入数据
![]()
函数原型
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait
);
函数功能
向指定的队列写入数据,写入的是队列的队尾。
函数参数
- QueueHandle_t xQueue:写入的队列的句柄。
- const void * pvItemToQueue:写入的数据缓存空间。
- TickType_t xTicksToWait:超时时间。
函数说明
xQueueGenericSend(
( xQueue ),
( pvItemToQueue ),
( xTicksToWait ),
queueSEND_TO_BACK
);
函数返回值
- pdPASS:数据被成功发送到队列。
- errQUEUE_FULL:1、写阻塞,没有设置超时时间;2、写阻塞,超时时间到达。
中断服务函数专用
xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
![]()
函数原型
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait
);
函数功能
向指定的队列写入数据,写入的是队列的队尾。
函数参数
- QueueHandle_t xQueue:写入的队列的句柄。
- const void * pvItemToQueue:写入的数据缓存空间。
- TickType_t xTicksToWait:超时时间。
函数说明
xQueueGenericSend(
( xQueue ),
( pvItemToQueue ),
( xTicksToWait ),
queueSEND_TO_BACK
);
xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
![]()
函数原型
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait
);
函数功能
向指定的队列写入数据,写入的是队列的队首。
函数参数
- QueueHandle_t xQueue:写入的队列的句柄。
- const void * pvItemToQueue:写入的数据缓存空间。
- TickType_t xTicksToWait:超时时间。
函数说明
xQueueGenericSend(
( xQueue ),
( pvItemToQueue ),
( xTicksToWait ),
queueSEND_TO_FRONT
);
xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
示例
定义队列的句柄变量:
QueueHandle_t Queue_t;
创建队列:
Queue_t = xQueueCreate(10, 5);
向队列写入数据:
xQueueSend(Queue_t, "ABCDE", 0);
1.3.3 读取数据
函数原型
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
);
函数功能
从指定的队列读取数据,读取的是队列的队首,并删除读取的数据。
函数参数
- QueueHandle_t xQueue:读取的队列的句柄。
- void * const pvBuffer:读取的数据存放缓存空间。
- TickType_t xTicksToWait:超时时间。
函数返回值
- pdPASS:数据被成功从队列读取。
- errQUEUE_FULL:1、读阻塞,没有设置超时时间;2、读阻塞,超时时间到达。
xQueueReceiveFromISR(
QueueHandle_t xQueue,
void * const pvBuffer,
BaseType_t * const pxHigherPriorityTaskWoken
) PRIVILEGED_FUNCTION;
函数原型
xQueuePeek(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
) PRIVILEGED_FUNCTION;
函数功能
从指定的队列读取数据,读取的是队列的队首,不会删除读取的数据。
函数参数
- QueueHandle_t xQueue:读取的队列的句柄。
- void * const pvBuffer:读取的数据存放缓存空间。
- TickType_t xTicksToWait:超时时间。
函数返回值
- pdPASS:数据被成功从队列读取。
- errQUEUE_FULL:1、读阻塞,没有设置超时时间;2、读阻塞,超时时间到达。
xQueuePeekFromISR(
QueueHandle_t xQueue,
void * const pvBuffer
) PRIVILEGED_FUNCTION;
1.3.4 删除队列
函数原型
void vQueueDelete( QueueHandle_t xQueue );
函数参数
- QueueHandle_t xQueue:删除队列的句柄。
1.4 队列实战
1.4.1 实战 1
创建两个任务,一个任务获取光照强度和 MCU 内部温度,然后写入到队列,另一个任务进行读取并打印。
注意:所使用的模块一定不能有 SysTick 延时函数,从现在开始就不能使用该延时函数。
1.4.2 实战 2

#include "main.h"
QueueHandle_t Quete_Handler;
TaskHandle_t TaskA_Handler;
TaskHandle_t TaskB_Handler;
TaskHandle_t TaskC_Handler;
typedef struct{
char name[10];
u8 flag;
} LED_Status;
void Start_Task(void *pvParameters)
{
Quete_Handler = xQueueCreate(4, sizeof(LED_Status));
xTaskCreate(Task_A, "LED1_FZ", 100, NULL, 2, &TaskA_Handler);
xTaskCreate(Task_B, "LED2_FZ", 100, NULL, 1, &TaskB_Handler);
xTaskCreate(Task_C, "READ_STATUS", 100, NULL, 0, &TaskC_Handler);
vTaskDelete(NULL);
}
void Task_A(void *pvParameters)
{
LED_Status LED1 = {"LED_1", 0};
while(1)
{
LED_G_Tog;
if(!GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_8))
LED1.flag = 1;
else
LED1.flag = 0;
xQueueSend(Quete_Handler, &LED1, 0);
vTaskDelay(50);
}
vTaskDelete(NULL);
}
void Task_B(void *pvParameters)
{
LED_Status LED2 = {"LED_2", 0};
while(1)
{
LED_R_Tog;
if(!GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_9))
LED2.flag = 1;
else
LED2.flag = 0;
xQueueSend(Quete_Handler, &LED2, 0);
vTaskDelay(30);
}
vTaskDelete(NULL);
}
void Task_C(void *pvParameters)
{
LED_Status Status;
LED_Status Dev[2];
while(1)
{
while(xQueueReceive(Quete_Handler, &Status, 0) == pdPASS)
{
if(strncmp(Status.name, "LED_1", 5) == 0)
{
Dev[0] = Status;
}
else
{
Dev[1] = Status;
}
}
printf("name:%s,Led_Status:%d\r\n", Dev[0].name, Dev[0].flag);
printf("name:%s,Led_Status:%d\r\n", Dev[1].name, Dev[1].flag);
printf("\r\n");
vTaskDelay(40);
}
vTaskDelete(NULL);
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)