当前位置:   article > 正文

手把手教你FreeRTOS源码详解(三)——队列

freertos源码详解

FreeRTOS源码解析集合(全网最详细)
手把手教你FreeRTOS源码解析(一)——内存管理
手把手教你FreeRTOS源码详解(二)——任务管理
手把手教你FreeRTOS源码详解(三)——队列
手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量

1、队列结构体Queue_t

队列与任务相同,同样也有一个结构体用来描述队列。删除部分宏定义后如下。

typedef struct QueueDefinition
{
	int8_t *pcHead;					/*< 指向队列存储区的开始地址 */
	int8_t *pcTail;					/*< 指向队列存储区的最后一个字节 */
	int8_t *pcWriteTo;				/*< 指向存储区的下一块空闲区域*/

	union							
	{
		int8_t *pcReadFrom;			/*< 指向最后一项出队时候的首地址*/
		UBaseType_t uxRecursiveCallCount;/*< 当使用递归互斥时,用于记录递归互斥调用的次数*/
	} u;

	List_t xTasksWaitingToSend;		/*<  等待发送任务列表,那些因为队列满入队失败而进入阻塞状态的任务将挂载在此列表上 */
	List_t xTasksWaitingToReceive;	/*<  等待接收任务列表,那些因为队列空出队失败而进入阻塞状态的任务将挂载在此列表上*/

	volatile UBaseType_t uxMessagesWaiting;/*<队列中队列项的数目 */
	UBaseType_t uxLength;			/*< 队列长度 */
	UBaseType_t uxItemSize;			/*< 队列每项的最大长度,单位字节 */

	
	volatile int8_t cRxLock;		/*< 当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为queueUNLOCKED */
	volatile int8_t cTxLock;		/*< 当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为queueUNLOCKED*
} xQUEUE;
/*更名*/
typedef xQUEUE Queue_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2、创建队列xQueueGenericCreate

创建普通的消息队列、创建队列集、创建互斥信号量等,最后都是调用通用的队列创建函数xQueueGenericCreate。

xQueueGenericCreate( const UBaseType_t uxQueueLength, 
                     const UBaseType_t uxItemSize, 
                     const uint8_t ucQueueType )
  • 1
  • 2
  • 3

其中:
uxQueueLength为队列长度
uxItemSize为队列项大小
ucQueueType 队列的类型,共有6种类型如下
queueQUEUE_TYPE_BASE
普通的消息队列
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量

2.1 详解 xQueueGenericCreate

if( uxItemSize == ( UBaseType_t ) 0 )
		{
			/* 队列项大小为0,因此不需要存储区 */
			xQueueSizeInBytes = ( size_t ) 0;
		}
		else
		{
			/* 分配足够的存储区,以便于随时能够保存所有的消息
			   存储区=队列长度*队列项大小*/
			xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

首先确定队列存储区的大小,如果队列项的大小为0,则队列不需要存储区,xQueueSizeInBytes 赋值为0;若队列项的大小不为0,则存储区的大小为队列长度*队列项大小

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
  • 1

计算队列总共需要的内存空间,需在存储区基础上再加上结构体Queue_t 大小

pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
  • 1

pucQueueStorage 指向队列存储区的起始地址,在所申请的总内存空间的起始位置上偏移一个结构体Queue_t 大小
在这里插入图片描述

	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
		{
			pxNewQueue->ucStaticallyAllocated = pdFALSE;
		}
  • 1
  • 2
  • 3
  • 4

如果队列是动态创建的,则将其ucStaticallyAllocated标志位置为pdFALSE 便于后续删除

prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
  • 1

初始化队列

2.2 初始化队列prvInitialiseNewQueue

if( uxItemSize == ( UBaseType_t ) 0 )
{
	pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
	pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

首先初始化队列存储区的起始地址,若队列长度为0(队列没有存储区),则指向队列的起始地址,若队列长度不为0,指向队列存储区的起始地址

pxNewQueue->uxLength = uxQueueLength;
  • 1

初始化队列长度

pxNewQueue->uxItemSize = uxItemSize;
  • 1

初始化队列每项大小

( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
  • 1

复位队列

2.3 复位队列xQueueGenericReset

我们首先来看一下xQueueGenericReset的参数

BaseType_t xQueueGenericReset( QueueHandle_t xQueue, 
		                        BaseType_t xNewQueue )
  • 1
  • 2

xQueue:需要复位或者初始化队列的句柄
xNewQueue :为pdFALSE,表明队列不是第一次初始化,只复位队列即可
为pdTRUE,表明队列是第一次初始化

taskENTER_CRITICAL();
  • 1

首先进入临界区,防止队列初始化、复位过程被打断

pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
  • 1

pcTail指向队列存储区的尾部

pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
  • 1

队列刚创建好时,队列当前项数为0,即消息数为0

pxQueue->pcWriteTo = pxQueue->pcHead;
  • 1

pcWriteTo指向队列存储区下一块空闲区域,初始创建队列时,下一块空闲区域即为pcHead

pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
  • 1

pcReadFrom指向队列最后一项出队时的首地址

	pxQueue->cRxLock = queueUNLOCKED;
	pxQueue->cTxLock = queueUNLOCKED;
  • 1
  • 2

队列未上锁,cRxLock,cTxLock均为queueUNLOCKED

if( xNewQueue == pdFALSE )
		{
		/* 队列复位后为空,出队阻塞的任务仍然保存阻塞状态,但入队阻塞的任务不再阻塞,应该从对应的列表中删除*/
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
					queueYIELD_IF_USING_PREEMPTION();
				}
else
		{
			/* 初始化入队阻塞、出队阻塞列表 */
			vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当xNewQueue为pdTRUE时,即该队列为第一次创建,因此需要初始化入队阻塞列表和出队阻塞列表
当xNewQueue为pdFALSE时,该队列已经初始化过了,只需要进行复位,队列复位后为空,出队阻塞的任务仍然保存阻塞状态,但入队阻塞的任务不再阻塞,应该从对应的列表中删除
普通的消息队列复位函数xQueueReset最终调用的函数仍是xQueueGenericReset,如下:

#define xQueueReset( xQueue ) xQueueGenericReset( xQueue, pdFALSE )
  • 1

3、通用入队函数xQueueGenericSend

FreeRtos的队列有很多入队函数,但是这些入队函数最终调用的都是通用入队函数xQueueGenericSend

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
							  const void * const pvItemToQueue,
 							  TickType_t xTicksToWait, 
  							  const BaseType_t xCopyPosition )
  • 1
  • 2
  • 3
  • 4

xQueue:队列句柄
pvItemToQueue:需要发送的消息(入队消息)
xTicksToWait:阻塞时间
xCopyPosition :入队的方式。
queueSEND TO_BACK:后向入队
queueSEND_TO_FRONT:前向入队
queueOVERWRITE:覆写入队

taskENTER_CRITICAL();
  • 1

首先进入临界区

if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
  • 1

判断队列是否还有剩余存储空间,如果采用覆写就不用在乎是否还有存储空间

xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
  • 1

若队列还有剩余存储空间,将入队消息复制到队列中
此处我们不考虑使用队列集的情况,将队列集部分的宏定义删除

if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
  • 1

消息入队以后,先检查是否有任务因等待读取消息而阻塞

if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
	{
					/* 解除阻塞的任务具有更高优先级,因此需要进行一次任务切换 */
					queueYIELD_IF_USING_PREEMPTION();
	}
  • 1
  • 2
  • 3
  • 4
  • 5

如果有任务因等待读取消息而阻塞,则将该任务从阻塞列表中移除,因解除阻塞的任务具有更高优先级,因此需要进行一次任务切换
若队列已满,没有剩余的存储空间:

				if( xTicksToWait == ( TickType_t ) 0 )
				{
					taskEXIT_CRITICAL();
					return errQUEUE_FULL;
				}
  • 1
  • 2
  • 3
  • 4
  • 5

若不阻塞,则直接退出临界区,并返回errQUEUE_FULL

			else if( xEntryTimeSet == pdFALSE )
			{
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}
  • 1
  • 2
  • 3
  • 4
  • 5

若阻塞时间不为0,则初始化超时结构体,并将超时标志位xEntryTimeSet 设为pdTRUE,表明超时结构体已经初始化了

taskEXIT_CRITICAL();
  • 1

退出临界区

vTaskSuspendAll();
prvLockQueue( pxQueue );
  • 1
  • 2

挂起任务调度器,并将队列上锁

if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
  • 1

更新超时状态,并判断时间是否已到

if( prvIsQueueFull( pxQueue ) != pdFALSE )
  • 1

如果超时时间未到,判断队列是否已满

				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果超时时间未到,且队列已满,则将入队任务置于对应的阻塞列表中,然后恢复任务调度器

			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
  • 1
  • 2

如果超时时间未到,队列不满,则解锁队列,恢复任务调度器,重新执行通用入队任务一次

			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			return errQUEUE_FULL;
  • 1
  • 2
  • 3

若超时时间已到,则恢复任务调度器,返回errQUEUE_FULL,表明入队失败

3.1 prvCopyDataToQueue

uxMessagesWaiting = pxQueue->uxMessagesWaiting;
  • 1

首先获取当前队列中的消息数目
在这里插入图片描述
一个队列初始化完成后,大致如上图所示;数据复制采用的是c/c++中的函数memcpy,其原型如下:

void *memcpy(void *destin, void *source, unsigned n);
  • 1

其作用是以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中,注意n的单位是字节
我们去掉互斥锁相关宏定义

else if( xPosition == queueSEND_TO_BACK )
  • 1

采用后向入队方式:

( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
  • 1

队列首次初始化完成后,第一个消息的入队起始地址为pcHead(pcWriteTo),将消息pvItemToQueue复制到对应的区域

pxQueue->pcWriteTo += pxQueue->uxItemSize;
  • 1

pcWriteTo指向下一个消息入队的起始地址(下一块空闲区域)

		if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
		{
			pxQueue->pcWriteTo = pxQueue->pcHead;
		}
  • 1
  • 2
  • 3
  • 4

如果消息已经写至队尾pcTail ,则pcWriteTo 重新指向队列头部pcHead

采用前向入队方式:

( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
  • 1

第一个消息的入队起始地址为pcReadFrom,将消息pvItemToQueue复制到对应的区域

pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
  • 1

pcReadFrom 指向下一个消息入队的起始地址

	if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) 
	{
		pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
	}
  • 1
  • 2
  • 3
  • 4

如果消息已经写至队首pcHead ,则pcReadFrom 重新指向最后一个出队的队列项首地址
采用覆写入队方式
需要注意的是采用覆写入队的队列长度必须为1

	if( xPosition == queueOVERWRITE )
	{
		if( uxMessagesWaiting > ( UBaseType_t ) 0 )
		{
			--uxMessagesWaiting;
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果采用覆写入队,直接将队列消息数目减1,在任务结尾会重新将队列消息数目加1,这样队列中的消息数目会一直为0,队列不会进行阻塞,在下一次入队时会直接将前面的数据进行覆盖

pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
  • 1

将队列消息数目增1

3.2 xQueueSend、xQueueSendToBack、xQueueSendToFront、xQueueOverwrite

我们来看一下常用的几种入队函数的封装:
xQueueSend和xQueueSendToBack:
xQueueSend和xQueueSendToBack的封装完全一样:

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
  • 1
  • 2

xQueueSendToFront:

#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
  • 1

xQueueOverwrite:

#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
  • 1

上述几种封装都类似,仅仅对通用入队函数xQueueGenericSend的参数xCopyPosition进行了不同选择

4、通用出队函数xQueueGenericReceive

taskENTER_CRITICAL();
  • 1

首先进入临界区

const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
  • 1

获取队列中的消息数目,保存至uxMessagesWaiting

if( uxMessagesWaiting > ( UBaseType_t ) 0 )
  • 1

首先判断队列中是否有消息可读取,若队列中的消息不为空:

pcOriginalReadPosition = pxQueue->u.pcReadFrom;
  • 1

记录当前读取消息的地址位置,用于实现“偷看”,即只读取消息,不删除消息

prvCopyDataFromQueue( pxQueue, pvBuffer );
  • 1

将需要读取的消息复制至缓存区pvBuffer
如果消息出队,不是“偷看消息”:

pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
  • 1

消息出队,消息数减一

					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
					{
						/*将任务从入队阻塞列表中移除*/
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
						{
							queueYIELD_IF_USING_PREEMPTION();
						}
						}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

消息出队后,队列就不满了,一些在入队阻塞列表中的任务就可以解除阻塞状态了,上述代码即判断入队阻塞列表是否为空,不为空则将任务从阻塞列表中移除,再进行一次任务切换
“偷看”消息:

pxQueue->u.pcReadFrom = pcOriginalReadPosition;
  • 1

重新赋值下次读取消息的地址位置,这样读取消息后而不会删除队列消息

				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
				{
					if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
					{
						/* The task waiting has a higher priority than this task. */
						queueYIELD_IF_USING_PREEMPTION();
					}
					}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

判断是否有任务因等待消息而阻塞,如果有,则将任务从出队阻塞列表中删除,并进行一次任务切换
队列中的消息为空:

			if( xTicksToWait == ( TickType_t ) 0 )
			{
				taskEXIT_CRITICAL();
				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

未设置阻塞时间,则直接退出临界区,返回errQUEUE_EMPTY

			else if( xEntryTimeSet == pdFALSE )
			{
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}
  • 1
  • 2
  • 3
  • 4
  • 5

设置了阻塞时间,即阻塞时间不为0,初始化超时结构体,并将超时标志位xEntryTimeSet 置为pdTRUE

	taskEXIT_CRITICAL();
	vTaskSuspendAll();
	prvLockQueue( pxQueue );
  • 1
  • 2
  • 3

退出临界区,挂起任务调度器,将队列上锁

if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
  • 1

更新超时状态,并判断超时时间是否结束

if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
  • 1

判断队列是否为空

vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
  • 1
  • 2

若超时时间未结束且队列为空,则直接将出队任务挂载至出队阻塞列表,再重新将任务解锁

prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
  • 1
  • 2

若超时时间未结束,队列不为空,则将任务解锁,恢复任务调度器,重新执行一次出队任务

		prvUnlockQueue( pxQueue );
		( void ) xTaskResumeAll();
		if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
		{
			traceQUEUE_RECEIVE_FAILED( pxQueue );
			return errQUEUE_EMPTY;
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

若超时时间结束,则将队列解锁,恢复任务调度器,并判断队列是否为空,若队列此时仍为空,表明无消息可读,返回errQUEUE_EMPTY;若队列不为空,则再次执行该函数进行消息读取

4.1 prvCopyDataFromQueue

队列通常采用的是先进先出(FIFO)缓冲机制,当队列入队采用后向入队时(往队列发送数据时,发送到队列尾部,从队列读取数据时,从队列头部读取)

假设队列采用后向入队,入队了两个消息,项目1先入队,项目2随后入队,则此时队列结构如下图所示(注意pcWriteTo和pcReadFrom指向
在这里插入图片描述

按照出队函数的流程:

if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
  • 1

首先判断队列项是否不为0

pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
  • 1

然后将pcReadFrom 向后偏移一个队列项大小,偏移后如下图所示:
在这里插入图片描述

		if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) 
		{
			pxQueue->u.pcReadFrom = pxQueue->pcHead;
		}
  • 1
  • 2
  • 3
  • 4

按上图所示pcReadFrom 指向了队列尾pcTail ,应重新将pcReadFrom 指向队列头pcHead,如下图所示:
在这里插入图片描述

( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
  • 1

最后调用memcpy函数,将以pcReadFrom为起始地址,向后uxItemSize个字节的内容复制到缓冲区pvBuffer中,即实现了先入队的消息先出队FIFO

假设队列采用前向入队,入队了两个消息,则此时队列结构如下图所示(注意pcWriteTo和pcReadFrom指向
在这里插入图片描述
其中第一个入队的消息为项目1,第二个入队的消息为项目2
同样,我们按照出队函数的流程:

pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
  • 1

然后将pcReadFrom 向后偏移一个队列项大小,偏移后如下图所示:
在这里插入图片描述

	if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) 
	{
		pxQueue->u.pcReadFrom = pxQueue->pcHead;
	}
  • 1
  • 2
  • 3
  • 4

判断是否到队列尾部pcTail,目前还未到队列尾部,忽略该段代码

( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
  • 1

最后调用memcpy函数,将以pcReadFrom为起始地址,向后uxItemSize个字节的内容复制到缓冲区pvBuffer中,优先将后入队的项目2先读取,即实现了后入队先出队LIFO

4.2 xQueueReceive、xQueuePeek

最后我们来看一下两个常用出队函数的封装
xQueueReceive:

#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )
  • 1

该函数直接将“偷看”置为pdFALSE
xQueuePeek:

#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdTRUE )
  • 1

相反,peek函数直接将“偷看”标志位置为pdTRUE

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/825862
推荐阅读
相关标签
  

闽ICP备14008679号