当前位置: 首页 > article >正文

FreeRTOS 23:事件组EventGroup创建、删除、置位操作

FreeRTOS 提供了事件标志组的一些相关操作函数,如下表所示:

创建一个事件组

xEventGroupCreate() 动态方式创建事件标志组

xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。 要想使用该函数必须
在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1(在FreeRTOS.h 中默认定义为 1) 。


#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
/**
 * @brief 创建一个事件组。
 *
 * 此函数用于创建一个新的事件组,并初始化其成员变量。
 * 事件组可以用来同步多个任务之间的状态。
 *
 * @return 返回新创建的事件组句柄,如果内存分配失败则返回NULL。
 */
EventGroupHandle_t xEventGroupCreate( void )
{
    EventGroup_t * pxEventBits;

    /* 分配事件组内存。
     * 偏离MISRA规则的原因如下:
     * pvPortMalloc() 总是确保返回的内存块符合MCU堆栈的对齐要求。
     * 在这种情况下,pvPortMalloc() 必须返回一个指针,该指针保证满足 EventGroup_t 结构的对齐要求
     * (如果跟踪下去,就是 TickType_t 类型的对齐要求,因为 EventBits_t 本身是 TickType_t 类型)。
     * 因此,只要堆栈对齐要求大于或等于 TickType_t 的对齐要求,转换就是安全的。
     * 在其他情况下,如果架构的自然字大小小于 sizeof(TickType_t),TickType_t 变量将通过两次或多次读取操作访问,
     * 对齐要求只是每次单独读取的对齐要求。*/
    pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) ); /*lint !e9087 !e9079 见上述注释。*/

    if( pxEventBits != NULL )
    {
        pxEventBits->uxEventBits = 0; // 初始化事件位为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(); /*lint !e9063 否则分支仅用于跟踪,如果未定义跟踪宏,则不会生成代码。*/
    }

    return pxEventBits;
}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

函数 xEventGroupCreate,用于创建一个新的事件组。具体功能如下:

  1. 内存分配:使用 pvPortMalloc 分配 EventGroup_t 结构体所需的内存。

  2. 初始化

    • 将事件位 uxEventBits 初始化为 0。

    • 初始化等待事件的任务列表 xTasksWaitingForBits

    • 如果支持静态分配,记录此事件组是动态分配的。

  3. 跟踪

    • 如果内存分配成功,调用 traceEVENT_GROUP_CREATE 记录事件组创建。

    • 如果内存分配失败,调用 traceEVENT_GROUP_CREATE_FAILED 记录创建失败。

事件创建函数,顾名思义, 就是创建一个事件,与其他内核对象一样,都是需要先创
建才能使用的资源, FreeRTOS 给我们提供了一个创建事件的函数xEventGroupCreate(),当创建一个事件时, 系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。

xEventGroupCreateStatic () 静态方式创建事件标志组

#if ( configSUPPORT_STATIC_ALLOCATION == 1 )

    EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer )
    {
        EventGroup_t * pxEventBits;

        /* 必须提供一个 StaticEventGroup_t 对象。 */
        configASSERT( pxEventGroupBuffer );

        #if ( configASSERT_DEFINED == 1 )
            {
                /* 检查声明 StaticEventGroup_t 变量的结构体大小是否等于实际事件组结构体的大小。 */
                volatile size_t xSize = sizeof( StaticEventGroup_t );
                configASSERT( xSize == sizeof( EventGroup_t ) );
            } /*lint !e529 如果定义了 configASSERT(),则 xSize 会被引用。 */
        #endif /* configASSERT_DEFINED */

        /* 用户提供了静态分配的事件组 - 使用它。 */
        pxEventBits = ( EventGroup_t * ) pxEventGroupBuffer; /*lint !e740 !e9087 EventGroup_t 和 StaticEventGroup_t 故意别名化,以隐藏数据,并保证具有相同大小和对齐要求 - 通过 configASSERT() 检查。 */

        if( pxEventBits != NULL )
        {
            pxEventBits->uxEventBits = 0; // 初始化事件位为0
            vListInitialise( &( pxEventBits->xTasksWaitingForBits ) ); // 初始化等待事件的任务列表

            #if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
                {
                    /* 如果同时支持静态和动态分配,则记录此事件组是静态创建的,以防以后删除事件组。 */
                    pxEventBits->ucStaticallyAllocated = pdTRUE;
                }
            #endif /* configSUPPORT_DYNAMIC_ALLOCATION */

            traceEVENT_GROUP_CREATE( pxEventBits ); // 调用跟踪宏记录事件组创建
        }
        else
        {
            /* xEventGroupCreateStatic 应该只在 pxEventGroupBuffer 指向预分配(编译时分配)的 StaticEventGroup_t 变量时被调用。 */
            traceEVENT_GROUP_CREATE_FAILED(); /*lint !e9063 否则分支仅用于跟踪,如果未定义跟踪宏,则不会生成代码。*/
        }

        return pxEventBits;
    }

#endif /* configSUPPORT_STATIC_ALLOCATION */

函数 xEventGroupCreateStatic,用于创建一个静态事件组。具体功能如下:

  1. 参数检查

    • 检查传入的 pxEventGroupBuffer 是否为 NULL,并进行断言。

    • 检查 StaticEventGroup_t 和 EventGroup_t 的大小是否一致,确保结构体的兼容性。

  2. 内存分配

    • 使用传入的 pxEventGroupBuffer 作为事件组的内存。

  3. 初始化

    • 将事件位 uxEventBits 初始化为 0。

    • 初始化等待事件的任务列表 xTasksWaitingForBits

    • 如果支持动态分配,记录此事件组是静态分配的。

  4. 跟踪

    • 如果内存分配成功,调用 traceEVENT_GROUP_CREATE 记录事件组创建。

    • 如果内存分配失败,调用 traceEVENT_GROUP_CREATE_FAILED 记录创建失败。

事件删除函数vEventGroupDelete()

在很多场合,某些事件只用一次的,就好比在事件应用场景说的危险机器的启动,假
如各项指标都达到了,并且机器启动成功了,那这个事件之后可能就没用了,那就可以进行销毁了。想要删除事件怎么办? FreeRTOS 给我们提供了一个删除事件的函数——
vEventGroupDelete(),使用它就能将事件进行删除了。当系统不再使用事件对象时,可以通过删除事件对象控制块来释放系统资源 。

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

    vTaskSuspendAll();
    {
        traceEVENT_GROUP_DELETE( xEventGroup ); // 调用跟踪宏记录事件组删除

        while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 )
        {
            /* 解阻塞任务,返回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,用于删除一个事件组。具体功能如下:

  1. 参数解析

    • 将 EventGroupHandle_t 类型的 xEventGroup 转换为 EventGroup_t 类型的指针 pxEventBits

    • 获取等待事件的任务列表 pxTasksWaitingForBits

  2. 任务调度暂停

    • 调用 vTaskSuspendAll 暂停任务调度,确保在删除事件组时不会有任务干扰。

  3. 跟踪

    • 调用 traceEVENT_GROUP_DELETE 记录事件组删除。

  4. 解阻塞任务

    • 遍历等待事件的任务列表,逐个解阻塞任务,并返回0,因为事件列表正在被删除,因此不可能有任何位被设置。

  5. 内存释放

    • 根据配置选项 configSUPPORT_DYNAMIC_ALLOCATION 和 configSUPPORT_STATIC_ALLOCATION,决定是否释放事件组的内存:

      • 如果只支持动态分配,直接释放内存。

      • 如果同时支持静态和动态分配,检查事件组是否是动态分配的,如果是则释放内存,否则不做任何操作。

  6. 恢复任务调度

    • 调用 xTaskResumeAll 恢复任务调度。

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

事件组置位函数

xEventGroupSetBits()事件组置位函数

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

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

    /* 检查用户是否试图设置内核自身使用的位。 */
    configASSERT( xEventGroup );
    configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

    pxList = &( pxEventBits->xTasksWaitingForBits );
    pxListEnd = listGET_END_MARKER( pxList ); /* lint !e826 !e740 !e9087 使用 mini list 结构作为列表结束标志,以节省 RAM。这是有效的。 */
    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(); // 覆盖测试标记
                }

                /* 在从事件列表中移除任务之前,将实际的事件标志值存储在任务的事件列表项中。设置 eventUNBLOCKED_DUE_TO_BIT_SET 位,以便任务知道它是由于所需的位匹配而解除阻塞,而不是因为超时。 */
                vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
            }

            /* 移动到下一个列表项。注意这里不使用 pxListItem->pxNext,因为列表项可能已被从事件列表中移除并插入到就绪/待读取列表中。 */
            pxListItem = pxNext;
        }

        /* 清除当控制字中的 eventCLEAR_EVENTS_ON_EXIT_BIT 位被设置时匹配的位。 */
        pxEventBits->uxEventBits &= ~uxBitsToClear;
    }
    ( void ) xTaskResumeAll(); // 恢复所有任务

    return pxEventBits->uxEventBits; // 返回当前的事件位
}

函数 xEventGroupSetBits,用于设置事件组中的位。具体功能如下:

  1. 参数解析

    • 将 EventGroupHandle_t 类型的 xEventGroup 转换为 EventGroup_t 类型的指针 pxEventBits

    • 获取等待事件的任务列表 pxList 和列表结束标志 pxListEnd

  2. 2参数检查

    • 检查 xEventGroup 是否为 NULL

    • 检查 uxBitsToSet 是否包含内核自身使用的位。

  3. 任务调度暂停

    • 调用 vTaskSuspendAll 暂停任务调度,确保在设置事件位时不会有任务干扰。

  4. 跟踪

    • 调用 traceEVENT_GROUP_SET_BITS 记录设置事件位。

  5. 设置位

    • 将 uxBitsToSet 设置到事件组的 uxEventBits 中。

  6. 检查任务

    • 遍历等待事件的任务列表,逐个检查任务等待的位是否与新设置的位匹配。

    • 如果匹配,根据控制位决定是否清除这些位,并解除任务的阻塞。

  7. 清除位

    • 清除当控制字中的 eventCLEAR_EVENTS_ON_EXIT_BIT 位被设置时匹配的位。

  8. 恢复任务调度

    • 调用 xTaskResumeAll 恢复任务调度。

  9. 返回结果

    • 返回当前的事件位。

xEventGroupSetBitsFromISR() 事件组中断置位函数

#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 );

        /* 调用 xTimerPendFunctionCallFromISR 函数,将设置事件位的操作挂起到一个定时器回调函数中执行。
           参数包括回调函数指针、事件组句柄、要设置的位以及一个指向更高优先级任务唤醒标志的指针。
           注意:lint 工具警告可以忽略,因为强制转换为 void* 是安全的,回调函数会将其转换回原始类型。 */
        xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken ); /*lint !e9087 Can't avoid cast to void* as a generic callback function not specific to this use case. Callback casts back to original type so safe. */

        return xReturn;
    }

#endif /* if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) ) */

函数 xEventGroupSetBitsFromISR,用于从中断服务例程(ISR)中设置事件组中的位。具体功能如下:

  1. 条件编译

    • 只有在 configUSE_TRACE_FACILITYINCLUDE_xTimerPendFunctionCall 和 configUSE_TIMERS 均为 1 的情况下,才会编译这段代码。

  2. 函数声明

    • BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t * pxHigherPriorityTaskWoken)

      • xEventGroup:事件组句柄。

      • uxBitsToSet:要设置的位。

      • pxHigherPriorityTaskWoken:指向一个标志的指针,用于指示是否有更高优先级的任务被唤醒。

  3. 记录操作

    • traceEVENT_GROUP_SET_BITS_FROM_ISR(xEventGroup, uxBitsToSet):调用跟踪宏记录从 ISR 设置事件位的操作。

  4. 调用定时器回调函数

    • xTimerPendFunctionCallFromISR(vEventGroupSetBitsCallback, (void *) xEventGroup, (uint32_t) uxBitsToSet, pxHigherPriorityTaskWoken)

      • vEventGroupSetBitsCallback:回调函数指针,用于实际设置事件位。

      • (void *) xEventGroup:事件组句柄,强制转换为 void * 类型。

      • (uint32_t) uxBitsToSet:要设置的位,强制转换为 uint32_t 类型。

      • pxHigherPriorityTaskWoken:指向一个标志的指针,用于指示是否有更高优先级的任务被唤醒。

      • 注意:lint 工具警告可以忽略,因为强制转换为 void * 是安全的,回调函数会将其转换回原始类型。

  5. 返回结果

    • 返回 xTimerPendFunctionCallFromISR 的结果,表示操作是否成功。

#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; // 设置消息 ID
        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 */

函数 xTimerPendFunctionCallFromISR,用于从中断服务例程(ISR)中挂起一个函数调用。具体功能如下:

  1. 条件编译

    • 只有在 INCLUDE_xTimerPendFunctionCall 为 1 的情况下,才会编译这段代码。

  2. 函数声明

    • BaseType_t xTimerPendFunctionCallFromISR(PendedFunction_t xFunctionToPend, void * pvParameter1, uint32_t ulParameter2, BaseType_t * pxHigherPriorityTaskWoken)

      • xFunctionToPend:要挂起的回调函数指针。

      • pvParameter1:传递给回调函数的第一个参数。

      • ulParameter2:传递给回调函数的第二个参数。

      • pxHigherPriorityTaskWoken:指向一个标志的指针,用于指示是否有更高优先级的任务被唤醒。

  3. 消息初始化

    • 创建一个 DaemonTaskMessage_t 类型的消息结构体 xMessage

    • 设置消息的 xMessageID 为 tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR,表示这是一个从中断服务例程执行回调函数的命令。

    • 设置消息的回调函数、第一个参数和第二个参数。

  4. 发送消息

    • 调用 xQueueSendFromISR 函数,将消息发送到定时器队列 xTimerQueue

    • xQueueSendFromISR 函数的第三个参数 pxHigherPriorityTaskWoken 用于指示是否有更高优先级的任务被唤醒。

  5. 记录操作

    • 调用 tracePEND_FUNC_CALL_FROM_ISR 宏记录从中断服务例程挂起函数调用的操作。

  6. 返回结果

    • 返回 xQueueSendFromISR 的结果,表示操作是否成功。

xEventGroupSync()设置事件标志位,并等待事件标志位

此函数一般用于多任务同步,其中每个任务都必须等待其他任务达到同步点,然后才能继续执行。

/**
 * @brief 事件组同步函数
 * 
 * 该函数用于在多个任务之间进行同步。它会设置指定的事件位,并等待指定的事件位被设置。
 * 如果在指定的超时时间内没有完成同步,则返回当前的事件位值。
 * 
 * @param xEventGroup 事件组句柄
 * @param uxBitsToSet 要设置的事件位
 * @param uxBitsToWaitFor 要等待的事件位
 * @param xTicksToWait 等待的最长时间(ticks)
 * @return EventBits_t 返回当前的事件位值
 */
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
                             const EventBits_t uxBitsToSet,
                             const EventBits_t uxBitsToWaitFor,
                             TickType_t xTicksToWait )
{
    EventBits_t uxOriginalBitValue, uxReturn;
    EventGroup_t * pxEventBits = xEventGroup;
    BaseType_t xAlreadyYielded;
    BaseType_t xTimeoutOccurred = pdFALSE;

    // 断言检查:确保等待的事件位不包含控制字节,并且等待的事件位不为0
    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();
    {
        // 获取当前的事件位值
        uxOriginalBitValue = pxEventBits->uxEventBits;

        // 设置指定的事件位
        ( void ) xEventGroupSetBits( xEventGroup, uxBitsToSet );

        // 检查设置后的事件位是否满足等待的条件
        if( ( ( uxOriginalBitValue | uxBitsToSet ) & uxBitsToWaitFor ) == uxBitsToWaitFor )
        {
            // 所有需要的事件位都已设置,无需阻塞
            uxReturn = ( uxOriginalBitValue | uxBitsToSet );

            // 清除已经设置的事件位
            pxEventBits->uxEventBits &= ~uxBitsToWaitFor;

            // 设置等待时间为0,表示不需要阻塞
            xTicksToWait = 0;
        }
        else
        {
            // 如果设置了等待时间
            if( xTicksToWait != ( TickType_t ) 0 )
            {
                // 记录事件组同步阻塞
                traceEVENT_GROUP_SYNC_BLOCK( xEventGroup, uxBitsToSet, uxBitsToWaitFor );

                // 将任务添加到等待事件位的列表中,并进入阻塞状态
                vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | eventCLEAR_EVENTS_ON_EXIT_BIT | eventWAIT_FOR_ALL_BITS ), xTicksToWait );

                // 初始化返回值,避免编译器警告
                uxReturn = 0;
            }
            else
            {
                // 需要的事件位未设置,且没有指定等待时间,直接返回当前事件位值
                uxReturn = pxEventBits->uxEventBits;
                xTimeoutOccurred = pdTRUE;
            }
        }
    }
    // 恢复任务调度
    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( ( uxReturn & uxBitsToWaitFor ) == uxBitsToWaitFor )
                {
                    // 清除已经设置的事件位
                    pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
                }
                else
                {
                    // 覆盖测试标记
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            taskEXIT_CRITICAL();

            xTimeoutOccurred = pdTRUE;
        }
        else
        {
            // 任务因事件位被设置而解除阻塞
        }

        // 清除控制字节
        uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
    }

    // 记录事件组同步结束
    traceEVENT_GROUP_SYNC_END( xEventGroup, uxBitsToSet, uxBitsToWaitFor, xTimeoutOccurred );

    // 防止编译器警告
    ( void ) xTimeoutOccurred;

    return uxReturn;
}
  1. 参数检查

    • 使用 configASSERT 宏检查 uxBitsToWaitFor 是否包含控制字节,并且不为0。

    • 如果配置了调度器状态检查或定时器支持,则进一步检查调度器是否挂起。

  2. 暂停任务调度

    • 调用 vTaskSuspendAll() 暂停所有任务调度,确保在修改事件组时不会发生竞态条件。

  3. 获取当前事件位值

    • 从 pxEventBits->uxEventBits 获取当前的事件位值。

  4. 设置指定的事件位

    • 调用 xEventGroupSetBits(xEventGroup, uxBitsToSet) 设置指定的事件位。

  5. 检查设置后的事件位是否满足等待条件

    • 如果 ( ( uxOriginalBitValue | uxBitsToSet ) & uxBitsToWaitFor ) == uxBitsToWaitFor,则所有需要的事件位都已设置,无需阻塞。

    • 清除已经设置的事件位。

    • 设置等待时间为0,表示不需要阻塞。

    • 返回当前的事件位值。

  6. 处理未满足条件的情况

    • 如果设置了等待时间 (xTicksToWait != 0):

      • 记录事件组同步阻塞。

      • 将任务添加到等待事件位的列表中,并进入阻塞状态。

      • 初始化返回值,避免编译器警告。

    • 如果没有设置等待时间 (xTicksToWait == 0):

      • 直接返回当前事件位值,并设置超时标志。

  7. 恢复任务调度

    • 调用 xTaskResumeAll() 恢复任务调度。

    • 检查是否发生了上下文切换,如果没有,手动进行一次上下文切换。

  8. 处理任务阻塞等待

    • 如果设置了等待时间:

      • 任务阻塞等待所需的事件位被设置。

      • 如果任务因事件位被设置而解除阻塞,返回当前事件位值。

      • 如果任务超时,返回当前事件位值,并清除已经设置的事件位。

  9. 清除控制字节

    • 清除返回值中的控制字节。

  10. 记录事件组同步结束

    • 记录事件组同步结束。

  11. 防止编译器警告

    • 使用 (void) xTimeoutOccurred 防止编译器警告。

  12. 返回结果

    • 返回当前的事件位值。


http://www.kler.cn/a/389740.html

相关文章:

  • Python多进程间通讯(包含共享内存方式)
  • ODOO学习笔记(8):模块化架构的优势
  • 第三十六章 Vue之路由重定向/404页面设置/路径模式设置
  • 【Python】爬虫通过验证码
  • 量化交易系统开发-实时行情自动化交易-3.4.2.Okex行情交易数据
  • 回归分析学习
  • 逆向攻防世界CTF系列27-200simple-check-100
  • 【HarmonyNext】显示提示文字的方法
  • 【大数据学习 | HBASE高级】storeFile文件的合并
  • 【智谱开放平台-注册/登录安全分析报告】
  • 数据中心类DataCenter(二)
  • 【Linux 31】网络层协议 - IP
  • 【嵌入式设备】蓝牙鼠标使用教程——遥控器编码值
  • Netty篇(WebSocket)
  • VSCode + linux 远程免密登录
  • WSL 2 中 FastReport 与 FastCube 的设置方法与优化策略
  • Android 单元测试环境配置问题 Execution failed for task ‘:mergeDebugJavaResource‘.
  • J2EE平台
  • CompletableFuture:supplyAsync与runAsync
  • 【Spring】Spring框架中有有哪些常见的设计模式
  • macOS 下的 ARM 裸机嵌入式开发入门- 第二部分:实现第一个裸机应用并且调试
  • 深入提升Python编程能力的全方位指南
  • mac 安装指定的node和npm版本
  • 中间件安全
  • 大数据新视界 -- 大数据大厂之 Impala 性能优化:数据加载策略如何决定分析速度(上)(15/30)
  • 机器学习(基础1)