· Esp32 · 4 min read
Task Synchronization with FreeRTOS Event Groups on ESP32 (ESP-WROVER-KIT Example)
FreeRTOS event groups provide an efficient way to synchronize tasks based on multiple conditions using binary flags. In this post, we explore how to use event groups on the ESP-WROVER-KIT with ESP-IDF. Through a practical example involving sensor and network initialization, we demonstrate how event bits can signal system readiness to a processing task. This approach enables clean, scalable task coordination in real-time embedded applications using the ESP32.
Real-time operating systems often require flexible and efficient mechanisms to synchronize multiple tasks or signal task execution based on multiple conditions. Event groups in FreeRTOS provide a powerful way to manage such task synchronization. Unlike semaphores or mutexes that handle individual signals or locks, event groups can handle multiple binary flags in a single object—allowing tasks to wait for one or more events simultaneously.
In this post, we’ll dive into FreeRTOS event groups and demonstrate how to use them on the ESP-WROVER-KIT, a development board based on the ESP32 microcontroller. You’ll learn what event groups are, how they differ from other synchronization primitives, and how to apply them using a real-world example.
What Are Event Groups?
An event group in FreeRTOS is a collection of binary flags (bits) that tasks can set, clear, and wait on. Each event group contains up to 24 user-definable bits (bits 0–23). A task can:
- Wait for one or more bits to be set.
- Specify whether to wait for any or all of the bits.
- Block until the required condition is met or a timeout occurs.
- Automatically clear bits once the wait condition is satisfied.
Event groups are ideal for:
- Task-to-task synchronization based on multiple signals.
- Waiting on multiple conditions (e.g., Wi-Fi connected + sensor ready).
- Complex ISR-to-task signaling (via
xEventGroupSetBitsFromISR).
Event Groups API Overview
FreeRTOS provides the following API functions for event groups:
Create an event group:
EventGroupHandle_t xEventGroupCreate(void);Set bits in the group:
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);Clear bits:
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);Wait for bits:
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);Set bits from ISR:
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken);Example Scenario: Task Synchronization Using Multiple Events
Let’s say we have a system with the following components:
- Sensor Task: Simulates a sensor becoming ready.
- Network Task: Simulates a network connection becoming active.
- Processing Task: Waits until both the sensor is ready and the network is connected before proceeding.
We’ll use event groups to signal these conditions and synchronize the processing task.
Define Event Bits
#define SENSOR_READY_BIT (1 << 0)
#define NETWORK_READY_BIT (1 << 1)These bits will be set by the sensor and network tasks respectively.
Declare Global Event Group Handle
EventGroupHandle_t system_event_group;Sensor Task
void sensor_task(void *pvParameters)
{
while (true)
{
ESP_LOGI("SensorTask", "Initializing sensor...");
vTaskDelay(pdMS_TO_TICKS(2000)); // Simulate delay
ESP_LOGI("SensorTask", "Sensor ready");
xEventGroupSetBits(system_event_group, SENSOR_READY_BIT);
vTaskDelete(NULL); // One-time initialization
}
}Network Task
void network_task(void *pvParameters)
{
while (true)
{
ESP_LOGI("NetworkTask", "Connecting to network...");
vTaskDelay(pdMS_TO_TICKS(3000)); // Simulate delay
ESP_LOGI("NetworkTask", "Network connected");
xEventGroupSetBits(system_event_group, NETWORK_READY_BIT);
vTaskDelete(NULL); // One-time initialization
}
}Processing Task
void processing_task(void *pvParameters)
{
ESP_LOGI("ProcessingTask", "Waiting for system to be ready...");
EventBits_t bits = xEventGroupWaitBits(
system_event_group,
SENSOR_READY_BIT | NETWORK_READY_BIT,
pdTRUE, // Clear bits on exit
pdTRUE, // Wait for all bits
portMAX_DELAY
);
if ((bits & (SENSOR_READY_BIT | NETWORK_READY_BIT)) == (SENSOR_READY_BIT | NETWORK_READY_BIT))
{
ESP_LOGI("ProcessingTask", "System ready. Starting data processing...");
}
while (true)
{
ESP_LOGI("ProcessingTask", "Processing data...");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}app_main Function
void app_main(void)
{
system_event_group = xEventGroupCreate();
if (system_event_group == NULL)
{
ESP_LOGE("Main", "Failed to create event group");
return;
}
xTaskCreate(sensor_task, "SensorTask", 2048, NULL, 2, NULL);
xTaskCreate(network_task, "NetworkTask", 2048, NULL, 2, NULL);
xTaskCreate(processing_task, "ProcessingTask", 2048, NULL, 2, NULL);
}Expected Output
Your serial monitor will show:
SensorTask: Initializing sensor...
NetworkTask: Connecting to network...
SensorTask: Sensor ready
NetworkTask: Network connected
ProcessingTask: System ready. Starting data processing...
ProcessingTask: Processing data...
ProcessingTask: Processing data...
...This demonstrates how the processing task waits for multiple conditions to be met using event groups.
Tips for Using Event Groups
- Use bitwise flags for clarity and control.
- Event bits 24–31 are reserved for internal use by FreeRTOS.
- Use
xEventGroupSetBitsFromISR()when setting bits inside an ISR. - Always test edge cases, such as missed signals or simultaneous bit setting.
- Be mindful of race conditions if multiple tasks are modifying bits simultaneously.
Summary
FreeRTOS event groups are a flexible synchronization tool for embedded systems. They allow multiple tasks to set, clear, and wait for bit-level events efficiently. Unlike semaphores and mutexes, event groups are ideal when multiple conditions must be met before proceeding.
In this post, we used the ESP-WROVER-KIT and ESP-IDF to build a simple system where two tasks signal when their components are ready, and a third task waits for both to proceed. This type of synchronization is essential in many real-world embedded applications, including sensor fusion, communication stacks, and power management systems.
Whether you’re synchronizing complex subsystems or managing multiple states, event groups offer a clean and scalable solution for FreeRTOS-based projects on ESP32.