The Role of the Idle Task in FreeRTOS

The idle task is a core task automatically managed by the FreeRTOS kernel, designed to ensure the basic operation of the system when no user tasks are running. This article provides a comprehensive explanation from four dimensions: lifecycle, core functions, practical code, and execution flow, along with best practices.

01

The Lifecycle of the Idle Task

1.Creation Mechanism

When the vTaskStartScheduler() function is called to start the scheduler, the kernel automatically creates the idle task through the xIdleTaskCreate() function. Its priority is defined by configIDLE_TASK_PRIORITY (default 0, lowest priority), and the stack size is configured by configMINIMAL_STACK_SIZE.

2.Running Conditions

The idle task is scheduled to run only when there are no user tasks in the ready state (all user tasks are in blocked, suspended, or deleted states).

3.Destruction Timing

When vTaskEndScheduler() is called to stop the scheduler, the idle task is destroyed along with the kernel resources.

02

The Role and Application of the Idle Task

1.Automatic Resource Recovery

The kernel automatically recovers the TCB (Task Control Block) and stack memory of deleted tasks through the prvCheckTasksWaitingTermination() function of the idle task (no user intervention required).

2.Low Power Management

Put the MCU into low power mode (such as WFI/WFE) when idle, implemented through hook functions, or use FreeRTOS’s built-in Tickless mode.

3.System Monitoring

Statistics on CPU usage, task running status, and other low-level information to assist in system performance optimization.

4.Lightweight Background Tasks

Periodic tasks with extremely low execution priority (such as log buffer flushing, temperature sampling, etc.).

03

Reference Code

The following code is based on FreeRTOS v10.4.3.

#include "FreeRTOS.h"
#include "task.h"
#include "stm32f1xx_hal.h"  // Example for STM32, other platforms need to be replaced
/* FreeRTOS configuration (must be defined in FreeRTOSConfig.h)
#define configUSE_IDLE_HOOK        1       // Enable idle hook
#define configIDLE_TASK_PRIORITY   0       // Idle task priority (lowest)
#define configMINIMAL_STACK_SIZE   128     // Idle task stack size
#define configUSE_TICKLESS_IDLE    1       // Enable Tickless low power mode (optional)
#define configUSE_TASK_DELETION    1       // Allow task deletion*/
/************************** Global Variables (with concurrency protection) **************************/
// CPU usage statistics variable (shared between interrupts and tasks)
static volatile uint32_t ulIdleCount = 0UL;
static volatile uint32_t ulTotalCount = 0UL;
static float fCPUUsage = 0.0f;
// Test task handle
TaskHandle_t xTestTaskHandle = NULL;
/************************** Hardware Initialization **************************/
// Initialize 1ms timer (for CPU statistics)
static void TIMER_Init(void) {
    // Example for STM32 timer, other platforms need to adapt
    TIM_HandleTypeDef htim;
    htim.Instance = TIM2;
    htim.Init.Prescaler = 72 - 1;  // 72MHz -> 1MHz
    htim.Init.Period = 1000 - 1;   // 1ms interrupt
    HAL_TIM_Base_Init(&htim);
    HAL_TIM_Base_Start_IT(&htim);
}
// Timer interrupt service function (triggered every 1ms)
void TIM2_IRQHandler(void) {
    HAL_TIM_IRQHandler(&htim);
    ulTotalCount++;  // Modify shared variable in interrupt
}
/************************** Test Task **************************/
static void vTestTask(void *pvParameters) {
    for (int i = 0; i < 5; i++) {
        printf("Test task running... %d\n", i);  // Allow printing in user task
        vTaskDelay(pdMS_TO_TICKS(1000));     // Block for 1 second
    }
    // Self-delete the task, triggering resource recovery by the idle task
    printf("Test task deleted, waiting for idle task to recover resources\n");
    vTaskDelete(NULL);
}
/************************** Idle Task Hook Function **************************/
// Hook function design principles: short, non-blocking, no time-consuming operations
void vApplicationIdleHook(void) {
    // 1. Count idle time
    ulIdleCount++;
    // 2. Implement low power mode
    // Method 1: Simple low power (suitable for scenarios that do not require precise ticks)
    __WFI();  // Wait for interrupt, any interrupt can wake up (do not disable global interrupts)
    // Method 2: Use FreeRTOS Tickless mode (recommended)
    // No need to manually call WFI, the kernel will automatically calculate sleep duration
    // Must configure configUSE_TICKLESS_IDLE=1 in FreeRTOSConfig.h
}
/************************** CPU Usage Statistics Task **************************/
static void vCPUUsageTask(void *pvParameters) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));  // Statistics every second
        // When reading shared variables, interrupts must be disabled to avoid concurrency conflicts
        uint32_t ulLocalIdle, ulLocalTotal;
        taskENTER_CRITICAL();  // Enter critical section (disable interrupts)
        ulLocalIdle = ulIdleCount;
        ulLocalTotal = ulTotalCount;
        ulIdleCount = 0UL;     // Reset counter
        ulTotalCount = 0UL;
        taskEXIT_CRITICAL();   // Exit critical section
        // Calculate CPU usage
        if (ulLocalTotal > 0) {
            fCPUUsage = (1.0f - (float)ulLocalIdle / ulLocalTotal) * 100.0f;
            printf("CPU Usage: %.1f%%\n", fCPUUsage);
        }
    }
}
/************************** Main Function (System Startup Process) **************************/
int main(void) {
    // 1. Hardware initialization
    HAL_Init();
    TIMER_Init();       // Initialize statistics timer
    printf("System initialization complete\n");
    // 2. Create user task
    xTaskCreate(
        vTestTask,          // Task function
        "TestTask",         // Task name
        128,                // Stack size
        NULL,               // Parameters
        1,                  // Priority (higher than idle task)
        &xTestTaskHandle    // Task handle
    );
    // 3. Create CPU statistics task
    xTaskCreate(
        vCPUUsageTask,         "CPUUsage",         128,         NULL,         1,         NULL
    );
    // 4. Start the scheduler (idle task is created automatically)
    printf("Starting scheduler, idle task created automatically\n");
    vTaskStartScheduler();
    // 5. If the scheduler starts successfully, the following code will never execute
    for (;;);
    return 0;
}
/************************** Error Handling Hook (Optional) **************************/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
    // Stack overflow handling (e.g., reset the system)
    for (;;);
}

04

Analysis of Program Execution Flow

1.Creation Phase

After the main function calls vTaskStartScheduler(), the kernel first initializes the task scheduler, then creates the idle task through xIdleTaskCreate() (priority 0). The entry function of the idle task is prvIdleTask(), whose core logic is to loop and check if there are tasks that need resource recovery, and call the user-defined vApplicationIdleHook() hook function.

2.Running Phase

When user tasks are running: the test task (priority 1) and CPU statistics task (priority 1) run alternately, and the idle task is preempted due to its lower priority and does not execute. After user tasks finish: after the test task is deleted, there are no ready user tasks in the system, and the idle task begins to run: the kernel automatically executes prvCheckTasksWaitingTermination() to recover the memory of the test task. The vApplicationIdleHook() is called to execute user logic (idle counting, low power).

3.CPU Usage Statistics Logic

The 1ms timer interrupt accumulates total time (ulTotalCount). The idle task accumulates idle time (ulIdleCount) when running. The statistics task reads the variables protected by the critical section and calculates the usage rate: (1 – idle time/total time) × 100%.

05

Notes

Taboos for Hook Functions

Do not call any API that may block (such as vTaskDelay(), xQueueReceive() with timeout). Do not perform time-consuming operations (such as printf) to avoid delaying resource recovery or hindering low power.

Shared Resource Protection

Variables shared between interrupts and tasks (such as ulIdleCount) must be protected by critical sections (taskENTER_CRITICAL()) or interrupt masking to prevent data inconsistency.

Best Practices for Low Power

In simple scenarios, __WFI() can be called directly in the hook (without disabling interrupts). In complex scenarios, it is recommended to enable Tickless mode using configUSE_TICKLESS_IDLE, allowing the kernel to automatically optimize sleep duration and avoid frequent wake-ups.

Resource Recovery Mechanism

The resources of deleted tasks are automatically recovered by the idle task, requiring no user intervention; the hook function is only used to extend functionality (separate from recovery logic).

Previous Articles:

Brightness and Color Adjustment Program for WS2812B Based on STM32

Key Parameters and Selection Points for ADC

The Essence of FreeRTOS Task Switching

Introduction to a Domestic Low-Power Series MCU

The Difference Between Firmware and Software in Embedded Development

The Role of the Embedded “Clock Tree”

MCU Registers vs. Library Functions: Which Should You Choose?

AI Smart Ring: Small Size, Big Technological Energy

Detailed Explanation of Motor Principles and Classifications

Introduction to Peripheral Component Selection and Layout for Switching Power Supplies (DCDC)

Leave a Comment