FreeRTOS个人笔记-事件

根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

事件

事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。
与信号量不同的是,它可以实现一对多,多对多的同步。
即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。
也可以是多个任务同步多个事件。
每一个事件组只需要很少的 RAM 空间来保存事件组的状态。 事件组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义。

在 STM32 中 , 我们一般将 configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事件标志组。 
每一位代表一个事件, 任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。
事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;
事件“逻辑与” 则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。

事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步。
各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件, 任务仅对感兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件, 任务将被唤醒并进行后续的处理动作。
信号量只能识别单一同步动作,而不能同时等待多个事件的同步。

任务可以通过设置事件位来实现事件的触发和等待操作。 FreeRTOS 的事件仅用于同步,不提供数据传输功能。
FreeRTOS 提供的事件具有如下特点:
 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生),一共 24 种事件类型。
 事件仅用于同步,不提供数据传输功能。
 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走), 等效于只设置一次。
 允许多个任务对同一事件进行读写操作。
 支持事件等待超时机制。

在 FreeRTOS 事件中,每个事件获取的时候,用户可以选择感兴趣的事件,并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。
当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求。
如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。

应用场景

在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不是一会就能检测完毕,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用。

运作机制

接收事件时,可以根据感兴趣的事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。
用户可以自定义通过传入参数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。
事件不与任务相关联,事件相互独立,一个 32 位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生)。

FreeRTOS个人笔记-事件_第1张图片

任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。
而任务 2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事件 5 都发生的时候, 任务 2 才会被唤醒。
如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果在接收事件函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。

事件组结构体

typedef struct xEventGroupDefinition
{
	EventBits_t uxEventBits;
	List_t xTasksWaitingForBits;		

	#if( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxEventGroupNumber;
	#endif

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated; 
	#endif
} EventGroup_t;

事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义。EventBits_t实际上是TickType_t的重别名,而TickType_t可选16位或32位。
在 STM32 中, uxEventBits 是 32 位的, 所以我们有 24 个位用来实现事件组。
除了事件标志组变量之外, FreeRTOS 还使用了一个链表来记录等待事件的任务,所有在等待此事件的任务均会被挂载在等待事件列表 xTasksWaitingForBits。

创建事件

xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。需要把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。
当创建一个事件时, 系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。 

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	EventGroupHandle_t xEventGroupCreate( void )
	{
		EventGroup_t *pxEventBits;

		/* 分配事件控制块的内存 */
		pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );

		if( pxEventBits != NULL )
		{
			pxEventBits->uxEventBits = 0;								//事件标志组变量清零
			vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );	//初始化事件等待链表

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
				
				pxEventBits->ucStaticallyAllocated = pdFALSE;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */

			traceEVENT_GROUP_CREATE( pxEventBits );
		}
		else
		{
			traceEVENT_GROUP_CREATE_FAILED();
		}

		return ( EventGroupHandle_t ) pxEventBits;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

xEventGroupCreate()实例    

static EventGroupHandle_t Event_Handle =NULL;

Event_Handle = xEventGroupCreate();

if ( Event_Handle != NULL )
	printf("Event_Handle 事件创建成功!\r\n");
else
	/* 创建失败,应为内存空间不足 */

删除事件

void vEventGroupDelete( EventGroupHandle_t xEventGroup )
{
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	const List_t *pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits );

	/*挂起调度器,因为接下来的操作不知道需要多长的时间,并且在删除的时候,不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。*/
	vTaskSuspendAll();	
	{
		traceEVENT_GROUP_DELETE( xEventGroup );

		while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 )	//当有任务被阻塞在事件等待列表中
		{
			//如果有任务阻塞在这个事件上,那么就要把事件从等待事件中移除
			configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( const ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) );
			vTaskRemoveFromUnorderedEventList( pxTasksWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET );
		}

		#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
		{
			vPortFree( pxEventBits );	//释放事件的内存
		}
		#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
		{
			if( pxEventBits->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
			{
				vPortFree( pxEventBits );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
	}
	( void ) xTaskResumeAll();	//解挂调度器
}

vEventGroupDelete()用于删除由函数 xEventGroupCreate()创建的事件组,只有被创建成功的事件才能被删除,但是需要注意的是该函数不允许在中断里面使用。 
当事件组被删除之后,阻塞在该事件组上的任务都会被解锁,并向等待事件的任务返回事件组的值为 0。

vEventGroupDelete()函数实例

static EventGroupHandle_t Event_Handle =NULL;

/* 创建 Event_Handle */
Event_Handle = xEventGroupCreate();

if( Event_Handle != NULL )
{
	printf("Event_Handle 事件创建成功!\r\n");

	/* 创建成功,可以删除 */
	xEventGroupCreate(Event_Handle);
}else
	/* 创建失败,应为内存空间不足 */

置位事件组中指定的位

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。该函数不允许在中断中使用。
通过参数指定的事件标志来设定事件的标志位,然后遍历事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配。
如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

uxBitsToSet指定事件中的事件标志位。如uxBitsToSet = 0x08 则只置位bit3,如果设置 uxBitsToSet = 0x09 则置位bit3和bit0。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
{
	ListItem_t *pxListItem, *pxNext;
	ListItem_t const *pxListEnd;
	List_t *pxList;
	EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
	EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
	BaseType_t xMatchFound = pdFALSE;

	configASSERT( xEventGroup );											//判断事件是否有效			
	configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );	//判断要设置的事件标志位是否有效,bit24至bit31是否为0。

	pxList = &( pxEventBits->xTasksWaitingForBits );	//pxList为事件等待链表
	pxListEnd = listGET_END_MARKER( pxList ); 			//pxListEnd为事件等待链表的尾结点
	vTaskSuspendAll();
	{
		traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );

		pxListItem = listGET_HEAD_ENTRY( pxList );		//pxListItem为事件等待链表的头结点
		
		pxEventBits->uxEventBits |= uxBitsToSet;		//设置事件标志位

		/*设置这个事件标志位可能是某个任务在等待的事件,就遍历等待事件列表中的任务*/
		while( pxListItem != pxListEnd )	//事件等待链表的头结点不等于尾节点,即事件等待链表不为空
		{
			pxNext = listGET_NEXT( pxListItem );
			uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
			xMatchFound = pdFALSE;

			/* 获取要等待事件的标记信息,是逻辑与还是逻辑或 */
			uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
			/* 获取任务的等待事件是什么 */
			uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;

			if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 )	//如果只需要有一个事件标志位满足即可
			{
				if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 )	//判断要等待的事件是否发生了
				{
					xMatchFound = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor )	//所有事件都发生的时候才能解除阻塞
			{
				xMatchFound = pdTRUE;		//所有事件都发生
			}
			else
			{
				/* Need all bits to be set, but not all the bits were set. */
			}

			if( xMatchFound != pdFALSE )
			{
				/* 找到了,然后看下是否需要清除标志位。如果需要,就记录下需要清除的标志位,等遍历完队列之后统一处理 */
				if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
				{
					uxBitsToClear |= uxBitsWaitedFor;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* 将满足事件条件的任务从等待列表中移除,并且添加到就绪列表中 */
				vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
			}

			pxListItem = pxNext;	/* 循环遍历事件等待列表,可能不止一个任务在等待这个事件 */
		}

		/* 遍历完毕,清除事件标志位 */
		pxEventBits->uxEventBits &= ~uxBitsToClear;
	}
	( void ) xTaskResumeAll();

	return pxEventBits->uxEventBits;
}

xEventGroupSetBits()运用例子:
比如我们要记录一个事件的发生,这个事件在事件组的位置是 bit0,当它还未发生的时候,那么事件组 bit0 的值也是 0,当它发生的时候,我们往事件集合 bit0 中写入这个事件,也就是 0x01,那这就表示事件已经发生了,为了便于理解,一般操作我们都是用宏定义来实现 #define EVENT (0x01 << x),“<< x”表示写入事件集合的 bit x , 在使用该函数之前必须先创建事件。

xEventGroupSetBits()函数实例

#define KEY1_EVENT (0x01 << 0)	//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)	//设置事件掩码的位1

static EventGroupHandle_t Event_Handle =NULL;

Event_Handle = xEventGroupCreate();	/* 创建 Event_Handle */
if ( Event_Handle != NULL )
	printf("Event_Handle 事件创建成功!\r\n");

static void KEY_Task(void* parameter)
{
	/* 任务都是一个无限循环,不能返回 */
	while (1) 
	{	
		if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 	//如果 KEY1 被按下
		{
			printf ( "KEY1 被按下\n" );
			
			xEventGroupSetBits(Event_Handle,KEY1_EVENT);			/* 触发一个事件 1 */
		}
	

		if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 	//如果 KEY2 被按下
		{
			printf ( "KEY2 被按下\n" );
			
			xEventGroupSetBits(Event_Handle,KEY2_EVENT);			/* 触发一个事件 2 */
		}
		vTaskDelay(20); //每 20ms 扫描一次
	}
}

xEventGroupSetBitsFromISR()是 xEventGroupSetBits()的中断版本,用于置位事件组中指定的位。 
置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。
FreeRTOS 是不允许不确定的操作在中断和临界段中发生的,所以xEventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成。
守护任务是基于调度锁而非临界段的机制来实现的。

在中断中事件标志的置位是在守护任务(软件定时器服务任务)中完成的。 因此 FreeRTOS 的守护任务与其他任务一样, 都是系统调度器根据其优先级进行任务调度的,
但守护任务的优先级必须比任何任务的优先级都要高,保证在需要的时候能立即切换任务从而达到快速处理的目的,因为这是在中断中让事件标志位置位 ,
其优先级由 FreeRTOSConfig.h 中的宏 configTIMER_TASK_PRIORITY 来定义。
xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的,所以它实际上执行的上下文环境依旧是在任务中。
要想使用该函数,必须把 configUSE_TIMERS 和 INCLUDE_xTimerPendFunctionCall 这些宏在 FreeRTOSConfig.h 中都定义为 1,并且把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中编译。

#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) )

	BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )
	{
		BaseType_t xReturn;

		traceEVENT_GROUP_SET_BITS_FROM_ISR( xEventGroup, uxBitsToSet );
		xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken );

		return xReturn;
	}

#endif

pxHigherPriorityTaskWoken 在使用之前必须初始化成pdFALSE。调用 xEventGroupSetBitsFromISR()会给守护任务发送一个消息,如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务的优先级设 置为所有任务中最高优先级),pxHigherPriorityTaskWoken 会被置为 pdTRUE,然后在中断退出前执行一次上下文切换。

消息成功发送给守护任务之后则返回 pdTRUE,否则返回 pdFAIL。如果定时器服务队列满了将返回 pdFAIL。

xEventGroupSetBitsFromISR()函数实例

#define BIT_0 ( 0x01 << 0 )
#define BIT_4 ( 0x01 << 4 )

EventGroupHandle_t xEventGroup;		/* 假定事件组已经被创建 */


void anInterruptHandler( void )		/* 中断 ISR */
{
	BaseType_t xHigherPriorityTaskWoken, xResult;
		
	xHigherPriorityTaskWoken = pdFALSE;		/* xHigherPriorityTaskWoken 在使用之前必须先初始化为 pdFALSE */
	
	/* 置位事件组 xEventGroup 的的 Bit0 和 Bit4 */
	xResult = xEventGroupSetBitsFromISR( xEventGroup,BIT_0 | BIT_4,&xHigherPriorityTaskWoken );
	
	if ( xResult != pdFAIL ) 		/* 信息是否发送成功 */
	{
		/* 如果 xHigherPriorityTaskWoken 的值为 pdTRUE,则进行一次上下文切换*/
		portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
	}
}

等待事件

xEventGroupWaitBits()用于获取事件组中的一个或多个事件发生标志,当要读取的事件标志位没有被置位时任务将进入阻塞等待状态。要想使用该函数必须把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。 
通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制, 
当且仅当任务等待的事件发生时,任务才能获取到事件信息。
在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态。
这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,
因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

uxBitsToWaitFor,一个按位或的值,指定需要等待事件组中的哪些位置 1。
xClearOnExit,当 xEventGroupWaitBits()等待到满足任务唤醒的事件时。
pdTRUE: 系统 将 清除由形参 uxBitsToWaitFor 指定的事件标志位。
pdFALSE:系统不会清除由形参 uxBitsToWaitFor 指定的事件标志位。
xWaitForAllBits,
pdTRUE: 逻辑与
pdFALSE:逻辑或
返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要对返回值进行判断再处理。

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )
{
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    EventBits_t uxReturn, uxControlBits = 0;
    BaseType_t xWaitConditionMet, xAlreadyYielded;
    BaseType_t xTimeoutOccurred = pdFALSE;

	configASSERT( xEventGroup );
	configASSERT( ( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0 );
	configASSERT( uxBitsToWaitFor != 0 );
	#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
	{
		configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
	}
	#endif

	vTaskSuspendAll();
	{
		const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;

		/* 当前事件中的标志位是否已经满足条件 */
		/* 判断用户等待的事件是否与当前事件标志位一致 */
		xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );

		if( xWaitConditionMet != pdFALSE )
		{
			uxReturn = uxCurrentEventBits;		//返回当前事件的所有标志位
			xTicksToWait = ( TickType_t ) 0;

			if( xClearOnExit != pdFALSE )		//退出时是否需要清除对应的事件标志位
			{
				pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else if( xTicksToWait == ( TickType_t ) 0 )	//不等待
		{
			uxReturn = uxCurrentEventBits;		//返回当前事件的所有标志位
			xTimeoutOccurred = pdTRUE;
		}
		else	//用户超时指定时间,进入等待状态
		{/* 保存一下当前任务的信息标记,以便在恢复任务的时候对事件进行相应的操作 */
			if( xClearOnExit != pdFALSE )
			{
				uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			if( xWaitForAllBits != pdFALSE )
			{
				uxControlBits |= eventWAIT_FOR_ALL_BITS;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 当前任务进入事件等待列表中,任务将被阻塞指定时间 xTicksToWait */
			vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );

			uxReturn = 0;

			traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
		}
	}
	xAlreadyYielded = xTaskResumeAll();

	if( xTicksToWait != ( TickType_t ) 0 )
	{
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();		//进行一次任务切换
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 进入到这里说明当前的任务已经被重新调度了 */

		uxReturn = uxTaskResetEventItemValue();

		if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 )
		{
			taskENTER_CRITICAL();
			{
				/* 超时返回时,直接返回当前事件的所有标志位 */
				uxReturn = pxEventBits->uxEventBits;

				/* 再判断一次是否发生了事件 */
				if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE )
				{
					if( xClearOnExit != pdFALSE )		/* 如果发生了,那就清除事件标志位并且返回 */
					{
						pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				xTimeoutOccurred = pdTRUE;
			}
			taskEXIT_CRITICAL();
		}
		else
		{
			/* The task unblocked because the bits were set. */
		}

		/* 返回事件所有标志位 */
		uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
	}
	traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );

	( void ) xTimeoutOccurred;

	return uxReturn;
}

当用户调用这个函数接口时,系统首先根据用户指定参数和接收选项来判断它要等待的事件是否发生,
如果事件已经发生,则根据参数 xClearOnExit 来决定是否清除事件的相应标志位,并且返回事件标志位的值,
但是这个值并不是一个稳定的值,所以在等待到对应事件的时候,还需我们判断事件是否与任务需要的一致; 
如果事件没有发生,则把任务添加到事件等待列表中, 把任务感兴趣的事件标志值和等待选项填用列表项的值来表示,直到事件发生或等待时间超时。

xEventGroupWaitBits()实例

static void LED_Task(void* parameter)
{
	EventBits_t r_event; 	/* 定义一个事件接收变量 */

	while (1) 
	{
		/****************************************************************
		* 等待接收事件标志
		*
		* 如果 xClearOnExit 设置为 pdTRUE,那么在 xEventGroupWaitBits()返回之前,
		* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置的 uxBitsToWaitFor 中的任何位都将被清除。
		* 如果 xClearOnExit 设置为 pdFALSE,则在调用 xEventGroupWaitBits()时,不会更改事件组中设置的位。
		*
		* xWaitForAllBits 如果 xWaitForAllBits 设置为 pdTRUE,则当 uxBitsToWaitFor 中的所有位都设置或指定的块时间到期时, xEventGroupWaitBits()才返回。
		* 如果 xWaitForAllBits 设置为 pdFALSE,则当设置 uxBitsToWaitFor 中设置的任何一个位置 1 或指定的块时间到期时, xEventGroupWaitBits()都会返回。
		* 阻塞时间由 xTicksToWait 参数指定。
		***************************************************************/
		r_event = xEventGroupWaitBits(	Event_Handle, 			/* 事件对象句柄 */
										KEY1_EVENT|KEY2_EVENT,	/* 接收任务感兴趣的事件 */
										pdTRUE, 				/* 退出时清除事件位 */
										pdTRUE, 				/* 满足感兴趣的所有事件 */
										portMAX_DELAY);			/* 指定超时事件,一直等 */
		
		if ((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) 	/* 如果接收完成并且正确 */
		{
			printf ( "KEY1 与 KEY2 都按下\n");
			LED1_TOGGLE; //LED1 反转
		} else
			printf ( "事件错误! \n");
	}
}

清除事件组中指定的位

xEventGroupClearBits()用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除, 
要想使用该函数必须把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
{
	EventGroup_t 	*pxEventBits = ( EventGroup_t * ) xEventGroup;
	EventBits_t 	uxReturn;

	configASSERT( xEventGroup );
	configASSERT( ( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

	taskENTER_CRITICAL();
	{
		traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear );

		uxReturn = pxEventBits->uxEventBits;

		/* 对应的标志位清除 */
		pxEventBits->uxEventBits &= ~uxBitsToClear;
	}
	taskEXIT_CRITICAL();

	return uxReturn;
}

xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能的xEventGroupClearBitsFromISR() 来代替。
中断清除事件标志位的操作在守护任务(定时器服务任务)里面完成。守护进程的优先级由 FreeRTOSConfig.h 中的宏 configTIMER_TASK_PRIORITY 来定义。
xEventGroupClearBitsFromISR()是xEventGroupClearBits()的中断保护版本。

#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) )

	BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
	{
		BaseType_t xReturn;

		traceEVENT_GROUP_CLEAR_BITS_FROM_ISR( xEventGroup, uxBitsToClear );
		xReturn = xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL );

		return xReturn;
	}

#endif

xEventGroupClearBits()函数使用实例

#define BIT_0 ( 0x01 << 0 )
#define BIT_4 ( 0x01 << 4 )

void aFunction( EventGroupHandle_t xEventGroup )
{
	EventBits_t uxBits;
	
	/* 清楚事件组的 bit 0 and bit 4 */
	uxBits = xEventGroupClearBits( xEventGroup,BIT_0 | BIT_4 );
	
	if ( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) ) 
	{
		/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都置位,但是现在是被清除了*/
	} else if ( ( uxBits & BIT_0 ) != 0 ) 
	{
		/* 在调用 xEventGroupClearBits()之前 bit0 已经置位,但是现在是被清除了*/
	} else if ( ( uxBits & BIT_4 ) != 0 ) 
	{
		/* 在调用 xEventGroupClearBits()之前 bit4 已经置位,但是现在是被清除了*/
	} else {
		/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都没被置位 */
	}
}

事件实验

创建了两个任务,一个是设置事件任务,一个是等待事件任务,两个任务独立运行。
设置事件任务通过检测按键的按下情况设置不同的事件标志位。
等待事件任务则获取这两个事件标志位,并且判断两个事件是否都发生。
如果是则输出相应信息,LED 进行翻转。等待事件任务的等待时间是 portMAX_DELAY,一直在等待事件的发生,等待到事件之后清除对应的事件标记位。

#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"

static TaskHandle_t AppTaskCreate_Handle = NULL;	/* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle 	 = NULL;	/* LED_Task 任务句柄 */
static TaskHandle_t KEY_Task_Handle 	 = NULL;	/* KEY_Task 任务句柄 */

static EventGroupHandle_t Event_Handle 	 = NULL;

#define KEY1_EVENT (0x01 << 0)						//设置事件掩码的位 0
#define KEY2_EVENT (0x01 << 1)						//设置事件掩码的位 1
				
int main(void)
{
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
	
	BSP_Init();

	xReturn = xTaskCreate(	(TaskFunction_t )AppTaskCreate,				/* 任务入口函数 */
							(const char* )"AppTaskCreate",				/* 任务名字 */
							(uint16_t )512,								/* 任务栈大小 */
							(void* )NULL,								/* 任务入口函数参数 */
							(UBaseType_t )1, 							/* 任务的优先级 */
							(TaskHandle_t* )&AppTaskCreate_Handle);		/*任务控制块指针 */

	if (pdPASS == xReturn)
		vTaskStartScheduler(); /* 启动任务,开启调度 */
	else
		return -1;
	
	while (1); /* 正常不会执行到这里 */
 }

static void AppTaskCreate(void)
{
	BaseType_t xReturn = pdPASS;			/* 定义一个创建信息返回值,默认为 pdPASS */
	
	taskENTER_CRITICAL(); 					//进入临界区
	
	Event_Handle = xEventGroupCreate();		/* 创建 Event_Handle */
	if (NULL != Event_Handle)
		printf("Event_Handle 事件创建成功!\r\n");
	
	xReturn = xTaskCreate(	(TaskFunction_t )LED_Task, 				/* 任务入口函数 */
							(const char* )"LED_Task",				/* 任务名字 */
							(uint16_t )512, 						/* 任务栈大小 */
							(void* )NULL, 							/* 任务入口函数参数 */
							(UBaseType_t )2, 						/* 任务的优先级 */
							(TaskHandle_t* )&LED_Task_Handle);		/* 任务控制块指针 */
	if (pdPASS == xReturn)
		printf("创建 LED_Task 任务成功!\r\n");
	
	xReturn = xTaskCreate(	(TaskFunction_t )KEY_Task,				/* 任务入口函数 */
							(const char* )"KEY_Task",				/* 任务名字 */
							(uint16_t )512, 						/* 任务栈大小 */
							(void* )NULL,							/* 任务入口函数参数 */
							(UBaseType_t )3, 						/* 任务的优先级 */
							(TaskHandle_t* )&KEY_Task_Handle);		/* 任务控制块指针 */
	if (pdPASS == xReturn)
		printf("创建 KEY_Task 任务成功!\n");
	
	vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
	
	taskEXIT_CRITICAL(); //退出临界区
}

static void LED_Task(void* parameter)
{
	EventBits_t r_event;	 /* 定义一个事件接收变量 */

	while (1) 
	{
		/*************************************************************
		* 等待接收事件标志
		*
		* 如果 xClearOnExit 设置为 pdTRUE,那么在 xEventGroupWaitBits()返回之前,
		* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置
		* 的 uxBitsToWaitFor 中的任何位都将被清除。
		* 如果 xClearOnExit 设置为 pdFALSE,
		* 则在调用 xEventGroupWaitBits()时,不会更改事件组中设置的位。
		* xWaitForAllBits 如果 xWaitForAllBits 设置为 pdTRUE,则当 uxBitsToWaitFor 中
		* 的所有位都设置或指定的块时间到期时, xEventGroupWaitBits()才返回。
		* 如果 xWaitForAllBits 设置为 pdFALSE,则当设置 uxBitsToWaitFor 中设置的任何
		* 一个位置 1 或指定的块时间到期时, xEventGroupWaitBits()都会返回。
		* 阻塞时间由 xTicksToWait 参数指定。
		*********************************************************/
		r_event = xEventGroupWaitBits(	Event_Handle, 			/* 事件对象句柄 */
										KEY1_EVENT|KEY2_EVENT,	/* 接收任务感兴趣的事件 */
										pdTRUE, 				/* 退出时清除事件位 */
										pdTRUE, 				/* 满足感兴趣的所有事件 */
										portMAX_DELAY);			/* 指定超时事件,一直等 */

		if ((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) 
		{
			/* 如果接收完成并且正确 */
			printf ( "KEY1 与 KEY2 都按下\n");
			LED1_TOGGLE; 	//LED1 反转
		} else
			printf ( "事件错误! \n");
	}
}

static void KEY_Task(void* parameter)
{
	while (1) 
	{
		if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 	//如果 KEY1 被按下
		{
			printf ( "KEY1 被按下\n" );			
			xEventGroupSetBits(Event_Handle,KEY1_EVENT);			/* 触发一个事件 1 */
		}		
		if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 	//如果 KEY2 被按下
		{
			printf ( "KEY2 被按下\n" );		
			xEventGroupSetBits(Event_Handle,KEY2_EVENT);			/* 触发一个事件 2 */
		}
		vTaskDelay(20); 											//每 20ms 扫描一次
	}
}

至此,事件内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xEventGroupCreate()                                                                 创建事件组
vEventGroupDelete( EventGroupHandle_t xEventGroup )        删除事件组

置位事件组中指定的位
xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )        
xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )

获取事件组中的一个或多个事件发生标志,当要读取的事件标志位没有被置位时任务将进入阻塞等待状态。
xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )

清除事件组中指定的位
xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )

你可能感兴趣的:(#,FreeRTOS个人笔记,操作系统,stm32,c语言,单片机)