[embedded] transplantation of FreeRTOS, task running status and optimization and improvement of source code

Keywords: Embedded system Single-Chip Microcomputer FreeRTOS

preface

FreeRTOS is very popular in the embedded field because of its simplicity, compactness and complete functions. As shown in the figure below, FreeRTOS is the real-time operating system with the largest market share in the global embedded field except Linux. uCOS, RTX, ThreadX, etc. are far behind. In addition, it has been acquired by Amazon in recent years, and the application of FreeRTOS should be further expanded. There are many comparisons of various real-time operating systems on the Internet. I have also used several operating systems, including FreeRTOS, uCOS, etc., but FreeRTOS gives me the deepest impression that it is easy to transplant, can transplant quickly for different chips, and has changed some functions.

This article includes the following contents (this article is not suitable for beginners. The default readers have a good foundation for engineering construction and good ability to read and write embedded code):
1. Briefly describe the key points in the process of FreeRTOS transplantation;
2. Explain how to configure the task operation status monitoring required in the development stage;
3 according to the actual software stability, the source code of FreeRTOS is slightly modified, so that the task running state can be expanded freely according to their own needs. In this paper, the attribute [task switching times] is added to the source code. It is added automatically when task switching occurs, and can be obtained through the task state acquisition function of FreeRTOS.

1, Considerations for FreeRTOS migration

There are many migration tutorials online, and there are many engineering demos based on KEIL and IAR. Therefore, this paper will not explain the migration process of FreeRTOS in detail, but only the general steps and main precautions.
1. From [official website] After downloading the source code, add the c file in the source directory to the project, copy the include directory to the project directory, and then find the interface file suitable for your MCU kernel in the source/portable directory and add it to the project.
2. Find a folder suitable for your MCU in the Demo folder, and copy the FreeRTOSConfig.h file to the project directory;
3. The operating system needs to run the scheduling function regularly in the tick timer interrupt, rely on the SVC exception handling function to obtain the current task control block, and rely on the Pending interrupt for context switching. The functions executed during the above interrupts and exceptions are given in the file port.c or portasm.s in the source/portable directory. They are generally assembly functions. FreeRTOS is named as follows:

xPortSysTickHandler
vPortSVCHandler
xPortPendSVHandler

In the interrupt vector table of the startup file, the above three interrupts and exceptions are named as follows:

SysTick_Handler  
SVC_Handler 
PendSV_Handler 

Therefore, when the above interrupts and exceptions occur, in order to successfully call the scheduling and switching functions written by FreeRTOS, the following macro definitions need to be made in FreeRTOSConfig.h:

#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler

In this way, the vPortSVCHandler and xPortPendSVHandler functions in the FreeRTOS source code are replaced with the corresponding interrupt service functions in the interrupt vector table, so that the scheduling and task switching functions written by FreeRTOS can be entered when an interrupt occurs.
Here, some people will ask, don't you have an xPortSysTickHandler? Why isn't it replaced. The reason is that the current embedded system, whether bare metal or running operating system, generally enables the tick timer interrupt, and performs self addition operation on a variable in the interrupt, so that this variable becomes the timestamp of the whole system. Therefore, as soon as the system is initialized, I configure the tick timer interrupt with a cycle of 1ms, and add the time stamp in the interrupt function. In this way, I only need to put the xPortSysTickHandler function into the interrupt processing function of the tick timer, as shown below:

volatile static uint32_t uwTick = 0;

void uwTickInc(void)
{
	uwTick++;
}

uint32_t sys_getTick(void)
{
	return uwTick;
}

void SysTick_Handler(void)
{
	uwTickInc();
	xPortSysTickHandler();
}

In this way, with the tick timer driven scheduling function and the Pending interrupt function for context switching, FreeRTOS can run, and the specific task creation and system startup will not be repeated.

2, Acquisition of task running status

The development phase needs to find and solve problems in long-term operation and testing, so it is necessary to obtain the running status of each task. Fortunately, FreeRTOS also provides this function. Generally speaking, the system monitoring is best placed in the tasks with the highest level, so as to ensure that all tasks can be monitored. Enabling task running status acquisition in FreeRTOS mainly includes the following steps:
1. Add macro definition in FreeRTOSConfig.h:

#define configUSE_TRACE_FACILITY	1
#define configGENERATE_RUN_TIME_STATS 1

In this way, the total running time of each task and the total running time of the system will be counted in the idle tasks. When it comes to time statistics, the timer is essential and will be configured in the next step.
2. Two macro definitions given by FreeRTOS need to be configured:

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE()

The first is the timer configuration function of running time, and the second is the timer timestamp acquisition function. Since we have configured a 1ms tick timer and created a time stamp variable that is always self added, the first macro definition here can be directly null, and the second macro definition is configured as sys in the above_ Gettick():

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE()      sys_getTick( )

3. calling uxTaskGetSystemState in monitoring tasks to get the status of each task:

void watchTask(void *arg)
{
	TaskStatus_t task_state[N];
	uint32_t total_time;

	while(1)
	{
		vTaskDelay(pdMS_TO_TICKS(arg));
		uxTaskGetSystemState(task_state,N,&total_time);
	}

Among them, the incoming parameter void *arg of the function is the idle time of the task, which is used to release the cpu occupation right for other low priority tasks; N is the total number of tasks created for you (including an idle task). After calling uxTaskGetSystemState, pull the status of all tasks to task_state array, and assign the total running time of the system to total_time, that is, the timestamp in the tick timer. Where, TaskStatus_t structure elements are as follows, which can display task handle, name, sequence number, current status, etc

/* Used with the uxTaskGetSystemState() function to return the state of each task
 * in the system. */
typedef struct xTASK_STATUS
{
    TaskHandle_t xHandle;                            /* The handle of the task to which the rest of the information in the structure relates. */
    const char * pcTaskName;                         /* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    UBaseType_t xTaskNumber;                         /* A number unique to the task. */
    eTaskState eCurrentState;                        /* The state in which the task existed when the structure was populated. */
    UBaseType_t uxCurrentPriority;                   /* The priority at which the task was running (may be inherited) when the structure was populated. */
    UBaseType_t uxBasePriority;                      /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
    uint32_t ulRunTimeCounter;                       /* The total run time allocated to the task so far, as defined by the run time stats clock.  See https://www.FreeRTOS.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
    StackType_t * pxStackBase;                       /* Points to the lowest address of the task's stack area. */
    configSTACK_DEPTH_TYPE usStackHighWaterMark;     /* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */
} TaskStatus_t;

However, it is a little bad that the TCB control block in uCOS also has the element [task switching times], but there is no element in the TCB control block of FreeRTOS. This element often reveals whether there are exceptions in system switching, which is an important information in the development stage. Some people will say that there is not a ulluntimecounter element, which can reflect the total running time of the task. Does this seem to replace the switching times? In fact, it is not, because the statistics of task running time depend on #define portconfiguration_ TIMER_ FOR_ RUN_ TIME_ The minimum step size of the timer defined by stats (). Through reading the statistical source code of ulluntimecounter, it is found that if the timer step size time is too long, such as 10ms, it is likely that the task has completed one cycle and still cannot be counted into ulluntimecounter, because the time taken from task execution to task release CPU is less than 10ms when the task is switched, It cannot be accumulated to ulluntimecounter. The statistical source code of ulluntimecounter is as follows:

#if ( configGENERATE_RUN_TIME_STATS == 1 )
            {
                #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
                    portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
                #else
                    ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
                #endif

                /* Add the amount of time the task has been running to the
                 * accumulated time so far.  The time the task started running was
                 * stored in ulTaskSwitchedInTime.  Note that there is no overflow
                 * protection here so count values are only valid until the timer
                 * overflows.  The guard against negative values is to protect
                 * against suspect run time stat counter implementations - which
                 * are provided by the application, not the kernel. */
                if( ulTotalRunTime > ulTaskSwitchedInTime )
                {
                    pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                ulTaskSwitchedInTime = ulTotalRunTime;
            }
        #endif /* configGENERATE_RUN_TIME_STATS */

3, Source code optimization of task running state

In order to solve the problem that the [task switching times] element is not included in the operation status information, the source code needs to be modified to support this function, so that we can intuitively view how many times a task has been scheduled. The specific steps are [add this element to the task control block] - [initialize this element when creating a task] - [add this element to the task status structure] - [add 1 to this element during task switching] - [return this element when obtaining task status]. Follow the steps below to modify the source code:
1. Add uint32 to the typedef struct tsktaskcontrollblock structure in tasks.c_ T switchtime element, as shown in the following code:

typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
    #endif
	uint32_t switchTime;
    ListItem_t xStateListItem;                  /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t xEventListItem;                  /*< Used to reference a task from an event list. */
    UBaseType_t uxPriority;                     /*< The priority of the task.  0 is the lowest priority. */
    StackType_t * pxStack;                      /*< Points to the start of the stack. */
    char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTCBNumber;  /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
        UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        UBaseType_t uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )

        /* Allocate a Newlib reent structure that is specific to this task.
         * Note Newlib support has been included by popular demand, but is not
         * used by the FreeRTOS maintainers themselves.  FreeRTOS is not
         * responsible for resulting newlib operation.  User must be familiar with
         * newlib and must provide system-wide implementations of the necessary
         * stubs. Be warned that (at the time of writing) the current newlib design
         * implements a system-wide malloc() that must be provided with locks.
         *
         * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
         * for additional information. */
        struct  _reent xNewLib_reent;
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif

    /* See the comments in FreeRTOS.h with the definition of
     * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;                     /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

    #if ( configUSE_POSIX_ERRNO == 1 )
        int iTaskErrno;
    #endif
} tskTCB;

2. Add the initialization of switchTime to the prvInitialiseNewTask task initialization function in tasks.c. some codes are as follows:

/* This is used as an array index so must ensure it's not too large.  First
     * remove the privilege bit if one is present. */
    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;
    pxNewTCB->switchTime = 0;
    #if ( configUSE_MUTEXES == 1 )
        {
            pxNewTCB->uxBasePriority = uxPriority;
            pxNewTCB->uxMutexesHeld = 0;
        }
    #endif /* configUSE_MUTEXES */

3. In taskstatus of task.h_ Uint32 is added to the T structure_ T switchtime, the code is as follows. Because the uxTaskGetSystemState function needs to be called to obtain status information, its parameter is TaskStatus_t pointer.

typedef struct xTASK_STATUS
{
    TaskHandle_t xHandle;                            /* The handle of the task to which the rest of the information in the structure relates. */
    const char * pcTaskName;                         /* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    UBaseType_t xTaskNumber;                         /* A number unique to the task. */
    eTaskState eCurrentState;                        /* The state in which the task existed when the structure was populated. */
    UBaseType_t uxCurrentPriority;                   /* The priority at which the task was running (may be inherited) when the structure was populated. */
    UBaseType_t uxBasePriority;                      /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
    uint32_t ulRunTimeCounter;                       /* The total run time allocated to the task so far, as defined by the run time stats clock.  See https://www.FreeRTOS.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
    StackType_t * pxStackBase;                       /* Points to the lowest address of the task's stack area. */
    configSTACK_DEPTH_TYPE usStackHighWaterMark;     /* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */
	uint32_t switchTime;
} TaskStatus_t;

4. Perform + + operation on switchTime in the function of task context switching. Specifically: add pxcurrenttcb - > switchTime + + to the vTaskSwitchContext function of tasks.c, and the code is as follows:

void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
        /* The scheduler is currently suspended - do not allow a context
         * switch. */
        xYieldPending = pdTRUE;
    }
    else
    {
        xYieldPending = pdFALSE;
        pxCurrentTCB->switchTime++;
        traceTASK_SWITCHED_OUT();

        #if ( configGENERATE_RUN_TIME_STATS == 1 )
        .
        .
        .
        .
 }

5. By reading the source code, we know that the uxTaskGetSystemState function really obtains the task running information is the vTaskGetInfo function called. Therefore, pxtaskstatus - > switchTime = pxtcb - > switchTime is added to the vtask getinfo function of tasks.c, and the code is as follows. In this way, when the uxTaskGetSystemState function is called, the switchTime element in TCB is assigned to taskstatus_ The T pointer is missing.

#if ( configUSE_TRACE_FACILITY == 1 )

    void vTaskGetInfo( TaskHandle_t xTask,
                       TaskStatus_t * pxTaskStatus,
                       BaseType_t xGetFreeStackSpace,
                       eTaskState eState )
    {
        TCB_t * pxTCB;

        /* xTask is NULL then get the state of the calling task. */
        pxTCB = prvGetTCBFromHandle( xTask );

        pxTaskStatus->xHandle = ( TaskHandle_t ) pxTCB;
        pxTaskStatus->pcTaskName = ( const char * ) &( pxTCB->pcTaskName[ 0 ] );
        pxTaskStatus->uxCurrentPriority = pxTCB->uxPriority;
        pxTaskStatus->pxStackBase = pxTCB->pxStack;
        pxTaskStatus->xTaskNumber = pxTCB->uxTCBNumber;
        pxTaskStatus->switchTime = pxTCB->switchTime;
        .
        .
        .
}

After the above optimization, uxTaskGetSystemState can be directly called in the monitoring task to obtain the switching times of each task, which adds a strong regulatory judgment condition for the monitoring task, that is, if the switching times of a task remain unchanged within a certain time, what should be done in response.

Posted by Deemo on Mon, 06 Dec 2021 17:30:34 -0800