FreeRTOS is an open-source real-time operating system kernel, and tasks are its core execution units. Each task has its own stack and execution context, managed by the scheduler based on priority. In embedded systems like the ESP32, tasks can run on different CPU cores, supporting preemptive scheduling (higher priority tasks can interrupt lower priority tasks). Task states include: running, ready, blocked (e.g., waiting for delay or semaphore), and suspended (requires explicit resumption).
01
—
Common FreeRTOS APIs
ESP-IDF v5.3 is based on FreeRTOS v10.4.x and provides task management APIs tailored for the ESP32 series chips. The following are core functions:
|
Function |
Function Prototype |
|
Create Task |
BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pxTaskCode, const char *const pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask, BaseType_t xCoreID) |
|
Create Task |
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, const char *const pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask) |
|
Delete Task |
Void vTaskDelete(TaskHandle_t xTaskToDelete) |
|
Start Scheduler |
void vTaskStartScheduler(void) |
|
Suspend Task |
void vTaskSuspend(TaskHandle_t xTaskToSuspend) |
|
Resume Task |
void vTaskResume(TaskHandle_t xTaskToResume) |
|
Delay |
void vTaskDelay(TickType_t xTicksToDelay) |
|
Get Current Task Handle |
TaskHandle_t xTaskGetCurrentTaskHandle(void) |
02
—
Common Operation Steps and Implementation Code
1. Create Task
Steps:
-
Define the task function (returns void, takes void*, usually contains an infinite loop);
-
Call xTaskCreatePinnedToCore in app_main (recommended, specify core) to create the task;
-
Configure task parameters (stack size, priority, core ID, etc.), and save the task handle (for subsequent operations).
Implementation Code:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "task_demo";// Task 1: Print count (running on core 0)
void task1_func(void *pvParameters) {
int count = 0;
while (1) {
ESP_LOGI(TAG, "Task 1 (Core 0) count: %d", count++);
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay 1 second
}}
// Task 2: Print fixed information (running on core 1)
void task2_func(void *pvParameters) {
while (1) {
ESP_LOGI(TAG, "Task 2 (Core 1) running");
vTaskDelay(2000 / portTICK_PERIOD_MS); // Delay 2 seconds
}}
void app_main(void) {
TaskHandle_t task1_handle, task2_handle;
// Create task 1, core 0, priority 1, stack 4096 bytes
BaseType_t ret1 = xTaskCreatePinnedToCore(
task1_func, // Task function
"Task1", // Task name (for debugging)
4096, // Stack size (bytes)
NULL, // Parameters to pass to the task
1, // Priority (0-31, higher number means higher priority)
&task1_handle, // Output task handle
0 // Run on core 0
);
// Create task 2, core 1, priority 1
BaseType_t ret2 = xTaskCreatePinnedToCore(
task2_func,
"Task2",
4096,
NULL,
1,
&task2_handle,
1 // Run on core 1
);
// Check creation result
if (ret1 != pdPASS || ret2 != pdPASS) {
ESP_LOGE(TAG, "Failed to create tasks!");
}
// In ESP-IDF v5.3, the scheduler is automatically started, tasks enter ready state after creation
}
2. Delete Task
Steps:
-
Save the TaskHandle_t handle returned when creating the task;
-
Call vTaskDelete at the point where the task needs to be deleted (from another task or itself), passing the target handle (NULL indicates deleting the current task).
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "task_delete_demo";
TaskHandle_t temp_task_handle; // Handle for the task to be deleted
// Temporary task: self-delete after running 5 times
void temp_task_func(void *pvParameters) {
int count = 0;
while (1) {
ESP_LOGI(TAG, "Temporary task count: %d", count++);
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (count >= 5) {
ESP_LOGI(TAG, "Temporary task will delete itself");
vTaskDelete(NULL); // Delete current task
}
}}
// Monitor task: delete temporary task (optional logic)
void monitor_task_func(void *pvParameters) {
vTaskDelay(3000 / portTICK_PERIOD_MS); // Wait 3 seconds
ESP_LOGI(TAG, "Monitor task deleting temporary task");
vTaskDelete(temp_task_handle); // Delete specified task
vTaskDelete(NULL); // Self-delete after monitor task completes
}
void app_main(void) {
// Create temporary task
xTaskCreatePinnedToCore(
temp_task_func,
"TempTask",
4096,
NULL,
1,
&temp_task_handle,
0
);
}
3. Start Task Scheduler
Note: In pure FreeRTOS, vTaskStartScheduler() must be called manually, but in ESP-IDF v5.3, the scheduler is automatically started during system initialization (before app_main runs). After creating tasks, the scheduler automatically manages task execution without additional operations.
If you need to start the scheduler in a custom scenario (e.g., after special initialization), you can call:
// Example only, no need to manually call vTaskStartScheduler() in ESP-IDF
4. Suspend and Resume Tasks
Steps:
-
Save the handle of the target task;
-
Call vTaskSuspend(handle) to suspend the task (the task stops running and does not occupy CPU);
-
Call vTaskResume(handle) to resume the task (the task returns to the ready state, waiting for scheduling).
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "task_suspend_demo";
TaskHandle_t worker_task_handle; // Worker task handle
// Worker task: continuously print information
void worker_task_func(void *pvParameters) {
while (1) {
ESP_LOGI(TAG, "Worker task is running");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}}
// Control task: suspend and resume worker task
void control_task_func(void *pvParameters) {
// 1. Suspend worker task after running for 2 seconds
vTaskDelay(2000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Suspending worker task");
vTaskSuspend(worker_task_handle);
// 2. Resume worker task after suspending for 3 seconds
vTaskDelay(3000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Resuming worker task");
vTaskResume(worker_task_handle);
// 3. Self-delete after control task completes
vTaskDelete(NULL);
}
void app_main(void) {
// Create worker task
xTaskCreatePinnedToCore(
worker_task_func,
"WorkerTask",
4096,
NULL,
1,
&worker_task_handle,
0
);
// Create control task (higher priority to ensure execution)
xTaskCreatePinnedToCore(
control_task_func,
"ControlTask",
4096,
NULL,
2, // Higher priority than worker task
NULL,
1
);
}
03
—
FreeRTOS Task States
1. FreeRTOS Task States
-
Running State:
The task currently using the processor; in a single-core system, only one task is in the running state.
-
Ready State:
The task is ready to run but is temporarily not executed due to higher or equal priority tasks running.
-
Blocked State:
The task is waiting for an event (e.g., delay, semaphore, queue) and cannot run temporarily. Blocked tasks can automatically switch to the ready state by setting a timeout.
-
Suspended State:
The task is explicitly suspended and will not be scheduled until explicitly resumed. Suspended tasks do not automatically switch to the ready state.
2. FreeRTOS Task State Transition Logic

-
Transition between Ready and Running States
Ready State → Running State:
After calling vTaskStartScheduler() to start the scheduler, the scheduler switches the highest priority ready task to the running state. If the current running task’s time slice expires or is preempted by a higher priority task, it will return to the ready state.
Running State → Ready State:
The current running task will return to the ready state when it actively calls vTaskDelay() for delay or during time-slice rotation among tasks of the same priority, waiting for the next scheduling.
-
Transition between Running and Blocked States
Running State → Blocked State:
When a running task calls API functions like xSemaphoreTake() or xQueueReceive() to wait for resources, if the resources are unavailable, the task will switch from the running state to the blocked state until the waiting condition is met.
Blocked State → Ready State:
When the event the task is waiting for in the blocked state occurs (e.g., semaphore is released or data is available in the queue), the task will switch from the blocked state to the ready state, waiting for the scheduler to schedule it to the running state again.
-
Transition between Ready and Suspended States
Ready State → Suspended State:
By calling vTaskSuspend(), a ready state task can be suspended, entering the suspended state. Suspended tasks do not participate in scheduling until explicitly resumed.
Suspended State → Ready State:
Calling vTaskResume() can restore a suspended task to the ready state, allowing it to participate in scheduling again, waiting for the scheduler to decide whether to schedule it to the running state.
-
Transition between Running and Suspended States
Running State → Suspended State:
Even if a task is in the running state, it can be suspended by calling vTaskSuspend(), entering the suspended state. Suspended tasks do not participate in scheduling until resumed.
Suspended State → Ready State:
A suspended task can be restored to the ready state by calling vTaskResume(). The restored task will participate in scheduling again, waiting for the scheduler to decide whether to schedule it to the running state.
-
Transition between Blocked State and Suspended/Ready States
Blocked State → Suspended State:
Other tasks or interrupts calling vTaskSuspend() can switch a blocked state task to the suspended state, and suspended tasks do not participate in scheduling.
Blocked State → Ready State:
When the event the blocked task is waiting for occurs (e.g., delay ends, semaphore released), the task switches from the blocked state to the ready state, participating in scheduling.
Previous Articles:
Microcontroller AD Sampling Filtering Algorithm
MCU Low Power Modes
ADXL345 Three-Axis Accelerometer Driver Program
ESP32 Infrared Remote Control Technology
ESP32 I2S Driver MAX98357 Amplifier Program Design
ESP32-S3 WIFI UDP Communication