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. start_task (Start Task): Responsible for creating the other three tasks and automatically deleting itself after creation.
- 2. task1: Makes LED1 blink every 500ms.
- 3. task2: Makes LED2 blink every 500ms.
- 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.
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*)&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 *)&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 *)&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 *)&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 & (key_old ^ key_val); // Detect press edge
key_up = ~key_val & (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:
- • 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. ✅ Use
<span>xTaskCreate()</span><span> to dynamically create tasks</span> - 2. ✅ Use
<span>vTaskDelete()</span><span> to delete tasks, remember to set handle to NULL after deletion</span> - 3. ✅ Set task stack size reasonably to avoid overflow
- 4. ✅ Use
<span>vTaskDelay()</span><span> instead of </span><code><span>HAL_Delay()</span> - 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!