· 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.

    Related articles

    View All Articles »

    Why OTA for ESP32 Is Still Too Hard — and What I'm Building to Fix It

    Over-the-air (OTA) firmware updates are critical for modern embedded devices, but implementing a secure, automated OTA system on the ESP32 is still more complex than it should be. In this post, I break down the current state of OTA for ESP32, common pain points with existing solutions, and why I’m building an open-source, developer-friendly OTA update system with CI/CD support, firmware signing, and a lightweight OTA agent.

    Using FreeRTOS Mutex with ESP-WROVER-KIT for Resource Protection

    FreeRTOS mutexes are essential for managing shared resources in embedded systems, ensuring that only one task accesses a resource at a time. In this post, we explore how mutexes work in FreeRTOS and demonstrate their use on the ESP-WROVER-KIT by protecting UART output shared between two tasks. This practical example shows how to prevent race conditions and output conflicts, while highlighting the importance of mutual exclusion in real-time applications on the ESP32 platform.

    Understanding FreeRTOS Semaphores with ESP-WROVER-KIT

    FreeRTOS semaphores are powerful synchronization tools that help manage task coordination and shared resource access in real-time embedded systems. In this post, we explore how binary semaphores work in FreeRTOS and demonstrate their use with the ESP-WROVER-KIT by creating a simple application that synchronizes sensor data collection and UART transmission. Whether you're dealing with task-to-task signaling or interrupt-driven events, semaphores are essential for building stable and responsive applications on the ESP32 platform.