Task Analysis in FreeRTOS

1.1 Task StatesApplications can contain multiple tasks, but generally, a microcontroller (MCU) in an application has only one core, meaning that at any given time, only one task is actually executed. This means a task can have one of two states, namelyRunning StateandNon-Running State. However, the non-running state can be further divided into several sub-states. The simplest model is shown in the figure below:Task Analysis in FreeRTOSThe FreeRTOS scheduler is the only entity that allows tasks to be switched in and out.The non-running states in FreeRTOS include the following three sub-states:Blocked State:If a task is waiting for an event, it is said to be in a “blocked” state.Tasks can wait in the blocked state for two different types of events:1. Timed (time-related) events: These events can be either a delay expiration or an absolute time point.2. Synchronization events: Originating from other tasks or interrupt events, such as waiting for queue data, semaphores, etc.Tasks can specify a wait timeout when entering the blocked state to wait for synchronization events, allowing them to effectively wait for two types of events simultaneously in the blocked state.Suspended State:A task in the “suspended” state is invisible to the scheduler. The only way for a task to enter the suspended state is by calling the vTaskSuspend() API function; the only way to wake a suspended task is by calling vTaskResume() or vTaskResumeFromISR() API functions. Generally, applications do not use the suspended state.Ready State:When a task is in a non-running state but is neither blocked nor suspended, it is in the ready state. A task in the ready state is able to run but is only prepared to run and is not currently running. The complete task state machine is shown in the figure below:Task Analysis in FreeRTOS1.2 Task PriorityThe value of the compile-time configuration constant configMAX_PRIORITIES set in the FreeRTOSConfig.h file of the application is the maximum number of priorities that can be assigned. The larger this value, the more memory space the kernel requires. Therefore, it is generally recommended to set this constant to the smallest usable value. A smaller priority value represents a lower priority, with 0 indicating the lowest priority. The valid range of priority numbers is 0 to (configMAX_PRIORITIES – 1), and any number of tasks can share the same priority. The parameter uxPriority of the xTaskCreate() API function assigns an initial priority to the created task. This priority can be modified by calling the vTaskPrioritySet() API function after the scheduler starts.1.3 Task CreationThe API functions for creating tasks in FreeRTOS are xTaskCreate() and xTaskCreateStatic().1. Dynamic Task Creation Function: xTaskCreate() is defined as followsTask Analysis in FreeRTOSParameter Explanation:pxTaskCode: The task entry function, which must be an internal infinite loop function in the format void func(void *params).pcName: The task description name (string) used for debugging. The length is defined by configMAX_TASK_NAME_LEN (default 16 bytes).usStackDepth: The task stack size, measured in words, where for a 32-bit system, 1 word = 4 bytes.pvParameters: Parameters passed to the task (of type void*).uxPriority: The initial priority of the task. The range is 0 to (configMAX_PRIORITIES – 1). The larger the value, the higher the priority.pxCreatedTask: The task handle, which can be used for subsequent operations on the task (such as deleting the task, modifying the priority). If not needed, it can be set to NULL.Return Value: There are two possible return values:pdTRUE: Indicates that the task was created successfully.errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: Indicates that FreeRTOS could not allocate enough space to store data and the task stack due to insufficient heap memory.2. Static Task Creation Function: xTaskCreateStatic()Task Analysis in FreeRTOSTo use this function, configSUPPORT_STATIC_ALLOCATION must be defined as 1 in the FreeRTOSConfig.h file.Key Difference: Memory must be pre-allocated by the user.puxStackBuffer: User-provided stack array memory.pxTaskBuffer: User-provided task control block memory (TCB).1.4 Idle Task and Idle Task Hook FunctionThe Idle TaskWhen vTaskStartScheduler() is called to start the scheduler, the scheduler automatically creates an idle task. The idle task is a very short loop. The idle task has the lowest priority (priority 0) to ensure it does not interfere with the operation of higher-priority application tasks.Idle Task Hook FunctionThrough the idle task hook function (also known as a callback, hook), application-related functionality can be added directly to the idle task. The idle task hook function is automatically called once for each loop of the idle task. In FreeRTOSConfig.h, the configUSE_IDLE_HOOK macro must be configured to 1 for the idle hook function to be called. The idle task hook function is mainly used for the following:1. Executing low-priority, background functionality that needs to be processed continuously.2. Testing system processing margins.3. Configuring the processor to low-power mode.The idle task hook function has the following implementation restrictions:1. It must never block or suspend.2. If the application uses the task deletion function vTaskDelete API, the idle hook function must return as quickly as possible.1.5 Scheduling Algorithms1. FreeRTOS supports two scheduling modesConfigured through the macroconfigUSE_PREEMPTION: 1 represents preemptive, 0 represents cooperative.Preemptive SchedulingCooperative Scheduling2. Core Scheduling Algorithm of FreeRTOSFixed-Priority Preemptive SchedulingCore Mechanism:1. Each task is assigned a fixed priority (0 to configMAX_PRIORITIES – 1).2. The scheduler always selects the highest-priority ready task to run.3. High-priority tasks can preempt low-priority tasks (immediate switching).The behavior of preemptive scheduling is shown in the figure below:Task Analysis in FreeRTOS1. Idle TaskThe idle task has the lowest priority, so whenever a higher-priority task is in the ready state, the idle task will be preempted, as shown at times t3, t5, and t9.2. Task3Task 3 is an event-driven task, whose priority is just higher than the idle task. It spends most of its time in the blocked state, switching to the ready state whenever an event occurs. All inter-task communication mechanisms in FreeRTOS (queues, semaphores, etc.) can serve as notification events. As shown in the figure, events occur at some point between t3, t5, and t9 to t12. Events occurring at times t3 and t5 can be processed immediately, while events occurring between t9 and t12 are executed only at t12 due to the execution of higher-priority tasks.3. Task2Task 2 is a periodic task, whose priority is higher than Task 3 and lower than Task 1. Based on the periodic interval, Task 2 is expected to execute at times t1, t6, and t9.4. Task1Task 1 is an event-driven task with the highest priority. Therefore, it can preempt any other task in the system.Round-Robin SchedulingThis feature requires the macro configUSE_TIME_SLICING to be set to 1 in the FreeRTOSConfig.h file. When multiple tasks of the same priority are in the ready state, each task runs for a fixed time slice, executing in a round-robin manner. The length of the time slice is configured through the macro configTICK_RATE_HZ.The execution diagram is as follows:Task Analysis in FreeRTOS3. Cooperative SchedulingThe cooperative scheduler only performs context switching when a running task enters the blocked state or when a running task explicitly calls taskYIELD(). Tasks are never preempted, and tasks with the same priority do not automatically share processor time. Cooperative scheduling may lead to slower system response. Mixed scheduling requires explicit context switching in interrupt service routines, allowing synchronization events to cause preemption, but time events do not. This results in a preemptive system without a time-slice mechanism.1.6 Practical Test CodeThe test code is as follows:

#define OSTASKA_PRIORITY                        8#define OSTASKB_PRIORITY                        6#define OSTASKC_PRIORITY                        7//Task handlesTaskHandle_t    OsTaskAEvent_Handler;TaskHandle_t    OsTaskBEvent_Handler;TaskHandle_t    OsTaskCPeriod_Handler;//Semaphore handlesSemaphoreHandle_t   xEventASemaphore_Handler = NULL;SemaphoreHandle_t   xEventBSemaphore_Handler = NULL;uint32 A_count = 0;uint32 B_count = 0;uint32 C_count = 0;/*************************************************Function Implementation**********************************************/void ATaskEvent_Function(void){    for(;;)    {        /* Wait for A semaphore, blocking for the event*/        if(xSemaphoreTake(xEventASemaphore_Handler, portMAX_DELAY) == pdTRUE)        {            A_count++;        }    }}void BTaskEvent_Function(void){    for(;;)    {        /* Wait for B semaphore, blocking for the event*/        if(xSemaphoreTake(xEventBSemaphore_Handler, portMAX_DELAY) == pdTRUE)        {            B_count++;        }    }}void CTaskPeriod_Function(void){    for(;;)    {        C_count++;        if( C_count % 2 == 0)        {            xSemaphoreGive(xEventASemaphore_Handler);   //Give A semaphore            xSemaphoreGive(xEventBSemaphore_Handler);   //Give B semaphore        }        printf("C Running! Running count A: %d B: %d C: %d\r\n", A_count, B_count, C_count);        vTaskDelay(500);    }}int main(void){    uint32 ostick1OOMs = 0;    uint8 IcuPinlevel = 0;    extern const Mcu_ConfigType Mcu_Config;    Mcu_Init(&Mcu_Config);    Mcu_InitClock(0);    while (MCU_PLL_LOCKED != Mcu_GetPllStatus())    {        /* Busy wait until the System PLL is locked */    }    Mcu_DistributePllClock();    /* enable systick*/    OsIf_Init(NULL_PTR);    /* enable pin mux */    extern const Port_ConfigType Port_Config;    Port_Init(&Port_Config);    /* init uart module*/    Uart_Init(&Uart_Config);    /* enable interrupt of peripheral */    Platform_Init(NULL_PTR);    /*Binary semaphore creation*/    xEventASemaphore_Handler = xSemaphoreCreateBinary();    xEventBSemaphore_Handler = xSemaphoreCreateBinary();    if((xEventASemaphore_Handler == NULL)||(xEventBSemaphore_Handler == NULL))    {        /*Semaphore creation failed*/        while(1){}    }    /**Task creation */    xTaskCreate(ATaskEvent_Function, "A_Event", 1024, NULL, OSTASKA_PRIORITY, &OsTaskAEvent_Handler);    xTaskCreate(BTaskEvent_Function, "B_Event", 1024, NULL, OSTASKB_PRIORITY, &OsTaskBEvent_Handler);    xTaskCreate(CTaskPeriod_Function, "C_Period_500ms", 1024, NULL, OSTASKC_PRIORITY, &OsTaskCPeriod_Handler);    /**Start scheduling */    vTaskStartScheduler();    while(1)    {    }    /* enable clock, disable module reset */}

Code debugging is as follows:Task Analysis in FreeRTOS

Leave a Comment