FreeRTOS源码探析之——事件标志组

事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。

1 基础概述

1.1 基本作用

事件标志的作用类似于全局型的flag,多个标志组合在一起构成事件标志组,这里先分析一下事件标志组于全局flag的区别:

  • 使用事件标志组可以让 FreeRTOS内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现
  • 使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心
  • 使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题

1.2 运行原理

在这里插入图片描述
如上图:

  • 任务1在一个循环中等待事件的到来,等待时处于阻塞状态。阻塞的时间可用设置。当有事件位被置位时,执行处理事件。
  • 任务1的处理事件可以触发事件(给自身的任务触发事件),但这种方式触发后,只能等程序再次运行到xEventGroupWaitBits才能处理
  • 任务2或其它任务可以触发事件(这是比较常用的用法),这样,在任务2触发事件后,任务1可以从阻塞态变为就绪态,如果任务1的优先级较高,则任务1开始运行,执行处理事件,实现任务间的同步
  • 中断中也可以可以触发事件(这也是比较常用的用法),作用也是实现同步,只是需要使用加FromISR后缀的API函数

1.3 事件组的最大位数

事件标志存储在一个EventBits_t类型的变量中,该变量在事件组结构体中定义,事件标志组的可用位数通过宏定义确定:

  • configUSE_16_BIT_TICKS定义为1,uxEventBits是16位的,低8位用来存储事件组
  • configUSE_16_BIT_TICKS 定义为0,uxEventBits是32位的,低24位用来存储事件组

注:高8位用于系统内核使用,不可用户使用!

#if configUSE_16_BIT_TICKS == 1
	#define eventCLEAR_EVENTS_ON_EXIT_BIT	0x0100U
	#define eventUNBLOCKED_DUE_TO_BIT_SET	0x0200U
	#define eventWAIT_FOR_ALL_BITS			0x0400U
	#define eventEVENT_BITS_CONTROL_BYTES	0xff00U
#else
	#define eventCLEAR_EVENTS_ON_EXIT_BIT	0x01000000UL     /*在退出时清除位*/
	#define eventUNBLOCKED_DUE_TO_BIT_SET	0x02000000UL     /*事件被置位,解除阻塞*/
	#define eventWAIT_FOR_ALL_BITS			0x04000000UL     /*等待所有位*/
	#define eventEVENT_BITS_CONTROL_BYTES	0xff000000UL
#endif

2 源码分析

首先来看一下事件控制块:

2.1 事件控制块

除了事件标志组变量之外,FreeRTOS还使用了一个链表来记录等待事件的任务,所有在等待此事件的任务均会被挂载在等待事件列表xTasksWaitingForBits

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;

2.2 创建事件标志组

主要功能是为事件标志组控制块分配内存以及创建一个任务等待列表

/*创建事件标志组*/
#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 */

2.3 等待事件

先放一张整体结构图:
在这里插入图片描述
再来看源码:

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 );
    /* 断言,判断要设置的事件标志位是否有效,防止用户使用高8位 */
	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;
            /*等待时间强制置0*/
			xTicksToWait = ( TickType_t ) 0;

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

            /*若需要等待所有事件*/
			if( xWaitForAllBits != pdFALSE )
			{
                /* 保存一下当前任务的信息标记,以便在恢复任务的时候对事件进行相应的操作 */
				uxControlBits |= eventWAIT_FOR_ALL_BITS; /*0x04000000UL 等待所有位*/
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

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

			uxReturn = 0;

			traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
		}
	}
     /*恢复任务调度*/
	xAlreadyYielded = xTaskResumeAll();

    /*xTicksToWait为0时不执行(1.本身设置的为0  2.符合的事件触发后被清0)*/
	if( xTicksToWait != ( TickType_t ) 0 )
	{
        /*恢复任务后还没有进行任务切换*/
		if( xAlreadyYielded == pdFALSE )
		{
            /* 进行一次任务切换!!! */
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 进入到这里说明当前的任务已经被重新调度了!!!(1.符合的事件被触发  2.事件等待超时) */
		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();
				}
			}
            /*退出临界区*/
			taskEXIT_CRITICAL();

			/* 在未使用跟踪宏时防止编译器警告 */
			xTimeoutOccurred = pdFALSE;
		}
		else
		{
			/* 因为已经设置了位,所以任务解除了阻塞 */
		}

		/* 清除内核使用的事件位(高8位 ) */
		uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;/*0xff000000UL*/
	}
	traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );

	return uxReturn;
}

注意这里的参数:

  • xEventGroup:事件标志组句柄
  • uxBitsToWaitFor:等待 24 个事件标志位中的指定标志
  • xClearOnExit:是否清除已经被置位的事件标志
    • 如果这个参数设置为 pdTRUE,且函数xEventGroupWaitBits 在参数 xTicksToWait 设置的溢出时间内返回,那么相应被设置的事件标志位会被清零。
    • 如果这个参数设置为 pdFALSE,对已经被设置的事件标志位没有影响。
  • xWaitForAllBits:是否等待所有的标志位都被设置
    • 如果这个参数设置为 pdTRUE,要等待第 2 个参数 uxBitsToWaitFor 所指定的标志位全部被置 1,函数才可以返回。当然,超出了在参数xTicksToWait 设置的溢出时间也是会返回的。
    • 如果这个参数设置为 pdFALSE,第 2 个参数uxBitsToWaitFor 所指定的任何标志位被置 1,函数都会返回,超出溢出时间也会返回。
  • xTicksToWait :设置等待时间,单位时钟节拍周期。 如果设置为 portMAX_DELAY,表示永久等待。

对于返回值:

  • 如果由于设置的等待时间超时,函数的返回值可能会有部分事件标志位被置 1
  • 如果由于指定的事件标志位被置1而返回, 并且设置了这个函数的参数xClearOnExit为pdTRUE,那么此函数的返回值是清零前的事件标志组数值
  • 调用此函数的任务在离开阻塞状态到退出函数 xEventGroupWaitBits 之间这段时间,如果一个高优先级的任务抢占执行了,并且修改了事件标志位,那么此函数的返回值会跟当前的事件标志组数值不同

2.3.1 检查等待的条件

等待事件中,要检查等待的条件是否满足要求

static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits )
{
	BaseType_t xWaitConditionMet = pdFALSE;

    /*不是等待所有事件*/
	if( xWaitForAllBits == pdFALSE )
	{
		/* 当前事件状态存在某一等待事件 */
		if( ( uxCurrentEventBits & uxBitsToWaitFor ) != ( EventBits_t ) 0 )
		{   
            /*返回结果设为TRUE*/
			xWaitConditionMet = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else/*等待所有事件*/
	{
		/* 当前事件状态等于要等待的所有事件 */
		if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor )
		{	
            /*返回结果设为TRUE*/
			xWaitConditionMet = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	return xWaitConditionMet;
}

2.3.2 将等待任务加入等待列表(无序列表)

等待事件中,若此次事件还不满足等待条件,则先将任务加入等待列表继续等待

/*将等待任务加入事件组的“事件等待列表”  参数:等待列表,信息参数,等待时间*/
void vTaskPlaceOnUnorderedEventList( List_t * pxEventList, const TickType_t xItemValue, const TickType_t xTicksToWait )
{
	configASSERT( pxEventList );

	/* 此函数必须在挂起调度程序的情况下调用,它由事件组实现使用 */
	configASSERT( uxSchedulerSuspended != 0 );

	/* 存储Item值在事件列表项中。在这里访问事件列表项是安全的,因为中断不会访问未处于阻塞状态的任务的事件列表项 */
	listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xEventListItem ), xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE ); /*0x80000000UL*/

	/* 将TCB的事件列表项放在事件列表的末尾。在这里访问事件列表是安全的,因为它是事件组的一部分——中断不直接访问事件组(相反,它们通过挂起任务级别的函数调用来间接访问事件组) */
	vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) );

    /* 添加任务到延时列表 */
	prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}

添加任务到延时列表的具体实现代码为

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		/* 即将进入一个延迟列表,因此确保ucDelayAborted标志被重置为pdFALSE,
		这样当任务离开阻塞状态时,就可以检测到它被设置为pdTRUE */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* 在将该任务添加到阻塞列表之前,请从就绪列表中删除该任务,因为两个列表使用相同的列表项 */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* 当前任务必须在就绪列表中,因此不需要检查,可以直接调用端口重置宏 */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* 将任务添加到挂起的任务列表而不是延迟的任务列表中,以确保它不会被计时事件唤醒。它会无限期地阻塞 */
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* 如果事件没有发生,计算任务应该被唤醒的时间。这可能会溢出,但这无关紧要,内核会正确地管理它 */
			xTimeToWake = xConstTickCount + xTicksToWait;

			/* 列表项将按照唤醒时间顺序插入 */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount )
			{
				/* 唤醒的时间已经过了。将此项放在溢出列表中 */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				/*唤醒时间没有溢出,因此使用当前的阻塞列表 */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				/* 如果进入阻塞状态的任务位于阻塞任务列表的最前面,那么xNextTaskUnblockTime也需要更新 */
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	...省略部分
	#endif /* INCLUDE_vTaskSuspend */
}

2.4 设置事件组标志位(非中断中)

先放一张整体结构图:


再来看源码:

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 );
    /* 断言,判断要设置的事件标志位是否有效,防止用户使用高8位 */
	configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );/*0xff000000UL*/

    /*获取事件标志组的等待位列表*/
	pxList = &( pxEventBits->xTasksWaitingForBits ); 
    
    /*获取列表的末尾项*/
	pxListEnd = listGET_END_MARKER( pxList ); 
    
    /*先停止任务调度*/
	vTaskSuspendAll();
	{
		traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );

        /*获取列表的首项*/
		pxListItem = listGET_HEAD_ENTRY( pxList ); 

		/* 指定的事件标志组的事件位,设置事件标志 */
		pxEventBits->uxEventBits |= uxBitsToSet;

		/* 设置这个事件标志位可能是某个任务在等待的事件,就遍历等待事件列表中的任务 */
		while( pxListItem != pxListEnd )
		{
            /*从列表首项开始检查*/
			pxNext = listGET_NEXT( pxListItem );
            /*获取列表的值*/
			uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
			xMatchFound = pdFALSE;

			/* 将等待的位从控制位中分离出来*/  /*0xff000000UL*/
			uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;  /*控制位*/
			uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;                /*等待位*/

            /* 如果只需要有一个事件标志位满足即可(等待所有的位没有被标记) */ /*0x04000000UL*/
			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();
				}

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

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

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

	return pxEventBits->uxEventBits;
}

注意,此函数的参数uxBitsToSet是要设置的事件位,函数返回一个EventBits_t的数据,它是当前事件组中事件位的值,但uxBitsToSetEventBits_t的值可能不相同:

  • 如果设置一个事件位后,使得等待该位的任务离开阻塞状态(注意是离开阻塞态,即使没有进入运行态,只要离开阻塞态即可),则该位可能会被自动清除
  • 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数xEventGroupWaitBits清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits 的返回值已经被修改

2.4.1 将满足事件条件的任务从等待列表中移除

BaseType_t xTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue )
{
TCB_t *pxUnblockedTCB;
BaseType_t xReturn;

	/*如果进入阻塞状态的任务位于阻塞任务列表的最前面,那么xNextTaskUnblockTime也需要更新。
	必须在挂起调度程序的情况下调用此函数。它由事件标志实现使用。 */
	configASSERT( uxSchedulerSuspended != pdFALSE );

	/* 将新项值存储在事件列表中 */
	listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE );

	/* 从事件标志中删除事件列表。中断不访问事件标志 */
	pxUnblockedTCB = ( TCB_t * ) listGET_LIST_ITEM_OWNER( pxEventListItem );
	configASSERT( pxUnblockedTCB );
	( void ) uxListRemove( pxEventListItem );

	/* 从延迟列表中删除任务并将其添加到就绪列表中。调度器被挂起,因此中断将不会访问就绪列表 */
	( void ) uxListRemove( &( pxUnblockedTCB->xStateListItem ) );
	prvAddTaskToReadyList( pxUnblockedTCB );

	if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
	{
		/* 如果从事件列表中删除的任务的优先级高于调用任务,则返回true。
		这允许调用任务知道它现在是否应该强制上下文切换。 */
		xReturn = pdTRUE;

		/* 在用户没有使用ISR安全FreeRTOS函数的“xHigherPriorityTaskWoken”参数的情况下,
		将yield标记为pending */
		xYieldPending = pdTRUE;
	}
	else
	{
		xReturn = pdFALSE;
	}

	return xReturn;
}

2.5 设置事件组标志位(中断中)

#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

由于该函数对事件标志组的操作是不确定性操作,因为不知道当前有多少个任务在等待此事件标志。而FreeRTOS 不允许在中断服务程序和临界段中执行不确定性操作

为了不在中断服务程序中执行,就通过此函数给 FreeRTOS 的 daemon 任务(即软件定时器任务)发送消息,在 daemon 任务中执行事件标志的置位操作。 同时也为了不在临界段中执行此不确定操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
关于daemon 任务(软件定时器任务)的介绍,可查看之前的文章:
FreeRTOS源码探析之——软件定时器

#if( INCLUDE_xTimerPendFunctionCall == 1 )

	BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken )
	{
        DaemonTaskMessage_t xMessage;
        BaseType_t xReturn;

		/* 设置消息的参数 */
		xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR;
		xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend;
		xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1;
		xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2;
		
        /*发送消息队列*/
		xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );

		tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn );

		return xReturn;
	}

#endif /* INCLUDE_xTimerPendFunctionCall */

再来看一下回调函数,其是就是非中断方式的事件标志组置位,所以,中断方式的事件标志组置位,是通过消息队列告知Daemon任务,然后在Daemon任务中实现置维操作

void vEventGroupSetBitsCallback( void *pvEventGroup, const uint32_t ulBitsToSet )
{
	( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet );
}

3 使用示例

3.1 测试1

//事件组触发任务(按键)
#define EVENTSETBIT_TASK_PRIO	2
#define EVENTSETBIT_STK_SIZE 	256  
TaskHandle_t EventSetBit_Handler;
void eventsetbit_task(void *pvParameters);

//事件组等待任务
#define EVENTGROUP_TASK_PRIO	3
#define EVENTGROUP_STK_SIZE 	256  
TaskHandle_t EventGroupTask_Handler;
void eventgroup_task(void *pvParameters);

#define KEY1_EVENT	  (1<<1)	//按键KEY1置位bit1
#define KEY0_EVENT	  (1<<0)    //按键KEY0置位bit0
#define KEY_UP_EVENT  (1<<2)    //按键KEY_UP置位bit2
#define EVENT_1_0	(KEY1_EVENT|KEY0_EVENT) //等待KEY1与KEY0

//设置事件位的任务
void eventsetbit_task(void *pvParameters)
{
	u8 key;
	EventBits_t uxBits;
	while(1)
	{
		if(EventGroupHandler!=NULL)
		{
			key=KEY_Scan(0);
			switch(key)
			{
				case KEY1_PRES:
					uxBits = xEventGroupSetBits(EventGroupHandler,KEY1_EVENT);
					if((uxBits & KEY1_EVENT) != 0)
					{
						printf("K1键按下,事件标志的bit1被设置\r\n");
					}
					else
					{
						printf("K1键按下,事件标志的bit1被清除,说明任务已经接收到bit0和bit1被设置的情况\r\n");
					}
					break;
				case KEY0_PRES:
					uxBits = xEventGroupSetBits(EventGroupHandler,KEY0_EVENT);
					if((uxBits & KEY0_EVENT) != 0)
					{
						printf("K0键按下,事件标志的bit0被设置\r\n");
					}
					else
					{
						printf("K0键按下,事件标志的bit0被清除,说明任务已经接收到bit0和bit1被设置的情况\r\n");
					}
					break;
				case WKUP_PRES:
					printf("K_UP键按下,事件标志的bit2被设置,这时一个无用事件\r\n");
					xEventGroupSetBits(EventGroupHandler,KEY_UP_EVENT);
					break;	
			}
		}
        vTaskDelay(10); //延时10ms,也就是10个时钟节拍
	}
}

//等待事件标志组并执行处理的任务
void eventgroup_task(void *pvParameters)
{
	EventBits_t uxBits;
	while(1)
	{

		if(EventGroupHandler!=NULL)
		{
			//等待事件组中的相应事件位
			uxBits=xEventGroupWaitBits((EventGroupHandle_t	)EventGroupHandler,		
										   (EventBits_t			)EVENT_1_0, //等待bit1与bit0
										   (BaseType_t			)pdTRUE, //退出前清除事件标志				
										   (BaseType_t			)pdTRUE, //等待bit1与bit0都被置位
								           (TickType_t			)5000);	 //最多阻塞5秒
			if((uxBits & EVENT_1_0) == EVENT_1_0)
			{
				printf("等待ok,接收到bit0和bit1:事件标志组的值:%d\r\n",uxBits);
			}
			else
			{
				printf("等待超时:事件标志组的值:%d\r\n",uxBits);
			}
			LED1=!LED1;	
		}
		else
		{
			vTaskDelay(10);
		}
	}
}

注意这里的一些参数,等待事件是要等待KEY1与KEY0都被置位,成功等待到事件后会把自动清除事件标志,每次等待的事件最大为5秒。

另外,这里设置等待事件的任务要比按键触发事件任务的优先级高。

测试结果如下:

等待超时:事件标志组的值:0
K1键按下,事件标志的bit1被设置
等待ok,接收到bit0和bit1:事件标志组的值:3
K0键按下,事件标志的bit0被清除,说明任务已经接收到bit0和bit1被设置的情况
    
K_UP键按下,事件标志的bit2被设置,这时一个无用事件
等待超时:事件标志组的值:4
K0键按下,事件标志的bit0被设置
等待ok,接收到bit0和bit1:事件标志组的值:7
K1键按下,事件标志的bit1被清除,说明任务已经接收到bit0和bit1被设置的情况
等待超时:事件标志组的值:4

先看第1段的结果

  • 首先,如何一直没有任何按键按下,则5秒后,等待超时,则会打印出等待超时,且当前的事件标志组的值就是0
  • 然后,KEY1按下,触发事件位bit1,此时xEventGroupWaitBits会得到bit1,由于需要等待bit1与bit0都被置位,所以仍会继续等待
  • 接着,当KEY0按下时,触发事件位bit0,此时bit1与bit0都被置位,由于等待任务的优先级高,所以在条件满足后,会切换到等待任务,打印出等待ok,并清除对应的事件事件标志,打印出的“事件标志组的值:3”而不是清除后的0,是因为返回值的清除前的值
  • 最后,调度器再次回到低优先级的按键处理任务,因为此时标志位已经被清除了,所以打印出“K0键按下,事件标志的bit0被清除”

再看第2段的结果

  • 首先,按下K_UP键,这个事件不是我们想等待的,在5秒后,等待超时,但依然会返回这个不需要的等待值
  • 然后,依次按下KEY0和KEY1,这时xEventGroupWaitBits等到了bit0、bit1与bit2,满足等待条件,但清除标志时只会清除自己等待的bit0和bit1
  • 所以,再过5秒后,虽然没有再次按下KEY_UP键,但bit2的值仍然保留在之间标志组中

3.2 测试2

下面,再将测试程序中两个任务的优先级换一下,即设置按键触发事件任务的优先级更高。

//事件组触发任务(按键)
#define EVENTSETBIT_TASK_PRIO	3
#define EVENTSETBIT_STK_SIZE 	256  
TaskHandle_t EventSetBit_Handler;
void eventsetbit_task(void *pvParameters);

//事件组等待任务
#define EVENTGROUP_TASK_PRIO	2
#define EVENTGROUP_STK_SIZE 	256  
TaskHandle_t EventGroupTask_Handler;
void eventgroup_task(void *pvParameters);

结果如下:

K1键按下,事件标志的bit1被设置
K0键按下,事件标志的bit0被清除,说明任务已经接收到bit0和bit1被设置的情况
等待ok,接收到bit0和bit1:事件标志组的值:3
    
等待超时:事件标志组的值:0
K1键按下,事件标志的bit1被设置
等待超时:事件标志组的值:2
等待超时:事件标志组的值:2

先来看第1段结果

  • 首先,KEY1按下,打印第1行
  • 然后KEY0按下,由于将按键任务的优先级调高,此时调度器不会立即切换到低优先级的事件等待任务,而会继续执行自身直到被阻塞,所以会有第2行的打印,但是注意,第2行按下KEY0的打印却依旧显示的是被清除了,因为在等待任务中使用了事件等待,而KEY0按下的时候,FreeRTOS操作系统会知道等待两个按键按下的事件已经触发了,此时,在按键任务中,xEventGroupSetBits的返回值,也不是当前获取的置位值了,而是经过xEventGroupSetBits函数自动清零之后的值,所以第2行打印的是清零信息
  • 最后,第3行打印的是都被置位,为什么不是清零?因为此时的xEventGroupWaitBits返回值是清零前的事件标志组的值

再看第2段的结果

  • 首先,第1行的等待超时时,事件组的数值是0
  • 然后,只按下KEY1,让等待超时
  • 最后,当等待超时后,虽然我们设置的退出时清除事件标志,但因为没有同时等待到bit1与bit0,所以超时退出后不会清除单一的事件标志

3.3 测试3

在来测试一下等待任一事件的情况,修改如下,将xEventGroupWaitBits的第4个参数改为pdFALSE:

void eventgroup_task(void *pvParameters)
{
	EventBits_t uxBits,NewValue;
	while(1)
	{

		if(EventGroupHandler!=NULL)
		{

			//等待事件组中的相应事件位
			uxBits=xEventGroupWaitBits((EventGroupHandle_t	)EventGroupHandler,		
										   (EventBits_t			)EVENT_1_0, //等待bit1与bit0
										   (BaseType_t			)pdTRUE, //退出前清除事件标志				
										   (BaseType_t			)pdFALSE, //等待bit1或bit0被置位
								           (TickType_t			)5000);	 //最多阻塞5秒
			if(((uxBits & KEY1_EVENT) == KEY1_EVENT)||((uxBits & KEY0_EVENT) == KEY0_EVENT))
			{
				printf("等待ok,接收到bit0或bit1:事件标志组的值:%d\r\n",uxBits);
			}
			else
			{
				printf("等待超时:事件标志组的值:%d\r\n",uxBits);
			}			
			LED1=!LED1;	
		}
		else
		{
			vTaskDelay(10);
		}
	}
}

测试结果:

等待超时:事件标志组的值:0
K1键按下,事件标志的bit1被清除,说明任务已经接收到bit0和bit1被设置的情况
等待ok,接收到bit0或bit1:事件标志组的值:2
K0键按下,事件标志的bit0被清除,说明任务已经接收到bit0和bit1被设置的情况
等待ok,接收到bit0或bit1:事件标志组的值:1
K_UP键按下,事件标志的bit2被设置,这时一个无用事件
等待超时:事件标志组的值:4

结果中的 “说明任务已经接收到bit0和bit1被设置的情况” 这半句先忽略。

  • KEY_1按下,设置标志位后又被自动清除,因为等待任务等待到bit0或bit1其中一个即可‘
  • KEY_0按下,同理
  • KEY_UP按下,不是想要等待的事件,会执行到超时等待,并且bit2始终没有被清除(如果需要清除可通过xEventGroupClearBits手动清除)

4 总结与注意事项

  • 事件标志组可以指定要等待哪些事件,但如果是其它事件触发了,在等待超时后,这些不希望等待的事件也会在超时后作为等待的返回结果
  • 由于函数 xEventGroupSetBitsFromISR 对事件标志的置位操作是在 daemon 任务里面执行的,如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置 daemon 任务的优先级高于使用此事件标志组的所有其它任务
  • 设置自动清除,只能清除要等待的位,如果是等到多个事件同时发送,当只有部分事件发送且等到超时后,事件位是不会被自动清除的,只能等之后所有事件都触发后才能自动清除,或通过手工清除
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值