Task Control
Note
This document is automatically translated using AI. Please excuse any detailed errors. The official English version is still in progress.
In FreeRTOS, task control is one of the core functions, where mechanisms such as task delay, priority control, and state switching provide flexibility and controllability for task scheduling and execution.
Task Delay API
FreeRTOS provides two commonly used task delay APIs to control the execution rhythm of tasks. Both can put the current task into a blocked state for a specified number of ticks, temporarily relinquishing the use of the CPU.
vTaskDelay()
vTaskDelay()
is used to delay the current task for a specified number of system clock ticks from the moment of call. During the delay, the task enters a blocked state, releasing the use of the CPU.
This API implements relative delay, that is, the delay time is calculated from the current tick at the time of the API call, rather than based on a fixed time base. Therefore, this API is suitable for implementing simple delay behaviors, but not suitable for tasks that require strict cycle control, as its execution cycle may be offset due to fluctuations in task execution time or scheduling mechanisms.
API Prototype
void vTaskDelay( const TickType_t xTicksToDelay );
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Set the duration of task delay. |
In units of system clock ticks. During the delay, the task is in a blocked state and will not be scheduled for execution. |
Note
System clock tick is the basic unit of time in FreeRTOS, generated periodically by the system timer. The actual time corresponding to each tick is represented by the macro portTICK_PERIOD_MS
, in milliseconds. This value is automatically calculated based on the macro configTICK_RATE_HZ
, with the calculation formula: portTICK_PERIOD_MS = 1000 / configTICK_RATE_HZ
.
Where, configTICK_RATE_HZ
represents how many ticks are generated per second, i.e., the system clock tick frequency. Both of these macros are defined in the system configuration file (such as FreeRTOSConfig.h
), and can be adjusted according to application requirements.
It should be noted that only configTICK_RATE_HZ
should be modified to change the system tick interval, portTICK_PERIOD_MS
will automatically update according to its value. After changing the tick frequency, ensure that all time-dependent functions (such as delay, timer, timeout control, etc.) can still work as expected, to avoid scheduling abnormalities or timing errors due to unit changes.
Note
It is recommended to use the pdMS_TO_TICKS()
macro to convert delay time from milliseconds to ticks in actual development.
Since vTaskDelay()
accepts ticks, not milliseconds, directly entering milliseconds may lead to misunderstandings or calculation errors.
By using this macro, the desired delay duration can be expressed in common time units (milliseconds), and the program will automatically convert it to the corresponding number of ticks based on the system tick configuration.
This not only makes the code more readable and easier to understand, but also helps maintain consistency in time control logic when changing configTICK_RATE_HZ
or porting to different platforms, reducing maintenance costs.
Example 1: Simple Delay Loop Task
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Task function
void vDelayTask (void *pvParameters)
{
while (1) {
TickType_t now = xTaskGetTickCount(); // Read the current tick
printf("Tick: %lu - Task running\n", now);
vTaskDelay(100); // Delay 100 ticks
}
}
void app_main(void)
{
xTaskCreate(vDelayTask, "Task1", 2048, NULL, 1, NULL);
}
The execution result of the above code is as follows:
Tick: 2 - Task running
Tick: 102 - Task running
Tick: 202 - Task running
Tick: 302 - Task running
Tick: 402 - Task running
Tick: 502 - Task running
This example demonstrates the basic usage of vTaskDelay()
: the task delays 100 ticks after each execution and prints the current tick value when awakened. The running result shows that the task outputs once every 100 ticks, showing its fixed execution cycle.
Example 2: Cycle drift caused by variable task execution time
// The main function part is the same as Example 1, and it is not repeated here
void vDelayTask (void *pvParameters)
{
while (1) {
TickType_t now = xTaskGetTickCount();
printf("Tick: %lu - Task running\n", now);
vTaskDelay(rand() % 50); // Simulate the variable time consumption of task processing
vTaskDelay(100); // Delay 100 ticks
}
}
The execution result of the above code is as follows:
Tick: 2 - Task running
Tick: 135 - Task running
Tick: 278 - Task running
Tick: 390 - Task running
Tick: 519 - Task running
Tick: 619 - Task running
This example adds a random length processing process before the task delay to simulate the uncertainty of execution time in actual tasks. Since the time consumed by each task execution is different, the moment of calling vTaskDelay(100)
also changes, thereby causing the interval of task output tick value to gradually offset, and the cycle shows instability.
This reflects the relative delay feature of vTaskDelay()
, that is, the delay starts from the moment the function is called, rather than based on a fixed time reference point. Since the starting point of each delay depends on the completion time of the previous task, once the task execution time changes, the subsequent delay will also offset accordingly, leading to a gradual drift of the cycle. Therefore, this method is not suitable for tasks that require high cycle accuracy.
Note
“Cycle drift” refers to the task gradually deviating from the originally set cycle rhythm due to the inconsistent time consumed by each task execution.
Example 3: Delay accuracy limitation
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h" // Get microsecond counter
// The main function part is the same as Example 1, and it is not repeated here
void vDelayTask (void *pvParameters)
{
while (1) {
int64_t start_us = esp_timer_get_time(); // Get the current time (before delay), unit: microseconds
vTaskDelay(1); // Delay 1 tick
int64_t end_us = esp_timer_get_time(); // Get the current time (after delay), unit: microseconds
float actual_delay_ms = (end_us - start_us)/1000.0f; // Convert the unit to milliseconds
printf("Theoretical delay: %lu ms, Actual delay: %.2f ms\n", portTICK_PERIOD_MS, actual_delay_ms); // Compare the theoretical delay time and the actual delay time
}
}
The result of the above code execution is as follows:
Theoretical delay: 10 ms, Actual delay: 4.45 ms
Theoretical delay: 10 ms, Actual delay: 9.54 ms
Theoretical delay: 10 ms, Actual delay: 9.80 ms
Theoretical delay: 10 ms, Actual delay: 9.84 ms
Theoretical delay: 10 ms, Actual delay: 9.83 ms
Theoretical delay: 10 ms, Actual delay: 9.84 ms
Theoretical delay: 10 ms, Actual delay: 9.83 ms
Theoretical delay: 10 ms, Actual delay: 9.84 ms
Theoretical delay: 10 ms, Actual delay: 9.83 ms
This example is used to compare the difference between the theoretical delay and the actual delay of vTaskDelay()
, verifying that there is a certain precision error in the delay based on tick.
In order to more accurately assess the delay accuracy based on tick, this example introduces esp_timer_get_time()
to obtain the system time at the microsecond level, which is convenient for measuring the deviation between the actual delay of vTaskDelay()
and the theoretical value.
Each time the task is executed, it first records the current time, calls vTaskDelay(1)
for a delay of 1 tick, then reads the current time again, and calculates the time difference before and after as the actual delay duration.
Finally, this value is compared with the theoretical delay portTICK_PERIOD_MS
(currently configured as 10 ms) to observe the difference between the two.
From the running results, it can be seen that the first delay is significantly shorter than the theoretical value, only about 4.45 ms. This is usually because the task is close to the next tick interrupt when it first calls vTaskDelay()
, causing the actual waiting time to be shortened.
The subsequent multiple delays tend to be stable, about 9.8 ms, although close to the set 10 ms, but there is still a slight deviation.
This shows that when delaying by setting the number of ticks, the actual waiting time may be slightly shorter or longer, causing the task to start earlier or later than the expected time.
It should be noted that even if the delay time is lengthened, the error of a single call will not increase accordingly, and it will deviate at most one tick cycle, but in applications sensitive to time accuracy, it may still affect the rhythm of the task or accumulate deviation.
xTaskDelayUntil()
xTaskDelayUntil()
is designed to solve the instability of vTaskDelay()
in cycle control, providing a “beat-accurate” delay method to make the task run at a fixed cycle.
Compared with the relative delay mechanism, it can maintain a stable cycle rhythm and effectively avoid the problem of cycle drift caused by inconsistent task execution time.
This function is suitable for scenarios with high cycle accuracy requirements, such as sensor data collection, timing control, periodic communication and other tasks.
Note
You may see the writing of vTaskDelayUntil()
.
This function is functionally consistent with xTaskDelayUntil()
, the only difference is whether to return the delay result:
xTaskDelayUntil()
will return whether the task is awakened as expected.
vTaskDelayUntil()
does not provide a return value.
It is recommended to use xTaskDelayUntil()
first, so as to monitor the scheduling deviation when needed.
API Prototype
BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_t xTimeIncrement );
Input parameters |
Parameter Function |
Parameter Description |
---|---|---|
|
A variable pointer pointing to the last time the task was awakened. |
Before the first call, use |
|
Cycle time length. |
In units of system ticks. The function will block the task until the tick time corresponding to the specified cycle start point |
Return Value |
Description |
---|---|
|
Task successfully delayed. |
|
Task was not delayed. |
Note
xTaskDelayUntil()
is mainly used to implement periodic tasks.
If the call time is later than the expected wake-up time (for example, the execution cycle becomes longer), the function will return pdFALSE
immediately, indicating that the task is running behind and may need to check the task execution time, system load, or task priority.
Example 1: Simple Delay Loop Task
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Task function
void vDelayTask (void *pvParameters)
{
const TickType_t xDelay = pdMS_TO_TICKS(1000); // Expected delay cycle: 1000 ms, i.e., 100 ticks
TickType_t xLastWakeTime = xTaskGetTickCount(); // Initialize to current tick
while (1) {
printf("Tick: %lu - Task running\n", xTaskGetTickCount());
vTaskDelay(rand() % 50); // Simulate the variable time consumption of the task processing process
vTaskDelayUntil(&xLastWakeTime, xDelay);
}
}
void app_main(void)
{
xTaskCreate(vDelayTask, "Task1", 2048, NULL, 1, NULL);
}
The execution result of the above code is as follows:
Tick: 2 - Task running
Tick: 102 - Task running
Tick: 202 - Task running
Tick: 302 - Task running
Tick: 402 - Task running
Tick: 502 - Task running
This example demonstrates how to use vTaskDelayUntil()
to achieve stable task scheduling.
The task running cycle is set to 1000 ms (i.e., 100 ticks), which is consistent with the previous vTaskDelay()
Example 2, for easy comparison of the scheduling behavior differences between the two.
By adding a random length processing process before each round of the loop, it simulates the uncertainty of task processing time, verifying that even if the task execution time fluctuates, using vTaskDelayUntil()
can still ensure the task runs at a fixed cycle, which is helpful for precise timing control.
The execution result shows that the task running time point is stably spaced 100 ticks, indicating that the cycle has been effectively controlled.
Compared to vTaskDelay()
, vTaskDelayUntil()
can avoid the problem of cycle drift caused by different task execution times. Its mechanism is based on “last expected wake-up time” for delay calculation, rather than timing from the moment of call, thus maintaining a constant cycle.
For example, even if the task is executed faster or slower this round, the next round will still be triggered at the original planned time, unless the task execution time exceeds the entire cycle.
Note
vTaskDelayUntil()
only works precisely when the actual execution time of the task is less than the cycle interval.
If the task takes longer than the set cycle, the periodic execution will not be accurate and will cause delay backlog. To ensure stable scheduling, it is recommended to evaluate the task execution time in advance and set the cycle parameters reasonably.
Task Priority Management API
FreeRTOS provides two commonly used task priority management APIs for obtaining or modifying the running priority of tasks, controlling the execution order and response speed of tasks in the scheduler.
uxTaskPriorityGet()
uxTaskPriorityGet()
is used to get the current priority of a specified task.
API Prototype
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Specifies the task handle to be queried. |
If a non-empty handle is passed in, it returns the current priority of the corresponding task; if |
Return Value |
Description |
---|---|
Returns the current priority of the queried task. |
The type is |
Example 1: Query Task Priority
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TaskHandle_t xHandleTask1 = NULL;
// Task 1 function
void vTask1 (void *pvParameters)
{
while (1) {
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// Task 2 function
void vTask2 (void *pvParameters)
{
// Create Task 1 in Task 2, ensure to get its handle immediately after creation, for subsequent query of its priority
xTaskCreate(vTask1, "Task1", 2048, NULL, 1, &xHandleTask1);
while (1) {
// Get task priority
UBaseType_t xPriorityTask1 = uxTaskPriorityGet(xHandleTask1); // Task 1
UBaseType_t xPriorityTask2 = uxTaskPriorityGet(NULL); // This task
printf("Priority of Task 1 is %d\n", xPriorityTask1);
printf("Priority of Task 2 is %d\n", xPriorityTask2);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void)
{
xTaskCreate(vTask2, "Task2", 2048, NULL, 2, NULL);
}
The execution result of the above code is as follows:
Priority of Task 1 is 2
Priority of Task 2 is 1
Priority of Task 1 is 2
Priority of Task 2 is 1
Priority of Task 1 is 2
Priority of Task 2 is 1
This example demonstrates how to use uxTaskPriorityGet()
to get the priority of the current task and other tasks.
Task 1 is created with priority 2, and Task 2 is created with priority 1. In Task 1, the priority of itself is obtained by passing in NULL
, and the priority of Task 2 is obtained through the task handle xHandleTask2
.
To ensure the validity of the task handle, Task 1 creates Task 2 during its own execution, and enters the main loop after successfully obtaining its handle, to ensure the validity of subsequent operations.
The running results show that the priorities of the two tasks are consistent with the settings at creation, indicating that uxTaskPriorityGet()
can correctly obtain the current priority of the specified task.
Note
The task handle passed in must have been successfully created and can be scheduled. If the handle passed in has not been initialized (for example, it has not returned from xTaskCreate()
or the global variable has not been assigned), it may cause abnormal results or system errors.
vTaskPrioritySet()
vTaskPrioritySet()
is used to modify the priority of a specified task, and supports dynamically adjusting the task scheduling strategy during system operation.
API Prototype
void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Specifies the task handle to be modified. |
If a non-empty handle is passed in, it returns the current priority of the corresponding task; if |
|
The priority value to be set for the target task. |
The range is from |
Example 1: Modify and Query Task Priority
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TaskHandle_t xHandleTask1 = NULL;
// Task 1 function
void vTask1 (void *pvParameters)
{
// Delay one tick to ensure that the priority is correctly updated before starting execution
vTaskDelay(pdMS_TO_TICKS(10));
while (1) {
printf("Task 1 running at priority %d\n", uxTaskPriorityGet(NULL));
vTaskDelay(pdMS_TO_TICKS(200));
}
}
// Task 2 function
void vTask2 (void *pvParameters)
{
// Create Task 1 in Task 2, ensure that its handle is obtained immediately after creation, for subsequent modification of its priority
xTaskCreate(vTask1, "Task1", 2048, NULL, 1, &xHandleTask1);
while (1) {
// Get task priority
UBaseType_t xPriorityTask1 = uxTaskPriorityGet(xHandleTask1); // Task 1
UBaseType_t xPriorityTask2 = uxTaskPriorityGet(NULL); // This task
printf("Priority of Task 1 is %d\n", xPriorityTask1);
printf("Priority of Task 2 is %d\n", xPriorityTask2);
// Raise the priority of Task 1 (if less than Task 2)
if (xPriorityTask1 < xPriorityTask2) {
printf("Raising Task 1 priority to %d\n", xPriorityTask1+1);
vTaskPrioritySet(xHandleTask1, xPriorityTask1+1);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void)
{
xTaskCreate(vTask2, "Task2", 2048, NULL, 2, NULL);
}
The execution result of the above code is as follows:
Priority of Task 1 is 1
Priority of Task 2 is 2
Raising Task 1 priority to 2
Task 1 running at priority 2
Priority of Task 1 is 2
Priority of Task 2 is 2
Priority of Task 1 is 2
Priority of Task 2 is 2
Task 1 running at priority 2
This example demonstrates how to dynamically modify task priorities at runtime and control task scheduling behavior through the uxTaskPriorityGet()
and vTaskPrioritySet()
functions.
After Task 2 creates Task 1, it obtains its task handle and determines whether its priority needs to be raised. To avoid Task 1 reading an unupdated priority when it starts, Task 1 uses vTaskDelay()
to delay one tick at the beginning, ensuring that the priority displayed for the first time is the updated one.
The program output shows that the initial priority of Task 1 is 1. When Task 2 finds that it is lower than its own priority 2, it raises it to 2 through vTaskPrioritySet()
. After that, Task 1 runs and prints its current priority as 2, verifying that the priority modification has taken effect.
In subsequent loops, the priorities of the two tasks remain consistent, and no further adjustments are needed.
Task Status Management API
The Task Status Management API is used to control the execution status of tasks, such as suspension, resumption, delay termination, etc., to achieve more flexible task scheduling and control.
vTaskSuspend()
vTaskSuspend()
is used to suspend a specified task, causing it to stop running until it is resumed by another task. The suspended task will not participate in scheduling, regardless of its priority, and will hardly consume system resources, making it suitable for temporarily yielding the CPU.
API Prototype
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Specifies the task handle to be suspended. |
If a non-null handle is passed in, the corresponding task is suspended; if |
Example 1: Creating and Suspending Tasks with the Same Priority
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TaskHandle_t xHandleTask2 = NULL;
static int cnt = 0;
// Task 1 function
void vTask1 (void *pvParameters)
{
int *cnt = pvParameters;
// The body of the task function is usually a dead loop
while (1) {
if (*cnt != 2) {
printf("Task 1 is running\n");
}
else {
printf("Task 2 is suspended\n");
vTaskSuspend(xHandleTask2);
}
(*cnt)++;
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay 1 s
}
}
// Task 2 function
void vTask2 (void *pvParameters)
{
while (1) {
printf("Task 2 is running\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
xTaskCreate(vTask1, "Task1", 2048, (void *)&cnt, 1, NULL);
xTaskCreate(vTask2, "Task2", 2048, NULL, 1, &xHandleTask2);
}
The execution result of the above code is as follows:
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 2 is suspended
Task 1 is running
Task 1 is running
Task 1 is running
This example demonstrates the basic usage of vTaskSuspend()
to suspend another task in two tasks with the same priority. Task 1 and Task 2 are created with priority 1. When the count variable cnt
increases to 2, Task 1 suspends Task 2 through the saved task handle, causing Task 2 to no longer execute and only Task 1 continues to run.
This example verifies that even if two tasks have the same priority, as long as the task is in the running state, it can suspend another task through vTaskSuspend()
, achieving dynamic scheduling control between tasks.
At the same time, tasks with different priorities can also be suspended through vTaskSuspend()
.
However, it is recommended that high-priority tasks suspend low-priority tasks, because low-priority tasks may not get the opportunity to execute for a long time in system scheduling, and they cannot suspend high-priority tasks in a timely or reliable manner, which may cause unexpected behavior or scheduling delays.
The active management of the state of low-priority tasks by high-priority tasks can improve the determinism and response efficiency of the system.
vTaskResume()
vTaskResume()
is used to resume tasks that have been suspended due to vTaskSuspend()
, allowing them to re-enter the ready state and participate in scheduling.
Note
A task that has been suspended multiple times only needs to be called once vTaskResume()
to recover, but multiple suspensions may cause state management confusion, and it should be avoided as much as possible to repeatedly suspend the same task in the design.
API Prototype
void vTaskResume( TaskHandle_t xTaskToResume );
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Specifies the task handle to be resumed. |
The task passed in must have been suspended by |
Example 1: Task Suspension and Resumption
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TaskHandle_t xHandleTask2 = NULL;
static int cnt = 0;
// Task 1 function
void vTask1 (void *pvParameters)
{
int *cnt = pvParameters;
while (1) {
if (*cnt == 2) { // Suspend Task 2
printf("Task 2 is suspended\n");
vTaskSuspend(xHandleTask2);
}
else if (*cnt == 4){ // Resume Task 2
printf("Task 2 is resumed\n");
vTaskResume(xHandleTask2);
}
else {
printf("Task 1 is running\n");
}
(*cnt)++;
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay 1 s
}
}
// Task 2 function
void vTask2 (void *pvParameters)
{
while (1) {
printf("Task 2 is running\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
xTaskCreate(vTask1, "Task1", 2048, (void *)&cnt, 2, NULL);
xTaskCreate(vTask2, "Task2", 2048, NULL, 1, &xHandleTask2);
}
The execution result of the above code is as follows:
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 2 is suspended
Task 1 is running
Task 2 is resumed
Task 2 is running
Task 1 is running
Task 2 is running
This example demonstrates how to use vTaskSuspend()
and vTaskResume()
to control the suspension and resumption of tasks.
Task 1 controls the logic flow through the counter variable cnt
: when the counter is 2, it calls vTaskSuspend()
to suspend Task 2, stopping its output; when the counter is 4, it resumes Task 2 by calling vTaskResume()
.
The output clearly reflects the suspension and resumption process of Task 2, verifying that the execution status of other tasks can be dynamically controlled during runtime.
This mechanism can be used to flexibly enable or disable tasks based on specific conditions, thereby more effectively managing system resources and behavior.
Note
If a task suspends another critical task and fails to ensure timely resumption, it may cause the system logic to hang. It is recommended to pair with conditional judgment or timeout mechanism to avoid this situation.