Introduction
FreeRTOS is one of the most popular real-time operating systems for microcontrollers. It brings multitasking capabilities to embedded systems by providing essential features like task scheduling, inter-task communication, synchronization, and time management.
The ESP-WROVER-KIT, powered by the ESP32 SoC, is a powerful development board ideal for FreeRTOS experimentation. It supports dual-core processing, ample RAM, and comes preloaded with FreeRTOS as part of the ESP-IDF (Espressif IoT Development Framework).
This guide focuses on understanding and using FreeRTOS Tasks—the building blocks of concurrent applications.
What Are FreeRTOS Tasks?
A task in FreeRTOS is a function that executes independently and can be scheduled alongside other tasks. It is equivalent to a thread in general operating system terminology.
Tasks allow you to divide your embedded application into concurrent modules—e.g., one for handling sensors, another for communication, and yet another for user interface or data logging.
ESP-WROVER-KIT and ESP-IDF
Before diving into tasks, let’s set up a minimal development environment.
Hardware Required
- ESP-WROVER-KIT (ESP32)
- USB-C cable
- A computer with ESP-IDF installed
Software Prerequisites
- ESP-IDF version 5.x or later
- VS Code with Espressif plugin (optional but recommended)
- Python 3.x
Once ESP-IDF is installed and configured, we can start creating FreeRTOS tasks.
Anatomy of a FreeRTOS Task
Here’s what you need to know about a task:
- A task is a function of type
void
with avoid *pvParameters
parameter. - It is created using
xTaskCreate()
orxTaskCreatePinnedToCore()
. - It must include an infinite loop (
while (1)
), unless designed to self-delete.
Here is a generic task function:
void myTask(void *pvParameters) {
while (1) {
// Task code here
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for 1 second
}
}
Example 1: Blinking an LED with a Task
Let’s write a simple task that blinks an LED connected to GPIO2.
Code
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED_PIN GPIO_NUM_2
void blinkTask(void *pvParameters) {
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(LED_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(LED_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void) {
xTaskCreate(blinkTask, "Blink LED", 2048, NULL, 5, NULL);
}
Explanation
gpio_set_level()
sets the GPIO high/low.vTaskDelay()
pauses the task for a period.xTaskCreate()
creates and schedules the task.
Task Parameters
Tasks can receive input parameters via the pvParameters
argument. This is useful for creating reusable task functions.
Example: Multiple Blinking LEDs
typedef struct {
gpio_num_t pin;
int delay_ms;
} led_config_t;
void genericBlinkTask(void *pvParameters) {
led_config_t *cfg = (led_config_t *)pvParameters;
gpio_set_direction(cfg->pin, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(cfg->pin, 1);
vTaskDelay(pdMS_TO_TICKS(cfg->delay_ms));
gpio_set_level(cfg->pin, 0);
vTaskDelay(pdMS_TO_TICKS(cfg->delay_ms));
}
}
void app_main(void) {
static led_config_t led1 = { GPIO_NUM_2, 500 };
static led_config_t led2 = { GPIO_NUM_4, 1000 };
xTaskCreate(genericBlinkTask, "Blink LED1", 2048, &led1, 5, NULL);
xTaskCreate(genericBlinkTask, "Blink LED2", 2048, &led2, 5, NULL);
}
Task Priorities
FreeRTOS allows assigning priorities from 0 (lowest) to configMAX_PRIORITIES-1
.
A higher number means a higher priority. Tasks with the same priority are scheduled using round-robin.
You can adjust priority like this:
xTaskCreate(myTask, "MyTask", 2048, NULL, 10, NULL); // Higher priority
Starvation Warning
Tasks with high priority can starve lower-priority tasks if they never yield or delay. Always use vTaskDelay()
or taskYIELD()
to allow context switching.
Task Handles and Deletion
You can manage tasks using a TaskHandle_t
.
TaskHandle_t taskHandle = NULL;
xTaskCreate(myTask, "ManagedTask", 2048, NULL, 5, &taskHandle);
vTaskDelete(taskHandle); // Delete from another task
vTaskDelete(NULL); // Delete self
Task Lifecycle
Tasks can be:
- Created: with
xTaskCreate
- Running: when scheduled
- Blocked: using
vTaskDelay
,xQueueReceive
, etc. - Suspended: using
vTaskSuspend
- Deleted: using
vTaskDelete
FreeRTOS uses preemptive multitasking, so higher-priority tasks interrupt lower ones.
Debugging Tips with ESP-WROVER-KIT
The ESP-WROVER-KIT supports JTAG debugging. Here are a few suggestions to debug tasks:
- Use
vTaskList()
to list all tasks and their states. - Use logging with
ESP_LOGI
,ESP_LOGW
, etc. - Monitor heap usage with
xPortGetFreeHeapSize()
.
Example:
#include "esp_log.h"
ESP_LOGI("Heap", "Free heap: %d bytes", xPortGetFreeHeapSize());
Example 2: Temperature Sensor Task + Logging Task
Let’s assume you have a temperature sensor and want to read it periodically while another task logs the readings.
Sensor Task (Simulated)
static float temperature = 0;
void temperatureTask(void *pvParameters) {
while (1) {
temperature = 20.0 + (rand() % 1000) / 100.0; // Fake value
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
Logger Task
void loggerTask(void *pvParameters) {
while (1) {
ESP_LOGI("Logger", "Temperature: %.2f C", temperature);
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
app_main
void app_main(void) {
xTaskCreate(temperatureTask, "TempTask", 2048, NULL, 5, NULL);
xTaskCreate(loggerTask, "LoggerTask", 2048, NULL, 4, NULL);
}
Advanced Features
FreeRTOS offers more advanced task management:
Task Notifications
Lightweight inter-task signaling:
xTaskNotifyGive(taskHandle); // From ISR or other task
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for notification
CPU Core Pinning (ESP32 Only)
You can bind tasks to a specific core using xTaskCreatePinnedToCore
.
xTaskCreatePinnedToCore(taskFunc, "Task", 2048, NULL, 5, NULL, 0); // Core 0
Useful for balancing load or isolating timing-critical tasks.
Summary
FreeRTOS tasks are fundamental to writing efficient, organized, and responsive embedded applications. With the ESP-WROVER-KIT and ESP-IDF, you can build real-time systems that take full advantage of FreeRTOS features.
We covered:
- Creating tasks with
xTaskCreate
- Managing task priorities
- Passing parameters to tasks
- Deleting tasks and using task handles
- Debugging and logging tasks
- Using real-world examples like blinking LEDs and simulated sensor data
Sample Project Ideas
To solidify your understanding, try these sample projects using tasks:
- Multicolor LED Controller – Use three tasks to control RGB LEDs with different blinking patterns.
- UART Logger – One task reads from a sensor, another writes logs to UART.
- Wi-Fi Scanner – A periodic task scans available networks and logs them.
- Data Logger – Read ADC values and save them to SPIFFS using separate tasks.
- Weather Station – Combine temperature, humidity, and pressure tasks that share data with a display task.
Conclusion
FreeRTOS tasks are powerful tools to organize complex embedded applications into manageable, concurrent processes. By mastering task creation, scheduling, and synchronization, you’re well on your way to building responsive and scalable firmware for any real-time application.
Start small, experiment with task priorities and delays, and gradually layer in real-world complexity. The ESP-WROVER-KIT, combined with FreeRTOS, is an excellent platform to begin your real-time programming journey.
Happy coding!