FreeRTOS 教程指南 学习笔记 第八章 事件组

FreeRTOS 教程指南 学习笔记 第八章 事件组

一、简介

人们已经注意到,实时嵌入式系统必须对事件采取相应的行动。前几章描述了FreeRTOS允许事件和任务之间通信的特性。这类特性的示例包括信号量和队列,它们都具有以下属性:

  • 它们允许任务在阻塞状态下等待单个事件的发生。
  • 他们在事件发生时解除阻塞单个任务——解除阻塞的任务是等待该事件的最高优先级任务。

事件组是FreeRTOS的另一个特性,它允许事件通信给任务。与队列和信号量不同:

  • 事件组允许任务在阻塞状态下等待其中一个事件的组合发生。
  • 事件发生时,事件组取消阻塞等待同一事件或事件组合的所有任务。

这些独特的属性的事件组使他们有用的同步多个任务,广播事件到多个任务,允许一个任务等待在阻塞状态下的任何一组事件发生,并允许一个任务在阻塞状态下等待多个操作完成。
事件组还提供了减少应用程序所使用的RAM的机会,因为通常可以用单个事件组替换许多二进制信号量。
事件组功能是可选的。要包含事件组功能,请将FreeRTOS源文件event_groups.c作为项目的一部分。
本章旨在让读者能够很好地理解:

  • 对事件组的实际用途。
  • 事件组相对于其他FreeRTOS特性的优点和优缺点。
  • 如何在事件组中设置位。
  • 如何在阻止状态中等待位在事件组中设置。
  • 如何使用事件组来同步一组任务。

二、一个事件组的特征

事件“标志”是一个布尔(1或0)值,用于指示是否发生了事件。事件“组”是一组事件标志。
事件标志只能为1或0,允许事件标志的状态存储在单个位中,并且事件组中所有事件标志的状态存储在同一个变量中;事件组中每个事件标志的状态由类型为EventBits_t的变量中的单个位表示。因此,事件标志也被称为事件“位”。如果EventBits_t变量中的一个位被设置为1,则由该位表示的事件已经发生。如果EventBits_t变量中的一个位设置为0,则该位表示的事件没有发生。

图71显示了如何将各个事件标记映射到EventBits_t类型的变量中的各个位。

在这里插入图片描述


例如,如果事件组的值为0x92(二进制1001 0010),则只设置事件位1、4和7,因此只发生了由位1、4和7表示的事件。图72显示了一个类型为EventBits_t的变量,它设置了事件位1、4和7,并且所有其他事件位都被清除,从而使事件组的值为0x92。

在这里插入图片描述


由应用程序作者来为事件组中的单个位分配意义。例如,应用程序编写器可能会创建一个事件组,然后:

  • 在事件组中定义位0,表示“已从网络收到消息”。
  • 在事件组中定义第1位,表示“准备发送到网络的消息”。
  • 在事件组中的位2定义为“中止当前网络连接”。
关于EventBits_t数据类型的详细信息

事件组中的事件位数取决于FreeRTOSConfig.h1中的configUSE_16_BIT_TICKS编译时配置常数。

  • 如果configUSE_16_BIT_TICKS为1,那么每个事件组就包含8个可用的事件位。
  • 如果configUSE_16_BIT_TICKS为0,则每个事件组包含24个可用的事件位。
通过多个任务访问

事件组本身是任何知道它们存在的任务或ISR都可以访问的对象。任意数量的任务都可以在同一事件组中设置位,任意数量的任务都可以从同一事件组中读取位。

使用事件组的一个实际实例

FreeRTOS+TCPTCP/IP堆栈的实现提供了一个实际示例,说明如何使用事件组来同时简化设计和最小化资源使用。
一个TCP套接字必须响应许多不同的事件。事件的示例包括接受事件、绑定事件、读取事件和关闭事件。套接字在任何给定时间都可以期望的事件取决于套接字的状态。例如,如果已经创建了套接字,但尚未绑定到地址,那么它可以期望接收绑定事件,但不期望接收读取事件(如果没有地址,它就不能读取数据)。
FreeRTOS+TCP套接字的状态保存在一个称为FreeRTOS_Socket_t的结构中。该结构包含一个事件组,该事件组为套接字必须处理的每个事件定义了一个事件位。FreeRTOS+TCPAPI调用该阻塞来等待一个事件或一个事件组,阻塞在事件组上。
事件组还包含一个“中止”位,允许中止TCP连接,无论套接字当时正在等待哪个事件。

三、使用事件组进行事件管理

必须显式创建事件组才能使用。
使用EventGroupHandle_t类型的变量来引用事件组。xEventGroupCreate()API函数用于创建一个事件组,并返回一个EventGroupHandle_t来引用它所创建的事件组。

EventGroupHandle_t xEventGroupCreate( void );
/*返回值:如果返回NULL,则无法创建事件组,因为FreeRTOS没有足够的堆内存来分配事件组数据结构。第2章提供了关于堆内存管理的更多信息。
         返回的非空值表示已成功创建事件组。返回的值应该存储为已创建的事件组的句柄。*/
The xEventGroupSetBits() API Function

xEventGroupSetBits()API函数在事件组中设置一个或多个位,通常用于通知任务,由位或位表示的事件已经发生。
注意:永远不要从中断服务例程中调用xEventGroupSetBits()。应该使用中断安全版本xEventGroupSetBitsFromISR()。

//Listing 133. The xEventGroupSetBits() API function prototype
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
/*参数xEventGroup:正在在其中设置位的事件组的句柄。。*/
/*参数uxBitsToSet:一种位掩码,指定事件位或事件位设置为事件组中的1。事件组的值通过使用uxbitsToSet来更新事件组的现有值。
				  例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被设置(如果尚未设置),同时保留事件组中的所有其他事件位不变*/
/*返回值:调用xEventGroupSetBits()返回时事件组的值。请注意,返回的值不一定设置了由uxBitsToSet指定的位,因为这些位可能已经被其他任务再次清除了。*/
The xEventGroupSetBitsFromISR() API Function

xEventGroupSetBits()的中断安全版本。
给出一个信号量是一个确定性的操作,因为它事先就知道,给出一个信号量最多可以导致一个任务离开阻塞状态。当在事件组中设置位时,事先不知道有多少任务将离开阻塞状态,因此在事件组中设置位不是一个确定性操作。
FreeRTOS的设计和实现标准不允许在中断服务例程中或在禁用中断时执行不确定性操作。因此,xEventGroupSetBitsFromISR()并不直接在中断服务程序中设置事件位,而是将操作延迟到RTOS守护进程任务。

//Listing 134. The xEventGroupSetBitsFromISR() API function prototype
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );
/*参数xEventGroup:事件组的句柄*/
/*参数uxBitsToSet:需要设置的位*/
/*参数pxHigherPriorityTaskWoken:xEventGroupSetBitsFromISR()并不直接在中断服务例程中设置事件位,而是通过在计时器命令队列上发送命令,将操作延迟到RTOS守护进程任务中。如果守护进程任务处于阻塞状态,等待数据在计时器命令队列中可用,那么写入定时器命令队列将导致守护进程任务离开阻塞状态。如果守护进程任务的优先级高于当前执行的任务(被中断的任务)的优先级,那么在内部,xEventGroupSetBitsFromISR()将pxHigherPriorityTaskWoken设置为pdTRUE。
								如果xEventGroupSetBitsFromISR()将此值设置为pdTRUE,则应该在退出中断之前执行上下文切换。这将确保中断直接返回到守护进程任务,因为守护进程任务将是最高优先级的准备就绪状态任务。*/
/*返回值:有两个可能的返回值:
		 只有当数据被成功地发送到计时器命令队列时才会返回pdPASS。 
		 如果“设置位”命令无法写入计时器命令队列,则将返回pdFALSE。*/
The xEventGroupWaitBits() API Function

xEventGroupWaitBits()API函数允许任务读取事件组的值,如果事件位尚未设置,则可以选择在阻塞状态下等待事件组中的一个或多个事件位被设置。

//Listing 135. The xEventGroupWaitBits() API function prototype
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
 								 const EventBits_t uxBitsToWaitFor,
 								 const BaseType_t xClearOnExit,
 								 const BaseType_t xWaitForAllBits,
 								 TickType_t xTicksToWait );
 /*参数xEventGroup:包含正在被读取的事件位的事件组的句柄。事件组句柄将从调用xEventGroupCreate()时返回。*/
 /*参数uxBitsToWaitFor:在事件组中指定事件位或事件位的位掩码。例如,如果调用任务希望等待事件位0和/或事件位2在事件组中被设置,则将uxBitsToWaitFor设置为0x05(二进制0101)。更多示例请参见表45。*/
 /*参数xClearOnExit:如果满足调用任务的解除阻止条件,并且xClearOnExit设置为pdTRUE,则任务在退出xEventGroupWaitBits()之前,将事件组中由uxBitsToWaitFor指定的事件位清除为0。
 					如果xClearOnExit设置为pdFALSE,则不会修改事件组中事件位的状态。*/
 /*参数xWaitForAllBits:pdFALSE表示在uxBitsToWaitFor中设定的任意事件发生,则调用函数退出阻塞状态
 						pdTRUE表示只有在uxBitsToWaitFor中设定的所有事件都发生后,调用函数才退出阻塞状态*/
 /*参数xTicksToWait:在阻塞状态中等待退出的超时时间。设置为0,则xEventGroupWaitBits()立刻返回。否则等待时间发生才返回
 					 阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
 					 如果INCLUDE_vTaskSuspend在freesTonfig.h中设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。*/
 /*返回值:如果由于满足调用任务的取消阻塞条件而返回,则返回的值是满足调用任务的取消阻止条件时事件组的值(如果xClearOnExit是pdTRUE,则返回在自动清除任何位之前的值)。在这种情况下,返回的值也将满足解除阻塞的条件。
 		  如果因超时返回,则返回的值为超时时刻的值,这种情况下,返回值没有满足解除阻塞的调级*/

调度程序用于确定任务是否进入已阻塞状态以及任务何时离开阻塞状态的条件称为“取消阻塞条件”。解除阻塞条件由uxBitsToWaitFor和xWaitForAllBits参数值的组合指定:

  • uxBitsToWaitFor指定事件组中的哪些事件位需要被检测。
  • xWaitForAllBits制定是否需要被检测的所有事件位同时被检测到为1才退出阻塞状态。

如果在调用xEventGroupWaitBits()时满足其取消阻塞条件,则任务将不会进入阻塞状态。
表45提供了将导致任务进入阻塞状态或退出阻塞状态的条件示例。表45仅显示了事件组中最重要的四个二进制位和uxBitsToWait值-这两个值中的其他位被假定为零。

Existing Event Group Value uxBitsToWaitFor value xWaitForAllBits value Resultant Behavior
0000 0101 pdFALSE 调用任务将进入阻塞状态,因为在事件组中没有设置位0或位2,并且在事件组中设置位0或位2时将离开阻塞状态。
0100 0101 pdTRUE 调用任务将进入阻塞状态,因为位0和位2没有同时在事件组中设置,当位0和位2都在事件组中设置时,将离开阻塞状态。
0100 0110 pdFALSE 调用任务将不会进入阻塞状态,因为xWaitForAllBits是pdFALSE,并且由uxBitsToWaitFor指定的两个位中的一个已经在事件组中设置。
0100 0110 pdTRUE 调用任务将进入阻塞状态,因为xWaitForAllBits是pdTRUE,并且uxBitsToWaitFor指定的两个位中只有一个已经在事件组中设置。当事件组中设置位2和位3时,任务将离开“阻止”状态。

调用任务指定使用uxBitsToWait参数进行测试的位,调用任务可能在满足解除阻止条件后需要将这些位清除回零。事件位可以使用xEventGroupClearBits()API函数清除,但使用该函数手动清除事件位将导致应用程序代码中产生竞争条件,如果:

  • 有多个任务在使用同一个事件组。
  • 位由不同的任务或中断服务例程在事件组中设置。

提供了xClearOnExit参数,以避免这些潜在的竞争条件。如果xClearOnExit设置为pdTRUE,则事件位的测试和清除在调用任务中显示为原子操作(不被其他任务或中断中断)。

Example 22. Experimenting with event groups

本示例演示了如何:

  • 创建一个事件组。
  • 从中断服务例程中设置事件组中的位。
  • 从任务中设置事件组中的位。
  • 在一个事件组上阻塞。
//Listing 136. Event bit definitions used in Example 22
/* Definitions for the event bits in the event group. */
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, which is set by a task. */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* Event bit 1, which is set by a task. */
#define mainISR_BIT ( 1UL << 2UL ) /* Event bit 2, which is set by an ISR. */

//Listing 137. The task that sets two bits in the event group in Example 22
static void vEventBitSettingTask( void *pvParameters )
{
	const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0;
 	for( ;; )
 	{
 		/* Delay for a short while before starting the next loop. */
 		vTaskDelay( xDelay200ms );
 		/* Print out a message to say event bit 0 is about to be set by the task, then set event bit 0. */
 		vPrintString( "Bit setting task -\t about to set bit 0.\r\n" );
 		xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT );
 		/* Delay for a short while before setting the other bit. */
 		vTaskDelay( xDelay200ms );
 		/* Print out a message to say event bit 1 is about to be set by the task, then set event bit 1. */
 		vPrintString( "Bit setting task -\t about to set bit 1.\r\n" );
 		xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT );
 	}
 }

//Listing 138. The ISR that sets bit 2 in the event group in Example 22
static uint32_t ulEventBitSettingISR( void )
{
	/* The string is not printed within the interrupt service routine, but is instead sent to the RTOS daemon task for printing. It is therefore declared static to ensure the compiler does not allocate the string on the stack of the ISR, as the ISR's stack frame will not exist when the string is printed from the daemon task. */
	static const char *pcString = "Bit setting ISR -\t about to set bit 2.\r\n";
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 	/* Print out a message to say bit 2 is about to be set. Messages cannot be printed from an ISR, so defer the actual output to the RTOS daemon task by pending a function call to run in the context of the RTOS daemon task. */
 	xTimerPendFunctionCallFromISR( vPrintStringFromDaemonTask, 
 								 ( void * ) pcString, 
 								 0, 
 								 &xHigherPriorityTaskWoken );
 	/* Set bit 2 in the event group. */
 	xEventGroupSetBitsFromISR( xEventGroup, mainISR_BIT, &xHigherPriorityTaskWoken );
 	/* xTimerPendFunctionCallFromISR() and xEventGroupSetBitsFromISR() both write to  the timer command queue, and both used the same xHigherPriorityTaskWoken  variable. If writing to the timer command queue resulted in the RTOS daemon task  leaving the Blocked state, and if the priority of the RTOS daemon task is higher  than the priority of the currently executing task (the task this interrupt  interrupted) then xHigherPriorityTaskWoken will have been set to pdTRUE. .
 	xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR(). If  xHigherPriorityTaskWoken equals pdTRUE, then calling portYIELD_FROM_ISR() will  request a context switch. If xHigherPriorityTaskWoken is still pdFALSE, then  calling portYIELD_FROM_ISR() will have no effect. The implementation of portYIELD_FROM_ISR() used by the Windows port includes a  return statement, which is why this function does not explicitly return a value. */
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

//Listing 139. The task that blocks to wait for event bits to become set in Example 22
static void vEventBitReadingTask( void *pvParameters )
{
	EventBits_t xEventGroupValue;
	const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT | 
 										 mainSECOND_TASK_BIT | 
 										 mainISR_BIT );
 	for( ;; )
 	{
 		/* Block to wait for event bits to become set within the event group. */
 		xEventGroupValue = xEventGroupWaitBits( xEventGroup,/* The event group to read. */
 												xBitsToWaitFor,/* Bits to test. */
 												pdTRUE,/* Clear bits on exit if the unblock condition is met. */
 												pdFALSE, /* Don't wait for all bits. This parameter is set to pdTRUE for the  second execution. */
 												portMAX_DELAY ); /* Don't time out. */
 
 		/* Print a message for each bit that was set. */
 		if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 )
 		{
 			vPrintString( "Bit reading task -\t Event bit 0 was set\r\n" );
 		}
 		if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 )
 		{
 			vPrintString( "Bit reading task -\t Event bit 1 was set\r\n" );
 		}
 		if( ( xEventGroupValue & mainISR_BIT ) != 0 )
 		{
 			vPrintString( "Bit reading task -\t Event bit 2 was set\r\n" );
 		}
 	}
}

//Listing 140. Creating the event group and tasks in Example 22
int main( void )
{
 	/* Before an event group can be used it must first be created. */
 	xEventGroup = xEventGroupCreate();
 	/* Create the task that sets event bits in the event group. */
 	xTaskCreate( vEventBitSettingTask, "Bit Setter", 1000, NULL, 1, NULL );
 	/* Create the task that waits for event bits to get set in the event group. */
 	xTaskCreate( vEventBitReadingTask, "Bit Reader", 1000, NULL, 2, NULL );
 	/* Create the task that is used to periodically generate a software interrupt. */
 	xTaskCreate( vInterruptGenerator, "Int Gen", 1000, NULL, 3, NULL );
 	/* Install the handler for the software interrupt. The syntax necessary to do  this is dependent on the FreeRTOS port being used. The syntax shown here can  only be used with the FreeRTOS Windows port, where such interrupts are only  simulated. */
 	vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulEventBitSettingISR );
 	/* Start the scheduler so the created tasks start executing. */
 	vTaskStartScheduler();
 	/* The following line should never be reached. */
 	for( ;; );
 	return 0;
}

在这里插入图片描述


在图73中,可以看到,由于xWaitForAllBits位参数设置为pdFALSE,事件组中xBitsToWaitFor设定的任意一个位被设置成1,则读取任务立刻离开阻塞状态。

在这里插入图片描述

在图74中,可以看到,由于xWaitForAllBits位参数设置为pdTRUE,事件组中xBitsToWaitFor所有设定的位被设置成1,则读取任务立刻离开阻塞状态。

四、使用事件组进行任务同步

有时,应用程序的设计需要两个或多个任务来相互同步。例如,考虑任务A接收一个事件的设计,然后将事件需要的一些处理委托给其他三个任务:任务B、任务C和任务D。如果任务A在任务B、C和D任务都完成先前事件的处理之前无法接收另一个事件,则所有四个任务都需要相互同步。每个任务的同步点将在该任务完成其处理之后进行,在其他每个任务完成相同的任务之前不能继续进行。任务A只能在所有四个任务到达同步点后接收另一个事件。
在一个FreeRTOS+TCP演示项目中发现了一个不那么抽象的关于需要这种类型的任务同步的例子。该演示在两个任务之间共享一个TCP套接字;一个任务将数据发送到该套接字,而另一个任务从同一个套接字中接收数据。在确定其他任务不会再次尝试访问该套接字之前,对任何一个任务关闭TCP套接字都是不安全的。如果两个任务中的任何一个希望关闭套接字,那么它必须通知另一个任务其意图,然后等待其他任务停止使用套接字,然后再继续。清单140中所示的伪代码演示了向希望关闭套接字的套接字发送数据的任务的场景。
清单140所示的场景非常简单,因为只有两个任务需要相互同步,但是很容易看出这个场景是如何变得更加复杂的,并且需要更多的任务来加入同步,如果其他任务正在执行依赖于套接字被打开的处理。

void SocketTxTask( void *pvParameters )
{
	xSocket_t xSocket;
	uint32_t ulTxCount = 0UL;
 	for( ;; )
 	{
 		/* Create a new socket. This task will send to this socket, and another task will receive from this socket. */
 		xSocket = FreeRTOS_socket( ... );
 		/* Connect the socket. */
 		FreeRTOS_connect( xSocket, ... );
 
 		/* Use a queue to send the socket to the task that receives data. */
 		xQueueSend( xSocketPassingQueue, &xSocket, portMAX_DELAY );
 		/* Send 1000 messages to the socket before closing the socket. */
 		for( ulTxCount = 0; ulTxCount < 1000; ulTxCount++ )
 		{
 			if( FreeRTOS_send( xSocket, ... ) < 0 )
 			{
 				/* Unexpected error - exit the loop, after which the socket will be closed. */
 				break;
 			}
 		}
 		/* Let the Rx task know the Tx task wants to close the socket. */
 		TxTaskWantsToCloseSocket();
 		/* This is the Tx task’s synchronization point. The Tx task waits here for the Rx task to  reach its synchronization point. The Rx task will only reach its synchronization point  when it is no longer using the socket, and the socket can be closed safely. */
 		xEventGroupSync( ... );
 		/* Neither task is using the socket. Shut down the connection, then close the socket. */
 		FreeRTOS_shutdown( xSocket, ... );
		WaitForSocketToDisconnect();
 		FreeRTOS_closesocket( xSocket );
 	}
 }


/*-----------------------------------------------------------*/
void SocketRxTask( void *pvParameters )
{
	xSocket_t xSocket;
 	for( ;; )
 	{
 		/* Wait to receive a socket that was created and connected by the Tx task. */
 		xQueueReceive( xSocketPassingQueue, &xSocket, portMAX_DELAY );
 		/* Keep receiving from the socket until the Tx task wants to close the socket. */
 		while( TxTaskWantsToCloseSocket() == pdFALSE )
 		{
 			/* Receive then process data. */
 			FreeRTOS_recv( xSocket, ... );
 			ProcessReceivedData();
 		}
 		/* This is the Rx task’s synchronization point - it only reaches here when it is no onger using the socket, and it is therefore safe for the Tx task to close the socket. */
 		xEventGroupSync( ... );
 	}
 }

事件组可用于创建同步点:

  • 必须参与同步的每个任务都在事件组中分配一个唯一的事件位。
  • 每个任务在到达同步点时都会设置自己的事件位。
  • 在设置了自己的事件位后,事件组上的每个任务都阻塞在等待代表所有其他同步任务的事件位也被设置好。

但是,在这个场景中不能使用xEventGroupSetBits()和xEventGroupWaitBits()API函数。如果使用它们,则对一个位的设置(以指示一个任务已达到其同步点)和对位的测试(以确定其他同步任务是否已达到它们的同步点)将作为两个单独的操作来执行。要了解为什么这将会是一个问题,请考虑一个任务A、任务B和任务C尝试使用一个事件组进行同步的场景:

  1. 任务A和任务B已经到达同步点,所以它们的事件位在事件组中被设置,并且它们处于阻塞状态,等待任务C的事件位也被设置。
  2. 任务C到达同步点,并使用xEventGroupSetBits()来设置其在事件组中的位。一旦设置了任务C的位,任务A和任务B就会离开阻塞状态,并清除所有三个事件位。
  3. 然后任务C调用xEventGroupWaitBits()等待所有三个事件位被设置,但此时,所有三个事件位已经被清除,任务A和任务B已经离开各自的同步点,因此同步失败。

要成功地使用事件组来创建同步点,必须将事件位的设置和随后的事件位测试作为单一的不间断操作来执行。为此目的,提供了xEventGroupSync()API函数。

The xEventGroupSync() API Function

xEventGroupSync()允许两个或多个任务使用一个事件组相互同步。该函数允许任务在事件组中设置一个或多个事件位,然后等待事件位组合在同一事件组中设置,作为一个不间断操作。
xEventGroupSync() 的uxBitsToWaitFor参数指定调用任务的解除阻止条件。如果xEventGroupSync()返回,是因为满足了解除阻止条件,则在返回xEventGroupSync()之前,指定的事件位将被清除回零。

//Listing 142. The xEventGroupSync() API function prototype
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
							 const EventBits_t uxBitsToSet,
							 const EventBits_t uxBitsToWaitFor,
							 TickType_t xTicksToWait );
/*参数xEventGroup:要设置事件位的事件组的句柄。事件组句柄将从调用xEventGroupCreate()创建事件组时返回。*/
/*参数uxBitsToSet:一种位掩码,指定事件位或事件位设置为事件组中的1。事件组的值通过使用uxbitsToSet来更新事件组的现有值。
				  例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件位3被设置(如果尚未设置),而使事件组中的所有其他事件位保持不变。*/
/*参数uxBitsToWaitFor:在事件组中指需要检测位掩码。例如,如果调用任务希望等待事件位0、1和2在事件组中被设置,则将uxBitsToWaitFor设置为0x07(二进制111)。*/
/*参数xTicksToWait:任务应保持在“阻止”状态等待其取消阻塞条件满足的最长时间。如果此值零,或者在调用时满足解除阻止条件,则xEventGroupSync()将立即返回。
					阻塞块时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在RTOsConfig.h中设置为1,则将xToWiit设置为portMAX_DELAY将导致任务无限等待(不超时)。*/
/*返回值:如果由于满足调用任务的取消阻止条件,那么返回的值是满足调用任务的取消阻止条件时事件组(在自动清除回零之前)的值。在这种情况下,返回的值还将满足调用任务的解除阻止条件。
		 如果由于xToWait参数指定的阻塞时间过期,则返回的值是阻塞时间过期时事件组的值。在这种情况下,返回的值将不满足调用任务的解除阻止条件。*/
Example 23. Synchronizing tasks

示例23使用xEventGroupSync()来同步单个任务实现的三个实例。任务参数用于需要设置的事件位传递给每个实例。
该任务在调用xEventGroupSync()之前打印消息,并在调用xEventGroupSync()函数返回之后再次打印消息。每个消息都包含一个时间戳。这允许在产生的输出中观察到个执行序列。采用伪随机延迟来防止所有任务同时到达同步点。

//Listing 143. The implementation of the task used in Example 23
static void vSyncingTask( void *pvParameters )
{
	const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL );
	const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL );
	TickType_t xDelayTime;
	EventBits_t uxThisTasksSyncBit;
	const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT | 
 										mainSECOND_TASK_BIT | 
 										mainTHIRD_TASK_BIT );
 	/* Three instances of this task are created - each task uses a different event  bit in the synchronization. The event bit to use is passed into each task  instance using the task parameter. Store it in the uxThisTasksSyncBit variable. */
 	uxThisTasksSyncBit = ( EventBits_t ) pvParameters;
 	for( ;; )
 	{
 		/* Simulate this task taking some time to perform an action by delaying for a  pseudo random time. This prevents all three instances of this task reaching the synchronization point at the same time, and so allows the example’s  behavior to be observed more easily. */
 		xDelayTime = ( rand() % xMaxDelay ) + xMinDelay;
 		vTaskDelay( xDelayTime );
 		/* Print out a message to show this task has reached its synchronization point. pcTaskGetTaskName() is an API function that returns the name assigned  to the task when the task was created. */
 		vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" );
 		/* Wait for all the tasks to have reached their respective synchronization  points. */
 		xEventGroupSync( xEventGroup,/* The event group used to synchronize. */ 
 						 uxThisTasksSyncBit,/* The bit set by this task to indicate it has reached the  synchronization point. */ 
 						 uxAllSyncBits,/* The bits to wait for, one bit for each task taking part in the synchronization. */ 
 						 portMAX_DELAY );/* Wait indefinitely for all three tasks to reach the synchronization point. */
 
 		/* Print out a message to show this task has passed its synchronization point. As an indefinite delay was used the following line will only be  executed after all the tasks reached their respective synchronization  points. */
 		vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" );
 	}
 }

/* Definitions for the event bits in the event group. */
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, set by the first task. */
#define mainSECOND_TASK_BIT( 1UL << 1UL ) /* Event bit 1, set by the second task. */
#define mainTHIRD_TASK_BIT ( 1UL << 2UL ) /* Event bit 2, set by the third task. */
/* Declare the event group used to synchronize the three tasks. */
EventGroupHandle_t xEventGroup;
int main( void )
{
 	/* Before an event group can be used it must first be created. */
 	xEventGroup = xEventGroupCreate();
 	/* Create three instances of the task. Each task is given a different name, which is later printed out to give a visual indication of which task is executing. The event bit to use when the task reaches its synchronization point is passed into the task using the task parameter. */
 	xTaskCreate( vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL );
 	xTaskCreate( vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL );
 	xTaskCreate( vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL );
 	/* Start the scheduler so the created tasks start executing. */
 	vTaskStartScheduler();
 	/* As always, the following line should never be reached. */
 	for( ;; );
 	return 0;
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340