Task Utilities

[中文]

Note

This document is automatically translated using AI. Please excuse any detailed errors. The official English version is still in progress.

In FreeRTOS, task utilities are used to obtain task status, task handles, stack usage, and other information to assist in task debugging and system monitoring.

This document only covers some commonly used APIs in task utilities. For more information, please refer to the FreeRTOS official documentation.

Task Information API

The task information API is mainly used to obtain the basic identification information of the task, such as the current task handle or task name, which is convenient for locating the task source in debugging or logs.

xTaskGetCurrentTaskHandle()

xTaskGetCurrentTaskHandle() is used to get the handle of the task currently running. It is commonly used in scenarios where you need to reference your own task, such as task self-suspension, self-deletion, and other operations.

API Prototype

TaskHandle_t xTaskGetCurrentTaskHandle( void );
Return Value Description

Return Value

Description

Returns the handle of the task currently running (i.e., calling this API)

The return value is valid in the task context.

Example 1: Get the handle of your own task and modify the priority

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Task 1 function
void vTask1 (void *pvParameters)
{
    // Get the current task handle
    TaskHandle_t xHandleTask1 = xTaskGetCurrentTaskHandle();

    while (1) {
        // Get task priority
        UBaseType_t xPriorityTask1 = uxTaskPriorityGet(xHandleTask1);

        printf("Priority of Task 1 is %d\n", xPriorityTask1);

        // Raise the priority of Task 1 (if less than Task 2)
        if (xPriorityTask1 < 2) {
            printf("Raising Task 1 priority to %d\n", xPriorityTask1+1);
            vTaskPrioritySet(xHandleTask1, xPriorityTask1+1);
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 1, NULL);
}

The execution result of the above code is as follows:

Priority of Task 1 is 1
Raising Task 1 priority to 2
Priority of Task 1 is 2
Priority of Task 1 is 2
Priority of Task 1 is 2
Priority of Task 1 is 2

This example demonstrates the basic method of obtaining the handle of the task itself and its application in task self-management.

After the task is started, it obtains its own handle through xTaskGetCurrentTaskHandle(), and combines uxTaskPriorityGet() and vTaskPrioritySet() to dynamically adjust its own priority.

Compared with directly passing in NULL to represent the current task, using the task handle has stronger universality and flexibility. It can be used in combination with APIs that only accept handles such as pcTaskGetName(), eTaskGetState(), and it is also convenient for debugging information output and cross-module transmission of task control information.

Example 2: Pass out its own handle from within the task

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Define a global variable to save the task handle
TaskHandle_t xHandleTask = NULL;

// Task 1 function
void vTask1 (void *pvParameters)
{
    while (1) {
        if (xHandleTask != NULL){
            vTaskSuspend(xHandleTask);
            printf("Task 2 is suspended\n");
            xHandleTask = NULL;
        }
        else {
            printf("Task 1 is running\n");
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vTask2 (void *pvParameters)
{
    // Get the current task handle
    xHandleTask = xTaskGetCurrentTaskHandle();

    while (1) {
        printf("Task 2 is running\n");
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 1, NULL);
}

The execution result of the above code is as follows:

Task 1 is running
Task 2 is running
Task 2 is suspended
Task 1 is running
Task 1 is running
Task 1 is running
Task 1 is running

This example demonstrates that the task actively obtains its own task handle through xTaskGetCurrentTaskHandle() during its operation, and saves it to a global variable for other tasks to use, achieving direct control between tasks.

Compared with the method of passing in and saving the handle from the outside when the task is created, this method is managed by the task itself, so it has higher decoupling. This method is particularly suitable for scenarios such as task internal initialization, self-registration or dynamic binding after startup, and helps to build a clear, flexible and controllable task system.

Note

Decoupling refers to the low degree of mutual dependence between modules or components, which can be independently developed, used and modified, and the operation of another module will not be affected by the change of one module.

pcTaskGetName()

pcTaskGetName() is used to get the name string of the specified task, which is commonly used for debugging, log output or task identification.

API prototype

char * pcTaskGetName( TaskHandle_t xTaskToQuery );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

xTaskToQuery

The task handle whose name needs to be queried.

If a non-empty handle is passed in, the name of the corresponding task is queried; if NULL is passed in, the name of the current task itself is queried.

Return Value Description

Return Value

Description

Task name string pointer

Returns a pointer to the queried task name, which is a standard string ending with NULL.

Note

The string pointed to by the return value is statically allocated and should not be modified or released in user code. It can be directly passed to standard string functions for use, suitable for printing logs, debugging task status, and other scenarios.

Example 1: Query the task name and print

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Define a global variable to save the task handle
TaskHandle_t xHandleTask = NULL;

void vTask1 (void *pvParameters)
{
    // Get the task name
    const char *NameTask2 = NULL;
    const char *NameTask1 = pcTaskGetName(NULL);

    while (1) {
        if (xHandleTask != NULL){
            NameTask2 = pcTaskGetName(xHandleTask);
            printf("Task 2 name: %s\n", NameTask2);
        }
        printf("Task 1 name: %s\n", NameTask1);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vTask2 (void *pvParameters)
{
    // Get the current task handle
    xHandleTask = xTaskGetCurrentTaskHandle();

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 1, NULL);
}

The execution result of the above code is as follows:

Task 1 name: Task1
Task 2 name: Task2
Task 1 name: Task1
Task 2 name: Task2
Task 1 name: Task1
Task 2 name: Task2

This example demonstrates how to use pcTaskGetName() to get the name of a specified task and periodically print it at runtime. This example gets the name of the current task by passing in NULL, and also passes in the valid handle of other tasks to query the name of the corresponding task.

Although the task name is usually specified by the developer when creating the task, pcTaskGetName() still has practical value when accessing across modules, outputting logs, creating dynamic tasks, or using it in conjunction with other APIs that only accept task handles, such as eTaskGetState(). It provides a unified way of information acquisition, avoiding manual maintenance of task name strings, and improving the decoupling and maintainability of the code.

State and Scheduling API

The state and scheduling APIs are used to query the current status of tasks or the running status of the system scheduler, which is convenient for judging task behavior and system scheduling.

eTaskGetState()

eTaskGetState() is used to get the current status of a specified task, such as running, ready, blocked, suspended, or deleted. It is commonly used for task status monitoring and debugging.

API Prototype

eTaskState eTaskGetState( TaskHandle_t xTask );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

xTask

The task handle for which the status needs to be obtained.

If a non-empty handle is passed in, the name of the corresponding task is queried; if NULL is passed in, the name of the current task itself is queried.

Return Value Description

Return Value

Corresponding Status

Status Description

eReady

Ready State

The task is ready and waiting to be scheduled for execution.

eRunning

Running State

The task is currently running (only applicable to the current task).

eBlocked

Blocked State

The task is waiting for an event or timeout.

eSuspended

Suspended State

The task is suspended and will not be scheduled.

eDeleted

Deleted State

The task has been deleted and is waiting for resource release.

Note

The return value is an enumeration of type eTaskState, returning the current status of the task.

Example 1: Get Task Status

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t task1Handle = NULL;

void vtask1(void *pvParameters) {
    while (1) {
        printf("Task 1 is running\n");
        vTaskDelay(pdMS_TO_TICKS(2000)); // Enter blocked state
    }
}

void vtask2(void *pvParameters) {
    xTaskCreate(vtask1, "Task1", 2048, NULL, 2, &task1Handle);

    eTaskState state;
    int counter = 0;

    while (1) {
        if (eTaskGetState(xTaskGetCurrentTaskHandle()) == eRunning) {
            printf("Task 2 state: RUNNING\n");
        }

        counter++;

        if (task1Handle != NULL) {
            state = eTaskGetState(task1Handle);

            switch (state) {
                case eRunning:   printf("Task 1 state: RUNNING\n"); break;
                case eReady:     printf("Task 1 state: READY\n"); break;
                case eBlocked:   printf("Task 1 state: BLOCKED\n"); break;
                case eSuspended: printf("Task 1 state: SUSPENDED\n"); break;
                case eDeleted:   printf("Task 1 state: DELETED\n"); break;
                default:         printf("Task 1 state: UNKNOWN\n"); break;
            }

            // Control Task 1 state changes
            if (counter == 2) {
                vTaskSuspend(task1Handle); // Task 1 suspended
                printf("Task 1 suspended\n");
            }

            if (counter == 4) {
                vTaskDelete(task1Handle); // Task 1 deleted
                printf("Task 1 deleted\n");
            }
        }

        vTaskDelay(pdMS_TO_TICKS(1000)); // Main task runs periodically
    }
}

void app_main() {
    xTaskCreate(vtask2, "Task2", 2048, NULL, 3, NULL); // Higher priority, convenient for obtaining Task 1 status
}

The execution result of the above code is as follows:

Task 2 state: RUNNING
Task 1 is running
Task 1 state: READY
Task 2 state: RUNNING
Task 1 state: BLOCKED
Task 1 suspended
Task 2 state: RUNNING
Task 1 state: SUSPENDED
Task 2 state: RUNNING
Task 1 state: SUSPENDED
Task 1 deleted
Task 2 state: RUNNING
Task 1 state: DELETED

This example demonstrates the use of eTaskGetState() to obtain and observe the state changes of other tasks.

Two tasks are created in the system, where Task 1 has a lower priority, prints information during operation and periodically delays into a blocked state; the main control task Task 2 has a higher priority, immediately creates Task 1 after startup, and periodically performs the following operations:

  • Print its own status (verify that the current task is in the running state)

  • Obtain Task 1 status and print description through switch

  • Suspend and delete Task 1 at specific periods, simulating state switching

  • Delay 1 second for each loop

According to the example output, the status and execution of Task 1 and Task 2 are as follows:

Loop number

Execution result

Task 1

Task 2

1

Task 2 state: RUNNING

Task 1 has been created, but has not yet run.

Task 2 is executing, judging the current state as eRunning through eTaskGetState(xTaskGetCurrentTaskHandle()), and printing.

1

Task 1 is running

Task 1 starts executing printf(), at which point its state is eRunning.

Task 2 yields the CPU after executing printf(), and the scheduler switches execution to Task 1.

1

Task 1 state: READY

Task 1 may have finished executing printf() but has not yet entered vTaskDelay(), at this time its state is briefly in eReady (waiting to be scheduled again). Or it could be that Task 1 has entered a blocked state, but the task control block has not been updated yet, resulting in reading eReady.

The scheduler switches back to Task 2, and Task 2 uses eTaskGetState(task1Handle) to query the state of Task 1.

2

Task 2 state: RUNNING

Task 1 is in a blocked state.

Again, use eTaskGetState() to determine the current task state, confirming that Task 2 is running.

2

Task 1 state: BLOCKED

Task 1 has finished executing printf() and called vTaskDelay(), entering a blocked state, the scheduler will not allocate CPU to it.

Task 2 queries the state of Task 1 and prints it as eBlocked.

2

Task 1 suspended

Task 1 is suspended, and the task enters the eSuspended state.

At this time counter == 2, Task 2 calls vTaskSuspend() to suspend Task 1.

3

Task 2 state: RUNNING

Task 1 is in a suspended state.

Again, use eTaskGetState() to determine the current task state, confirming that Task 2 is running.

3

Task 1 state: SUSPENDED

Task 1 is in a suspended state.

Task 2 queries the state of Task 1, confirming that it is in a suspended state.

4

Task 2 state: RUNNING

Task 1 is in a suspended state.

Again, use eTaskGetState() to determine the current task state, confirming that Task 2 is running.

4

Task 1 state: SUSPENDED

Task 1 is in a suspended state.

Task 2 queries the state of Task 1, confirming that it is in a suspended state.

4

Task 1 deleted

The delete operation takes effect immediately, and Task 1 is marked for deletion.

At this time counter == 4, Task 2 calls vTaskDelete() to delete Task 1.

5

Task 2 state: RUNNING

Task 1 is in a deleted state.

Again, use eTaskGetState() to determine the current task state, confirming that Task 2 is running.

5

Task 1 state: DELETED

Task 1 is in a deleted state.

Query the state of Task 1, the system returns eDeleted, indicating that the task has been deleted and will not be scheduled again.

Although obtaining the task state provides an effective way to debug tasks, due to the very rapid state switching process and dependence on scheduler behavior, the following points should be noted when using:

  • After a task is created, it is immediately in a ready state, but whether it actually runs immediately depends on the scheduler’s scheduling decision (including priority and whether the current task yields the CPU).

  • Common triggers for the blocked state include: calling vTaskDelay(), waiting for a semaphore, queue, or event group.

  • After a task is suspended, it will no longer be scheduled by the scheduler unless vTaskResume() is explicitly called to resume it.

  • The status of a deleted task is eDeleted, but this status is only temporarily retained after the task is deleted. After that, its task handle may become invalid, and access will lead to unpredictable behavior. To avoid unpredictable behavior caused by accessing an invalid handle, it is recommended to set the corresponding task handle to NULL after deleting the task.

  • printf() is a blocking operation, which may cause task switching and affect the timing of state observation.

  • The switching of task status (especially eReady and eBlocked) is extremely brief, and it is easy to encounter boundary states in actual observation, which may cause misreading.

  • The status information is maintained by the scheduler and updated at the appropriate time, so the status obtained through eTaskGetState() cannot be completely equivalent to the instantaneous execution situation of the task, and is only for reference.

Understanding the task status mechanism and its limitations can help make reasonable judgments in debugging and task scheduling design, and avoid misuse of status values.

xTaskGetSchedulerState()

xTaskGetSchedulerState() is used to get the current state of the scheduler, to judge whether the scheduler is running, suspended or not started, and to help understand whether the system scheduling is normal.

API Prototype

BaseType_t xTaskGetSchedulerState( void );
Return value description

Return value

Description

taskSCHEDULER_NOT_STARTED

The scheduler has not started yet, at this time all tasks will not be scheduled for execution, even if the task has been created.

taskSCHEDULER_RUNNING

The scheduler has started and is running normally, tasks are scheduled according to priority.

taskSCHEDULER_SUSPENDED

The scheduler is suspended, task scheduling is temporarily stopped, all tasks stop switching, but the task status remains unchanged, waiting for the scheduler to resume and continue scheduling.

Example 1: Print scheduler status

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

void exampleTask(void *param)
{
    int counter = 0;
    bool print_resume = false;
    BaseType_t state;
    while (1) {
        if (counter == 2) {
            printf(">>> Suspended scheduler from task\n");

            vTaskSuspendAll();

            // Quietly busy wait for 3 seconds, unable to perform printf or delay
            int64_t start = esp_timer_get_time();
            while (esp_timer_get_time() - start < 3000000) {}
            state = xTaskGetSchedulerState(); // Save the current scheduler status

            xTaskResumeAll();  // Resume scheduler
            print_resume = true;
        }

        if (print_resume) {
            if (state == taskSCHEDULER_SUSPENDED) {
                printf("Scheduler state: SUSPENDED\n");
            }
            printf(">>> Resumed scheduler from task\n");
            print_resume = false;
        }

        state = xTaskGetSchedulerState();
        if (state == taskSCHEDULER_RUNNING) {
            printf("Scheduler state: RUNNING\n");
        }

        counter++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(exampleTask, "exampleTask", 2048, NULL, 5, NULL);
}

The execution result of the above code is as follows:

Scheduler state: RUNNING
Scheduler state: RUNNING
>>> Suspended scheduler from task
Scheduler state: SUSPENDED
>>> Resumed scheduler from task
Scheduler state: RUNNING
Scheduler state: RUNNING

This example demonstrates how to use xTaskGetSchedulerState() to print the scheduler state in a task, and the process of suspending and resuming the scheduler during execution. The task periodically outputs the current state of the scheduler, showing that the scheduler still allows the current task to continue executing after suspension, and simulates the delay during suspension by busy waiting based on esp_timer_get_time(). After the scheduler is resumed, the task continues to print the running state normally.

It should be noted that:

  • Suspending the scheduler only prevents task switching, it does not pause the current task execution.

  • During the suspension of the scheduler, any blocking function, such as vTaskDelay() or printf() that uses a mutex internally, cannot be called, otherwise it will cause the system to assert failure.

  • In the ESP-IDF environment, the scheduler startup is automatically completed by the system, and the taskSCHEDULER_NOT_STARTED state cannot be observed.

The operations of suspending and resuming the scheduler must be used in pairs to ensure the stable operation of the system.

Among them, vTaskSuspendAll() is used to temporarily suspend the FreeRTOS task scheduler to prevent task switching; xTaskResumeAll() resumes task scheduling and allows task switching to continue. The two are used together to temporarily prohibit task switching in a section of code, ensuring the continuity and consistency of execution.

Note

Calling xTaskResumeAll() only resumes the scheduler suspended by vTaskSuspendAll(), but it will not resume the task suspended by vTaskSuspend().

In the native FreeRTOS environment, vTaskSuspendAll() only suspends the task scheduler and does not disable interrupts, so interrupts can still be executed normally, which is suitable for scenarios where the critical area is large and interrupt response is allowed. After calling xTaskResumeAll(), the scheduler resumes operation and may trigger a task switch.

In the ESP-IDF environment, the principles of the two functions are similar, but the implementation is more complex. The suspension of scheduling generally only affects the calling core itself and will not suspend the scheduling of other cores. Due to the multi-core characteristics, it is usually necessary to combine hardware characteristics and multi-core synchronization mechanisms (such as spin locks) for flexible application when protecting shared resources.

For further information about suspending and resuming the scheduler in the ESP-IDF environment, please refer to the section on FreeRTOS Scheduler Suspension in the official ESP-IDF documentation.

Runtime API

The Runtime Class API is used to obtain system runtime information and task runtime statistics, assisting in performance analysis and system monitoring.

xTaskGetTickCount()

xTaskGetTickCount() is used to obtain the tick count since system startup, commonly used for implementing delays or calculating time intervals.

API Prototype

TickType_t xTaskGetTickCount( void );
Return Value Explanation

Return Value

Explanation

Returns system tick count (ticks)

The system tick count (ticks) that has passed since xTaskGetTickCount() was called, i.e., since the scheduler started.

Example 1: Loop to read the current tick

#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 \n", now);

        vTaskDelay(10); // Delay 10 ticks
    }
}

void app_main(void)
{
    xTaskCreate(vDelayTask, "Task1", 2048, NULL, 1, NULL);
}

The execution result of the above code is as follows:

Tick: 2
Tick: 12
Tick: 22
Tick: 32
Tick: 42
Tick: 52

This example creates a periodic task that calls xTaskGetTickCount() in a loop to get the current system tick count and prints it out. The task runs every 10 ticks through vTaskDelay(10), forming a fixed rhythm.

From the output results, it can be seen that the tick count starts to increase after the scheduler starts, and the task outputs the tick value stably according to the set cycle.

This API is also often used to calculate the time difference after the task starts. You can call xTaskGetTickCount() at the beginning of the task to record the initial tick value, call it again at a later moment and subtract it to calculate the number of ticks passed during the task execution. It is suitable for running time statistics or logic interval judgment.

In addition, when using vTaskDelayUntil() to implement periodic tasks, a tick base time needs to be maintained in the task. This time base is initialized by xTaskGetTickCount() to record the cycle start point, ensuring that each delay aligns with the tick to avoid cycle drift caused by task execution time fluctuations.

vTaskGetRunTimeStats()

vTaskGetRunTimeStats() is used to obtain the runtime proportion information of each task, output in string form, commonly used for performance analysis and task execution time monitoring (requires enabling related configuration).

API Prototype

void vTaskGetRunTimeStats( char *pcWriteBuffer );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Explanation

pcWriteBuffer

A pointer to a character array allocated by the user.

Used to receive runtime statistics information (in ASCII text format) from various tasks.

Note

This buffer does not perform boundary checks, users need to ensure its capacity is large enough. It is recommended to reserve about 40 bytes for each task to prevent the output content from being truncated.

API Configuration

The relevant configuration needs to be enabled before calling this API:

API Configuration

Target Macro

Function

configGENERATE_RUN_TIME_STATS

Generate runtime statistics. After enabling, it can collect CPU time usage information for each task.

configUSE_STATS_FORMATTING_FUNCTIONS

Enable formatting output functions such as vTaskList() and vTaskGetRunTimeStats().

configSUPPORT_DYNAMIC_ALLOCATION

Support dynamic memory allocation. Enable dynamic creation of tasks/queues/timers and other APIs.

portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()

Configure the runtime statistics timer. Users need to provide an implementation to initialize the hardware timer to collect time information.

portGET_RUN_TIME_COUNTER_VALUE()

Get the current runtime for sampling by statistical functions.

The native FreeRTOS configuration steps are as follows:

  1. Open the FreeRTOSConfig.h file in the FreeRTOS project.

  2. Check if the macros in the table are defined (they may be commented out or set to 0).

  3. If set to 0, change it to 1. If not, manually add the following content:

#define configGENERATE_RUN_TIME_STATS            1
#define configUSE_STATS_FORMATTING_FUNCTIONS     1
#define configSUPPORT_DYNAMIC_ALLOCATION         1

// Can be changed to other self-implemented timer reading functions, returning an increasing time base (units can be microseconds, milliseconds, etc.).
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     // If SysTick has been initialized by FreeRTOS, leave it blank
#define portGET_RUN_TIME_COUNTER_VALUE()             (xTaskGetTickCount() * portTICK_PERIOD_MS)
  1. Save the file and recompile.

The configuration steps based on the ESP-IDF environment are as follows:

  1. Open the ESP-IDF terminal.

  2. Enter the following command to open the menuconfig configuration interface:

idf.py menuconfig
  1. Follow the path: Component config > FreeRTOS > Kernel, then check configGENERATE_RUN_TIME_STATS, configUSE_TRACE_FACILITY and configUSE_STATS_FORMATTING_FUNCTIONS.

  2. If these two macros are not under this path, you can press / and enter the macro name to search.

  3. Press s to save the changes.

  4. Define macros in the project code:

// Take esp_timer as an example
#include "esp_timer.h"

#ifndef portCONFIGURE_TIMER_FOR_RUN_TIME_STATS
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()   // esp_timer is automatically initialized, no implementation required
#endif

#ifndef portGET_RUN_TIME_COUNTER_VALUE
#define portGET_RUN_TIME_COUNTER_VALUE()   (esp_timer_get_time())
#endif

Note

In ESP-IDF, FreeRTOSConfig.h is automatically managed by the system component. Direct modifications by the user will be overwritten during compilation, so it is not recommended to directly modify this file. The correct approach is to configure related options through menuconfig, and define necessary macros in the project code to implement custom functions.

Example 1: Obtaining the proportion of task runtime information

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"

// Bind esp_timer as the runtime statistics timer
#ifndef portCONFIGURE_TIMER_FOR_RUN_TIME_STATS
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()   // esp_timer is automatically initialized, no implementation required
#endif

#ifndef portGET_RUN_TIME_COUNTER_VALUE
#define portGET_RUN_TIME_COUNTER_VALUE() (esp_timer_get_time())  // Returns microseconds
#endif

void vExampleTask(void *pvParameters)
{
    while (1) {
        printf("Example Task running...\n");
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void app_main(void)
{
    // Create example task
    xTaskCreate(vExampleTask, "ExampleTask", 2048, NULL, 5, NULL);

    // Allocate buffer to store runtime statistics string
    char *stats_buffer = pvPortMalloc(512);
    if (stats_buffer == NULL) {
        printf("无法分配内存\n");
        return;
    }

    // After enabling the runtime statistics function, continuously print task runtime information
    while (1) {
        vTaskGetRunTimeStats(stats_buffer);
        printf("\nTask Run Time Stats (microseconds):\n%s\n", stats_buffer);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }

    // This point will never be reached
    vPortFree(stats_buffer);
}

The execution result of the above code is as follows:

Example Task running...

Task Run Time Stats (microseconds):
main            20518           38%
IDLE1           19828           37%
IDLE0           0               <1%
ExampleTask     111             <1%
ipc1            32304           60%
ipc0            32149           60%

Example Task running...

Task Run Time Stats (microseconds):
main            32851           1%
IDLE1           2019828         98%
IDLE0           1991926         96%
ExampleTask     208             <1%
ipc0            32149           1%
ipc1            32304           1%

This example demonstrates how to use vTaskGetRunTimeStats() in ESP-IDF to obtain the CPU usage information of tasks.

The example achieves runtime statistics by creating a simple task and combining it with esp_timer_get_time(), periodically printing the cumulative runtime and percentage of all tasks in the system. The statistical results include user tasks, the default main task, and system tasks (such as IDLE and IPC tasks), which are used to observe the running proportion of each task.

This method is suitable for system performance analysis and task behavior monitoring. Before using, you need to enable the runtime statistics function in the configuration and provide a string buffer to store the output content.

Stack and Statistics API

The stack and statistics class API is used to obtain the task stack usage and the system task quantity and status list, helping to detect the risk of stack overflow and system task management.

uxTaskGetStackHighWaterMark()

uxTaskGetStackHighWaterMark() is used to obtain the minimum remaining stack space since the task has been running, which is used to evaluate the peak of stack usage and assist in judging whether there is a risk of stack overflow.

API Prototype

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

xTask

The task handle to be queried.

If a non-empty handle is passed in, the stack space of the corresponding task is queried; if NULL is passed in, the stack space of the current task itself is queried.

Return Value Description

Return Value

Description

Returns the stack high water mark in words.

Used to monitor the safety margin of the task stack, helping to adjust the stack size in time to avoid overflow.

Note

For example, in a 32-bit system:

  • If the return value is 1, it means that there are 4 bytes of stack that have not been used.

  • If the return value is close to 0, it means that the task stack is close to the risk of overflow.

  • If the return value is 0, it means that the task stack may have overflowed.

Example 1: Query the remaining stack space of the task

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Define a global variable to save the handle of Task 2
TaskHandle_t xHandleTask2 = NULL;

void vTask1(void *pvParameters)
{
    while (1) {
        // Simulate some operations
        vTaskDelay(pdMS_TO_TICKS(1000));

        // Get the minimum remaining stack space of the task (unit: word)
        UBaseType_t uxHighWaterMarkForTask1 = uxTaskGetStackHighWaterMark(NULL);    // Current task
        UBaseType_t uxHighWaterMarkForTask2 = uxTaskGetStackHighWaterMark(xHandleTask2);    // Task 2

        printf("Minimum remaining stack for Task 1: %u \n", uxHighWaterMarkForTask1);
        printf("Minimum remaining stack for Task 2: %u \n", uxHighWaterMarkForTask2);
    }
}

void vTask2(void *pvParameters)
{
    // Get the current task handle
    xHandleTask2 = xTaskGetCurrentTaskHandle();
    while (1) {
        // Simulate some operations
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task 1", 2048, NULL, 2, NULL);
    xTaskCreate(vTask2, "Task 2", 2048, NULL, 2, NULL);
}

The execution result of the above code is as follows:

Minimum remaining stack for Task 1: 1344
Minimum remaining stack for Task 2: 1352
Minimum remaining stack for Task 1: 128
Minimum remaining stack for Task 2: 1352
Minimum remaining stack for Task 1: 128
Minimum remaining stack for Task 2: 1352

This example demonstrates the typical usage of uxTaskGetStackHighWaterMark(), by passing in different task handles, you can get the smallest remaining stack space since the task has been running.

The API returns the historical minimum remaining value, not the current remaining stack, which is used to evaluate the peak stack usage during task execution and determine whether there is a risk of approaching overflow. The return value is in units of word, which can be converted to bytes as needed.

By regularly printing this value, you can dynamically monitor the task stack usage during the debugging stage, ensure that the minimum remaining stack is always maintained within a reasonable range, thereby optimizing the stack configuration and improving the stability and reliability of the system.

uxTaskGetNumberOfTasks()

uxTaskGetNumberOfTasks() is used to get the total number of tasks currently running in the system, which is convenient for monitoring the number of tasks and system load.

API Prototype

UBaseType_t uxTaskGetNumberOfTasks( void );
Return Value Explanation

Return Value

Explanation

Returns the number of tasks currently being managed by the RTOS kernel.

Includes all ready, blocked and suspended tasks. Tasks that have been deleted but not yet released by the idle task are also included in the count.

Example 1: Query the current number of tasks

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t xHandleTask2 = NULL;

void vTask1(void *pvParameters)
{
    int count = 0;
    while (1) {
        UBaseType_t taskCount = uxTaskGetNumberOfTasks();
        printf("Current number of system tasks: %u\n", taskCount);

        if (count == 2) {
            printf(">>>>> Task 2 SUSPENDED\n");
            vTaskSuspend(xHandleTask2);
        }
        else if (count == 4) {
            printf(">>>>> Task 2 DELETED\n");
            vTaskDelete(xHandleTask2);
        }
        count++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vTask2(void *pvParameters)
{
    // Get the current task handle
    xHandleTask2 = xTaskGetCurrentTaskHandle();

    while (1)
    {
        printf(">>>>> Task 2 is RUNNING\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 5, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 5, NULL);
}

The execution result of the above code is as follows:

Current system task count: 6
>>>>> Task 2 is RUNNING
Current system task count: 6
>>>>> Task 2 is RUNNING
Current system task count: 6
>>>>> Task 2 SUSPENDED
Current system task count: 6
Current system task count: 6
>>>>> Task 2 DELETED
Current system task count: 5
Current system task count: 5

This example obtains the total number of tasks in the current system in real time by calling uxTaskGetNumberOfTasks(), verifying that this API will count tasks including ready state, blocked state, suspended state, and tasks that have been deleted but not yet released by idle tasks.

Task 1 periodically prints the task count, and suspends and deletes Task 2 during operation. The output result shows that the task count remains unchanged in the suspended state, and the task count decreases after the task is deleted and released, indicating that this API can accurately reflect the actual number of valid tasks in the system.

This function is often used in system debugging, task lifecycle monitoring, and resource management scenarios, making it easier for developers to determine whether the task is growing abnormally, whether it is being correctly released, thereby enhancing the controllability and stability of the system.

vTaskList()

vTaskList() is used to output the status information of all tasks in the system in text form, including task name, status, priority, remaining stack, and task number, often used for task monitoring and debugging (requires enabling related configuration).

API Prototype

void vTaskList( char *pcWriteBuffer );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

pcWriteBuffer

A pointer to a character array allocated by the user.

Used to receive the runtime statistics of each task (in ASCII text format).

In the ASCII table, the following letters are used to represent the status of the task:

  • X - Running

  • B - Blocked

  • R - Ready

  • D - Deleted (waiting for cleanup)

  • S - Suspended or blocked, no timeout

API Configuration

Before calling this API, you need to enable the related configuration:

API Configuration

Target Macro

Function

configUSE_TRACE_FACILITY

Enables task status tracking feature, must be set to 1 to use task statistics related functions.

configUSE_STATS_FORMATTING_FUNCTIONS

Enables formatting output functions such as vTaskList() and vTaskGetRunTimeStats().

The native FreeRTOS configuration steps are as follows:

  1. Open the FreeRTOSConfig.h file in the FreeRTOS project.

  2. Check if these two macros are defined (may be commented out or set to 0).

  3. If set to 0, change it to 1. If not, manually add the following content:

#define configUSE_TRACE_FACILITY                1
#define configUSE_STATS_FORMATTING_FUNCTIONS    1
  1. Save the file and recompile.

The configuration steps based on the ESP-IDF environment are as follows:

  1. Open the ESP-IDF terminal.

  2. Enter the following command to open the menuconfig configuration interface:

idf.py menuconfig
  1. Follow the path: Component config > FreeRTOS > Kernel, then select configUSE_TRACE_FACILITY and configUSE_STATS_FORMATTING_FUNCTIONS.

  2. If these two macros are not under this path, you can press / and enter the macro name to search.

  3. Press s to save the changes.

Note

In ESP-IDF, FreeRTOSConfig.h is automatically managed by system components. Direct modifications by users will be overwritten during compilation, so it is not recommended to directly modify this file. The correct approach is to configure related options through menuconfig and define necessary macros in the project code to implement custom functions.

Example 1: Query Task List

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t xHandleTask2 = NULL;

void vPrintTaskList(void)
{

    // Allocate enough buffer to store task list information
    // Note that the buffer size needs to be adjusted appropriately as the stack size and task number vary
    char *taskListBuffer = pvPortMalloc(512);
    if (taskListBuffer != NULL) {
        vTaskList(taskListBuffer);
        printf("Task list:\n%s\n", taskListBuffer);
        vPortFree(taskListBuffer);
    } else {
        printf("Insufficient memory, unable to print task list\n");
}

void vTask1(void *pvParameters)
{
    int count = 0;

    while (1) {
        UBaseType_t taskCount = uxTaskGetNumberOfTasks();
        printf("Current system task count: %u\n", taskCount);

        vPrintTaskList();

        if (count == 2) {
            printf(">>>>> Task 2 SUSPENDED\n");
            vTaskSuspend(xHandleTask2);
        }
        else if (count == 3) {
            printf(">>>>> Task 2 DELETED\n");
            vTaskDelete(xHandleTask2);
        }
        count++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vTask2(void *pvParameters)
{
    xHandleTask2 = xTaskGetCurrentTaskHandle();
    while (1)
    {
        printf(">>>>> Task 2 is RUNNING\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 4096, NULL, 2, NULL);
    vTaskDelay(pdMS_TO_TICKS(1000));
    xTaskCreate(vTask2, "Task2", 4096, NULL, 2, NULL);
}

The execution result of the above code is as follows:

Current system task count: 6
Task list:
Task1           X       2       2176    6
main            R       1       1920    3
IDLE1           R       0       800     5
IDLE0           R       0       1016    4
ipc1            S       24      536     2
ipc0            S       24      528     1

Current system task count: 6
Task list:
Task1           X       2       2144    6
main            R       1       1920    3
IDLE1           R       0       800     5
IDLE0           R       0       808     4
ipc0            S       24      528     1
ipc1            S       24      536     2

>>>>> Task 2 is RUNNING
Current system task count: 6
Task list:
Task1           X       2       2144    6
IDLE1           R       0       800     5
IDLE0           R       0       808     4
Task2           B       2       952     7
ipc1            S       24      536     2
ipc0            S       24      528     1

>>>>> Task 2 SUSPENDED
Current system task count: 6
Task list:
Task1           X       2       2144    6
IDLE1           R       0       800     5
IDLE0           R       0       808     4
Task2           S       2       952     7
ipc0            S       24      528     1
ipc1            S       24      536     2

>>>>> Task 2 DELETED
Current system task count: 5
Task list:
Task1           X       2       2144    6
IDLE1           R       0       800     5
IDLE0           R       0       808     4
ipc1            S       24      536     2
ipc0            S       24      528     1

Current system task count: 5
Task list:
Task1           X       2       2144    6
IDLE1           R       0       800     5
IDLE0           R       0       808     4
ipc0            S       24      528     1
ipc1            S       24      536     2

In the previous uxTaskGetNumberOfTasks() example, although only two tasks were explicitly created, the actual printed task count was 6, indicating that there are other default tasks in the system.

This example extends on this basis, introducing vTaskList(), which is used to output detailed information about all tasks in the current system, including task name, status, priority, remaining stack space, and task number.

From the running results, it can be seen that in addition to the user-created Task1 and Task2, the system also contains multiple internal tasks, such as idle tasks (IDLE0 and IDLE1), the initial main task (used to run app_main()), and kernel scheduling related tasks (ipc0 and ipc1). Therefore, after Task1 is created, the total number of system tasks is 6; after creating Task2, although one task is added, the main task exits and deletes itself after app_main() is executed, so the total number of tasks is still 6, until Task2 is deleted and then reduced to 5.

When using vTaskList(), note the following:

  • Properly delay before creating the second task (such as calling vTaskDelay()) to avoid stack overflow due to too fast task creation.

  • The stack space required for each task should be allocated according to the function, and the size of the task information buffer (that is, the buffer passed into vTaskList()) should also be reasonably allocated according to the number of tasks to prevent output truncation or insufficient memory.

  • In ESP-IDF, the main task that executes app_main() will automatically delete itself after the function ends, and the number of tasks will decrease. But in native FreeRTOS, main does not exist as a task and will not appear in the task list.

  • The task number is an internal auto-increment identifier of the system, and it is not reused due to task deletion, so the number may jump.