freertos-tarea-idle-hooks

What Does the FreeRTOS Idle Task Do

  • 5 min

The Idle Task is the lowest priority task that FreeRTOS executes when no other task is ready.

In previous articles, we’ve been very focused on creating tasks, assigning priorities, and making them work. Today, we’ll ask the opposite question: what happens when there’s no work?

Let’s consider a common scenario: we have three tasks in our system, but all of them are waiting for something (they are in the Blocked state):

Task 1 is waiting for a vTaskDelay of 1 second.

Task 2 is waiting for a byte to arrive via UART.

Task 3 is waiting for a user to press a button.

At that moment, no application task wants the CPU. The processor can either continue executing instructions or enter a low-power mode, depending on the port and configuration.

At that point, the most unassuming task in FreeRTOS appears: the Idle Task.

What is the Idle Task?

When the Scheduler starts, FreeRTOS automatically creates a special task for each available core.

  • Name: IDLE Task.
  • Priority: 0 (The lowest possible).
  • State: Always Ready (or Running).

Its sole purpose is to exist. It is the “safety net.” If no other task in the system (with priority 1 or higher) can run, the Scheduler automatically selects the Idle Task.

In its most basic form, the code for the Idle Task looks like this:

// Simplified pseudocode of the Idle Task
void prvIdleTask( void *pvParameters ) {
    for( ;; ) {
        // 1. Clean up memory of deleted tasks (Garbage Collection)
        prvCheckTasksWaitingTermination();

        // 2. Execute user Hooks (if they exist)
        #if ( configUSE_IDLE_HOOK == 1 )
        {
            vApplicationIdleHook();
        }
        #endif

        // 3. Simply loop
        // (On some micros, this is where the CPU enters a low-power mode)
    }
}
Copied!

The code we execute from the Idle task must not block with vTaskDelay, queues, or semaphores. It should also not take too long, as it would delay internal maintenance and entry into low-power mode.

Deferred Memory Deallocation

Although it might seem like the Idle Task does nothing, it has a critical responsibility: freeing the memory of dynamically deleted tasks.

As we will see in the next article, when a task decides to “commit suicide” by calling vTaskDelete(NULL), it deletes itself. But, paradoxically, a task cannot free its own Stack memory while it is still running (it would be like sawing off the branch you are sitting on).

Therefore, the task is marked for deletion, but it is the Idle Task’s job to physically free that RAM the next time it runs.

If you dynamically create and destroy tasks, you must ensure that the Idle task receives CPU time. If a higher-priority task monopolizes a core, the pending memory will not be recovered, and you might end up exhausting the heap.

Idle Hooks

Here comes the interesting part for us as developers. FreeRTOS allows us to “sneak” inside the Idle Task loop to execute our own code when the system has nothing better to do.

This is done via a callback function called Idle Hook.

To use the standard hook, configUSE_IDLE_HOOK must be set to 1. In ESP-IDF, there are also core-specific Idle hooks; their availability from Arduino depends on the version and how the libraries were built.

What is an Idle Hook used for?

  1. Power Saving: The port can use Idle to enter low-power mode. On the ESP32, it’s better to use the power management and tickless idle features of ESP-IDF, rather than inserting a sleep instruction manually.
  2. CPU Load Measurement: We can put a counter inside the Hook. If the counter increments very quickly, it means the system is very idle (spending a lot of time in Idle). If it increments slowly, we are saturated.
  3. Non-critical Background Tasks: Integrity checks, background CRC calculations, or simply blinking a “Heartbeat” LED to indicate the system is alive and hasn’t crashed.

Conceptual Example

// This function is automatically called by the operating system
// every time it enters the Idle Task.
void vApplicationIdleHook( void ) {
    // IMPORTANT!
    // We cannot use vTaskDelay or anything that blocks here.

    // Very short, non-blocking work.
    idleCounter++;
}
Copied!

The Special Case of the ESP32

On a dual-core ESP32, each core has its own Idle Task.

  • Core 0 has its own Idle Task.
  • Core 1 has its own Idle Task.

The Task Watchdog can monitor these Idle Tasks to detect if a core is not giving them any runtime. The time limit and reaction depend on the configuration.

How to “Do Things” in Idle on an ESP32

Instead of using the standard vApplicationIdleHook (which can sometimes cause compilation issues in Arduino depending on the Core version), the ESP32 SDK offers specific callbacks or simply a design recommendation:

If you want a background task with the lowest priority, simply create a task with Priority 0.

void BackgroundTask(void *pvParameters) {
    while(1) {
        // Do unimportant things
        Serial.println("The system is relaxed...");

        // Block this task so that Idle can do its work.
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// In setup():
xTaskCreate(BackgroundTask, "Background", 2048, NULL, 0, NULL);
Copied!

By setting it to priority 0, it will compete with Idle when it is in the Ready state. The vTaskDelay is essential to give it intervals in which it can perform kernel maintenance.