队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度 。在创建队列时,就要指定队列长度以及队列项目的大小。
当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
1、优先级最高的任务
2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态
typedef struct QueueDefinition
{
int8_t * pcHead /* 存储区域的起始地址 */
int8_t * pcWriteTo; /* 下一个写入的位置 */
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u ;
List_t xTasksWaitingToSend; /* 等待发送列表 */
List_t xTasksWaitingToReceive; /* 等待接收列表 */
volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */
UBaseType_t uxLength; /* 队列长度 */
UBaseType_t uxItemSize; /* 队列项目的大小 */
volatile int8_t cRxLock; /* 读取上锁计数器 */
volatile int8_t cTxLock; /* 写入上锁计数器 */
/* 其他的一些条件编译 */
} xQUEUE;
//当用于队列使用时:
typedef struct QueuePointers
{
int8_t * pcTail; /* 存储区的结束地址 */
int8_t * pcReadFrom; /* 最后一个读取队列的地址 */
} QueuePointers_t;
//当用于互斥信号量和递归互斥信号量时 :
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 互斥信号量持有者 */
UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;
使用队列的主要流程:创建队列——写队列——读队列。
动态方式创建队列:xQueueCreate()【本文重点讲解】
静态方式创建队列:xQueueCreateStatic()
区别:队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。
#define xQueueCreate(uxQueueLength, uxItemSize)
xQueueGenericCreate((uxQueueLength), (uxItemSize),(queueQUEUE_TYPE_BASE ))
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */
头部写入与尾部写入的区别:头部写入从队列项1开始往后写(可循环),尾部写入从队列n开始写(可循环)
本文重点讲前四个,这几个写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列*/
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueOverwrite( xQueue, pvItemToQueue ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition);
const void * const 表示一个不可修改的指针,它指向不可修改的 void 类型数据。void 指针通常用于表示未知类型的数据,而 const 修饰符确保无论是指针本身还是指向的数据都不会被修改。
无论是写入数字还是地址,都只需要把变量的地址传入即可,函数会自动赋值。
头部读取删除的底层原理:队列结构体成员uxMessagesWaiting的变化,删除则变化,不删除则不变化
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
void * const 表示一个指向不可修改的数据的指针,但它可以指向任何类型的数据。这种类型的指针通常用于需要保护指针不被意外修改,但允许修改指向的数据的情况。
无论是读出数字还是地址,都只需要把变量的地址传入即可,函数会自动赋值。