Mastering ESP32 FreeRTOS tutorial tasks queues unlocks the full potential of the ESP32’s dual-core architecture. FreeRTOS (Free Real-Time Operating System) is built into every ESP32 running Arduino or ESP-IDF, and understanding tasks, queues, and semaphores is essential for building robust multi-function IoT projects.
Table of Contents
- Why FreeRTOS on ESP32
- Creating and Managing Tasks
- Using Queues for Task Communication
- Semaphores and Mutexes
- Software Timers
- Best Practices for Indian IoT Projects
- Frequently Asked Questions
Why FreeRTOS on ESP32
ESP32’s dual-core Xtensa LX6 processor runs two cores simultaneously. FreeRTOS lets you pin tasks to specific cores, ensuring WiFi/Bluetooth stays on Core 0 while your sensor reading and UI run on Core 1 — preventing WiFi callbacks from interfering with real-time sensor sampling. This is critical for reliable ESP32 FreeRTOS tasks in production IoT devices.
Creating and Managing Tasks
Each FreeRTOS task is a function running in its own stack. Use xTaskCreatePinnedToCore() on ESP32 to assign tasks to specific cores.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Task for sensor reading on Core 1
void sensorTask(void *pvParameters) {
for (;;) {
int temp = readTemperature(); // Your sensor function
Serial.printf("Temp: %d C
", temp);
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 second delay
}
}
// Task for WiFi/MQTT on Core 0
void mqttTask(void *pvParameters) {
for (;;) {
mqttClient.loop();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void setup() {
Serial.begin(115200);
// Stack: 4096 bytes, priority 1, Core 1
xTaskCreatePinnedToCore(sensorTask, "SensorTask", 4096, NULL, 1, NULL, 1);
// Stack: 8192 bytes, priority 2, Core 0
xTaskCreatePinnedToCore(mqttTask, "MQTTTask", 8192, NULL, 2, NULL, 0);
}
Using Queues for Task Communication
Queues provide thread-safe data exchange between tasks. Never share global variables directly between tasks — always use queues or semaphores. In IoT projects, a sensor task pushes readings to a queue, and a WiFi task consumes them for upload.
#include "freertos/queue.h"
QueueHandle_t sensorQueue;
struct SensorData {
float temperature;
float humidity;
};
void sensorTask(void *pvParameters) {
SensorData data;
for (;;) {
data.temperature = 28.5; // Read from DHT22
data.humidity = 65.0;
xQueueSend(sensorQueue, &data, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void uploadTask(void *pvParameters) {
SensorData data;
for (;;) {
if (xQueueReceive(sensorQueue, &data, portMAX_DELAY)) {
// Upload to MQTT or HTTP
publishToMQTT(data.temperature, data.humidity);
}
}
}
void setup() {
sensorQueue = xQueueCreate(10, sizeof(SensorData));
xTaskCreatePinnedToCore(sensorTask, "Sensor", 4096, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(uploadTask, "Upload", 8192, NULL, 2, NULL, 0);
}
Semaphores and Mutexes
Use mutex semaphores to protect shared resources like I2C buses, SPI displays, or a shared Serial port from concurrent access by multiple tasks.
#include "freertos/semphr.h"
SemaphoreHandle_t i2cMutex;
void taskA(void *pvParameters) {
for (;;) {
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(100))) {
// Safe I2C access
readI2CSensor();
xSemaphoreGive(i2cMutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void setup() {
i2cMutex = xSemaphoreCreateMutex();
xTaskCreate(taskA, "TaskA", 4096, NULL, 1, NULL);
}
Software Timers
FreeRTOS software timers call a callback function after a specified period, without blocking a task. Useful for periodic LED blinks, timeout watchdogs, or debouncing buttons.
TimerHandle_t ledTimer;
void ledTimerCallback(TimerHandle_t xTimer) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
void setup() {
ledTimer = xTimerCreate("LED", pdMS_TO_TICKS(500), pdTRUE, 0, ledTimerCallback);
xTimerStart(ledTimer, 0);
}
Best Practices for Indian IoT Projects
- Always allocate sufficient stack — start with 4096 bytes and increase if you see stack overflow crashes
- Use
uxTaskGetStackHighWaterMark()to check actual stack usage - Pin WiFi-related tasks to Core 0 — ESP32’s WiFi stack runs on Core 0
- Use
vTaskDelay()instead ofdelay()— it yields to other tasks - For tasks that run rarely, use event groups or binary semaphores instead of polling loops
- Always initialise peripherals (I2C, SPI, Serial) before creating tasks
Frequently Asked Questions
How many tasks can run simultaneously on ESP32?
Theoretically unlimited by FreeRTOS, but practically limited by available SRAM (520 KB). Each task needs a stack (minimum 512 bytes, typically 2–8 KB). A realistic limit is 10–15 concurrent tasks.
What is the difference between vTaskDelay and delay on ESP32?
delay() busy-waits and blocks the entire core. vTaskDelay() puts the task into a blocked state, allowing other tasks to run — always use vTaskDelay() in FreeRTOS tasks.
How do I avoid stack overflow in FreeRTOS tasks?
Enable stack overflow detection with configCHECK_FOR_STACK_OVERFLOW 2 in FreeRTOSConfig.h, or call uxTaskGetStackHighWaterMark(NULL) periodically to monitor remaining stack space.
Add comment