FreeRTOS消息队列(09)

队列简介

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)

FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。

读写队列做好了保护,防止多任务同时访问冲突;
我们只需要直接调用API函数即可,简单易用!

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度

在这里插入图片描述

  1. 队列长度为:5个

  2. 队列项目大小为:10字节

在创建队列时,就要指定队列长度以及队列项目的大小!

队列特点:

  1. 数据入队出队方式 队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;

  2. 数据传递方式 FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递

  3. 多任务访问 队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息

  4. 出队、入队阻塞 当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队

①若阻塞时间为0 :直接返回不会等待;

②若阻塞时间为0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;

③若阻塞时间为port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

入队阻塞:

在这里插入图片描述

队列满了,此时写不进去数据;

①将该任务的状态列表项挂载在pxDelayedTaskList;

②将该任务的事件列表项挂载在xTasksWaitingToSend;

出队阻塞:

在这里插入图片描述

队列为空,此时读取不了数据;

①将该任务的状态列表项挂载在pxDelayedTaskList;

②将该任务的事件列表项挂载在xTasksWaitingToReceive;

队列操作基本过程

①创建队列

在这里插入图片描述

②往队列写入第一个消息

在这里插入图片描述

③往队列写入第二个消息

在这里插入图片描述

④从队列读取第一个消息

在这里插入图片描述

队列结构体介绍

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;

队列结构体整体示意图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用队列的主要流程:创建队列 -> 写队列 -> 读队列。

创建队列

队列在使用前必须先创建,和创建任务类似, FreeRTOS 也提供了动态或静态内存分配创建队列两个 API 函数,具体函数声明如下所示

/**
  * @brief  动态分配内存创建队列函数
  * @param  uxQueueLength:队列深度
  * @param  uxItemSize:队列中数据单元的长度,以字节为单位
  * @retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败
  */
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
 
/**
  * @brief  静态分配内存创建队列函数
  * @param  uxQueueLength:队列深度
  * @param  uxItemSize:队列中数据单元的长度,以字节为单位
  * @param  pucQueueStorageBuffer:队列栈空间数组
  * @param  pxQueueBuffer:指向StaticQueue_t类型的用于保存队列数据结构的变量
  * @retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败
  */
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
								 UBaseType_t uxItemSize,
								 uint8_t *pucQueueStorageBuffer,
								 StaticQueue_t *pxQueueBuffer);
 
/*example:创建一个队列长度为5,队列项目的大小为2字节的队列*/
QueueHandle_t QueueHandleTest;
QueueHandleTest = xQueueCreate(5, sizeof(uint16_t));

在这里插入图片描述
前面说 FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:

#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 )    /* 递归互斥信号量 */

向队列写入数据

任务或者中断向队列写入数据称为发送消息。通常情况下,队列被作为 FIFO(先入先出)使用,即数据由队列尾部进入,从队列首读出,当然可以通过更改写入方式将队列作为 LIFO(后入先出)使用,向队列中写入数据主要有三组 FreeRTOS API 函数,具体如下所示

/**
  * @brief  向队列后方发送数据(FIFO先入先出)
  * @param  xQueue:要写入数据的队列句柄
  * @param  pvItemToQueue:要写入的数据
  * @param  xTicksToWait:阻塞超时时间,单位为节拍数,portMAXDELAY表示无限等待
  * @retval pdPASS:数据发送成功,errQUEUE_FULL:队列满无法写入
  */
BaseType_t xQueueSend(QueueHandle_t xQueue,
					  const void * pvItemToQueue,
					  TickType_t xTicksToWait);
 
/**
  * @brief  向队列后方发送数据(FIFO先入先出),与xQueueSend()函数一致
  */
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
							const void * pvItemToQueue,
							TickType_t xTicksToWait);
 
/**
  * @brief  向队列前方发送数据(LIFO后入先出)
  */
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
							 const void * pvItemToQueue,
							 TickType_t xTicksToWait);
 
/**
  * @brief  以下三个函数为上述三个函数的中断安全版本
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  */
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
							 const void *pvItemToQueue,
							 BaseType_t *pxHigherPriorityTaskWoken);
 
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
								   const void *pvItemToQueue,
								   BaseType_t *pxHigherPriorityTaskWoken)
 
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
									const void *pvItemToQueue,
									BaseType_t *pxHigherPriorityTaskWoken);

另外还有一组稍微特殊的向队列写入数据的 FreeRTOS API 函数,这组函数只用于队列长度为 1 的队列,在队列已满时会覆盖掉队列原来的数据,具体如下所述

/**
  * @brief  向长度为1的队发送数据
  * @param  xQueue:要写入数据的队列句柄
  * @param  pvItemToQueue:要写入的数据
  * @retval pdPASS:数据发送成功,errQUEUE_FULL:队列满无法写入
  */
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
 
/**
  * @brief  以下函数为上述函数的中断安全版本
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  */
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
								  const void *pvItemToQueue,
								  BaseType_t *pxHigherPriorityTaskWoken);

队列写入消息

在这里插入图片描述
这几个写入函数调用的是同一个函数xQueueGenericSend(),只是指定了不同的写入位置!

队列一共有 3 种写入位置 :

#define queueSEND_TO_BACK                         ( ( BaseType_t ) 0 )        /* 写入队列尾部 */
#define queueSEND_TO_FRONT                        ( ( BaseType_t ) 1 )        /* 写入队列头部 */
#define queueOVERWRITE                            ( ( BaseType_t ) 2 )        /* 覆写队列*/

从队列接收数据

任务或者中断从队列中读取数据称为接收消息。从队列中读取数据主要有两组 FreeRTOS API 函数,具体如下所示

/**
  * @brief  从队列头部接收数据单元,接收的数据同时会从队列中删除
  * @param  xQueue:被读队列句柄
  * @param  pvBuffer:接收缓存指针
  * @param  xTicksToWait:阻塞超时时间,单位为节拍数
  * @retval pdPASS:数据接收成功,errQUEUE_FULL:队列空无读取到任何数据
  */
BaseType_t xQueueReceive(QueueHandle_t xQueue,
						 void *pvBuffer,
						 TickType_t xTicksToWait);
 
/**
  * @brief  从队列头部接收数据单元,不从队列中删除接收的单元
  */
BaseType_t xQueuePeek(QueueHandle_t xQueue,
					  void *pvBuffer,
					  TickType_t xTicksToWait);
 
/**
  * @brief  以下两个函数为上述两个函数的中断安全版本
  * @param  pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
  */
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
								void *pvBuffer,
								BaseType_t *pxHigherPriorityTaskWoken);
 
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer);

查询队列

FreeRTOS 还提供了一些用于查询队列当前有效数组单元个数和剩余可用空间数的 API 函数,具体如下所述

/**
  * @brief  查询队列剩余可用空间数
  * @param  xQueue:被查询的队列句柄
  * @retval 返回队列中可用的空间数
  */
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);
 
/**
  * @brief  查询队列有效数据单元个数
  * @param  xQueue:被查询的队列句柄
  * @retval 当前队列中保存的数据单元个数
  */
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue);
 
/**
  * @brief  查询队列有效数据单元个数函数的中断安全版本
  */
UBaseType_t uxQueueMessagesWaitingFromISR(const QueueHandle_t xQueue);

阻塞状态

当出现下面几种情况时,任务会进入阻塞状态

  1. 当某个任务向队列写入数据,但是被写的队列已满时,任务将进入阻塞状态等待队列出现新的位置
  2. 当某个任务从队列读取数据,但是被读的队列是空时,任务将进入阻塞状态等待队列出现新的数据

当出现下面几种情况时,任务会退出阻塞状态

  1. 进入阻塞状态的任务达到设置的阻塞超时时间之后会退出阻塞状态
  2. 向满队列中写数据的任务等到队列中出现新的位置
  3. 从空队列中读数据的任务等到队列.中出现新的数据

当存在多个任务处于阻塞状态时,如果同时满足解除阻塞的条件,则所有等待任务中 优先级最高的任务 或者 优先级均相同但等待最久的任务 将被解除阻塞状态

删除队列

/**
  * @brief  删除队列
  * @param  pxQueueToDelete:要删除的队列句柄
  * @retval None
  */
void vQueueDelete(QueueHandle_t pxQueueToDelete);

复位队列

/**
  * @brief  将队列重置为其原始空状态
  * @param  xQueue:要复位的队列句柄
  * @retval pdPASS(从FreeRTOS V7.2.0之后)
  */
BaseType_t xQueueReset(QueueHandle_t xQueue);

队列读写过程

如下图展示了用作 FIFO 的队列写入和读取数据的情况的具体过程
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值