FreeRTOS 教程指南 学习笔记 第四章 队列管理

FreeRTOS 教程指南 学习笔记 第四章 队列管理

一、简介

本章旨在让读者能够很好地理解:

  • 如何创建一个队列。
  • 队列如何管理其包含的数据。
  • 如何将数据发送到队列。
  • 如何从队列中接收数据。
  • 在队列上阻塞的含义。
  • 如何在多个队列上阻塞。
  • 如何覆盖队列中的数据。
  • 如何清除一个队列。
  • 写入队列和阅读队列时任务优先级的影响。

本章中只介绍了任务到任务的通信。第6章介绍了任务中断和中断到任务通信。

二、队列的特征

数据存储

一个队列可以容纳有限个数的固定大小的数据对象。一个队列可以保存对象的最大个数称为它的“长度”。在创建队列时,将设置每个数据项的长度和大小。
队列通常用作先进先出(FIFO)缓冲区,其中数据被写入队列的末端(尾部)并从队列的前端(头)删除。图31演示了从正在用作FIFO的队列中写入和读取的数据。也可以写入队列的前面,并覆盖已经在队列前面的数据。
在这里插入图片描述
有两种方式可以实现队列行为:

  1. 复制队列
    复制队列意味着发送到队列的数据被逐字节复制到队列中。
  2. 引用队列
    引用队列意味着队列只保存指向发送到队列的数据的指针,而不是数据本身。

FreeRTOS通过复制方法使用队列。通过复制的队列被认为比通过引用的队列更强大和更容易使用,因为:

  • 栈变量可以直接发送到队列,即使在声明它的函数退出后变量将不存在。
  • 数据可以发送到队列,而不需要首先分配缓冲区来保存数据,然后将数据复制到分配的缓冲区中。
  • 发送任务可以立即重用已发送到队列的变量或缓冲区。
  • 发送任务和接收任务是完全解耦合的——应用程序设计者不需要关心哪个任务“拥有”数据,或者哪个任务负责释放数据。
  • 复制队列并不阻止队列也被用于引用队列。也可以将指向数据的指针复制到队列中。
  • RTOS完全负责分配用于存储数据的内存。
  • 在内存保护系统中,任务可以访问的RAM将受到限制。在这种情况下,只有当发送和接收任务都可以访问存储数据的RAM时,才能使用引用排队。复制队列不施加这种限制;内核始终以完全的特权运行,允许使用队列跨内存保护边界传递数据。
通过多个任务访问

队列本身是任何知道其存在的任务或ISR都可以访问的对象。任意数量的任务都可以写入同一队列,任意数量的任务都可以从同一队列中读取。在实践中,队列有多个写入者很常见,但队列有多个读取者就不常见了。

在读取队列上阻塞

当一个任务尝试从队列中读取时,它可以选择指定一个“阻塞”时间。如果队列已为空,则此时该任务将保持在阻塞状态,以等待队列中的数据可用的时间。处于“阻塞”状态的任务,等待数据从队列中可用时,当另一个任务或中断将数据放入队列时,将自动移动到“就绪”状态。如果指定的阻塞时间在数据可用之前超时,则任务也将自动从“阻塞”状态移动到“就绪”状态。
队列可以有多个读取者,因此单个队列可能阻塞多个任务等待数据。在这种情况下,当数据可用时,只有一个任务将被解除阻塞。被解除阻塞的任务将始终是等待任务中优先级最高者。如果被阻塞的任务具有相同的优先级,那么等待时间最长的任务将被解除阻塞。

在写入队列上阻塞

正如从队列中读取时一样,任务可以在写入队列时选择指定阻塞时间。在这种情况下,如果队列已经满,阻塞时间是任务在阻塞状态下等待队列空间可用的最大时间。
队列可以有多个写入者,因此一个完整的队列可能会阻塞多个任务,等待完成一个发送操作。在这种情况下,当队列上的空间可用时,只有一个任务将被解除阻塞。被解除阻塞的任务将始终是等待任务中优先级最高者。如果被阻塞的任务具有相同的优先级,那么等待时间最长的任务将被解除阻塞。

在多个队列上阻塞

可以将多个队列为组成一个队列集,允许任务进入“阻止”状态,等待集合中的任何队列上的数据可用。队列集在第4.6节“从多个队列接收”中进行了演示。

三、使用队列

必须显式创建队列。队列由句柄引用,它们是QueueHandle_t类型的变量。xQueueCreate()API函数将创建一个队列,并返回一个引用它所创建的队列的QueueHandle_t。
FreeRTOS在创建队列时从FreeRTOS堆中分配RAM,用于保存队列数据结构和包含在队列中的项。如果没有足够的堆RAM,Create()将返回NULL。第2章提供了关于FreeRTOS堆的更多信息。

xQueueCreate() API Function
//Listing 40. The xQueueCreate() API function prototype
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
/*参数uxQueueLength:所创建的队列在任何时候都可以保存的最大项目数。即队列长度*/
/*参数uxItemSize:队列所保存对象的单个对象大小*/
/*返回值QueueHandle_t:如果返回NULL,则无法创建队列,因为FreeRTOS没有足够的堆内存来分配队列数据结构和存储区域。返回的非null值表示队列已成功创建。返回的值应该存储为已创建队列的句柄。*/

创建队列后,可以使用xQueueReset()API函数将队列返回到原来的空状态。

The xQueueSendToBack() and xQueueSendToFront() API Functions

正如预期的那样,xQueueSendToBack()用于将数据发送到队列的尾部(尾部),而xQueueSendToFront()用于将数据发送到队列的前端(头)。
xQueueSend()完全等价于xQueueSendToBack()。
注意:永远不要从中断服务例程调用xQueueSendToFront()或xQueueSendToBack()。中断安全版本的为xQueueSendToBackFromISR()和xQueueSendToFrontFromISR()。

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

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

/*参数xQueue:要发送数据的队列的句柄(写入)。队列句柄将从对用于创建队列的xQueueCreate()的调用中返回。*/
/*参数pvItemToQueue:一个指向要复制到队列中的数据的指针。在创建队列时设置了队列可以保存的每个项的大小,因此这些字节将从pvItemToQueue复制到队列存储区域中。*/
/*参数xTicksToWait :如果队列已满,则任务应保持在“阻塞”状态以等待队列上的空间可用的最长时间。如果排队等待为零,且队列已经满,那么xQueueSendToFront()和xQueueSendToBack()都将立即返回。阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。如果INCLUDE_vTaskSuspend在FreersTOSConfig.h中设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。*/
/*返回值:有两个可能的返回值:1.只有当数据成功发送到队列时,才返回PASSpdPASS。如果指定了阻塞时间(xTicksToWait不是零),则可能将调用任务放入阻塞状态,等待函数返回之前空间在队列中可用,但数据在阻塞时间超时之前成功写入队列。 2. 如果由于队列已满,数据无法写入队列,则将返回errQUEUE_FULL。如果指定了阻塞时间(xTicksToWait不是零),那么调用任务将被放置入阻塞状态,等待另一个任务或中断在队列中腾出空间,但是指定的阻塞时间在此发生之前已经超时。*/
The xQueueReceive() API Function

xQueueReceive()用于从队列中接收(读取)一个项目。接收到的项目将从队列中删除。
注意:永远不要从中断服务例程调用xQueueReceive()。xQueueReceiveFromISR()API函数在第6章中描述。

//Listing 43. The xQueueReceive() API function prototype
BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                         TickType_t xTicksToWait );
/*参数xQueue:要读取数据的队列的句柄(写入)。队列句柄将从对用于创建队列的xQueueCreate()的调用中返回。*/
/*参数pvBuffer:指向将接收到的数据复制到的内存的指针。在创建队列时,将设置队列所持有的每个数据项的大小。pvBuffer所指向的内存必须至少大到足以容纳那么多个字节。。*/
/*参数xTicksToWait :如果队列已空,则任务应保持在“阻塞”状态以等待队列上有数据可用的最长时间。如果排队等待为零,且队列已经空,那么xQueueReceive()将立即返回。阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。如果INCLUDE_vTaskSuspend在FreersTOSConfig.h中设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。*/
/*返回值:有两个可能的返回值:1.只有当数据成功从到队列读取时,才返回PASSpdPASS。如果指定了阻塞时间(xTicksToWait不是零),则可能将调用任务放入阻塞状态,等待函数返回之前队列中有数据可用,但数据在阻塞时间超时之前成功从队列读取到数据。 2. 如果由于队列已空,无法从队列读取数据,则将返回errQUEUE_EMPTY。如果指定了阻塞时间(xTicksToWait不是零),那么调用任务将被放置入阻塞状态,等待另一个任务或中断在队列中写入数据,但是指定的阻塞时间在此发生之前已经超时。*/
The uxQueueMessagesWaiting() API Function

uxQueueMessagesWaiting()用于查询当前在队列中的项目的数量。注意:永远不要从中断服务例程调用uxQueueMessagesWaiting()。应该使用uxQueueMessagesWaitingFromISR()来代替它。

//Listing 44. The uxQueueMessagesWaiting() API function prototype
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
/*参数xQueue:要查询的队列的句柄。队列句柄将从对用于创建队列的xQueueCreate()的调用中返回。*/
/*返回值:正在查询的队列当前保留的项目数。如果返回0,则该队列为空。*/
例子10:阻塞在队列接收上

本示例演示了创建队列、从多个任务向该队列发送数据以及从该队列接收数据。创建的队列用于保存int32_t类型的数据项。向队列发送速度的任务不指定阻塞时间,而从队列接收数据的任务则指定阻塞时间。
向队列发送的任务优先级低于从队列接收到任务。这意味着队列不应该包含超过一个项目,因为一旦数据被发送到队列,接收任务将解除阻塞,抢占发送任务,并删除数据——使队列再次为空。

static void vSenderTask( void *pvParameters )
{
	int32_t lValueToSend;
	BaseType_t xStatus;
 	/* Two instances of this task are created so the value that is sent to the queue is passed in via the task parameter - this way each instance can use  a different value. The queue was created to hold values of type int32_t,  so cast the parameter to the required type. */
 	lValueToSend = ( int32_t ) pvParameters;
 	/* As per most tasks, this task is implemented within an infinite loop. */
 	for( ;; )
 	{
 		/* Send the value to the queue. The first parameter is the queue to which data is being sent. The  queue was created before the scheduler was started, so before this task started to execute. The second parameter is the address of the data to be sent, in this case the address of lValueToSend. The third parameter is the Block time – the time the task should be kept in the Blocked state to wait for space to become available on the queue should the queue already be full. In this case a block time is not  specified because the queue should never contain more than one item, and therefore never be full. */
 		xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
 		if( xStatus != pdPASS )
 		{
 			/* The send operation could not complete because the queue was full - this must be an error as the queue should never contain more than  one item! */
 			vPrintString( "Could not send to the queue.\r\n" );
 		}
 	}
 }

static void vReceiverTask( void *pvParameters )
{
	/* Declare the variable that will hold the values received from the queue. */
	int32_t lReceivedValue;
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
 	/* This task is also defined within an infinite loop. */
 	for( ;; )
 	{
 		/* This call should always find the queue empty because this task will immediately remove any data that is written to the queue. */
 		if( uxQueueMessagesWaiting( xQueue ) != 0 )
 		{
 			vPrintString( "Queue should have been empty!\r\n" );
 		}
 		/* Receive data from the queue. The first parameter is the queue from which data is to be received. The queue is created before the scheduler is started, and therefore before this task runs for the first time. The second parameter is the buffer into which the received data will be placed. In this case the buffer is simply the address of a variable that has the required size to hold the received data. The last parameter is the block time – the maximum amount of time that the task will remain in the Blocked state to wait for data to be available  should the queue already be empty. */
 		xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
 		if( xStatus == pdPASS )
 		{
 			/* Data was successfully received from the queue, print out the received value. */
 			vPrintStringAndNumber( "Received = ", lReceivedValue );
 		}
 		else
 		{
 			/* Data was not received from the queue even after waiting for 100ms. This must be an error as the sending tasks are free running and will be continuously writing to the queue. */
 			vPrintString( "Could not receive from the queue.\r\n" );
 		}
 	}
 }
 
/* Declare a variable of type QueueHandle_t. This is used to store the handleto the queue that is accessed by all three tasks. */
QueueHandle_t xQueue;
int main( void )
{
 	/* The queue is created to hold a maximum of 5 values, each of which is large enough to hold a variable of type int32_t. */
 	xQueue = xQueueCreate( 5, sizeof( int32_t ) );
 	if( xQueue != NULL )
 	{
 		/* Create two instances of the task that will send to the queue. The task parameter is used to pass the value that the task will write to the queue, so one task will continuously write 100 to the queue while the other task  will continuously write 200 to the queue. Both tasks are created at priority 1. */
 		xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
 		xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
 		/* Create the task that will read from the queue. The task is created with priority 2, so above the priority of the sender tasks. */
 		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
 		/* Start the scheduler so the created tasks start executing. */
 		vTaskStartScheduler();
 	}
 	else
	 {
 		/* The queue could not be created. */
 	}
 
 	/* If all is well then main() will never reach here as the scheduler will  now be running the tasks. If main() does reach here then it is likely that  there was insufficient FreeRTOS heap memory available for the idle task to be  created. Chapter 2 provides more information on heap memory management. */
 	for( ;; );
}

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

四、从多个来源接收数据

在FreeRTOS设计中,为一个任务从多个源接收数据是很常见的。接收任务需要知道数据来自哪里,以确定应该如何处理数据。一个简单的设计解决方案是使用单个队列来传输结构体,结构体的字段中包含的数据值和数据源的结构。该方案如图34所示。
在这里插入图片描述
参考图34:

  • 创建了一个队列,其成员为包含了Data_t类型的结构体。结构成员允许数据值和枚举类型,以指示在一条消息中表明发送到队列的数据是何意义。
  • 有一个中央控制任务用于执行主系统功能。该任务必须对 在队列上传递的系统状态的输入和改变 做相应出反应。
  • 一个CAN总线任务用于封装CAN总线接口功能。当CAN总线任务接收到并解码了一条消息时,它将已经解码过的消息发送到Data_t结构中的控制器任务。传输结构中的eDataID成员用于让控制器任务知道数据是什么——在描述的情况下,它是一个电机速度值。使用传输结构中的lDataValue成员,让控制器任务知道实际的电机转速值。
  • 一个人机界面(HMI)任务用于封装所有的HMI功能。机器操作员可能会输入命令和查询值,这些操作必须在HMI任务中被检测和理解执行。当输入一个新命令时,HMI任务将该命令以Data_t结构发送给控制器任务。传输结构中的eDataID成员用于让控制器任务知道数据是什么——图中描述为操作员想要设定一个新的设定值。传输结构中的lDataValue成员用于让控制器任务知道实际的设定点值。
Example 11. Blocking when sending to a queue, and sending structures on a queue
/* Define an enumerated type used to identify the source of the data. */
typedef enum
{
 	eSender1,
 	eSender2
} DataSource_t;
/* Define the structure type that will be passed on the queue. */
typedef struct
{
 	uint8_t ucValue;
 	DataSource_t eDataSource;
} Data_t;
/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{
 	{ 100, eSender1 }, /* Used by Sender1. */
 	{ 200, eSender2 } /* Used by Sender2. */
}

static void vSenderTask( void *pvParameters )
{
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
 	/* As per most tasks, this task is implemented within an infinite loop. */
 	for( ;; )
 	{
 		/* Send to the queue. The second parameter is the address of the structure being sent. The address is passed in as the task parameter so pvParameters is used  directly. The third parameter is the Block time - the time the task should be kept in the Blocked state to wait for space to become available on the queue if the queue is already full. A block time is specified because the sending tasks have a higher priority than the receiving task so the queue is expected to become full. The receiving task will remove items from  the queue when both sending tasks are in the Blocked state. */
 		xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
 		if( xStatus != pdPASS )
 		{
 		/* The send operation could not complete, even after waiting for 100ms. This must be an error as the receiving task should make space in the  queue as soon as both sending tasks are in the Blocked state. */
 		vPrintString( "Could not send to the queue.\r\n" );
 		}
 	}
 }

static void vReceiverTask( void *pvParameters )
{
	/* Declare the structure that will hold the values received from the queue. */
	Data_t xReceivedStructure;
	BaseType_t xStatus;
 	/* This task is also defined within an infinite loop. */
 	for( ;; )
 	{
 		/* Because it has the lowest priority this task will only run when the sending tasks are in the Blocked state. The sending tasks will only enter the Blocked state when the queue is full so this task always expects the number of items in the queue to be equal to the queue length, which is 3 in this case. */
 		if( uxQueueMessagesWaiting( xQueue ) != 3 )
 		{
 			vPrintString( "Queue should have been full!\r\n" );
		 }
 		/* Receive from the queue. The second parameter is the buffer into which the received data will be placed. In this case the buffer is simply the address of a variable that has the required size to hold the received structure.  The last parameter is the block time - the maximum amount of time that the task will remain in the Blocked state to wait for data to be available  if the queue is already empty. In this case a block time is not necessary  because this task will only run when the queue is full. */
 		xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
 		if( xStatus == pdPASS )
 		{
 			/* Data was successfully received from the queue, print out the received value and the source of the value. */
			 if( xReceivedStructure.eDataSource == eSender1 )
 			{
 				vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
 			}
 			else
 			{
				 vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
 			}
 		}
 		else
 		{
 			/* Nothing was received from the queue. This must be an error as this  task should only run when the queue is full. */
 			vPrintString( "Could not receive from the queue.\r\n" );
 		}
 	}
 }
 
int main( void )
{
 	/* The queue is created to hold a maximum of 3 structures of type Data_t. */
 	xQueue = xQueueCreate( 3, sizeof( Data_t ) );
 	if( xQueue != NULL )
 	{
 		/* Create two instances of the task that will write to the queue. The parameter is used to pass the structure that the task will write to the  queue, so one task will continuously send xStructsToSend[ 0 ] to the queue while the other task will continuously send xStructsToSend[ 1 ]. Both  tasks are created at priority 2, which is above the priority of the receiver. */
 		xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );
 		xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );
 		/* Create the task that will read from the queue. The task is created with priority 1, so below the priority of the sender tasks. */
 		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
 		/* Start the scheduler so the created tasks start executing. */
 		vTaskStartScheduler();
 	}
 	else
 	{
 		/* The queue could not be created. */
 	}
 
 	/* If all is well then main() will never reach here as the scheduler will  now be running the tasks. If main() does reach here then it is likely that  there was insufficient heap memory available for the idle task to be created.  Chapter 2 provides more information on heap memory management. */
 	for( ;; );
}

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

  • t1:任务sener1执行并发送3个数据项到队列。
  • t2:队列已满,因此sener1进入阻塞状态,等待其下一次发送完成。任务sener2现在是能够运行的最高优先级任务,因此进入“运行”状态。
  • t3:任务sener2发现队列已满,因此进入“阻塞”状态,等待其第一次发送完成。任务Receiver现在是能够运行的最高优先级任务,因此进入“运行”状态。
  • t4:优先级高于Receiver的两个sener任务正在等待队列上的可用空间,导致任务Receiver一旦从队列中删除一个项目就被抢占。任务sener1和sener2具有相同的优先级,因此调度器选择等待时间最长的任务作为将进入运行状态的任务——在此情况下是任务sener1。
  • t5:任务sener1向队列发送另一个数据项。队列中只有一个空格,因此任务sener1进入被阻止状态,等待其下一次发送完成。任务Receiver现在是能够运行的最高优先级任务,因此会进入运行状态。任务sener1现在已向该队列发送了四个项,而任务sener2仍在等待将其第一个项发送到该队列。
  • t6:优先级高于Receiver的两个sener任务正在等待队列中的可用空间,因此一旦任务REceiver从队列中删除一个项目,就会被抢占。这次sener2等待的时间比sener1长,因此sener2进入“运行”状态。
  • t7:任务sener2将一个数据项发送到队列。队列中只有一个空格,因此sener2进入阻止状态,等待下一次发送完成。任务sener1和sener2都在等待队列中变为可用空间,因此任务Receiver是唯一可以进入“正在运行”状态的任务。

五、使用大型或可变大小的数据

指针型队列

如果存储在队列中的数据的大小很大,那么最好使用队列来传输指向数据的指针,而不是将数据本身逐字节地复制到队列中。传输指针在处理时间和创建队列所需的RAM数量方面都更有效。但是,在排队中保存指针时,必须非常小心地确保:

  1. 被指向的RAM的所有者已被明确定义。
    当通过指针在任务之间共享内存时,必须确保两个任务不会同时修改内存内容,或者采取任何其他可能导致内存内容无效或不一致的操作。理想情况下,在指向内存的指针被保存到队列之前,只允许发送任务访问内存,并且在从队列接收到指针后,只允许接收任务访问内存。
  2. 被指向的RAM仍然有效。
    如果被指向的内存是动态分配的,或者是从预先分配的缓冲区池中获得的,那么应该只有一个任务负责释放内存。在释放内存后,任何任务都不应尝试访问该内存。
    指针不应被用于访问已分配到任务栈上的数据。在栈帧发生更改后,该数据将无效。

通过下列示例,演示了如何使用队列将指向缓冲区的指针从一个任务发送到另一个任务:

/* Declare a variable of type QueueHandle_t to hold the handle of the queue being created. */
QueueHandle_t xPointerQueue;
/* Create a queue that can hold a maximum of 5 pointers, in this case character pointers. */
xPointerQueue = xQueueCreate( 5, sizeof( char * ) );

/* A task that obtains a buffer, writes a string to the buffer, then sends the address of the buffer to the queue created in Listing 52. */
void vStringSendingTask( void *pvParameters )
{
	char *pcStringToSend;
	const size_t xMaxStringLength = 50;
	BaseType_t xStringNumber = 0;
 	for( ;; )
 	{
 		/* Obtain a buffer that is at least xMaxStringLength characters big. The implementation of prvGetBuffer() is not shown – it might obtain the buffer from a pool of pre-allocated buffers, or just allocate the buffer dynamically. */
 		pcStringToSend = ( char * ) prvGetBuffer( xMaxStringLength );
 		/* Write a string into the buffer. */
 		snprintf( pcStringToSend, xMaxStringLength, "String number %d\r\n", xStringNumber );
 		/* Increment the counter so the string is different on each iteration of this task. */
 		xStringNumber++;
 		/* Send the address of the buffer to the queue that was created in Listing 52. The address of the buffer is stored in the pcStringToSend variable.*/
 		xQueueSend( xPointerQueue, /* The handle of the queue. */
 					&pcStringToSend, /* The address of the pointer that points to the buffer. */
 					portMAX_DELAY );
 	}
 }

/* A task that receives the address of a buffer from the queue created in Listing 52, and written to in Listing 53. The buffer contains a string, which is printed out. */
void vStringReceivingTask( void *pvParameters )
{
	char *pcReceivedString;
 	for( ;; )
 	{
 		/* Receive the address of a buffer. */
 		xQueueReceive( xPointerQueue, /* The handle of the queue. */
 						&pcReceivedString, /* Store the buffer’s address in pcReceivedString. */
 						portMAX_DELAY );
 		/* The buffer holds a string, print it out. */
 		vPrintString( pcReceivedString );
 		/* The buffer is not required any more - release it so it can be freed, or re-used. */
 		prvReleaseBuffer( pcReceivedString );
 	}
 }
使用队列来发送不同类型和长度的数据

前面的部分演示了两种强大的设计模式;向队列发送结构,以及向队列发送指针。结合这些技术,允许任务使用单个队列来接收来自任何数据源的任何数据类型。FreeRTOS+TCPTCP/IP栈的实现提供了一个如何实现这一点的实际示例。

在自身任务中运行的TCP/IP协议栈必须处理来自许多不同源的事件。不同的事件类型对应着不同的数据类型和长度。所有发生在TCP/IP任务之外的事件都由IPStackEvent_t类型的结构来描述,并使用队列发送给TCP/IP任务。IPStackEvent_t结构如下所示。IPStackEvent_t结构中的pvData成员是一个指针,可以用来直接保存一个值,或指向一个缓冲区。

//Listing 55. The structure used to send events to the TCP/IP stack task in FreeRTOS+TCP
/* A subset of the enumerated types used in the TCP/IP stack to identify events. */
typedef enum
{
 	eNetworkDownEvent = 0, /* The network interface has been lost, or needs (re)connecting. */
 	eNetworkRxEvent, /* A packet has been received from the network. */
 	eTCPAcceptEvent, /* FreeRTOS_accept() called to accept or wait for a new client. */
 	/* Other event types appear here but are not shown in this listing. */
} eIPEvent_t;

/* The structure that describes events, and is sent on a queue to the TCP/IP task. */
typedef struct IP_TASK_COMMANDS
{
 	/* An enumerated type that identifies the event. See the eIPEvent_t definition above. */
 	eIPEvent_t eEventType;
 	/* A generic pointer that can hold a value, or point to a buffer. */
 	void *pvData;
} IPStackEvent_t;

//示例TCP/IP事件及其关联数据包括:
/*eNetworkRxEvent:已从网络接收数据包。从网络接收到的数据将使用IPStackEvent_t类型的结构发送到TCP/IP任务。结构的eEventType成员设置为eNetworkRxEvent,结构的pvData成员用于指向包含接收数据的缓冲区。*/
void vSendRxDataToTheTCPTask( NetworkBufferDescriptor_t *pxRxedData ) 
{
	IPStackEvent_t xEventStruct;
 	/* Complete the IPStackEvent_t structure. The received data is stored in pxRxedData. */
 	xEventStruct.eEventType = eNetworkRxEvent;
 	xEventStruct.pvData = ( void * ) pxRxedData;
 	/* Send the IPStackEvent_t structure to the TCP/IP task. */
 	xSendEventStructToIPTask( &xEventStruct );
}

//eTCPAcceptEvent:Socket正在接受或者等待,一个来自客户端的连接。
/*接受事件将被使用IPStackEvent_t类型的结构从名为FReeRTOS_crenty()的任务发送到TCP/IP任务。结构的eEventType成员被设置为eTCP接受事件,并且结构的pvData成员被设置为正在接受连接的套接字的句柄。*/
void vSendAcceptRequestToTheTCPTask( Socket_t xSocket )
{
	IPStackEvent_t xEventStruct;
 	/* Complete the IPStackEvent_t structure. */
 	xEventStruct.eEventType = eTCPAcceptEvent;
 	xEventStruct.pvData = ( void * ) xSocket;
 	/* Send the IPStackEvent_t structure to the TCP/IP task. */
 	xSendEventStructToIPTask( &xEventStruct );
}

//eNetworkDownEvent:该网络需要连接,或重新连接。
/*网络关闭事件使用IPStackEvent_t类型的结构从网络接口发送到TCP/IP任务。结构的“事件类型”成员设置为“eNetworkDownEvent”。网络停机事件不与任何数据关联,因此不使用该结构的pvData成员*/
void vSendNetworkDownEventToTheTCPTask( Socket_t xSocket ) 
{
	IPStackEvent_t xEventStruct;
 	/* Complete the IPStackEvent_t structure. */
 	xEventStruct.eEventType = eNetworkDownEvent;
 	xEventStruct.pvData = NULL; /* Not used, but set to NULL for completeness. */
 	/* Send the IPStackEvent_t structure to the TCP/IP task. */
 	xSendEventStructToIPTask( &xEventStruct );
}

/*在TCP/IP任务中接收和处理这些事件的代码如下所示。可以看出,从队列中接收到的IPStackEvent_t结构中的eEventType成员被用来确定如何解释pvData成员。*/
IPStackEvent_t xReceivedEvent;
 /* Block on the network event queue until either an event is received, or xNextIPSleep ticks pass without an event being received. eEventType is set to eNoEvent in case the call to  xQueueReceive() returns because it timed out, rather than because an event was received. */
 xReceivedEvent.eEventType = eNoEvent;
 xQueueReceive( xNetworkEventQueue, &xReceivedEvent, xNextIPSleep );
 /* Which event was received, if any? */
 switch( xReceivedEvent.eEventType )
 {
 	case eNetworkDownEvent :
 		/* Attempt to (re)establish a connection. This event is not associated with any  data. */
 		prvProcessNetworkDownEvent();
 		break;
 	case eNetworkRxEvent:
 		/* The network interface has received a new packet. A pointer to the received data  is stored in the pvData member of the received IPStackEvent_t structure. Process  the received data. */
 		prvHandleEthernetPacket( ( NetworkBufferDescriptor_t * )( xReceivedEvent.pvData ) );
 		break;
 	case eTCPAcceptEvent:
 		/* The FreeRTOS_accept() API function was called. The handle of the socket that is accepting a connection is stored in the pvData member of the received IPStackEvent_t  structure. */
 		xSocket = ( FreeRTOS_Socket_t * ) ( xReceivedEvent.pvData );
 		xTCPCheckNewClient( pxSocket );
 		break;
 	/* Other event types are processed in the same way, but are not shown here. */
 
 }

六、从多个队列接收

队列集

应用程序设计通常需要单个任务来接收不同大小的数据、不同意义的数据和来自不同来源的数据。上一节演示了如何使用接收结构的单个队列以一种简洁而有效的方式来实现这一点。但是,有时应用程序的设计者会受到一些强制的约束条件,需要为某些数据源使用独立的队列。例如,被集成到设计中的第三方代码可能会假定存在一个专用队列。在这种情况下,可以使用“队列集”。
队列集允许任务从多个队列接收数据,而无需使用任务依次轮询每个队列,以确定哪个队列包含数据。
使用队列集接收从多个源的数据的设计,比使用接收结构的单个队列实现相同功能的设计缺少整洁性和效率更低。因此,建议只有在设计约束使其使用绝对必要时才使用队列集。
以下部分介绍如何使用队列集:

  1. 创建一个队列集。
  2. 正在向集合中添加队列。
    信号量也可以被添加到一个队列集中。信号量在这本书的后面会有描述。
  3. 从队列集中读取,以确定集合中的哪些队列包含数据。
    当队列集中的一个成员队列接收到数据时,该队列的句柄将被发送到队列集,该句柄将在某个任务调用从队列集读取的函数时返回。因此,如果一个队列句柄从队列集返回,则知道该句柄引用的队列包含数据,然后任务可以直接从队列中读取。

注意:如果队列是队列集的成员,在从队列集读取该队列的句柄前,那么不要从队列中读取数据。

通过在FreeRTOSConfig.h中将configUSE_QUEUE_SETS编译时配置常量设置为1来启用队列集功能。

The xQueueCreateSet() API Function

必须显式创建队列集才能使用。
队列集由句柄引用,它们是QueueSetHandle_t类型的变量。xQueueCreateSet()API函数创建一个队列集,并返回一个引用它所创建的队列集的QueueSetHandle_t。

//Listing 60. The xQueueCreateSet() API function prototype
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
/*参数uxEventQueueLength :当作为队列集成员的队列接收数据时,队列的句柄将发送到队列集。uxEventQueueLength 定义了正在创建的队列集随时都可以保存的最大队列句柄数。
						只有当队列集中的队列接收到数据时,队列句柄才会被发送到队列集。如果队列已满,则无法接收数据,因此,如果队列中的所有队列都已满,则不能将任何队列句柄发送到队列集。因此,队列集一次必须保持的最大项目数量是集合中每个队列的长度之和。
						例如,如果集合中有三个空队列,并且每个队列的长度为5,那么在集合中的所有队列都满之前,集合中的队列总共可以接收15个项目(每个队列乘以5个项目)。在本例中,队列长度必须设置为15,以保证队列集可以接收到发送到它的每个项目。
						信号量也可以被添加到一个队列集中。二进制和计数信号量将在这本书的后面讨论。为了计算所需的超像素长度,二进制信号量的长度为1,计数信号量的长度由信号量的最大计数值给出。
						另一个例子是,如果队列集包含长度为3的队列和二进制信号量(长度为1),则uxEventQueueLength 必须设置为4(3加1)。*/
/*返回值:如果返回NULL,则无法创建队列集,因为没有足够的FreeRTOS堆内存来分配队列集数据结构和存储区域。
		返回的非NULL值表示队列集已创建成功。返回的值应该存储为已创建的队列集的句柄。*/
The xQueueAddToSet() API Function

xQueueAddToSet()将一个队列或信号量添加到一个队列集中。信号量在这本书的后面会有描述。

//Listing 61. The xQueueAddToSet() API function prototype
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );
/*参数xQueueOrSemaphore:正在添加到队列集的队列或信号量的句柄。队列句柄和信号量句柄都可以被强制转换为QueueSetMemberHandle_t类型。*/
/*参数xQueueSet:要向其添加该队列或信号量的队列集的句柄。 */
/*返回值:有两个可能的返回值,1.只有当队列或信号量已成功添加到队列集时,才会返回pdPASS 。 2. 如果将队列或信号量无法添加到队列集中,则将返回pdFAIL 。
		队列和二进制信号量只能在它们为空时被添加到一个集合中。计数信号量只能在其计数为零时才能添加到一个集合中。队列和信号量一次只能是一个集合的成员。*/
The xQueueSelectFromSet() API Function

xQueueSelectFromSet()从队列集读取一个队列句柄。
当作为集合成员的队列或信号接收数据时,接收队列或信号的句柄被发送到队列集,并在任务调用xQueueSelectFromSet()时返回。如果句柄从调用xQueueSelectFromSet()返回,那么句柄引用的队列或信号量包含数据,然后调用任务必须直接从队列或信号量中读取。
注意:在调用xQueueSelectFromSet()并得到返回的句柄前,不要从集合成员的队列或信号量读取数据。并且,每次调用xQueueSelectFromSet()返回的句柄,只从队列或信号量中读取一个项。

//Listing 62. The xQueueSelectFromSet() API function prototype
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait );
/*参数xQueueSet:正在接收到队列句柄或信号量句柄的队列集的句柄(正在读取)。队列集句柄将从用于创建队列集的xQueueCreateSet()的调用返回。*/
/*参数xTicksToWait :如果队列集中的所有队列和信号量都为空,则调用任务应保持在阻塞状态以等待从队列集接收队列或信号量句柄的最长时间。
					如果xTicksToWait 为零,那么如果该集中的所有队列和信号量都为空,则xQueueSelectFromSet()将立即返回。
					阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在FreeRTOSConfig.h中设置为1,则将xTicksTo等待设置为portMAX_DELAY将导致任务无限期等待(不超时)。*/
/*返回值:非NULL的返回值将是已知包含数据的队列或信号量的句柄。如果指定了阻塞时间(xTicksToWait不是零),则该调用任务已被放入阻塞状态,等待数据从队列或信号集中可用,但是在阻塞时间过期之前成功从队列集中读取句柄。句柄作为QueueSetMemberHandle_t类型返回,可以强制转换为QueueHandle_t类型或SemaphoreHandle_t类型。
		如果返回值为NULL,则无法从队列集中读取句柄。如果指定了阻塞时间(xTicksToWait不是零),那么调用任务将被放置入阻塞状态,等待另一个任务或中断将数据发送到集中的队列或信号量,但在阻塞时间之前过期。*/
Example 12. Using a Queue Set
/* Declare two variables of type QueueHandle_t. Both queues are added to the same queue set. */
static QueueHandle_t xQueue1 = NULL, xQueue2 = NULL;
/* Declare a variable of type QueueSetHandle_t. This is the queue set to which the two queues are added. */
static QueueSetHandle_t xQueueSet = NULL;
int main( void )
{
 	/* Create the two queues, both of which send character pointers. The priority of the receiving task is above the priority of the sending tasks, so the queues will never have more than one item in them at any one time*/
 	xQueue1 = xQueueCreate( 1, sizeof( char * ) );
 	xQueue2 = xQueueCreate( 1, sizeof( char * ) );
 	/* Create the queue set. Two queues will be added to the set, each of which can contain 1 item, so the maximum number of queue handles the queue set will ever  have to hold at one time is 2 (2 queues multiplied by 1 item per queue). */
 	xQueueSet = xQueueCreateSet( 1 * 2 );
 	/* Add the two queues to the set. */
 	xQueueAddToSet( xQueue1, xQueueSet );
 	xQueueAddToSet( xQueue2, xQueueSet );
 	/* Create the tasks that send to the queues. */
 	xTaskCreate( vSenderTask1, "Sender1", 1000, NULL, 1, NULL );
 	xTaskCreate( vSenderTask2, "Sender2", 1000, NULL, 1, NULL );
 	/* Create the task that reads from the queue set to determine which of the two  queues contain data. */
 	xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
 	/* Start the scheduler so the created tasks start executing. */
 	vTaskStartScheduler();
 	/* As normal, vTaskStartScheduler() should not return, so the following lines  Will never execute. */
 	for( ;; );
 	return 0;
}

void vSenderTask1( void *pvParameters )
{
	const TickType_t xBlockTime = pdMS_TO_TICKS( 100 );
	const char * const pcMessage = "Message from vSenderTask1\r\n";
 	/* As per most tasks, this task is implemented within an infinite loop. */
 	for( ;; )
 	{
 		/* Block for 100ms. */
 		vTaskDelay( xBlockTime );
		 /* Send this task's string to xQueue1. It is not necessary to use a block time, even though the queue can only hold one item. This is because the  priority of the task that reads from the queue is higher than the priority of  this task; as soon as this task writes to the queue it will be pre-empted by  the task that reads from the queue, so the queue will already be empty again  by the time the call to xQueueSend() returns. The block time is set to 0. */
 		xQueueSend( xQueue1, &pcMessage, 0 );
 	}
 }


/*-----------------------------------------------------------*/
void vSenderTask2( void *pvParameters )
{
	const TickType_t xBlockTime = pdMS_TO_TICKS( 200 );
	const char * const pcMessage = "Message from vSenderTask2\r\n";
 	/* As per most tasks, this task is implemented within an infinite loop. */
 	for( ;; )
 	{
 		/* Block for 200ms. */
 		vTaskDelay( xBlockTime );
 		/* Send this task's string to xQueue2. It is not necessary to use a block  time, even though the queue can only hold one item. This is because the  priority of the task that reads from the queue is higher than the priority of  this task; as soon as this task writes to the queue it will be pre-empted by  the task that reads from the queue, so the queue will already be empty again  by the time the call to xQueueSend() returns. The block time is set to 0. */
 		xQueueSend( xQueue2, &pcMessage, 0 );
 	}
 }

void vReceiverTask( void *pvParameters )
{
	QueueHandle_t xQueueThatContainsData;
	char *pcReceivedString;
 	/* As per most tasks, this task is implemented within an infinite loop. */
 	for( ;; )
 	{
 		/* Block on the queue set to wait for one of the queues in the set to contain data. Cast the QueueSetMemberHandle_t value returned from xQueueSelectFromSet() to a  QueueHandle_t, as it is known all the members of the set are queues (the queue set  does not contain any semaphores). */
 		xQueueThatContainsData = ( QueueHandle_t ) xQueueSelectFromSet( xQueueSet, portMAX_DELAY );
 		/* An indefinite block time was used when reading from the queue set, so xQueueSelectFromSet() will not have returned unless one of the queues in the set  contained data, and xQueueThatContainsData cannot be NULL. Read from the queue. It  is not necessary to specify a block time because it is known the queue contains data. The block time is set to 0. */
 		xQueueReceive( xQueueThatContainsData, &pcReceivedString, 0 );
 		/* Print the string received from the queue. */
 		vPrintString( pcReceivedString );
 	}
 }

在这里插入图片描述

更真实的队列集用例

示例12演示了一个非常简单的情况:
队列集只包含队列,并且它所包含的两个队列都用于发送字符指针。在实际的应用程序中,队列集可能同时包含队列和信号量,而且队列可能不都包含相同的数据类型。在这种情况下,在使用返回的值之前,必须测试xQueueSelectFromSet()返回的值。下列代码演示了当集合有以下成员时,如何使用从xQueueSelectFromSet()返回的值:

  1. 一个二进制信号量。
  2. 从中读取字符指针的队列。
  3. 从中读取uint32_t值的队列。

清单66假定队列和信号量已经创建并添加到队列集中

/* The handle of the queue from which character pointers are received. */
QueueHandle_t xCharPointerQueue;
/* The handle of the queue from which uint32_t values are received. */
QueueHandle_t xUint32tQueue;
/* The handle of the binary semaphore. */
SemaphoreHandle_t xBinarySemaphore;
/* The queue set to which the two queues and the binary semaphore belong. */
QueueSetHandle_t xQueueSet;
void vAMoreRealisticReceiverTask( void *pvParameters )
{
	QueueSetMemberHandle_t xHandle;
	char *pcReceivedString;
	uint32_t ulRecievedValue;
	const TickType_t xDelay100ms = pdMS_TO_TICKS( 100 );
 	for( ;; )
 	{
 		/* Block on the queue set for a maximum of 100ms to wait for one of the members of the set to contain data. */
 		xHandle = xQueueSelectFromSet( xQueueSet, xDelay100ms );
 		/* Test the value returned from xQueueSelectFromSet(). If the returned value is NULL then the call to xQueueSelectFromSet() timed out. If the returned value is not  NULL then the returned value will be the handle of one of the set’s members. The  QueueSetMemberHandle_t value can be cast to either a QueueHandle_t or a  SemaphoreHandle_t. Whether an explicit cast is required depends on the compiler. */
 		if( xHandle == NULL )
 		{
 			/* The call to xQueueSelectFromSet() timed out. */
 		}
 		else if( xHandle == ( QueueSetMemberHandle_t ) xCharPointerQueue )
 		{
 			/* The call to xQueueSelectFromSet() returned the handle of the queue that  receives character pointers. Read from the queue. The queue is known to contain  data, so a block time of 0 is used. */
 			xQueueReceive( xCharPointerQueue, &pcReceivedString, 0 );
 			/* The received character pointer can be processed here... */
 		}
 		else if( xHandle == ( QueueSetMemberHandle_t ) xUint32tQueue )
 		{
 			/* The call to xQueueSelectFromSet() returned the handle of the queue that  receives uint32_t types. Read from the queue. The queue is known to contain  data, so a block time of 0 is used. */
 			xQueueReceive(xUint32tQueue, &ulRecievedValue, 0 );
 			/* The received value can be processed here... */
 		}
 		Else if( xHandle == ( QueueSetMemberHandle_t ) xBinarySemaphore )
 		{
 			/* The call to xQueueSelectFromSet() returned the handle of the binary semaphore.  Take the semaphore now. The semaphore is known to be available so a block time  of 0 is used. */
 			xSemaphoreTake( xBinarySemaphore, 0 );
 			/* Whatever processing is necessary when the semaphore is taken can be performed here... */
 		}
 	}
 }

七、使用队列来创建邮箱

在嵌入式社区中对术语没有共识,“邮箱”在不同的rtoses中意味着不同的东西。在本书中,术语邮箱用于指代长度为1的队列。队列可能被描述为邮箱是因为它在应用程序中使用的方式,而不是因为它与队列有功能差异:

  • 队列用于将数据从一个任务发送到另一个任务,或者从中断服务例程发送到一个任务。发送方将一个项目放在队列中,而接收方将从队列中删除该项目。数据通过队列从发送方传递到接收方。
  • 邮箱用于保存可由任何任务或任何中断服务例程读取的数据。数据不会通过邮箱,而是保留在邮箱中,直到它被覆盖。发件人将覆盖邮箱中的值。接收方会从邮箱中读取该值,但不会从邮箱中删除该值。

本章介绍了允许将队列用作邮箱的两个队列API函数。

//Listing 67. A queue being created for use as a mailbox
/* A mailbox can hold a fixed size data item. The size of the data item is set
when the mailbox (queue) is created. In this example the mailbox is created to
hold an Example_t structure. Example_t includes a time stamp to allow the data held 
in the mailbox to note the time at which the mailbox was last updated. The time 
stamp used in this example is for demonstration purposes only - a mailbox can hold 
any data the application writer wants, and the data does not need to include a time 
stamp. */
typedef struct xExampleStructure
{
 	TickType_t xTimeStamp;
 	uint32_t ulValue;
} Example_t;
/* A mailbox is a queue, so its handle is stored in a variable of type
QueueHandle_t. */
QueueHandle_t xMailbox;
void vAFunction( void )
{
 	/* Create the queue that is going to be used as a mailbox. The queue has a 
 length of 1 to allow it to be used with the xQueueOverwrite() API function, which 
 is described below. */
 	xMailbox = xQueueCreate( 1, sizeof( Example_t ) );
}
The xQueueOverwrite() API Function

与xQueueSendToBack()API函数一样,xQueueOverwrite()API函数将数据发送到队列。与xQueueSendToBack()不同,如果队列已经满,那么xQueueOverwrite()将覆盖已经在队列中的数据。
xQueueOverwrite()应该只用于长度为1的队列。该限制避免了函数的实现需要在队列已满的情况下,任意决定要覆盖队列中的哪个项。
注意:永远不要从中断服务例程中调用xQueueOverwrite()。应该使用中断安全版本xQueueOverwriteFromISR()来代替它。

//Listing 68. The xQueueOverwrite() API function prototype
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );
/*参数xQueue:要发送数据的队列的句柄(已写入)。队列句柄将从对用于创建队列的xQueueCreate()的调用中返回。*/
/*参数pvItemToQueue :一个指向要复制到队列中的数据的指针。在创建队列时设置了队列可以保存的每个项的大小,因此这些字节将从pvItemToQueue复制到队列存储区域中。*/
/*返回值:即使队列已满,也会写入队列,所以pdPASS是唯一可能的返回值。*/
//Listing 69. Using the xQueueOverwrite() API function
void vUpdateMailbox( uint32_t ulNewValue )
{
	/* Example_t was defined in Listing 67. */
	Example_t xData;
 	/* Write the new data into the Example_t structure.*/
 	xData.ulValue = ulNewValue;
 	/* Use the RTOS tick count as the time stamp stored in the Example_t structure. */
 	xData.xTimeStamp = xTaskGetTickCount();
 	/* Send the structure to the mailbox - overwriting any data that is already in the  mailbox. */
 	xQueueOverwrite( xMailbox, &xData );
}
The xQueuePeek() API Function

xQueuePeek()用于从队列中接收(读取)一个项目,而不需要从队列中删除该项目。xQueuePeek()从队列的头接收数据,而不修改存储在队列中的数据或数据存储在队列中的顺序。
注意:永远不要从中断服务例程调用xQueuePeek()。应该使用中断安全版本的xQueuePeekFromISR()来代替它。

//Listing 70. The xQueuePeek() API function prototype
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
//xQueuePeek() has the same function parameters and return value as xQueueReceive()
BaseType_t vReadMailbox( Example_t *pxData ) 
{
	TickType_t xPreviousTimeStamp;
	BaseType_t xDataUpdated;
 	/* This function updates an Example_t structure with the latest value received  from the mailbox. Record the time stamp already contained in *pxData before it  gets overwritten by the new data. */
 	xPreviousTimeStamp = pxData->xTimeStamp;
 	/* Update the Example_t structure pointed to by pxData with the data contained in the mailbox. If xQueueReceive() was used here then the mailbox would be left  empty, and the data could not then be read by any other tasks. Using  xQueuePeek() instead of xQueueReceive() ensures the data remains in the mailbox. A block time is specified, so the calling task will be placed in the Blocked  state to wait for the mailbox to contain data should the mailbox be empty. An  infinite block time is used, so it is not necessary to check the value returned  from xQueuePeek(), as xQueuePeek() will only return when data is available. */ 	
 	xQueuePeek( xMailbox, pxData, portMAX_DELAY );
 	/* Return pdTRUE if the value read from the mailbox has been updated since this  function was last called. Otherwise return pdFALSE. */
 	if( pxData->xTimeStamp > xPreviousTimeStamp )
 	{
 		xDataUpdated = pdTRUE;
 	}
 	else
 	{
 		xDataUpdated = pdFALSE;
 	}
 	return xDataUpdated;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值