In-Depth Guide to FreeRTOS Task Management: Practical Tutorial on Creating and Deleting Tasks (Dynamic Method)

In-Depth Guide to FreeRTOS Task Management: Practical Tutorial on Creating and Deleting Tasks (Dynamic Method)

📖 Introduction

Hello everyone! Today, I bring you a practical tutorial on FreeRTOS, mainly explaining how to use dynamic methods to create and delete tasks. This article is suitable for those who are new to RTOS, and I will try to explain each concept in simple and understandable language.

What is Dynamic Task Creation?

In simple terms, dynamic task creation means requesting a block of memory from the system’s “memory pool” (heap memory) to create a task while the program is running. It’s like ordering food at a restaurant, where the kitchen prepares it fresh as you order.

💡 Tip: In contrast, static task creation requires us to define the memory space needed for the task in advance, just like preparing ingredients beforehand.

Key Concept Explanations

🔹 Task Stack

Each task has its own “workbench” to store local variables, function call information, etc. The size of the stack is measured in words, and in a 32-bit MCU, 1 word = 4 bytes.

For example, defining <span>TASK_STACK_SIZE 128</span> actually occupies 128 × 4 = 512 bytes of memory.

🔹 Task Priority

The higher the number, the higher the priority. A higher priority task will execute first. For example, task3 with priority 4 will run before task1 with priority 2.

🔹 Task Handle

This can be understood as the “ID number” of the task, through which we can perform operations on the task (such as deleting, suspending, etc.).

🔹 Critical Section

A protected code segment that will not be interrupted while executing. It’s like locking the door when going to the bathroom to avoid being disturbed.

🎯 Experiment Objectives

In this experiment, we aim to achieve the following functionalities:

  1. 1. start_task (Start Task): Responsible for creating the other three tasks and automatically deleting itself after creation.
  2. 2. task1: Makes LED1 blink every 500ms.
  3. 3. task2: Makes LED2 blink every 500ms.
  4. 4. task3: Detects button KEY1, and deletes task1 when pressed.

🛠️ Implementation Steps

Step 1: Configure Enable Macros

Open <span>FreeRTOSConfig.h</span> file and ensure the following macros are enabled (dynamic methods are enabled by default):

#define configSUPPORT_DYNAMIC_ALLOCATION  1  // Enable dynamic memory allocation

Step 2: Create Files

Create <span>freertos_demo.c</span> and <span>freertos_demo.h</span> files to store FreeRTOS task-related code.

File Structure

Step 3: Write Task Code

📄 freertos_demo.h

#ifndef __FREERTOS_DEMO_H__
#define __FREERTOS_DEMO_H__

// Entry function to start FreeRTOS
void freertos_start(void);

#endif

📄 freertos_demo.c

#include "freertos_demo.h"
/* FreeRTOS core header file */
#include "FreeRTOS.h"
#include "task.h"
/* Hardware driver header files */
#include "led_app.h"
#include "uart_app.h"
#include "key_app.h"

/* ========== Start Task Configuration ========== */
#define START_TASK_STACK_SIZE    1024        // Stack size: 1024 words = 4096 bytes
#define START_TASK_PRIORITY      1           // Priority: 1 (lowest)
TaskHandle_t start_task_handle;              // Task handle
void start_task(void *pvParameters);         // Task function declaration

/* ========== Task 1 Configuration ========== */
#define TASK1_STACK_SIZE         1024
#define TASK1_PRIORITY           2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* ========== Task 2 Configuration ========== */
#define TASK2_STACK_SIZE         1024
#define TASK2_PRIORITY           3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/* ========== Task 3 Configuration ========== */
#define TASK3_STACK_SIZE         1024
#define TASK3_PRIORITY           4           // Highest priority
TaskHandle_t task3_handle;
void task3(void *pvParameters);

/**
 * @brief Start FreeRTOS system
 * @note This function is called in the main function
 */
void freertos_start(void)
{
    /* 1. Create start task */
    xTaskCreate(
        (TaskFunction_t)start_task,                // Task function pointer
        (char*)"start_task",                       // Task name (for debugging)
        (configSTACK_DEPTH_TYPE)START_TASK_STACK_SIZE,  // Stack size
        (void*)NULL,                               // Parameters passed to the task (not needed here)
        (UBaseType_t)START_TASK_PRIORITY,          // Task priority
        (TaskHandle_t*)&amp;start_task_handle          // Task handle (for subsequent operations)
    );
  
    /* 2. Start task scheduler (the code will not return after the scheduler starts) */
    vTaskStartScheduler();
}

/**
 * @brief Start task: specifically for creating other tasks
 * @param pvParameters Task parameters (not used)
 * @note After creating other tasks, this task will complete its mission and delete itself
 */
void start_task(void *pvParameters)
{
    /* Enter critical section: prevent task creation process from being interrupted */
    taskENTER_CRITICAL();
  
    /* Create task 1 */
    xTaskCreate(
        (TaskFunction_t)task1,
        (char *)"task1",
        (configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,
        (void *)NULL,
        (UBaseType_t)TASK1_PRIORITY,
        (TaskHandle_t *)&amp;task1_handle
    );

    /* Create task 2 */
    xTaskCreate(
        (TaskFunction_t)task2,
        (char *)"task2",
        (configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,
        (void *)NULL,
        (UBaseType_t)TASK2_PRIORITY,
        (TaskHandle_t *)&amp;task2_handle
    );

    /* Create task 3 */
    xTaskCreate(
        (TaskFunction_t)task3,
        (char *)"task3",
        (configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,
        (void *)NULL,
        (UBaseType_t)TASK3_PRIORITY,
        (TaskHandle_t *)&amp;task3_handle
    );

    /* Delete the start task itself (passing NULL means delete the current task) */
    vTaskDelete(NULL);
  
    /* Exit critical section */
    taskEXIT_CRITICAL();
}

/**
 * @brief Task 1: Control LED1 to blink every 500ms
 * @param pvParameters Task parameters
 */
void task1(void *pvParameters)
{
    while (1)  // Tasks must be in an infinite loop
    {
        Uart_Printf(DEBUG_UART, "task1 is running...\r\n");
        led_buf[0] ^= 1;        // Toggle LED1 state (0 to 1, 1 to 0)
        Led_Task();             // Update LED display
        vTaskDelay(500);        // Delay for 500 clock ticks (usually 500ms)
                                // Note: This will yield the CPU, allowing other tasks to run
    }
}

/**
 * @brief Task 2: Control LED2 to blink every 500ms
 * @param pvParameters Task parameters
 */
void task2(void *pvParameters)
{
    while (1)
    {
        Uart_Printf(DEBUG_UART, "task2 is running...\r\n");
        led_buf[1] ^= 1;        // Toggle LED2 state
        Led_Task();
        vTaskDelay(500);
    }
}

/* Button detection related variables */
uint8_t key_val = 0;   // Current button state
uint8_t key_old = 0;   // Previous button state
uint8_t key_down = 0;  // Press detection
uint8_t key_up = 0;    // Release detection

/**
 * @brief Task 3: Detect button, delete task1 when KEY1 is pressed
 * @param pvParameters Task parameters
 */
void task3(void *pvParameters)
{
    while (1)
    {
        Uart_Printf(DEBUG_UART, "task3 is running...\r\n");
      
        /* Read button state */
        key_val = key_read();
      
        /* Edge detection: only trigger when button state changes */
        key_down = key_val &amp; (key_old ^ key_val);      // Detect press edge
        key_up = ~key_val &amp; (key_old ^ key_val);       // Detect release edge
        key_old = key_val;                             // Update historical state
      
        /* Handle button events */
        switch (key_down)
        {
            case 1:  // KEY1 pressed
                /* Prevent repeated deletion (only delete if handle is not NULL) */
                if (task1_handle != NULL)
                {
                    Uart_Printf(DEBUG_UART, "Executing deletion of Task1\r\n");
                    vTaskDelete(task1_handle);    // Delete task 1
                    task1_handle = NULL;          // ⚠️ Important: Manually set handle to NULL after deletion
                }
                break;
        }
      
        vTaskDelay(20);  // Delay 20ms to reduce CPU usage
        // Note: Do not use HAL_Delay()! It will not put the task into a blocking state and will continuously occupy the CPU
    }
}

/**
 * @brief Step 4: Modify main.c
 */
#include "main.h"
#include "freertos_demo.h"  // Include FreeRTOS task header file

int main(void)
{
    /* Hardware initialization */
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
  
    /* System initialization */
    System_Init();
  
    /* Start FreeRTOS (the code will not return after the scheduler starts) */
    freertos_start();
  
    /* ⚠️ Note: The code below will never execute */
    while (1)
    {
        // This code will not be executed
    }
}

🎬 Experiment Results

After running the program, the serial output is as follows:

Running Results
  • • task1 and task2 execute every 500ms (LED blinking)
  • • task3 executes every 20ms (detecting button)
  • • After pressing KEY1, task1 is deleted, and LED1 stops blinking

⚠️ Important Notes

Issue 1: Task Stack Overflow Causes System Freeze

Phenomenon: The system freezes after task2 runs once, entering HardFault.

Cause Analysis:

  • • Task stack size set to 128 words (512 bytes)
  • <span>Uart_Printf</span> function internally uses a 512-byte buffer
  • • Insufficient stack space leads to stack overflow

Solution:

// Before modification
#define TASK1_STACK_SIZE  128  // ❌ Too small

// After modification
#define TASK1_STACK_SIZE  1024  // ✅ Increased to 1024 words (4096 bytes)

How to Determine an Appropriate Stack Size?

FreeRTOS provides a very useful function:

UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);

Return Value: The historical minimum remaining space of the task stack (in words)

Usage Example:

// Add debugging code in the task
void task1(void *pvParameters)
{
    while (1)
    {
        // Execute task...
      
        // Check stack usage
        UBaseType_t stack_left = uxTaskGetStackHighWaterMark(NULL);
        printf("Task1 stack remaining: %d words\r\n", stack_left);
      
        vTaskDelay(500);
    }
}

💡 Experience: Keep at least 20% stack margin to handle unexpected situations.

Issue 2: Why Manually Set Handle to NULL After Deleting a Task?

vTaskDelete(task1_handle);   // Delete task
task1_handle = NULL;         // ⚠️ Must manually set to NULL

Reason:<span>vTaskDelete()</span> only releases the memory occupied by the task, but does not automatically set the handle variable to NULL. If not manually set to NULL, it may lead to:

  • • Repeatedly deleting an already deleted task (dangling pointer)
  • • Condition checks failing

Issue 3: vTaskDelay() vs HAL_Delay()

Function Yields CPU? Can other tasks run? Recommended Usage Scenario
<span>vTaskDelay()</span> ✅ Yes ✅ Yes In FreeRTOS tasks
<span>HAL_Delay()</span> ❌ No ❌ No In bare-metal programs or interrupts

Conclusion: In FreeRTOS tasks, always use <span>vTaskDelay()</span><span>, otherwise low-priority tasks may starve.</span>

📚 Knowledge Expansion

1. Task Lifecycle

Create → Ready → Running → Blocked/Suspended → Deleted
  • Ready State: Waiting for CPU resources
  • Running State: Currently occupying the CPU
  • Blocked State: Waiting for events (such as delays, semaphores)
  • Suspended State: Paused execution

2. Idle Task

After calling <span>vTaskStartScheduler()</span><span>, the system automatically creates an idle task with priority 0. Its functions are:</span>

  • • Reclaim memory of deleted tasks
  • • Run when the system is idle (when all other tasks are blocked)

3. Task Scheduling Strategy

FreeRTOS uses preemptive scheduling by default:

  • • High-priority tasks can interrupt low-priority tasks
  • • Tasks of the same priority use time-slicing

🎓 Summary

This article introduced FreeRTOS dynamic task creation and deletion through a simple LED + button experiment. Key points include:

  1. 1. ✅ Use <span>xTaskCreate()</span><span> to dynamically create tasks</span>
  2. 2. ✅ Use <span>vTaskDelete()</span><span> to delete tasks, remember to set handle to NULL after deletion</span>
  3. 3. ✅ Set task stack size reasonably to avoid overflow
  4. 4. ✅ Use <span>vTaskDelay()</span><span> instead of </span><code><span>HAL_Delay()</span>
  5. 5. ✅ Use <span>uxTaskGetStackHighWaterMark()</span><span> to optimize stack size</span>

I hope this article helps those who are new to RTOS! If you have any questions, feel free to leave a comment for discussion~

📌 Next Issue Preview: Using Static Task Creation in FreeRTOS

If you find this useful, don’t forget to like 👍 + bookmark ⭐ + follow!

Leave a Comment