事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。
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位用于系统内核使用,不可用户使用!
1 2 3 4 5 6 7 8 9 10 11
| #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
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 创建事件标志组
主要功能是为事件标志组控制块分配内存以及创建一个任务等待列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #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
traceEVENT_GROUP_CREATE( pxEventBits ); } else { traceEVENT_GROUP_CREATE_FAILED(); }
return ( EventGroupHandle_t ) pxEventBits; }
#endif
|
2.3 等待事件
先放一张整体结构图:
再来看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| 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; } 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(); }
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(); } } taskEXIT_CRITICAL();
xTimeoutOccurred = pdFALSE; } else { }
uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES; } 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 检查等待的条件
等待事件中,要检查等待的条件是否满足要求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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 ) { xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } else { if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor ) { xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } }
return xWaitConditionMet; }
|
2.3.2 将等待任务加入等待列表(无序列表)
等待事件中,若此次事件还不满足等待条件,则先将任务加入等待列表继续等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void vTaskPlaceOnUnorderedEventList( List_t * pxEventList, const TickType_t xItemValue, const TickType_t xTicksToWait ) { configASSERT( pxEventList );
configASSERT( uxSchedulerSuspended != 0 );
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xEventListItem ), xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE );
vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) );
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); }
|
添加任务到延时列表的具体实现代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely ) { TickType_t xTimeToWake; const TickType_t xConstTickCount = xTickCount;
#if( INCLUDE_xTaskAbortDelay == 1 ) {
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 ) );
if( xTimeToWake < xNextTaskUnblockTime ) { xNextTaskUnblockTime = xTimeToWake; } else { mtCOVERAGE_TEST_MARKER(); } } } } #else ...省略部分 #endif }
|
2.4 设置事件组标志位(非中断中)
先放一张整体结构图:
再来看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| 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 );
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;
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 { }
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
的返回值已经被修改
2.4.1 将满足事件条件的任务从等待列表中移除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| BaseType_t xTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue ) { TCB_t *pxUnblockedTCB; BaseType_t xReturn;
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 ) {
xReturn = pdTRUE;
xYieldPending = pdTRUE; } else { xReturn = pdFALSE; }
return xReturn; }
|
2.5 设置事件组标志位(中断中)
1 2 3 4 5 6 7 8 9 10 11 12 13
| #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 任务中执行事件标志的置位操作。 同时也为了不在临界段中执行此不确定操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #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
|
再来看一下回调函数,其是就是非中断方式的事件标志组置位,所以,中断方式的事件标志组置位,是通过消息队列告知Daemon任务,然后在Daemon任务中实现置维操作。
1 2 3 4
| void vEventGroupSetBitsCallback( void *pvEventGroup, const uint32_t ulBitsToSet ) { ( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet ); }
|
3 使用示例
3.1 测试1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| #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) #define KEY0_EVENT (1<<0) #define KEY_UP_EVENT (1<<2) #define EVENT_1_0 (KEY1_EVENT|KEY0_EVENT)
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); } }
void eventgroup_task(void *pvParameters) { EventBits_t uxBits; while(1) {
if(EventGroupHandler!=NULL) { uxBits=xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, (EventBits_t )EVENT_1_0, (BaseType_t )pdTRUE, (BaseType_t )pdTRUE, (TickType_t )5000); 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秒。
另外,这里设置等待事件的任务要比按键触发事件任务的优先级高。
测试结果如下:
1 2 3 4 5 6 7 8 9 10 11
| 等待超时:事件标志组的值: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
下面,再将测试程序中两个任务的优先级换一下,即设置按键触发事件任务的优先级更高。
1 2 3 4 5 6 7 8 9 10 11
| #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);
|
结果如下:
1 2 3 4 5 6 7 8
| 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void eventgroup_task(void *pvParameters) { EventBits_t uxBits,NewValue; while(1) {
if(EventGroupHandler!=NULL) {
uxBits=xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, (EventBits_t )EVENT_1_0, (BaseType_t )pdTRUE, (BaseType_t )pdFALSE, (TickType_t )5000); 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); } } }
|
测试结果:
1 2 3 4 5 6 7
| 等待超时:事件标志组的值: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 任务的优先级高于使用此事件标志组的所有其它任务
- 设置自动清除,只能清除要等待的位,如果是等到多个事件同时发送,当只有部分事件发送且等到超时后,事件位是不会被自动清除的,只能等之后所有事件都触发后才能自动清除,或通过手工清除