13.消息队列

一、背景

在实际的项目开发中,经常会遇到在任务于任务之间或任务于中断之间需要进行“沟通交
流”,这里的“沟通交流”就是消息传递的过程。在不使用操作系统的情况下,函数与函数,或函数与中断之间的“沟通交流”一般使用一个或多多个全局变量来完成,但是在操作系统中,因为会涉及“资源管理”的问题,比方说读写冲突,因此使用全局变量在任务于任务或任务于中断之间进行消息传递,并不是很好的解决方案。FreeRTOS 为此提供了“队列”的机制。

二、简介

队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制。在队列中可以存
储数量优先、大小固定的多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项目的最大数量称为队列的长度,在创建队列的时候,就需要指定所创建队列的长度及队列项目的大小。因为队列是用来在任务与任务或任务于中断之间传递消息的一种机制,因此队列也叫做消息队列。
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。读写队列做好了保护,防止多任务同时访问冲突。
基于队列,FreeRTOS 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二
值信号量、递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列。
在这里插入图片描述

1. 数据存储

队列通常采用 FIFO(先进先出)的存储缓冲机制,当有新的数据被写入队列中时,永远都是写入到队列的尾部,而从队列中读取数据时,永远都是读取队列的头部数据。但同时 FreeRTOS的队列也支持将数据写入到队列的头部,并且还可以指定是否覆盖先前已经在队列头部的数据。

2.数据传递方式

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

3. 多任务访问

队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列
中读取消息。

4. 队列读取阻塞

在任务从队列读取消息时,可以指定一个阻塞超时时间。如果任务在读取队列时,队列为
空,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中进行阻塞,以等待队列中有可用的消息。当有其他任务或中断将消息写入队列中,因等待队列而阻塞任务将会被添加到就绪态任务列表中,并读取队列中可用的消息。如果任务因等待队列而阻塞的时间超过指定的阻塞超时时间,那么任务也将自动被转移到就绪态任务列表中,但不再读取队列中的数据。因为同一个队列可以被多个任务读取,因此可能会有多个任务因等待同一个队列,而被阻塞,在这种情况下,如果队列中有可用的消息,那么也只有一个任务会被解除阻塞并读取到消息,并且会按照阻塞的先后和任务的优先级,决定应该解除哪一个队列读取阻塞任务。
在这里插入图片描述
在这里插入图片描述

5. 队列写入阻塞

与队列读取一样,在任务往队列写入消息时,也可以指定一个阻塞超时时间。如果任务在
写入队列时,队列已经满了,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中进行阻塞,以等待队列有空闲的位置可以写入消息。指定的阻塞超时时间为任务阻塞的最大时间,如果在阻塞超时时间到达之前,队列有空闲的位置,那么队列写入阻塞任务将会解除阻塞,并往队列中写入消息,如果达到指定的阻塞超时时间,队列依旧没有空闲的位置写入消息,那么队列写入阻塞任务将会自动转移到就绪态任务列表中,但不会往队列中写入消息。因为同一个队列可以被多个任务写入,因此可有会有多个任务因等待统一个任务,而被阻塞,在这种情况下,如果队列中有空闲的位置,那么也之后一个任务会被解除阻塞并往队列中写入消息,并且会按照阻塞的先后和任务的优先级,决定应该解除哪一个队列写入阻塞任务。
在这里插入图片描述
问题:
当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?

在这里插入图片描述

6.队列的基本操作

(1) 创建队列

在这里插入图片描述
创建了一个用于任务 A 与任务 B 之间“沟通交流”的队列,这个队列最大可容纳 5 个队列项目,即队列的长度为 5。刚创建的队列是不包含内容的,因此这个队列为空。

(2) 往队列写入第一个消息

在这里插入图片描述
任务 A 将一个私有变量写入队列的尾部。由于在写入队列之前,队列是空的,因此新写入的消息,既是是队列的头部,也是队列的尾部。

(3) 往队列中写入第二个消息

在这里插入图片描述
任务 A 改变了私有变量的值,并将新值写入队列。现在队列中包含了队列 A写入的两个值,其中第一个写入的值在队列的头部,而新写入的值在队列的尾部。这时队列还有 3 个空闲的位置。

(4) 从队列读取第一个消息

在这里插入图片描述
任务 B 从队列中读取消息,任务 B 读取的消息是处于队列头部的消息,这是任务 A 第一次往队列中写入的消息。在任务 B 从队列中读取消息后,队列中任务 A 第二次写入的消息,变成了队列的头部,因此下次任务 B 再次读取消息时,将读取到这个消息。此时队列中剩余 4 个空闲的位置。

三、相关 API 函数

1.队列结构体

typedef struct QueueDefinition
{
 int8_t * pcHead; /* 存储区域的起始地址 */
 int8_t * pcWriteTo; /* 下一个写入的位置 */
 /* 信号量是由队列实现的,
 * 此结构体能用于队列和信号量,
 * 当用于队列时,使用联合体中的 xQueue,
 * 当用于信号量时,使用联合体中的 xSemaphore
 */
 union
 {
 QueuePointers_t xQueue;
 SemaphoreData_t xSemaphore;
 } u;
 
 List_t xTasksWaitingToSend; /* 写入阻塞任务列表 */
 List_t xTasksWaitingToReceive; /* 读取阻塞任务列表 */
 volatile UBaseType_t uxMessagesWaiting; /* 非空闲项目的数量 */
 UBaseType_t uxLength; /* 队列的长度 */
 UBaseType_t uxItemSize; /* 队列项目的大小 */
 
 /* 锁用于在任务因队列操作被阻塞前,防止中断或其他任务操作队列。
 * 上锁期间,队列可以写入和读取消息,但不会操作队列阻塞任务列表,
 * 当有消息写入时,cTxLock 加 1,当有消息被读取时,cRxLock 加 1,
 * 在解锁时,会统一处理队列的阻塞任务列表
 */
 volatile int8_t cRxLock; /* 读取上锁计数器 */
 volatile int8_t cTxLock; /* 写入上锁计数器 */
 
 /* 同时启用了静态和动态内存管理 */
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && \
 ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
 uint8_t ucStaticallyAllocated; /* 静态创建标志 */
#endif
 
 /* 此宏用于使能启用队列集 */
#if ( configUSE_QUEUE_SETS == 1 )
 struct QueueDefinition * pxQueueSetContainer; /* 指向队列所在队列集 */
#endif
 
 /* 此宏用于使能可视化跟踪调试 */
#if ( configUSE_TRACE_FACILITY == 1 )
 /* 仅用于调试,不用理会 */
 UBaseType_t uxQueueNumber;
 /* 队列的类型
 * 0: 队列或队列集
 * 1: 互斥信号量
 * 2: 计数型信号量
 * 3: 二值信号量
 * 4: 可递归信号量
 */
 uint8_t ucQueueType;
#endif
} xQUEUE;
/* 重定义成 Queue_t */
typedef xQUEUE Queue_t;

在队列的结构体中,就包含了一个联合体 u,当队列结构体用作队列时,使用联合体 u 中的 xQueue,其数据类型为 QueuePointers_t,在 queue.c 文件中有定义,具体的代码如下所示:

typedef struct QueuePointers
{
 int8_t * pcTail; /* 存储区域的结束地址 */
 int8_t * pcReadFrom; /* 最后一次读取队列的位置 */
} QueuePointers_t;

而当队列结构体用于互斥信号量和递归互斥信号量时,则是使用联合体 u 中的 xSemaphore,
其数据类型为 SemaphoreData_t,在 queue.c 文件中有定义,具体的代码如下所示:

typedef struct SemaphoreData
{
 TaskHandle_t xMutexHolder; /* 互斥信号量的持有者 */
 UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量被递归获取计数器 */
} SemaphoreData_t;

在这里插入图片描述

2.创建队列

在这里插入图片描述
在这里插入图片描述

1. 动态方式:函数 xQueueCreate()

此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理
的堆中分配。函数 xQueueCreate()实际上是一个宏定义,在 queue.h 文件中有定义,具体的代码
如下所示:

#define xQueueCreate( uxQueueLength, \
 uxItemSize ) \
 xQueueGenericCreate( ( uxQueueLength ), \
 ( uxItemSize ), \
 ( queueQUEUE_TYPE_BASE ))

在这里插入图片描述
函数 xQueueCreate()实际上是调用了函数 xQueueGenericCreate(),函数
xQueueGenericCreate()用于使用动态方式创建指定类型的队列,前面说 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 ) /* 递归互斥信号量 */
2.静态方式:函数 xQueueCreateStatic()

此函数用于使用静态方式创建队列,队列所需的内存空间需要由用户手动分配并提供。函
数 xQueueCreateStatic()实际上是一个宏定义,在 queue.h 文件中有定义,具体的代码如下所示:

#define xQueueCreateStatic( uxQueueLength, \
 uxItemSize, \
 pucQueueStorage, \
 pxQueueBuffer) \
 xQueueGenericCreateStatic( ( uxQueueLength ), \
 ( uxItemSize ), \
 ( pucQueueStorage ), \
 ( pxQueueBuffer ), \
 ( queueQUEUE_TYPE_BASE ))

在这里插入图片描述

3.函数 xQueueGenericCreate()
QueueHandle_t xQueueGenericCreate(
 const UBaseType_t uxQueueLength, /* 队列长度 */
 const UBaseType_t uxItemSize, /* 队列项目的大小 */
 const uint8_t ucQueueType) /* 队列类型 */
{
 Queue_t * pxNewQueue = NULL;
 size_t xQueueSizeInBytes;
 uint8_t * pucQueueStorage;
 
 /* 队列长度大于 0 才有意义
 * 检查参数设置
 */
 if( (uxQueueLength > (UBaseType_t) 0 ) &&
 ((SIZE_MAX / uxQueueLength) >= uxItemSize) &&
 ((SIZE_MAX - sizeof(Queue_t)) >= (uxQueueLength * uxItemSize)))
 {
 /* 计算队列存储空间需要的字节大小 */
 xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
 
 /* 为队列申请内存空间
 * 队列控制块+队列存储区域
 */
 pxNewQueue = (Queue_t *) pvPortMalloc( sizeof( Queue_t ) +
 xQueueSizeInBytes );
 
 /* 内存申请成功 */
 if( pxNewQueue != NULL )
 {
 /* 获取队列存储区域的起始地址 */
 pucQueueStorage = ( uint8_t * ) pxNewQueue;
 pucQueueStorage += sizeof( Queue_t );
 
 /* 此宏用于启用支持静态内存管理 */
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
 /* 标记此队列为非静态申请内存 */
 pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
 
 /* 初始化队列 */
 prvInitialiseNewQueue(uxQueueLength,
 uxItemSize,
 pucQueueStorage,
 ucQueueType,
 pxNewQueue);
 }
 else
 {
 /* 用于调试,不用理会 */
 traceQUEUE_CREATE_FAILED( ucQueueType );
 mtCOVERAGE_TEST_MARKER();
 }
 }
 else
 {
 configASSERT( pxNewQueue );
 mtCOVERAGE_TEST_MARKER();
 }
 
 return pxNewQueue;
}
4.函数 prvInitialiseNewQueue()

从上面的代码可以看出,函数 xQueueGenericCreate()主要负责为队列申请内存,然后调用
函数 prvInitialiseNewQueue()对队列进行初始化,函数 prvInitialiseNewQueue()在 queue.c 文件中
有定义,具体的代码如下所示:

static void prvInitialiseNewQueue(
 const UBaseType_t uxQueueLength, /* 队列长度 */
 const UBaseType_t uxItemSize, /* 队列项目的大小 */
 uint8_t * pucQueueStorage, /* 队列存储空间的起始地址 */
 const uint8_t ucQueueType, /* 队列类型 */
 Queue_t * pxNewQueue) /* 队列结构体 */
{
 /* 防止编译器警告(可能用不到这个入参) */
 ( void ) ucQueueType;
 
 /* 队列存储空间的起始地址 */
 if( uxItemSize == ( UBaseType_t ) 0 )
 {
 /* 如果队列项目大小为 0(类型为信号量),
 * 那么就不需要存储空间
 */
 pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
 }
 else
 {
 pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
 }
 
 /* 队列长度 */
 pxNewQueue->uxLength = uxQueueLength;
 /* 队列项目的大小 */
 pxNewQueue->uxItemSize = uxItemSize;
 /* 重置队列 */
 ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
 
 /* 此宏用于启用可视化跟踪调试 */
#if ( configUSE_TRACE_FACILITY == 1 )
{
 /* 队列的类型 */
 pxNewQueue->ucQueueType = ucQueueType;
}
#endif
 
 /* 此宏用于使能使用队列集 */
#if ( configUSE_QUEUE_SETS == 1 )
{
 /* 队列所在队列集设为空 */
 pxNewQueue->pxQueueSetContainer = NULL;
}
#endif
 
 /* 用于调试,不用理会 */
 traceQUEUE_CREATE( pxNewQueue );
}
5.函数 xQueueGenericReset()

从上面的代码可以看出,函数 prvInitialiseNewQueue()主要用于初始化队列结构体中的成员
变量,其中还会调用函数 xQueueGenericReset()对队列进行重置,函数 xQueueGenericReset()在
queue.c 文件中有定义,具体的代码如下所示:

BaseType_t xQueueGenericReset(
 QueueHandle_t xQueue, /* 待复位队列 */
 BaseType_t xNewQueue) /* 是否为新创建的队列 */
{
 BaseType_t xReturn = pdPASS;
  Queue_t * const pxQueue = xQueue;
 
 configASSERT( pxQueue );
 
 if( ( pxQueue != NULL ) &&
 ( pxQueue->uxLength >= 1U ) &&
 ( ( SIZE_MAX / pxQueue->uxLength ) >= pxQueue->uxItemSize ) )
 {
 /* 进入临界区 */
 taskENTER_CRITICAL();
 {
 /* 队列存储区域的结束地址 */
 pxQueue->u.xQueue.pcTail = pxQueue->pcHead +
 ( pxQueue->uxLength * pxQueue->uxItemSize );
 /* 队列中非空闲项目数量 */
 pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
 /* 下一个写入的位置 */
 pxQueue->pcWriteTo = pxQueue->pcHead;
 /* 最后一次读取的位置 */
 pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead +
 ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize );
 /* 读取上锁计数器 */
 pxQueue->cRxLock = queueUNLOCKED;
 /* 写入上锁计数器 */
 pxQueue->cTxLock = queueUNLOCKED;
 
 /* 判断是否为新创建的队列 */
 if( xNewQueue == pdFALSE )
 {
 /* 待复位的队列非新创建的队列 */
 /* 清空写入阻塞任务列表 */
 if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) ==
 pdFALSE )
 {
 if( xTaskRemoveFromEventList(
 &( pxQueue->xTasksWaitingToSend ) ) !=
 pdFALSE )
 {
 /* 如果取消阻塞的任务优先级
 * 为就绪态任务中的最高优先级
 * 则需要进行任务切换
 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 else
 {
 /* 待复位的队列为新创建的队列 */
 /* 初始化写入和读取阻塞任务列表 */
 vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
 vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
 }
 }
 /* 退出临界区 */
 taskEXIT_CRITICAL();
 }
 else
 {
 xReturn = pdFAIL;
 }
 
 configASSERT( xReturn != pdFAIL );
 
 return xReturn;
}

从上面的函数可以看出,函数 xQueueGenericReset()复位队列的操作也是复位队列的结构体
中的成员变量。

3.队列写入消息

在这里插入图片描述
在这里插入图片描述

1. 在任务中往队列写入消息的函数

在任务中往队列写入消息的函数有函数 xQueueSend() 、 xQueueSendToBack() 、
xQueueSendToFront()、xQueueOverwrite(),这 4 个函数实际上都是宏定义,在 queue.h 文件中有
定义,具体的代码如下所示:

#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)

从上面的代码中可以看到,函数 xQueueSend()、函数 xQueueSendToBack()、函数
xQueueSendToFront()和函数 xQueueOverwrite()实际上都是调用了函数 xQueueGenericSend(),只是指定了不同的写入位置,队列一共有 3 中写入位置,在 queue.h 文件中有定义,具体的代码如下所示:

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

要注意的是,覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用,这在下文讲解函数 xQueueGenericSend()时,会提到。
函 数 xQueueGenericSend() 用 于 在 任 务 中 往 队 列 的 指 定 位 置 写 入 消 息 。 函 数xQueueGenericSend()的函数原型如下所示:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
 const void * const pvItemToQueue,
 TickType_t xTicksToWait,
 const BaseType_t xCopyPosition);

在这里插入图片描述

2. 在中断中往队列写入消息的函数

在 任 务 中 往 队 列 写 入 消 息 的 函 数 有 函 数 xQueueSendFromISR() 、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()、xQueueOverwriteFromISR(),这 4个函数实际上都是宏定义,在 queue.h 文件中有定义,具体的代码如下所示:

#define xQueueSendFromISR( xQueue, \
 pvItemToQueue, \
 pxHigherPriorityTaskWoken)
 xQueueGenericSendFromISR( ( xQueue ), \
 ( pvItemToQueue ), \
 ( pxHigherPriorityTaskWoken ), \
 queueSEND_TO_BACK)
#define xQueueSendToBackFromISR( xQueue, \
 pvItemToQueue, \
 pxHigherPriorityTaskWoken) \
 xQueueGenericSendFromISR( ( xQueue ), \
 ( pvItemToQueue ), \
 ( pxHigherPriorityTaskWoken ), \
 queueSEND_TO_BACK)
#define xQueueSendToFrontFromISR( xQueue, \
 pvItemToQueue, \
 pxHigherPriorityTaskWoken) \
 xQueueGenericSendFromISR( ( xQueue ), \
 ( pvItemToQueue ), \
 ( pxHigherPriorityTaskWoken ), \
 queueSEND_TO_FRONT)
#define xQueueOverwriteFromISR( xQueue, \
 pvItemToQueue, \
 pxHigherPriorityTaskWoken) \
 xQueueGenericSendFromISR( ( xQueue ), \
 ( pvItemToQueue ), \
 ( pxHigherPriorityTaskWoken ), \
 queueOVERWRITE)

从上面的代码中可以看到,函数 xQueueSendFromISR()、函数 xQueueSendToBackFromISR()、
函数 xQueueSendToFrontFromISR()和函数 xQueueOverwriteFromISR()实际上都是调用了函数
xQueueGenericSendFromISR(),只是指定了不同的写入位置。
函数 xQueueGenericSendFromISR()用于在中断中往队列的指定位置写入消息。函数
xQueueGenericSendFromISR()的函数原型如下所示:

BaseType_t xQueueGenericSendFromISR(
 QueueHandle_t xQueue,
 const void * const pvItemToQueue,
 BaseType_t * const pxHigherPriorityTaskWoken,
 const BaseType_t xCopyPosition);

在这里插入图片描述
在这里插入图片描述

3.函数 xQueueGenericSend()
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
 const void * const pvItemToQueue,
 TickType_t xTicksToWait,
 const BaseType_t xCopyPosition)
{
 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
 TimeOut_t xTimeOut;
 Queue_t * const pxQueue = xQueue;
 
 configASSERT( pxQueue );
 configASSERT( !( ( pvItemToQueue == NULL ) &&
 ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
 /* 这里限制了只有在队列长度为 1 时,才能使用覆写 */
 configASSERT( !( ( xCopyPosition == queueOVERWRITE ) &&
 ( pxQueue->uxLength != 1 ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
 configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) &&
 ( xTicksToWait != 0 ) ) );
}
#endif
 
 for( ; ; )
 {
 /* 进入临界区 */
 taskENTER_CRITICAL();
 {
 /* 只有在队列有空闲位置或
 * 为覆写的情况才能写入消息
 */
 if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||
 ( xCopyPosition == queueOVERWRITE ) )
 {
 /* 用于调试,不用理会 */
 traceQUEUE_SEND( pxQueue );
 
 /* 此宏用于使能启用队列集 */
#if ( configUSE_QUEUE_SETS == 1 )
{
 /* 获取队列中非空闲项目的数量 */
 const UBaseType_t uxPreviousMessagesWaiting =
 pxQueue->uxMessagesWaiting;
 
 /* 将待写入消息按指定写入方式复制到队列中 */
 xYieldRequired =
 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
 
 /* 判断队列是否在队列集中 */
 if( pxQueue->pxQueueSetContainer != NULL )
 {
 /* 写入位置为覆写,且队列非空闲项目数量不为 0 */
 if( ( xCopyPosition == queueOVERWRITE ) &&
 ( uxPreviousMessagesWaiting != ( UBaseType_t ) 0 ) )
 {
 mtCOVERAGE_TEST_MARKER();
 }
 /* 通知队列集 */
 else if( prvNotifyQueueSetContainer( pxQueue ) != pdFALSE )
 {
 /* 根据需要进行任务切换 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 /* 队列不在队列集中 */
 else
 {
 /* 队列的读取阻塞任务列表非空 */
 if( listLIST_IS_EMPTY(
 &( pxQueue->xTasksWaitingToReceive ) ) ==
 pdFALSE )
 {
 /* 将队列读取阻塞任务从所在列表移除
 * 因为此时队列中已有可用消息
 */
 if( xTaskRemoveFromEventList(
 &( pxQueue->xTasksWaitingToReceive ) ) !=
 pdFALSE )
 {
 /* 根据需要进行任务切换 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 else if( xYieldRequired != pdFALSE )
 {
 /* 在互斥信号量释放完且任务优先级恢复后,
 * 需要进行任务切换
 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
}
#else
{
 /* 将消息写入到队列存储区域的指定位置 */
 xYieldRequired =
 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
 
 /* 队列有阻塞的读取任务 */
 if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ==
 pdFALSE )
 {
 /* 将读取阻塞任务从队列读取任务阻塞列表中移除,
 * 因为此时,队列中已经有非空闲的项目了
 */
 if( xTaskRemoveFromEventList(
 &( pxQueue->xTasksWaitingToReceive ) ) !=
 pdFALSE )
 {
 /* 有任务解除阻塞后,
 * 需要根据任务的优先级进行任务切换
 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 else if( xYieldRequired != pdFALSE )
 {
 /* 在互斥信号量释放完且任务优先级恢复后,
 * 需要进行任务切换
 */
 queueYIELD_IF_USING_PREEMPTION();
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
}
#endif
 
 /* 退出临界区 */
 taskEXIT_CRITICAL();
 return pdPASS;
 }
 else
 {
 /* 此时不能写入消息,因此要将任务阻塞 */
 if( xTicksToWait == ( TickType_t ) 0 )
 {
 /* 如果不选则阻塞等待 */
 /* 退出临界区 */
 taskEXIT_CRITICAL();
 
 /* 用于调试,不用理会 */
 traceQUEUE_SEND_FAILED( pxQueue );
 /* 返回队列满错误 */
 return errQUEUE_FULL;
 }
 else if( xEntryTimeSet == pdFALSE )
 {
 /* 队列满,任务需要阻塞,
 * 记录下此时系统节拍计数器的值和溢出次数
 * 用于下面对阻塞时间进行补偿
 */
 vTaskInternalSetTimeOutState( &xTimeOut );
 xEntryTimeSet = pdTRUE;
 }
 else
 {
 mtCOVERAGE_TEST_MARKER();
 }
 }
 }
 /* 退出临界区
 * 退出临界区后系统时钟节拍会发生更新,
 * 因此任务如果需要阻塞的话,
 * 需要对阻塞时间进行补偿
 */
 taskEXIT_CRITICAL();
 
 /* 挂起任务调度器 */
 vTaskSuspendAll();
 /* 队列上锁 */
 prvLockQueue( pxQueue );
 
 /* 判断阻塞时间补偿后,是否还需要阻塞 */
 if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
 {
 /* 阻塞时间补偿后,还需要进行阻塞 */
 if( prvIsQueueFull( pxQueue ) != pdFALSE )
 {
 /* 用于调试,不用理会 */
 traceBLOCKING_ON_QUEUE_SEND( pxQueue );
/* 将任务添加到队列写入阻塞任务列表中进行阻塞 */
 vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ),
 xTicksToWait );
 /* 解锁队列 */
 prvUnlockQueue( pxQueue );
 
 /* 恢复任务调度器 */
 if( xTaskResumeAll() == pdFALSE )
 {
 /* 根据需要进行任务切换 */
 portYIELD_WITHIN_API();
 }
 }
 else
 {
 /* 队列解锁 */
 prvUnlockQueue( pxQueue );
 /* 恢复任务调度器 */
 ( void ) xTaskResumeAll();
 }
 }
 /* 阻塞时间补偿后,已不需要阻塞 */
 else
 {
 /* 解锁队列 */
 prvUnlockQueue( pxQueue );
 /* 恢复任务调度器 */
 ( void ) xTaskResumeAll();
 
 /* 用于调试,不用理会 */
 traceQUEUE_SEND_FAILED( pxQueue );
 /* 返回队列满错误 */
 return errQUEUE_FULL;
 }
 }
}

4.队列读取消息

在这里插入图片描述
在这里插入图片描述

1. 函数 xQueueReceive()

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。该函数的函
数原型如下所示:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
 void * const pvBuffer,
 TickType_t xTicksToWait);

在这里插入图片描述

2. 函数 xQueuePeek()

此函数用于在任务中,从队列中读取消息,但与函数 xQueueReceive()不同,此函数在成功
读取消息后,并不会移除已读取的消息,这意味着,下次读取队列时,还能够读取到相同的内
容。消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。该函数
的函数原型如下所示:

BaseType_t xQueuePeek( QueueHandle_t xQueue,
 void * const pvBuffer,
 TickType_t xTicksToWait);

在这里插入图片描述

3. 函数 xQueueReceiveFromISR()

此函数用于在中断中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。该函数的函
数原型如下所示:

BaseType_t xQueueReceiveFromISR(
 QueueHandle_t xQueue,
 void * const pvBuffer,
 BaseType_t * const pxHigherPriorityTaskWoken);

在这里插入图片描述

4. 函数 xQueuePeekFromISR()

此函数用于在中断中,从队列中读取消息,但与函数 xQueueReceiveFromISR()不同,此函
数在成功读取消息后,并不会移除已读取的消息,这意味着,下次读取队列时,还能够读取到
相同的内容。消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。
该函数的函数原型如下所示:

BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,
 void * const pvBuffer);

在这里插入图片描述

5.队列锁

在上文讲解队列操作的函数时,提到了队列的上锁与解锁,通过在队列的结构提供,也包
含了队列读取上锁计数器和队列写入上锁计数器。在队列被上锁后,可以往队列中写入消息和
读取消息,但是队列消息的读取和写入不会影响到队列读取和写入阻塞任务列表中的任务阻塞,
队列的写入和读取阻塞任务列表会在队列解锁后,统一处理。
队列上锁的函数为 prvLockQueue(),函数 prvLockQueue()实际上是一个宏定义,在 queue.c
文件中有定义,具体的代码如下所示:

#define prvLockQueue( pxQueue ) \
 taskENTER_CRITICAL(); \
 { \
 if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
 { \
 ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
 } \
 if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
 { \
 ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
 } \
 } \
 taskEXIT_CRITICAL(

队列结构体中的 cRxLock 和 cTxLock 成员变量就是队列的读取和写入上锁计数器,这两个
成员变量用来表示队列的上锁状态。

四、实验

在这里插入图片描述

#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/******************************************************************************************************/
/*FreeRTOS配置*/

/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );


/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO         3
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

/* TASK3 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK3_PRIO         4
#define TASK3_STACK_SIZE   128
TaskHandle_t    task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t key_queue;        /* 小数据句柄 */
QueueHandle_t big_date_queue;   /* 大数据句柄 */
char buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{    
    /* 队列的创建 */
    key_queue = xQueueCreate( 2, sizeof(uint8_t) );
    if(key_queue != NULL)
    {
        printf("key_queue队列创建成功!!\r\n");
    }else printf("key_queue队列创建失败!!\r\n");
    
    big_date_queue = xQueueCreate( 1, sizeof(char *) );
    if(big_date_queue != NULL)
    {
        printf("big_date_queue队列创建成功!!\r\n");
    }else printf("big_date_queue队列创建失败!!\r\n");
    
    xTaskCreate((TaskFunction_t         )   start_task,
                (char *                 )   "start_task",
                (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   START_TASK_PRIO,
                (TaskHandle_t *         )   &start_task_handler );
    vTaskStartScheduler();
}


void start_task( void * pvParameters )
{
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
               
    xTaskCreate((TaskFunction_t         )   task3,
                (char *                 )   "task3",
                (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK3_PRIO,
                (TaskHandle_t *         )   &task3_handler );                
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
}

/* 任务一,实现入队 */
void task1( void * pvParameters )
{
    uint8_t key = 0;
    char * buf;
    BaseType_t   err = 0;
    buf = &buff[0]; /* buf = &buff[0] */
    while(1) 
    {
        key = key_scan(0);
        if(key == KEY0_PRES || key == KEY1_PRES)
        {
            err = xQueueSend( key_queue, &key, portMAX_DELAY );
            if(err != pdTRUE)
            {
                printf("key_queue队列发送失败\r\n");
            }
        }else if(key == WKUP_PRES)
        {
            err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );
            if(err != pdTRUE)
            {
                printf("key_queue队列发送失败\r\n");
            }
        }
        vTaskDelay(10);
    }
}

/* 任务二,小数据出队 */
void task2( void * pvParameters )
{
    uint8_t key = 0;
    BaseType_t err = 0;
    while(1)
    {
        err = xQueueReceive( key_queue,&key,portMAX_DELAY);
        if(err != pdTRUE)
        {
            printf("key_queue队列读取失败\r\n");
        }else 
        {
            printf("key_queue读取队列成功,数据:%d\r\n",key);
        }
    }
}

/* 任务三,大数据出队 */
void task3( void * pvParameters )
{
    char * buf;
    BaseType_t err = 0;
    while(1)
    {
        err = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
        if(err != pdTRUE)
        {
            printf("big_date_queue队列读取失败\r\n");
        }else 
        {
            printf("数据:%s\r\n",buf);
        }
    }
}
  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值