June 17, 2025
Task Management in FreeRTOS: An Overview

Task Management in FreeRTOS: An Overview

FreeRTOS is a popular open-source real-time operating system (RTOS) designed for embedded systems. It provides a lightweight and efficient platform for managing tasks in a real-time environment. One of the core functionalities of FreeRTOS is task management, which plays a critical role in how the operating system handles concurrent operations in a system.

What are task ?

In FreeRTOS, a task is essentially a thread of execution that performs a specific function within an embedded application. Tasks are fundamental to FreeRTOS’s multitasking capability, allowing multiple operations to be performed simultaneously (or seemingly so) on a single processor. Understanding how tasks are created, scheduled, and managed is key to effectively using FreeRTOS.

Key Concepts of Task Management in FreeRTOS

1. Task Creation:

Tasks in FreeRTOS are created using the function xTaskCreate(). This function takes parameters such as a pointer to the task function, the task name, the stack size, task priorities, and other attributes.A typical task in FreeRTOS is implemented as a function that contains an infinite loop or a set of instructions that the task will repeatedly execute until the task is deleted or suspended.

syntax :

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,   // Task function
    const char * const pcName,   // Task name
    uint16_t usStackDepth,       // Stack size in words
    void *pvParameters,          // Parameters passed to the task
    UBaseType_t uxPriority,      // Task priority
    TaskHandle_t *pxCreatedTask  // Task handle (optional)
);

example :

xTaskCreate(taskFunction, "Task1", 128, NULL, 2, NULL);

  • taskFunction: This is the function that defines the task’s behavior.
  • Task1″: The name of the task.
  • 128: The stack size for the task.
  • NULL: The parameter that will be passed to the task function.
  • 2: The priority of the task (higher values indicate higher priority).
  • NULL: A handle for the task, which can be used later for task manipulation

2. Task Scheduling:

  • FreeRTOS uses a preemptive scheduling algorithm (in most configurations) to manage tasks. This means that the scheduler will automatically switch between tasks based on their priorities or other conditions (such as task delays or waiting for an event).
  • FreeRTOS tasks have priorities, and the scheduler always runs the highest-priority ready task. If two tasks share the same priority, FreeRTOS performs round-robin scheduling, giving each task a slice of CPU time.

3. Task States: FreeRTOS tasks can exist in one of several states:

  • Running: The task is currently executing.
  • Ready: The task is ready to execute but is not running because the scheduler has chosen another task with higher priority.
  • Blocked: The task is waiting for an event, such as a semaphore or a message queue.
  • Suspended: The task is explicitly suspended and will not be scheduled until resumed.
  • Deleted: The task has been deleted and will not be scheduled again.

4. Task Delays:FreeRTOS provides several mechanisms to manage the execution flow of tasks, one of which is task delays. Tasks can be delayed for a fixed amount of time using vTaskDelay() or until a specific event occurs with functions like xQueueReceive() or xSemaphoreTake().

vTaskDelay: This function delays a task for a specified number of ticks (a tick is the basic unit of time in FreeRTOS, determined by the system’s tick rate).

vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for 1000ms (1 second)

5. Task Deletion and Cleanup:

  • Once a task has completed its work, or if it’s no longer needed, it can be deleted using vTaskDelete(). This function removes the task from the scheduler and frees its resources.
  • The deletion of a task may also trigger a cleanup of allocated resources such as memory or communication channels.
vTaskDelete(NULL); // Delete the calling task

6. Task Priorities in FreeRTOS

FreeRTOS assigns priorities to tasks to determine the order in which tasks are executed. Higher priority tasks are executed before lower priority tasks, and in a preemptive scheduling environment, a higher priority task will preempt a lower priority task if the higher priority task becomes ready to run.

Task priorities are specified when a task is created and can be adjusted dynamically using functions like vTaskPrioritySet(). FreeRTOS supports multiple priorities, and the priority level is typically defined as a numeric value, where a higher number indicates a higher priority.

// Change the priority of Task 2 to 0 (lower priority)
vTaskPrioritySet(Task2, 0);

Working examples

Basic Task Creation

With ESP-IDF

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

// Task 1 function (prints "Task 1 is running")
void task1_function(void *pvParameter)
{
    while(1) {
        printf("Task 1 is running\n");
        vTaskDelay(1000 / portTICK_PERIOD_MS);  // Delay for 1000ms (1 second)
    }
}

// Task 2 function (prints "Task 2 is running")
void task2_function(void *pvParameter)
{
    while(1) {
        printf("Task 2 is running\n");
        vTaskDelay(500 / portTICK_PERIOD_MS);  // Delay for 500ms
    }
}

void app_main(void)
{
    // Create Task 1 with priority 1
    xTaskCreate(task1_function, "task1", 2048, NULL, 1, NULL);

    // Create Task 2 with priority 2
    xTaskCreate(task2_function, "task2", 2048, NULL, 2, NULL);
    
    // Wait for 5 seconds before changing the task priority
    vTaskDelay(5000 / portTICK_PERIOD_MS);  // Delay for 5000ms (5 seconds)
    
    // Change the priority of Task 1 to 3 dynamically using vTaskPrioritySet
    vTaskPrioritySet(task1_handle, 3);

    // Optionally, change the priority of Task 2 to 0 (lowest priority)
    vTaskPrioritySet(task2_handle, 0);

}

Notes:

  • vTaskDelay() is a non-blocking function that allows FreeRTOS to switch between tasks while waiting for the specified time. This is why Task 2 will continue to execute during the 5-second delay and won’t be blocked.
  • Task preemption occurs when a task with a higher priority becomes ready to run. In this case, after 5 seconds, Task 1 will have a higher priority and will run more frequently than Task 2.

On Arduino

if you want to use Arduino library :

#include <Arduino.h>

// Task handles
TaskHandle_t Task1;
TaskHandle_t Task2;

// Task 1 function
void Task1Function(void *parameter) {
  while (true) {
    Serial.println("Task 1 is running");
    vTaskDelay(1000 / portTICK_PERIOD_MS);  // Delay for 1000 ms
  }
}

// Task 2 function
void Task2Function(void *parameter) {
  while (true) {
    Serial.println("Task 2 is running");
    vTaskDelay(500 / portTICK_PERIOD_MS);   // Delay for 500 ms
  }
}

void setup() {
  Serial.begin(115200);
  
  // Create tasks with initial priority (1)
  xTaskCreate(Task1Function, "Task 1", 1000, NULL, 1, &Task1);
  xTaskCreate(Task2Function, "Task 2", 1000, NULL, 1, &Task2);
  
  // Wait for a moment before changing task priorities
  delay(5000);  // 5 seconds
  
  // Change the priority of Task 1 to 2 (higher priority)
  vTaskPrioritySet(Task1, 2);
  
  // Change the priority of Task 2 to 0 (lower priority)
  vTaskPrioritySet(Task2, 0);
}

void loop() {
  // Nothing in loop, FreeRTOS tasks are running
}

Explanation

  • Task Creation:We create two tasks (Task1 and Task2) with an initial priority of 1.The task created by xTaskCreate() in FreeRTOS will start executing immediately after the function call (unless there are other system constraints like available CPU time, higher-priority tasks, etc.)
  • Priority Change:After a delay of 5 seconds (delay(5000)), we change the priorities of the tasks.
  • delay function of Arduino library is a blocking function .

Basic Task Deletion

To stop or delete a task in FreeRTOS, you can use the vTaskDelete() function. This function removes the task from the scheduler and frees the resources associated with it (such as memory and stack). Once a task is deleted, it will no longer be scheduled or executed.

example-1 (deletion by Task handle)



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

TaskHandle_t task1Handle = NULL; // Declare a handle for task1
TaskHandle_t task2Handle = NULL; // Declare a handle for task1


// Task 1 function (prints "Task 1 is running")
void task1_function(void *pvParameter)
{
    while(1) {
        printf("Task 1 is running\n");
        vTaskDelay(1000 / portTICK_PERIOD_MS);  // Delay for 1000ms (1 second)
    }
}

// Task 2 function (prints "Task 2 is running")
void task2_function(void *pvParameter)
{
    while(1) {
        printf("Task 2 is running\n");
        vTaskDelay(500 / portTICK_PERIOD_MS);  // Delay for 500ms
    }
}

void app_main(void)
{
    // Create Task 1 with priority 1
    xTaskCreate(task1_function, "task1", 2048, NULL, 1, &task1Handle);

    // Create Task 2 with priority 2
    xTaskCreate(task2_function, "task2", 2048, NULL, 2, &task2Handle);

    vTaskDelay(5000 / portTICK_PERIOD_MS);  // Delay for 5000ms (5 seconds)

    
    printf("Task2 is deleting itself\n");
    vTaskDelete(task1Handle);  // This will delete Task2 itself
    printf("Task2 has deleted\n");

    printf("Task1 is deleting itself\n");
    vTaskDelete(task2Handle);  // This will delete Task2 itself
    printf("Task1 has deleted\n");

}

example-2 (self deletion) [using Arduino lib]

#include <Arduino.h>

TaskHandle_t task1Handle = NULL; // Declare a handle for task1

// This is the task function for task1
void Task1(void *pvParameters) {
  Serial.println("Task1 started");
  
  // Task 1 runs in an infinite loop
  for (int i = 0; i < 5; i++) {
    Serial.printf("Task1 is running: %d\n", i);
    vTaskDelay(1000 / portTICK_PERIOD_MS);  // Delay for 1 second
  }

  // Delete task1 itself after running 5 times
  Serial.println("Task1 is deleting itself");
  vTaskDelete(task1Handle);  // This will delete task1

  // Code after vTaskDelete won't be executed
  Serial.println("This will not print");
}

// This is the task function for task2
void Task2(void *pvParameters) {
  Serial.println("Task2 started");

  for (int i = 0; i < 5; i++) {
    Serial.printf("Task2 is running: %d\n", i);
    vTaskDelay(500 / portTICK_PERIOD_MS);  // Delay for 0.5 seconds
  }

  Serial.println("Task2 is deleting itself");
  vTaskDelete(NULL);  // This will delete Task2 itself
}

void setup() {
  Serial.begin(115200);  // Start serial communication

  // Create task1
  xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, &task1Handle, 0);
  
  // Create task2
  xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, NULL, 1);
}

void loop() {
  // Main loop can stay empty or can include other logic
  // In this example, tasks are managed by FreeRTOS
}

Key Notes:

  • vTaskDelete() stops a task from running and cleans up its resources, freeing memory associated with the task.
  • If you call vTaskDelete(NULL), it will delete the current task, which is useful for deleting tasks from within themselves.
  • Make sure not to call vTaskDelete() on the idle task or the main loop task, as this could cause a crash or unexpected behavior.

example3- Pinning to specific core :

Sometimes we want to pin a FreeRTOS task to a specific core (CPU 0 or CPU 1) on the ESP32, which is a dual-core microcontroller. For we will use xTaskCreatePinnedToCore function to create task .

#include <Arduino.h>

// Define task handles
TaskHandle_t taskHandle1 = NULL;
TaskHandle_t taskHandle2 = NULL;

// Task 1: This task will run on Core 0
void Task1(void *pvParameters) {
  while (true) {
    Serial.println("Task1 is running on Core 0");
    vTaskDelay(1000 / portTICK_PERIOD_MS);  // Delay for 1 second
  }
}

// Task 2: This task will run on Core 1
void Task2(void *pvParameters) {
  while (true) {
    Serial.println("Task2 is running on Core 1");
    vTaskDelay(1500 / portTICK_PERIOD_MS);  // Delay for 1.5 seconds
  }
}

void setup() {
  // Initialize Serial
  Serial.begin(115200);
  delay(1000); // Wait for serial monitor to start

  // Create Task1 and pin it to Core 0
  xTaskCreatePinnedToCore(
    Task1,                    // Function to run as task
    "Task1",                  // Name of the task
    2048,                     // Stack size (in words)
    NULL,                     // Parameters for the task function
    1,                        // Priority of the task (0 is the lowest)
    &taskHandle1,             // Handle to store the task handle
    0                         // Pin this task to Core 0 (0 or 1)
  );

  // Create Task2 and pin it to Core 1
  xTaskCreatePinnedToCore(
    Task2,                    // Function to run as task
    "Task2",                  // Name of the task
    2048,                     // Stack size (in words)
    NULL,                     // Parameters for the task function
    1,                        // Priority of the task (0 is the lowest)
    &taskHandle2,             // Handle to store the task handle
    1                         // Pin this task to Core 1 (0 or 1)
  );
}

void loop() {
  // Main loop is empty as tasks are running in the background
  // You can add other logic here if needed
}

example-4 Passing parameters to Task

In FreeRTOS, you can pass parameters to tasks when you create them by using the pvParameters argument in the task function. This argument is a void* pointer, which allows you to pass any data type (such as an integer, struct, or pointer) to the task.

To illustrate this, let’s create a working example where we pass a parameter to a task. In this example, we will pass a pointer to an integer to a task, and the task will print the value of that integer periodically.

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

// Define a struct to hold some parameters
typedef struct {
    int param1;
    float param2;
} TaskParams;

// Task function to print a passed integer parameter
void task_function(void *pvParameter)
{
    // Cast the parameter back to the correct type (in this case, an integer pointer)
    int *param_value = (int*)pvParameter;

    while(1) {
        // Print the value passed to the task
        printf("The passed value is: %d\n", *param_value);
        
        // Wait for 1 second (1000ms)
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// Task function to print parameters of the struct
void task_function2(void *pvParameter)
{
    // Cast the parameter back to the correct struct type
    TaskParams *params = (TaskParams*)pvParameter;

    while(1) {
        // Print the parameters of the struct
        printf("param1 = %d, param2 = %.2f\n", params->param1, params->param2);
        
        // Wait for 1 second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main(void)
{
    // Define an integer value that we want to pass to the task
    int task_param = 42;
    
    // Create an instance of TaskParams and initialize it
    TaskParams task_params = { 42, 3.14f };


    // Create a task and pass the address of task_param as the parameter
    xTaskCreate(task_function, "TaskWithParameter", 2048, &task_param, 1, NULL);
    
    // Create a task and pass the address of task_params to it
    xTaskCreate(task_function2, "TaskWithStruct", 2048, &task_params, 1, NULL);
}

example-5 suspending and Resuming task


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

// Handle for the Producer task
TaskHandle_t producerTaskHandle = NULL;

// Timer handle
TimerHandle_t resumeTimerHandle;

// Timer callback function to resume the Producer task
void vResumeProducerTask(TimerHandle_t xTimer) {
    ESP_LOGI("Timer", "Timer triggered! Resuming producer task.");
    // Resume the producer task
    vTaskResume(producerTaskHandle);
}

// Producer Task: simulates data production
void producerTask(void *pvParameters) {
    int counter = 0;
    while (1) {
        // Simulate data production
        ESP_LOGI("Producer", "Produced data: %d", counter);
        counter++;

        // Suspend the task after each production
        vTaskSuspend(NULL);  // Suspend the task itself
        ESP_LOGI("Producer", "Task resumed.");
    }
}

// Main task to create the timer and tasks
void app_main(void) {
    // Create the Producer task
    xTaskCreate(producerTask, "Producer", 2048, NULL, 2, &producerTaskHandle);

    // Create a software timer that triggers every 1000ms (1 second)
    resumeTimerHandle = xTimerCreate(
        "ResumeTimer",                     // Timer name
        pdMS_TO_TICKS(5000),                // Timer period in ticks (1000 ms = 1 second)
        pdTRUE,                             // Autoreload (start over after firing)
        (void *)0,                          // Timer ID (unused)
        vResumeProducerTask                // Timer callback function
    );

    if (resumeTimerHandle == NULL) {
        ESP_LOGE("Main", "Failed to create timer.");
        return;
    }

    // Start the timer
    if (xTimerStart(resumeTimerHandle, 0) != pdPASS) {
        ESP_LOGE("Main", "Failed to start timer.");
        return;
    }

    ESP_LOGI("Main", "Main app started. Producer task will resume periodically.");
}

Conclusion

Task management is one of the central features of FreeRTOS that allows for efficient and deterministic multitasking in embedded systems. Understanding how tasks are created, scheduled, and synchronized is essential for optimizing the performance and responsiveness of an embedded application. FreeRTOS makes it easier for developers to implement complex, time-critical applications without the overhead of traditional operating systems, making it an ideal choice for many embedded and IoT devices.

Leave a Reply

Your email address will not be published. Required fields are marked *