Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

1. Basic Concepts

In the context of the entire microcontroller program, we refer to it as an application.

When using FreeRTOS, we can create multiple tasks within the application; some documents refer to tasks as threads.

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

For example, in daily life, a mother may need to do two things at the same time:

Feeding: This is one task.

Replying to messages: This is another task.

This introduces many concepts:

Task State:

Currently feeding, it is in the running state; the other task of “replying to messages” is in the “not running” state.

The “not running” state can be further divided:

Ready: Ready to run at any time.

Blocked: Blocked, waiting for a colleague to reply.

Suspended: Suspended, ignoring the colleague’s excessive chatter.

Priority

Balancing work and life: Feeding and replying to messages have the same priority, alternating between them.

Stealing a moment: There are idle tasks, taking a break.

The kitchen is on fire, nothing else matters, extinguish the fire first: higher priority.

Stack

When feeding the child, I need to remember that I just fed rice, now I need to feed vegetables.

When replying to messages, I need to remember what I was just talking about.

Different tasks have different details.

For humans, these are remembered in the brain.

For programs, they are remembered in the stack.

Each task has its own stack.

Event-Driven

The child eats too slowly: take a break, wait until he swallows, and then feed the next bite.

Co-operative Scheduling

You are replying to a colleague’s message.

The colleague says: Alright, you go feed the child a bite first, then you can leave.

The colleague won’t let you go, even if the child is crying, you cannot leave.

Finally, you can feed the child.

The child says: Okay, mom, you go handle some work, then you can leave.

The child won’t let you go, even if the colleague keeps messaging you cannot leave.

2. Task Creation and Deletion

2.1 What is a Task

In FreeRTOS, a task is a function, with the following prototype:

void ATaskFunction( void *pvParameters );

It is important to note:

This function cannot return.

The same function can be used to create multiple tasks; in other words, multiple tasks can run the same function.

Within the function, it is advisable to use local variables:

Each task has its own stack.

Each task runs this function.

Local variables of Task A are stored in Task A’s stack, and local variables of Task B are stored in Task B’s stack.

Local variables of different tasks have their own copies.

If the function uses global or static variables,

there is only one copy: multiple tasks use the same copy.

Conflicts must be avoided.

Here is an example:

void ATaskFunction( void *pvParameters ){    /* For different tasks, local variables are stored in the task's stack, with their own copies */    int32_t lVariableExample = 0;        /* Task functions are usually implemented as an infinite loop */    for( ;; )    {        /* Task code */    }        /* If the program exits the loop, it must use vTaskDelete to delete itself    * NULL indicates that it is deleting itself    */    vTaskDelete( NULL );        /* The program will not reach here, if it does, an error has occurred */}

2.2 Creating Tasks

The function used to create tasks is as follows:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // Function pointer, task function                        const char * const pcName, // Task name                        const configSTACK_DEPTH_TYPE usStackDepth, // Stack size, in words, 10 means 40 bytes                        void * const pvParameters, // Parameters passed to the task function                        UBaseType_t uxPriority,    // Priority                        TaskHandle_t * const pxCreatedTask ); // Task handle, used to operate on this task later

Parameter descriptions:

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

2.3 Example 1: Creating Tasks

Using two functions to create two tasks.

Code for Task 1:

void vTask1( void *pvParameters ){    const char *pcTaskName = "T1 run\r\n";    volatile uint32_t ul; /* volatile to prevent optimization */        /* The main body of the task function is generally an infinite loop */    for( ;; )    {        /* Print information for Task 1 */        printf( pcTaskName );                /* Delay for a while (quite simple and crude) */        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )        {        }    } }

Code for Task 2:

void vTask2( void *pvParameters ){    const char *pcTaskName = "T2 run\r\n";    volatile uint32_t ul; /* volatile to prevent optimization */    /* The main body of the task function is generally an infinite loop */    for( ;; )    {        /* Print information for Task 2 */        printf( pcTaskName );        /* Delay for a while (quite simple and crude) */        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )        {        }    }}

Main function:

int main( void ){    prvSetupHardware();    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);    xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);    /* Start the scheduler */    vTaskStartScheduler();    /* If the program reaches here, it indicates an error, usually due to insufficient memory */    return 0;}

Running results are as follows:

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

Note:

Task 2 runs first!

To analyze the code of xTaskCreate to understand the reason: higher priority tasks or tasks created later run first.

Task running diagram:

At t1: Task 2 enters the running state and runs until t2.

At t2: Task 1 enters the running state and runs until t3; at t3, Task 2 re-enters the running state.

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

2.4 Example 2: Using Task Parameters

We mentioned that multiple tasks can use the same function; how do we reflect their differences?

Different stacks.

When creating tasks, different parameters can be passed in.

We create two tasks using the same function, as shown in the code below:

void vTaskFunction( void *pvParameters ){    const char *pcTaskText = pvParameters;    volatile uint32_t ul; /* volatile to prevent optimization */        /* The main body of the task function is generally an infinite loop */    for( ;; )    {        /* Print task information */        printf(pcTaskText);           /* Delay for a while (quite simple and crude) */        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )        {        }    }}

In the above code, pcTaskText comes from the parameter pvParameters, where does pvParameters come from? It is passed in when creating the task.

Code is as follows:

When using xTaskCreate to create two tasks, the fourth parameter is pvParameters.

Different tasks have different pvParameters.

static const char *pcTextForTask1 = "T1 run\r\n";static const char *pcTextForTask2 = "T2 run\r\n";int main( void ){    prvSetupHardware();    xTaskCreate(vTaskFunction, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);    xTaskCreate(vTaskFunction, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);        /* Start the scheduler */    vTaskStartScheduler();        /* If the program reaches here, it indicates an error, usually due to insufficient memory */        return 0;}

2.5 Deleting Tasks

The function used to delete tasks is as follows:

void vTaskDelete( TaskHandle_t xTaskToDelete );

Parameter description:

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

How to delete a task? Here’s a bad example:

Suicide: vTaskDelete(NULL)

Murdered: Another task executes vTaskDelete(pvTaskCode), where pvTaskCode is its own handle.

Murder: Execute vTaskDelete(pvTaskCode), where pvTaskCode is another task’s handle.

2.6 Example 3: Deleting Tasks

This section of code will involve knowledge of priorities; you can just look at the usage of vTaskDelete and ignore the explanation of priorities.

We need to do the following:

Create Task 1: In the main loop of Task 1, create Task 2, then sleep for a while.

Task 2: Print a message, then delete itself.

Code for Task 1 is as follows:

void vTask1( void *pvParameters ){    const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );          BaseType_t ret;        /* The main body of the task function is generally an infinite loop */    for( ;; )    {        /* Print task information */        printf("Task1 is running\r\n");                ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &amp;xTask2Handle );        if (ret != pdPASS)            printf("Create Task2 Failed\r\n");                    // If not sleeping, the Idle task cannot execute        // The Idle task will clean up the memory used by Task 2        // If not sleeping, the Idle task cannot execute, and memory will eventually run out        vTaskDelay( xDelay100ms );    }    return 0;}

Code for Task 2 is as follows:

void vTask2( void *pvParameters ){       /* Print task information */    printf("Task2 is running and about to delete itself\r\n");        // You can directly pass NULL as a parameter, this is just to demonstrate function usage    vTaskDelete(xTask2Handle);}

Main function code is as follows:

int main( void ){    prvSetupHardware();    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);    /* Start the scheduler */    vTaskStartScheduler();    /* If the program reaches here, it indicates an error, usually due to insufficient memory */    return 0;}

Running results are as follows:

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

Task running diagram:

In the main function, Task 1 is created with a priority of 1. When Task 1 runs, it creates Task 2, which has a priority of 2.

Task 2 has the highest priority and executes immediately.

After Task 2 prints a message, it deletes itself.

After Task 2 is deleted, Task 1 has the highest priority and continues running, calling vTaskDelay() to enter the Block state.

During Task 1’s Block period, the Idle task executes: it releases the memory used by Task 2 (TCB, stack).

After the time is up, Task 1 becomes the highest priority task and continues executing.

This cycle continues.

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

In Task 1’s function, if vTaskDelay is not called, the Idle task will not have a chance to execute, and it will not be able to release the memory allocated for creating Task 2.

As a result, Task 1 keeps creating tasks, continuously consuming memory, and eventually runs out of memory and can no longer create new tasks.

The phenomenon is as follows:

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

In Task 1’s code, it is important to note the return value of xTaskCreate.

Many manuals state that it returns pdFAIL when it fails, which is 0.

In fact, when it fails, it returns errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY, which is -1.

To avoid confusion, we compare the return value with pdPASS, which is 1.

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

Comprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to DeletionComprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to DeletionComprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to DeletionComprehensive Analysis of FreeRTOS Task Management: Mastering Core Techniques from Creation to Deletion

Leave a Comment