FreeRTOS 原理 --- 事件组

发布时间 2023-10-03 15:04:36作者: 流水灯

简介

信号量,队列。它们都具有一下特性:

  • 它们允许在阻塞态下等待单个事件的发送。
  • 它们在事件发送时可以取消阻塞的单个任务

事件组是FreeRTOS提供另一种事件传递的方法,它与队列和信号量的不同点:

  • 时间组允许任务在阻塞态下等待一个或多个事件。
  • 事件发生时,事件组将取消阻塞等待同一事件或事件组合的所有任务。

 

实际事件发生与否记录器:

事件组描述符的 uxEventBits 用于指示是否有事件发生。每一个事件占1位。如果某个位为1,则表示该位表示的事件已发生。如果某个位为0,则表示该位表示的事件未发生。

期望事件发生记录器:

任务描述符的成员变量 xEventListItem 作为一个链表项,此链表项的成员变量 xItemValue 记录了此任务等待哪些事件,等待事件的动作,比如是否等待所有事件。

事件组相关的API

xEventGroupCreate()

xEventGroupSetBits()

xEventGroupSetBitsFromISR()

xEventGroupWaitBits()

xEventGroupSync()

代码分析

xEventGroupCreate()

事件组的描述符如下:

typedef struct xEventGroupDefinition
{
    EventBits_t uxEventBits;
    List_t xTasksWaitingForBits;        /*< List of tasks waiting for a bit to be set. */

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

    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the event group is statically allocated to ensure no attempt is made to free the memory. */
    #endif
} EventGroup_t;
 

 主要就是初始化 uxEventBits 为0,表示还没事件发生;初始化 xTaskWaitingForBits 链表,此链表用于记录等待事件的任务的xEventListItem,此值记录任务等待哪几个事件,等待任一事件还是所有事件

xEventGroupSetBits()

首先宏 eventEVENT_BITS_CONTROL_BYTES 规定了一个事件组描述符支持的事件数,也就是 uxEventBits 哪几位用于表示事件发生与否,比如宏为0xFF000000表示uxEventBits的最低24位可用于表示事件,事件组描述符的高8位不用

每个等待事件任务的xEventListItem也有一个32bit的值,低24位表示等待哪些事件,高8位表示等待事件的动作,比如是否等待所有事件、唤醒任务后是否清事件组描述符的事件

    #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

 

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;

    /* Check the user is not attempting to set the bits used by the kernel
    itself. */
    configASSERT( xEventGroup );
    configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

    pxList = &( pxEventBits->xTasksWaitingForBits );
    pxListEnd = listGET_END_MARKER( pxList ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
    vTaskSuspendAll();
    {
        traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );

        pxListItem = listGET_HEAD_ENTRY( pxList );

        /* Set the bits. */
        pxEventBits->uxEventBits |= uxBitsToSet; // 更新此事件描述符,一共发生哪些事件

        /* See if the new bit value should unblock any tasks. */
        while( pxListItem != pxListEnd ) // 遍历事件描述符记录的等待任务,看是否符合唤醒条件唤醒任务
        {
            pxNext = listGET_NEXT( pxListItem );
            uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
            xMatchFound = pdFALSE;

            /* Split the bits waited for from the control bits. */
            uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES; // 等待任务的事件动作,比如等待所有事件,还是否一事件
            uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;

            if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 )
            {
                /* Just looking for single bit being set. */
                if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 ) // 等待某一事件,并且事件发生
                {
                    xMatchFound = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor ) // 等待所有事件,并且事件发生
            {
                /* All bits are set. */
                xMatchFound = pdTRUE;
            }
            else
            {
                /* Need all bits to be set, but not all the bits were set. */
            }

            if( xMatchFound != pdFALSE )
            {
                /* The bits match.  Should the bits be cleared on exit? */
                if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
                {
                    uxBitsToClear |= uxBitsWaitedFor;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                /* Store the actual event flag value in the task's event list
                item before removing the task from the event list.  The
                eventUNBLOCKED_DUE_TO_BIT_SET bit is set so the task knows
                that is was unblocked due to its required bits matching, rather
                than because it timed out. */
                vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET ); // 唤醒等待事件的任务,把发生的事件记录到任务的 xEventListItem.xItemValue
            }

            /* Move onto the next list item.  Note pxListItem->pxNext is not
            used here as the list item may have been removed from the event list
            and inserted into the ready/pending reading list. */
            pxListItem = pxNext;
        }

        /* Clear any bits that matched when the eventCLEAR_EVENTS_ON_EXIT_BIT
        bit was set in the control word. */
        pxEventBits->uxEventBits &= ~uxBitsToClear; // 清楚事件组描述符发生的事件
    }
    ( void ) xTaskResumeAll();

    return pxEventBits->uxEventBits;
}

 

xEventGroupSetBitsFromISR()

#define xEventGroupSetBitsFromISR( xEventGroup, uxBitsToSet, pxHigherPriorityTaskWoken ) xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken )

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

        /* Complete the message with the function parameters and post it to the
        daemon task. */
        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;
    }

 

设置事件组描述符的事件,唤醒的等待事件任务可能不止一个,所以此函数指示向定时器任务相关的队列发送一条消息,让定时器任务去执行唤醒任务,从而保证中断函数不会允许太长事件。
既然中断不会直接设置事件组描述符,关于事件组的API都不需要关闭中断,只需要关闭调度器。

xEventGroupWaitBits()

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;

    /* Check the user is not attempting to wait on the bits used by the kernel
    itself, and that at least one bit is being requested. */
    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;

        /* Check to see if the wait condition is already met or not. */
        xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );

        if( xWaitConditionMet != pdFALSE ) // 当前已经发生的事件就是任务准备等待的事件
        {
            /* The wait condition has already been met so there is no need to
            block. */
            uxReturn = uxCurrentEventBits;
            xTicksToWait = ( TickType_t ) 0;

            /* Clear the wait bits if requested to do so. */
            if( xClearOnExit != pdFALSE )
            {
                pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else if( xTicksToWait == ( TickType_t ) 0 ) // 等待的事件没有发生,不阻塞,直接返回
        {
            /* The wait condition has not been met, but no block time was
            specified, so just return the current value. */
            uxReturn = uxCurrentEventBits;
            xTimeoutOccurred = pdTRUE;
        }
        else // 阻塞
        {
            /* The task is going to block to wait for its required bits to be
            set.  uxControlBits are used to remember the specified behaviour of
            this call to xEventGroupWaitBits() - for use when the event bits
            unblock the task. */
            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();
            }

            /* Store the bits that the calling task is waiting for in the
            task's event list item so the kernel knows when a match is
            found.  Then enter the blocked state. */
            vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait ); // 把期望的事件所在的链表项插入事件描述符的链表,并把当前任务放入阻塞链表中

            /* This is obsolete as it will get set after the task unblocks, but
            some compilers mistakenly generate a warning about the variable
            being returned without being set if it is not done. */
            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();
        }

        /* The task blocked to wait for its required bits to be set - at this
        point either the required bits were set or the block time expired.  If
        the required bits were set they will have been stored in the task's
        event list item, and they should now be retrieved then cleared. */
        uxReturn = uxTaskResetEventItemValue(); // 运行到这里,表示等待的事件发生了,或等待超时

        if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 ) // 等待超时
        {
            taskENTER_CRITICAL();
            {
                /* The task timed out, just return the current event bit value. */
                uxReturn = pxEventBits->uxEventBits;

                /* It is possible that the event bits were updated between this
                task leaving the Blocked state and running again. */
                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. */
        }

        /* The task blocked so control bits may have been set. */
        uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
    }
    traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );

    /* Prevent compiler warnings when trace macros are not used. */
    ( void ) xTimeoutOccurred;

    return uxReturn;
}

 

xEventGroupSync()

此函数允许几个任务等待相同的事件,事件发生,唤醒所有任务