Un Event Group es un conjunto de bits que permite esperar una o varias condiciones de sincronización dentro de FreeRTOS.
Hasta ahora hemos aprendido a sincronizar tareas usando Semáforos. Esto funciona genial si la condición es simple: “Espera a que se pulse el botón” o “Espera a que el buffer esté libre”.
La cosa se complica cuando programamos un dispositivo IoT con ESP32 y queremos que la tarea principal empiece a trabajar SOLO SI:
- Nos hemos conectado al WiFi.
- Y nos hemos conectado al servidor MQTT.
- Y la hora se ha sincronizado por NTP.
Si intentamos hacer esto con semáforos binarios, tendríamos que anidar tres xSemaphoreTake, lo cual es feo, ineficiente y propenso a errores (orden de bloqueo, timeouts parciales…).
Para estas situaciones, FreeRTOS nos ofrece los Event Groups (grupos de eventos), una herramienta pensada para combinar condiciones mediante bits.
¿Qué es un Event Group?
Imagina un Event Group como un conjunto de bits, donde cada uno representa una condición independiente.
Cada bit de ese número actúa como una bandera (Flag) independiente.
- El Bit 0 puede significar “WiFi Conectado”.
- El Bit 1 puede significar “Mensaje Recibido”.
- El Bit 2 puede significar “Motor Parado”.
Lo potente no es almacenar bits (eso lo hace una variable int normal), sino que FreeRTOS nos permite dormir una tarea hasta que una combinación específica de bits se cumpla.
Los 8 bits superiores están reservados para el kernel. Con EventBits_t de 32 bits, como en ESP32, quedan 24 bits disponibles. Los ports configurados con ticks de 16 bits solo ofrecen 8 bits de aplicación.
Comparación con semáforos y task notifications
Resumen para elegir sabiamente:
| Característica | Semáforo Binario | Task Notification | Event Group |
|---|---|---|---|
| Condición | 1 Evento | 1 Evento (o valor simple) | Múltiples Eventos (AND/OR) |
| Destinatario | Cualquiera que haga Take | Tarea específica | Múltiples tareas a la vez |
| Memoria | Objeto independiente | Estado dentro del TCB | Objeto independiente |
| Uso Típico | ISR a Tarea | ISR a Tarea (Rápido) | Máquinas de Estados, Startup |
Definición de los flags
Lo primero es definir qué significa cada bit. Usaremos máscaras de bits (1 << n) o constantes hexadecimales.
// Definimos los bits que vamos a usar
#define BIT_WIFI_CONECTADO (1 << 0) // 0000 0001
#define BIT_MQTT_CONECTADO (1 << 1) // 0000 0010
#define BIT_BOTON_PULSADO (1 << 2) // 0000 0100API de Event Groups
Necesitamos incluir la librería específica (aunque en ESP32 viene incluida por defecto con FreeRTOS).
Creación
#include "freertos/event_groups.h"
EventGroupHandle_t xEventos;
void setup() {
xEventos = xEventGroupCreate();
}Establecer bits (SetBits)
Cuando ocurre un evento (ej. el WiFi se conecta), una tarea o interrupción “levanta” la bandera correspondiente.
// Desde una Tarea
xEventGroupSetBits(xEventos, BIT_WIFI_CONECTADO);
// Desde una Interrupción (ISR)
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(xEventos, BIT_BOTON_PULSADO, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}Esta operación hace un OR a nivel de bits. Si ya había otros bits activos, se mantienen. La variante FromISR difiere el cambio mediante la cola del Timer Service, así que debemos comprobar que devuelve pdPASS si no podemos permitirnos perder la orden.
Esperar bits (WaitBits)
Esta es la función nuclear. Permite a una tarea bloquearse esperando condiciones.
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup, // El grupo
const EventBits_t uxBitsToWaitFor, // Qué bits nos interesan
const BaseType_t xClearOnExit, // ¿Borramos los bits al salir?
const BaseType_t xWaitForAllBits, // ¿Esperamos a TODOS (AND) o a UNO (OR)?
TickType_t xTicksToWait // Timeout
);-
uxBitsToWaitFor: Una máscara con los bits que vigilamos. Ej:BIT_WIFI | BIT_MQTT. -
xClearOnExit: -
pdTRUE: Al despertar, los bits que causaron el desbloqueo se limpian (vuelven a 0). Comportamiento de “evento consumido”. -
pdFALSE: Los bits se quedan en 1. Útil si queremos que múltiples tareas lean el mismo estado. -
xWaitForAllBits: -
pdTRUE(Lógica AND): Espera hasta que todos los bits indicados estén a 1 a la vez. -
pdFALSE(Lógica OR): Despierta en cuanto cualquiera de los bits se ponga a 1.
Ejemplo Práctico: Secuencia de Arranque
Vamos a simular un sistema que necesita conectarse a WiFi y validar una licencia antes de permitir que la tarea principal funcione. Simularemos las conexiones con tareas temporales.
#include "freertos/event_groups.h"
// 1. Definimos los Flags
#define FLG_WIFI (1 << 0)
#define FLG_LICENCIA (1 << 1)
// Objeto del grupo
EventGroupHandle_t grupoArranque;
// Tarea que simula conexión WiFi
void TareaWiFi(void *pvParameters) {
Serial.println("WiFi: Intentando conectar...");
vTaskDelay(pdMS_TO_TICKS(3000)); // Tarda 3 segundos
Serial.println("WiFi: ¡Conectado!");
// Activamos el bit 0
xEventGroupSetBits(grupoArranque, FLG_WIFI);
// La tarea muere (o se queda manteniendo la conexión)
vTaskDelete(NULL);
}
// Tarea que simula validar licencia
void TareaLicencia(void *pvParameters) {
Serial.println("Licencia: Validando...");
vTaskDelay(pdMS_TO_TICKS(1000)); // Tarda 1 segundo
Serial.println("Licencia: OK.");
// Activamos el bit 1
xEventGroupSetBits(grupoArranque, FLG_LICENCIA);
vTaskDelete(NULL);
}
// Tarea Principal (Main App)
void TareaPrincipal(void *pvParameters) {
Serial.println("MAIN: Esperando sistemas...");
// 2. Esperamos a que (WIFI) Y (LICENCIA) estén listos.
// WaitForAllBits = pdTRUE (AND)
// ClearOnExit = pdFALSE (No queremos borrarlos, el estado persiste)
EventBits_t bits = xEventGroupWaitBits(
grupoArranque,
FLG_WIFI | FLG_LICENCIA,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
// Si pasamos de aquí, es que todo está OK
Serial.println("MAIN: Sistemas listos. Iniciando aplicación.");
Serial.printf("Valor de los bits actuales: 0x%X\n", bits);
for(;;) {
// Bucle principal de la aplicación
Serial.println("App funcionando...");
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void setup() {
Serial.begin(115200);
// Crear grupo
grupoArranque = xEventGroupCreate();
if (grupoArranque == NULL) {
Serial.println("No se pudo crear el Event Group");
return;
}
// Lanzar tareas
xTaskCreate(TareaWiFi, "WiFi", 2048, NULL, 1, NULL);
xTaskCreate(TareaLicencia, "Lic", 2048, NULL, 1, NULL);
xTaskCreate(TareaPrincipal, "Main", 2048, NULL, 1, NULL);
}
void loop() {}Análisis del resultado
- Arranca
MAIN, llega alxEventGroupWaitBitsy se bloquea porque los bits están a 0. - Al segundo 1,
TareaLicenciapone el bit 1.MAINsigue dormida porque pedimos AND (WaitAll=True). - Al segundo 3,
TareaWiFipone el bit 0. - En ese instante, los dos bits de la máscara están activos.
MAINdespierta y empieza a imprimir “App funcionando…”.
Sincronización de Tareas (Rendezvous)
Existe una función avanzada llamada xEventGroupSync().
Imagina que tienes 3 tareas que hacen partes distintas de un cálculo y quieres que la siguiente fase empiece solo cuando las tres hayan terminado la fase actual.
Es un punto de encuentro (Rendezvous).
- Tarea A termina, pone su bit y espera a (A, B, C).
- Tarea B termina, pone su bit y espera a (A, B, C).
- Tarea C termina, pone su bit… ¡Y BOOM! Se desbloquean las tres a la vez.
Es muy útil para procesamiento paralelo de datos divididos.