FreeRTOS 队列(二)

news2024/9/22 17:29:03

文章目录

  • 一、向队列发送消息
    • 1. 函数原型
      • (1)函数 xQueueOverwrite()
      • (2)函数 xQueueGenericSend()
      • (3)函数 xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()
      • (4)函数 xQueueOverwriteFromISR()
      • (5)函数 xQueueGenericSendFromISR()
    • 2. 任务级通用入队函数
    • 3. 中断级通用入队函数
  • 二、队列上锁和解锁
  • 三、从队列读取消息
    • 1. 函数 xQueueReceive()
    • 2. 函数 xQueuePeek()
    • 3. 函数 xQueueGenericReceive()
    • 4. 函数 xQueueReceiveFromISR()
    • 5. 函数 xQueuePeekFromISR()


一、向队列发送消息

1. 函数原型

创建好队列以后就可以向队列发送消息了,FreeRTOS 提供了 8 个向队列发送消息的 API 函数,如下表所示:
在这里插入图片描述
1、函数 xQueueSend()、xQueueSendToBack()和 xQueueSendToFront()这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏,其中函数 xQueueSend()和 xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。然而!这三个函数最后都是调用的同一个函数:xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾,这三个函数的原型如下:

BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
 const void* pvItemToQueue,
 TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);

参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
**pvItemToQueue:**指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。如果为 0 的话当队列满的时候就立即返回;当为 portMAX_DELAY 的
话就会一直等待,直到队列有空闲的队列 项,也就是死等,但是宏INCLUDE_vTaskSuspend 必须为 1。

返回值:
pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。

(1)函数 xQueueOverwrite()

此函数也是用于向队列发送数据的,当队列满了以后会覆写掉旧的数据,不管这个旧数据有没有被其他任务或中断取走。这个函数常用于向那些长度为 1 的队列发送消息,此函数也是一个宏,最终调用的也是函数 xQueueGenericSend(),函数原型如下:

BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
						   const void * pvItemToQueue);

参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
**pvItemToQueue:**指向要发送的消息,发送的时候会将这个消息拷贝到队列中。

返回值:
pdPASS: 向队列发送消息成功,此函数也只会返回 pdPASS!因为此函数执行过程中不在乎队列满不满,满了的话我就覆写掉旧的数据,总之肯定能成功。

(2)函数 xQueueGenericSend()

此函数才是真正干活的,上面讲的所有的任务级入队函数最终都是调用的此函数,此函数也是我们后面重点要讲解的,先来看一下函数原型:

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

参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
**pvItemToQueue:**指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间。
xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。

上面讲解的入队 API 函数就是通过此参数来决定采用哪种入队方式的。

返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败

(3)函数 xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()

这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质也宏,其中函数 xQueueSendFromISR ()和 xQueueSendToBackFromISR ()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToFrontFromISR ()是前向入队,即将新消息插入到队列的前面。这三个函数同样调用同一个函数 xQueueGenericSendFromISR ()。这三个函数的原型如下:

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

参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
**pvItemToQueue:**指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:
pdTRUE: 向队列中发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。

(4)函数 xQueueOverwriteFromISR()

此函数是 xQueueOverwrite()的中断级版本,用在中断服务函数中,在队列满的时候自动覆写掉旧的数据,此函数也是一个宏,实际调用的也是函数xQueueGenericSendFromISR(),此函数原型如下:

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

此函数的参数和返回值同上面三个函数相同。

(5)函数 xQueueGenericSendFromISR()

上面说了 4 个中断级入队函数最终都是调用的函数 xQueueGenericSendFromISR(),这是真正干活的主啊,也是我们下面会详细讲解的函数,先来看一下这个函数的原型,如下:

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

参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
**pvItemToQueue:**指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。

返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。

2. 任务级通用入队函数

不 管 是 后 向 入 队 、 前 向 入 队 还 是 覆 写 入 队 , 最 终 调 用 的 都 是 通 用 入 队 函 数xQueueGenericSend(),这个函数在文件 queue.c 文件中由定义,缩减后的函数代码如下:

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 = ( Queue_t * ) xQueue;
	for( ;; )
	{
		taskENTER_CRITICAL(); //进入临界区
		{
			//查询队列现在是否还有剩余存储空间,如果采用覆写方式入队的话那就不用在
			//乎队列是不是满的啦。
			if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
				( xCopyPosition == queueOVERWRITE ) )
			{
				traceQUEUE_SEND( pxQueue );
				xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue,\ (2)
				 xCopyPosition );
				/**************************************************************************/
				/**************************省略掉与队列集相关代码**************************/
				/**************************************************************************/
				{
				//检查是否有任务由于等待消息而进入阻塞态
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ==\(3)
				pdFALSE )
				{
					if( xTaskRemoveFromEventList( &( pxQueue->\ (4)
						xTasksWaitingToReceive ) ) != pdFALSE )
					{
						//解除阻塞态的任务优先级最高,因此要进行一次任务切换
						queueYIELD_IF_USING_PREEMPTION(); (5)
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else if( xYieldRequired != pdFALSE )
				{
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL();
			return pdPASS; (6)
		}
		else
		{
			if( xTicksToWait == ( TickType_t ) 0 ) (7)
			{
				//队列是满的,并且没有设置阻塞时间的话就直接返回
				taskEXIT_CRITICAL();
				traceQUEUE_SEND_FAILED( pxQueue );
				return errQUEUE_FULL; (8)
			}
			else if( xEntryTimeSet == pdFALSE ) (9)
			{
				//队列是满的并且指定了任务阻塞时间的话就初始化时间结构体
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}
			else
			{
				//时间结构体已经初始化过了,
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
	taskEXIT_CRITICAL(); //退出临界区
	vTaskSuspendAll(); (10)
	prvLockQueue( pxQueue ); (11)
	//更新时间壮态,检查是否有超时产生
	if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (12)
	{
		if( prvIsQueueFull( pxQueue ) != pdFALSE ) (13)
		{
			traceBLOCKING_ON_QUEUE_SEND( pxQueue );
			vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), \ (14)
			xTicksToWait );
			prvUnlockQueue( pxQueue ); (15)
			if( xTaskResumeAll() == pdFALSE ) (16)
			{
				portYIELD_WITHIN_API();
			}
		}
		else
		{
			//重试一次
			prvUnlockQueue( pxQueue ); (17)
			( void ) xTaskResumeAll();
		}
	}
	else
	{
		//超时产生
		prvUnlockQueue( pxQueue ); (18)
		( void ) xTaskResumeAll();
		traceQUEUE_SEND_FAILED( pxQueue );
		return errQUEUE_FULL; (19)
	}
	}
}

(1)、要向队列发送数据,肯定要先检查一下队列是不是满的,如果是满的话肯定不能发送的。当队列未满或者是覆写入队的话就可以将消息入队了。

(2)、调用函数 prvCopyDataToQueue()将消息拷贝到队列中。前面说了入队分为后向入队、前向入队和覆写入队,他们的具体实现就是在函数 prvCopyDataToQueue()中完成的。如果选择后向入队 queueSEND_TO_BACK 的话就将消息拷贝到队列结构体成员 pcWriteTo 所指向的队列项,拷贝成功以后 pcWriteTo 增加 uxItemSize 个字节,指向下一个队列项目。当选择前向入队 queueSEND_TO_FRONT 或者 queueOVERWRITE 的话就将消息拷贝到 u.pcReadFrom 所指向的队列项目,同样的需要调整 u.pcReadFrom 的位置。当向队列写入一个消息以后队列中统计当前消息数量的成员uxMessagesWaiting 就会加一,但是选择覆写入队 queueOVERWRITE 的话还会将 uxMessagesWaiting 减一,这样一减一加相当于队列当前消息数量没有变。

(3) 、 检查是否有任务由于请求队列 消 息 而 阻 塞 , 阻 塞 的 任 务 会 挂 在 队 列 的
xTasksWaitingToReceive 列表上。

(4)、有任务由于请求消息而阻塞,因为在(2)中已将向队列中发送了一条消息了,所以调用函数 xTaskRemoveFromEventList()将阻塞的任务从列表 xTasksWaitingToReceive 上移除,并且把这个任务添加到就绪列表中,如果调度器上锁的话这些任务就会挂到列表 xPendingReadyList 上。如果取消阻塞的任务优先级比当前正在运行的任务优先级高还要标记需要进行任务切换。当函数 xTaskRemoveFromEventList()返回值为 pdTRUE 的话就需要进行任务切换。

(5)、进行任务切换。

(6)、返回 pdPASS,标记入队成功。

(7)、(2)到(6)都是非常理想的效果,即消息队列未满,入队没有任何障碍。但是队列满了以后呢?首先判断设置的阻塞时间是否为 0,如果为 0 的话就说明没有阻塞时间。

(8)、由(7)得知阻塞时间为 0,那就直接返回 errQUEUE_FULL,标记队列已满就可以了。

(9)、如果阻塞时间不为 0 并且时间结构体还没有初始化的话就初始化一次超时结构体变量,调用函数 vTaskSetTimeOutState()完成超时结构体变量 xTimeOut 的初始化。其实就是记录当前的系统时钟节拍计数器的值 xTickCount 和溢出次数 xNumOfOverflows。

(10)、任务调度器上锁,代码执行到这里说明当前的状况是队列已满了,而且设置了不为 0的阻塞时间。那么接下来就要对任务采取相应的措施了,比如将任务加入到队列的
xTasksWaitingToSend 列表中。

(11)、调用函数 prvLockQueue()给队列上锁,其实就是将队列中的成员变量 cRxLock 和
cTxLock 设置为 queueLOCKED_UNMODIFIED。

(12)、调用函数 xTaskCheckForTimeOut()更新超时结构体变量 xTimeOut,并且检查阻塞时间是否到了。

(13)、阻塞时间还没到,那就检查队列是否还是满的。

(14)、经过(12)和(13)得出阻塞时间没到,而且队列依旧是满的,那就调用函数vTaskPlaceOnEventList()将任务添加到队列的 xTasksWaitingToSend 列表中和延时列表中,并且将 任 务 从 就 绪 列 表 中 移 除 。 注 意 ! 如 果 阻 塞 时 间 是 portMAX_DELAY 并 且 宏INCLUDE_vTaskSuspend 为 1 的话,函数vTaskPlaceOnEventList()会将任务添加到列表xSuspendedTaskList 上。

(15)、操作完成,调用函数 prvUnlockQueue()解锁队列。

(16)、调用函数 xTaskResumeAll()恢复任务调度器

(17)、阻塞时间还没到,但是队列现在有空闲的队列项,那么就在重试一次。

(18)、相比于第(12)步,阻塞时间到了!那么任务就不用添加到那些列表中了,那就解锁队列,恢复任务调度器。

(19)、返回 errQUEUE_FULL,表示队列满了。

3. 中断级通用入队函数

讲完任务级入队函数再来看一下中断级入队函数 xQueueGenericSendFromISR(),其他的中断级入队函数都是靠此函数来实现的。中断级入队函数和任务级入队函数大同小异,函数代码如下:

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, 
									 const void * const pvItemToQueue, 
									 BaseType_t * const pxHigherPriorityTaskWoken, 
									 const BaseType_t xCopyPosition )
{
	BaseType_t xReturn;
	UBaseType_t uxSavedInterruptStatus;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
	 		( xCopyPosition == queueOVERWRITE ) )
		{
			const int8_t cTxLock = pxQueue->cTxLock; (2)
			traceQUEUE_SEND_FROM_ISR( pxQueue );
			( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (3)			
			{
				//队列上锁的时候就不能操作事件列表,队列解锁的时候会补上这些操作的。
				if( cTxLock == queueUNLOCKED ) (4)
				{
					/**************************************************************************/
					/**************************省略掉与队列集相关代码**************************/
					/**************************************************************************/
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (5)
						pdFALSE )
					{
						if( xTaskRemoveFromEventList( &( pxQueue->\ (6)
							xTasksWaitingToReceive ) ) != pdFALSE )
						{
							//刚刚从事件列表中移除的任务对应的任务优先级更高,所以
							标记要进行任务切换
							if( pxHigherPriorityTaskWoken != NULL )
							{
								*pxHigherPriorityTaskWoken = pdTRUE; (7)
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
			}
			else
			{
				//cTxLock 加一,这样就知道在队列上锁期间向队列中发送了数据
				pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); (8)
			}
		xReturn = pdPASS; (9)
		}
		else
		{
			traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
			xReturn = errQUEUE_FULL; (10)
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
	return xReturn;
}

(1)、队列未满或者采用的覆写的入队方式,这是最理想的壮态。

(2)、读取队列的成员变量 xTxLock,用于判断队列是否上锁。

(3)、将数据拷贝到队列中。

(4)、队列上锁了,比如任务级入队函数在操作队列中的列表的时候就会对队列上锁。

(5)、判断队列列表 xTasksWaitingToReceive 是否为空,如果不为空的话说明有任务在请求消息的时候被阻塞了。

(6)、将相应的任务从列表 xTasksWaitingToReceive 上移除。跟任务级入队函数处理过程一样。

(7)、如果刚刚从列表 xTasksWaitingToReceive 中移除的任务优先级比当前任务的优先级高,那么标记 pxHigherPriorityTaskWoken 为 pdTRUE,表示要进行任务切换。如果要进行任务切换的话就需要在退出此函数以后,退出中断服务函数之前进行一次任务切换。

(8)、如果队列上锁的话那就将队列成员变量 cTxLock 加一,表示进行了一次入队操作,在队列解锁(prvUnlockQueue())的时候会对其做相应的处理。

(9)、返回 pdPASS,表示入队完成。

(10)、如果队列满的话就直接返回 errQUEUE_FULL,表示队列满。

二、队列上锁和解锁

在上面讲解任务级通用入队函数和中断级通用入队函数的时候都提到了队列的上锁和解锁,队列的上锁和解锁是两个 API 函数:prvLockQueue()和 prvUnlockQueue()。首先来看一下队列上锁函数 prvLockQueue(),此函数本质上就是一个宏,定义如下:

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

prvLockQueue()函数很简单,就是将队列中的成员变量 cRxLock 和 cTxLock 设置为
queueLOCKED_UNMODIFIED 就行了。

在来看一下队列的解锁函数 prvUnlockQueue(),函数如下:

static void prvUnlockQueue( Queue_t * const pxQueue )
{
	//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
	//上锁以后队列项是可以加入或者移除队列的,但是相应的列表不会更新。
	taskENTER_CRITICAL();
	{
		//处理 cTxLock。
		int8_t cTxLock = pxQueue->cTxLock;
		while( cTxLock > queueLOCKED_UNMODIFIED ) (1)
		{
			/**************************************************************************/
			/**************************省略掉与队列集相关代码**************************/
			/**************************************************************************/
			{
				//将任务从事件列表中移除
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (2)
				pdFALSE )
				{
					if( xTaskRemoveFromEventList( &( pxQueue->\ (3)
					xTasksWaitingToReceive ) ) != pdFALSE )
					{
						//从列表中移除的任务优先级比当前任务的优先级高,因此要
						//进行任务切换。
						vTaskMissedYield(); (4)
					}
					else
					{	
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					break;
				}
			}
			--cTxLock; (5)
		}
		pxQueue->cTxLock = queueUNLOCKED; (6)
	}
	taskEXIT_CRITICAL();
	//处理 cRxLock。
	taskENTER_CRITICAL();
	{
		int8_t cRxLock = pxQueue->cRxLock;
		while( cRxLock > queueLOCKED_UNMODIFIED ) (7)
		{
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) !=\
				pdFALSE )
				{
					vTaskMissedYield();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				--cRxLock;
			}
			else
			{
				break;
			}
		}
		pxQueue->cRxLock = queueUNLOCKED; 
	}
	taskEXIT_CRITICAL();
}

(1)、判断是否有中断向队列发送了消息,在前小节讲解中断级通用入队函数的时候说了,如果当队列上锁的话那么向队列发送消息成功以后会将入队计数器 cTxLock 加一。

(2)、判断列表 xTasksWaitingToReceive 是否为空,如果不为空的话就要将相应的任务从列表中移除。

(3)、将任务从列表 xTasksWaitingToReceive 中移除。

(4)、如果刚刚从列表 xTasksWaitingToReceive 中移除的任务优先级比当前任务的优先级高,那么就要标记需要进行任务切换。这里调用函数 vTaskMissedYield()来完成此任务,函数vTaskMissedYield()只是简单的将全局变量 xYieldPending 设置为 pdTRUE。那么真正的任务切换是在哪里完成的呢?在时钟节拍处理函数 xTaskIncrementTick()中,此函数会判断 xYieldPending的值,从而决定是否进行任务切换

(5)、每处理完一条就将 cTxLock 减一,直到处理完所有的。

(6)、当处理完以后标记 cTxLock 为 queueUNLOCKED,也就说 cTxLock 是没有上锁的了。

(7)、处理完 cTxLock 以后接下来就要处理 xRxLock 了,处理过程和 xTxLock 很类似

三、从队列读取消息

有入队就有出队,出队就是从队列中获取队列项(消息),FreeRTOS 中出队函数如下表所示:
在这里插入图片描述

1. 函数 xQueueReceive()

此函数用于在任务中从队列中读取一条(请求)消息,读取成功以后就会将队列中的这条数
据删除,此函数的本质是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:

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

参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。

返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

2. 函数 xQueuePeek()

此函数用于从队列读取一条(请求)消息,只能用在任务中!此函数在读取成功以后不会将
消息删除,此函数是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:

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

参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏INCLUDE_vTaskSuspend 必须为 1。

返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

3. 函数 xQueueGenericReceive()

不 管 是 函 数 xQueueReceive() 还 是 xQueuePeek() ,最终都是调用的函数xQueueGenericReceive(),此函数是真正干事的,函数原型如下:

BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
								void* pvBuffer,
								TickType_t xTicksToWait
								BaseType_t xJustPeek)

参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
xJustPeek: 标记当读取成功以后是否删除掉队列项,当为 pdTRUE 的时候就不用删除,也就是说你后面再调用函数 xQueueReceive()获取到的队列项是一样的。当为
pdFALSE 的时候就会删除掉这个队列项。

返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

4. 函数 xQueueReceiveFromISR()

此函数是 xQueueReceive()的中断版本,用于在中断服务函数中从队列中读取(请求)一条消息,读取成功以后就会将队列中的这条数据删除。此函数在读取消息的时候是采用拷贝方式的,所以需要用户提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
								void* pvBuffer,
								BaseType_t * pxTaskWoken);

参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
pxTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值是由函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

5. 函数 xQueuePeekFromISR()

此函数是 xQueuePeek()的中断版本,此函数在读取成功以后不会将消息删除,此函数原型如下:

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
							 void * pvBuffer)

参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。

返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/447499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GIT的常见命令

前言&#xff1a; 在日常生活或者工作中&#xff0c;我们都会是不是用到Git&#xff0c;今天我就总结了一些Git常见命令。若有些木有的&#xff0c;可以使用git help 获取到git的常见命令&#xff0c;那我们接下来就从git help 中的介绍常见命令。 一&#xff1a;建立本地仓库…

TCP 与 bufferbloat

说到既能降低成本&#xff0c;又能降低时延&#xff0c;总觉得这在 pr&#xff0c;兜售自己或卖东西。毕竟哪有这么好的事&#xff0c;鱼与熊掌兼得。可事实上是人们对 buffer 的理解错了才导致了这种天上掉馅饼的事发生。 人们总觉得 buffer 越大越好&#xff0c;buffer 越大…

Maven安装教程以及修改下载镜像源等配置

第一步&#xff1a;下载maven&#xff08;本教程安装的是3.8.4&#xff09; 官方下载链接&#xff1a;Maven – Download Apache Maven Binary是可执行版本&#xff0c;已经编译好可以直接使用。 Source是源代码版本&#xff0c;需要自己编译成可执行软件才可使用。 我们选择…

【WSN定位】基于RSSI的加权质心定位算法【Matlab代码#14】

文章目录 1. 原始质心定位算法2. 基于RSSI的加权质心定位算法基本思想3. 基于RSSI的加权质心定位算法流程图4. 部分代码展示5. 运行结果展示6. 资源获取 1. 原始质心定位算法 可参考质心定位算法 2. 基于RSSI的加权质心定位算法基本思想 传统的质心算法在求解过程中只是将未…

Windows逆向安全(一)之基础知识(十六)

指针三 通过先前指针的学习&#xff0c;了解了指针和地址以及数据的关系&#xff0c;现在结合先前的知识继续学习巩固 指针遍历数组 有了先前的基础&#xff0c;再来看看如何用指针遍历数组 代码 #include "stdafx.h" void function(){short arr[5]{1,2,3,4,5};…

【ARM Coresight 4 - Rom Table 介紹】

文章目录 1.1 ROM Table1.1.1 Entry 寄存器 1.2 ROM Table 例子 1.1 ROM Table 在一个SoC中&#xff0c;有多个Coresight 组件&#xff0c;但是软件怎么去识别这些 Coresight 组件&#xff0c;去获取这些Coresight 组件的信息了&#xff1f;这个时候&#xff0c;就需要靠 Core…

COPU助力北大研究生开源公选课丨2023开源PostgreSQL内核开发通识课顺

COPU & Peking Univerisity 导读&#xff1a;2020年1月COPU&#xff08;中国开源软件推进联盟&#xff09;成员开会讨论面向高校的开源示范课程&#xff0c;由联盟副秘书长北京大学荆琦老师牵头筹备&#xff0c;并首先在北大软微学院试点。本次是中国PostgreSQL分会联合…

尚硅谷_宋红康_第14章_数据结构与集合源码

第14章_数据结构与集合源码 本章专题与脉络 1. 数据结构剖析 我们举一个形象的例子来理解数据结构的作用&#xff1a; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgDcr8wF-1682075329317)(images/image-20220412011531879.png)] **战场&#x…

本地白嫖AI绘画 ,Stable Diffusion 初探!

本文介绍我在本地搭建 Stable Diffusion Web UI 的体验过程&#xff0c;予以记录分享。 Stable Diffusion 是 2022 年 8 发布的深度学习文本到图像生成模型。它主要用于根据文本的描述产生详细图像&#xff0c;官方项目其实并不适合新手直接使用&#xff0c;好在有使用 Stable …

把握数字中国建设重大契机,实在智能携手山东商业职业技术学院共建“现代金融数字化实训中心”

今年2月&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff08;以下简称《规划》&#xff09;&#xff0c;明确了数字中国建设的整体框架&#xff0c;强调全面提升数字中国建设的整体性、系统性、协同性&#xff0c;促进数字经济和实体经济深度融合。其中&…

本地部署Stable Diffusion Webui AI 记录

Stable Diffusion Webui AI本地部署基本分为两种方式&#xff1a; 1、使用大佬的打包好的安装包一键部署 b站秋葉aaaki 2、手动部署&#xff08;个人实践记录&#xff09;参考文章 本地部署基本要求 1、 需要拥有NVIDIA显卡&#xff0c;GTX1060 &#xff08;或者同等算力的…

CopyOnWriteArrayList简介

1. 简介 CopyOnWriteArrayList 是 ArrayList 的线程安全版本 就是在进行写操作的时候会 copy 原数组&#xff0c;然后写完将指针指向新的数组&#xff0c;是一种读写分离的思想&#xff0c;可以并发的读&#xff0c;不能并发的写 优点&#xff1a; 保证线程安全读取时不加锁…

基于PyQt5的图形化界面开发——自制MQTT客户端软件

基于 PyQt5 的图形化界面开发——自制MQTT客户端 0. 前言1. 第三方库的安装及注意事项2. Editor.py2.1 配置界面效果演示&#xff1a; 3. Publish.py3.1 消息发布界面演示 4. Subcribe.py4.1 订阅消息效果演示&#xff1a; 界面切换——main.py5. 写在最后 0. 前言 使用 PyQt5…

葛兰一季度规模再度跌破900亿

一季度末管理规模再度跌破900亿元&#xff0c;中欧基金葛兰交出了公募主动权益基金管理规模的头把交椅。 4月22日零点刚过&#xff0c;葛兰在管基金悉数披露2023年一季报&#xff0c;从管理规模来看&#xff0c;一季度葛兰在管5只公募基金合计规模降至844.40亿元&#xff0c;较…

keep-alive 和 router-view 的使用方法(Vue3)

系列文章目录 提示&#xff1a;主要是介绍keep-alive 和 router-view在Vue3中的使用方法&#xff0c;以及适用场景&#xff01;&#xff01;&#xff01; 文章目录 系列文章目录前言&#xff1a;一、router-view&#xff1a;1. 常规使用方法2. 非常规使用方法&#xff08;插槽&…

UE5语音识别和语音合成-阿里云智能语音-短视频-翻译-文章-AI角色等

UE5智能语音 哈喽&#xff0c;大家好&#xff0c;我叫人宅&#xff0c;很高兴和大家一起分享本套课程&#xff0c;阿里云智能语音UE5版本开发。阿里云智能语音一共分为 语音合成&#xff0c;语音识别&#xff0c;什么是语音合成&#xff0c;它可以将您的文字转化成您设定的任何…

大数据数仓维度建模

目录 维度建模分为三种&#xff1a; 1、星型模型&#xff1a; 2、雪花模型&#xff1a; 3、星座模型&#xff1a; 模型的选择&#xff1a; 维度表和事实表&#xff1a; 维度表&#xff1a; 维度表特性 &#xff1a; 事实表&#xff1a; 事实表特性&#xff1a; 事务型…

程序员能干多久?程序员能干到多大年龄?

程序员可以工作多少年?大多数程序员认为程序员是吃青春饭的工作。编程只能干到30岁&#xff0c;最长可达35岁。我经常听到这样的话&#xff0c;都让人倍感压力。今天&#xff0c;我们来谈谈这个老话题...... 程序员能干多久&#xff1f; 根据国外的经验来说&#xff0c;干到…

ChatGPT 基础使用方法

文章目录 1. ChatGPT 是下一代搜索引擎2. ChatGPT 是学习助手3. ChatGPT API 简介4. ChatGPT API 身份5. 开发痛点6. 机会与前景7. Images8. Audio 1. ChatGPT 是下一代搜索引擎 根据 3 月份对 ChatGPT 的使用&#xff0c;我对它的理解是下一代的搜索引擎&#xff0c;即能够根…

【社区图书馆】读《大话数据结构溢彩加强版》

目录 书中简介&#xff1a; 选读原因 本书内容有哪些&#xff1a; 学会了什么&#xff1a; 书中简介&#xff1a; 《大话数据结构【溢彩加强版】》以一个计算机教师的教学过程为场景&#xff0c;讲解数据结构和相关算法的知识。全书以趣味方式来叙述&#xff0c;大量引用各…