事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。
1
1基础概述
事件标志的作用类似于全局型的flag,多个标志组合在一起构成事件标志组,这里先分析一下事件标志组于全局flag的区别:
如上图:
事件标志存储在一个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_BIT0x0100U
#define eventUNBLOCKED_DUE_TO_BIT_SET0x0200U
#define eventWAIT_FOR_ALL_BITS 0x0400U
#define eventEVENT_BITS_CONTROL_BYTES0xff00U
#else
#define eventCLEAR_EVENTS_ON_EXIT_BIT0x01000000UL /*在退出时清除位*/
#define eventUNBLOCKED_DUE_TO_BIT_SET0x02000000UL
#define eventWAIT_FOR_ALL_BITS 0x04000000UL /*等待所有位*/
#define eventEVENT_BITS_CONTROL_BYTES0xff000000UL
#endif
2
2源码分析
首先来看一下事件控制块:
除了事件标志组变量之外,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;
主要功能是为事件标志组控制块分配内存以及创建一个任务等待列表
/*创建事件标志组*/
#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 */
先放一张整体结构图:
再来看源码:
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;
}
注意这里的参数:
对于返回值:
等待事件中,要检查等待的条件是否满足要求
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;
}
等待事件中,若此次事件还不满足等待条件,则先将任务加入等待列表继续等待
/*将等待任务加入事件组的“事件等待列表” 参数:等待列表,信息参数,等待时间*/
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 */
}
先放一张整体结构图:
再来看源码:
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
的数据,它是当前事件组中事件位的值,但uxBitsToSet
与EventBits_t
的值可能不相同:
xEventGroupWaitBits
清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits
的返回值已经被修改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;
}
#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
使用示例
//事件组触发任务(按键)
#define EVENTSETBIT_TASK_PRIO2
#define EVENTSETBIT_STK_SIZE 256
TaskHandle_t EventSetBit_Handler;
void eventsetbit_task(void *pvParameters);
//事件组等待任务
#define EVENTGROUP_TASK_PRIO3
#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段的结果
再看第2段的结果
下面,再将测试程序中两个任务的优先级换一下,即设置按键触发事件任务的优先级更高。
//事件组触发任务(按键)
#define EVENTSETBIT_TASK_PRIO3
#define EVENTSETBIT_STK_SIZE 256
TaskHandle_t EventSetBit_Handler;
void eventsetbit_task(void *pvParameters);
//事件组等待任务
#define EVENTGROUP_TASK_PRIO2
#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段结果
再看第2段的结果
在来测试一下等待任一事件的情况,修改如下,将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被设置的情况” 这半句先忽略。
xEventGroupClearBits
手动清除)4
总结与注意事项