Handling FreeRTOS Queues and Tasks on Dual-Core ESP32
Handling FreeRTOS queues and tasks on dual-core ESP32 microcontrollers is essential when writing reliable production firmware. Moving beyond monolithic loop() architectures prevents blocking operations during network handshakes and ensures critical sensors sample deterministically.
1. Pinning tasks to specific CPU cores
The ESP32 features two Xtensa LX6/LX7 cores (Core 0 and Core 1). Core 0 typically manages underlying WiFi and Bluetooth radio stacks, while Core 1 executes Arduino user code. Use xTaskCreatePinnedToCore() to assign time-critical sampling tasks explicitly to Core 1 with high priority.
2. Thread-safe inter-task communication with FreeRTOS queues
Never share global variables directly between concurrently executing FreeRTOS tasks without synchronization. FreeRTOS Queues provide FIFO, thread-safe memory buffers that transfer data by value:
- Initialize the queue buffer using
xQueueCreate(queue_length, sizeof(DataType)). - Push telemetry data from sensor tasks using
xQueueSend(queue, &data, portMAX_DELAY). - Consume data in telemetry publishing tasks with
xQueueReceive(queue, &buffer, timeout).
3. Mutexes vs Semaphores for shared bus protection
When multiple tasks access a single hardware bus (such as I2C or SPI), wrap transactions inside FreeRTOS Mutexes (xSemaphoreCreateMutex()). This eliminates race conditions and sensor corruption. For peripheral wiring considerations, review our guide on I2C, SPI, and UART protocols[cite: 2].
4. Avoiding Task Watchdog Timer (TWDT) panics
FreeRTOS tasks must yield execution back to lower-priority kernel handlers. Infinite loops without vTaskDelay(pdMS_TO_TICKS(10)) trigger the Task Watchdog Timer, causing unexpected system panics and core reboots.
When to get help
If you'd rather have this handled directly, see our Firmware Architecture Services for scope and turnaround times.