Los grupos de eventos en FreeRTOS son un mecanismo de sincronización basado en máscaras de bits. A diferencia de las colas o semáforos, que establecen una comunicación uno-a-uno, un grupo de eventos permite que un único cambio de estado notifique a múltiples tareas de forma concurrente. Su núcleo es una palabra (de 16 o 32 bits) donde cada bit representa un evento booleano independiente.
Arquitectura y Dimensionamiento
El tamaño de la máscara de bits está determinado por la configuración del kernel. Si configUSE_16_BIT_TICKS es 1, se utiliza una variable de 16 bits, donde los 8 bits más significativos están reservados para el sistema, dejando 8 bits para el usuario. En caso contrario (valor 0), se emplea una varible de 32 bits, reservando 8 bits y ofreciendo 24 para aplicaciones. Esta elección optimiza la aritmética del planificador según la arquitectura de la CPU.
La siguiente tabla compara los mecanismos de sincronización habituales:
| Mecanismo | Objetivo Principal | Modelo de Despertar |
|---|---|---|
| Cola | Transferencia de datos estructurados en orden FIFO | Despierta una tarea por cada mensaje enviado |
| Semáforo | Señalización simple o gestión de recursos contables | Despierta una tarea al liberar el semáforo |
| Grupo de Eventos | Gestión combinada de múltiples señales booleanas | Puede despertar a varias tareas que esperan los mismos bits |
Patrones de Uso y Operaciones
Las operaciones centrales permiten establecer bits (señalar eventos) y esperar por combinaciones específicas de bits. Una tarea puede bloquearse hasta que uno cualquiera de varios bits se establezca (operación OR) o hasta que todos los bits requeridos estén presentes (operación AND). Además, se puede configurar si los bits se borran automáticamente al salir de la espera.
Un escenario común es la sincronización de inicialización. Varios sensores pueden establecer bits independientes en un mismo grupo. Una tarea de alto nivel puede esperar hasta que el bit SENSOR_A_LISTO Y el bit SENSOR_B_LISTO estén ambos activos (operación AND) para continuar.
Las funciones API esenciales son las siguientes para crear, modificar y esperar grupos de eventos:
/* Crear un nuevo grupo de eventos */
EventGroupHandle_t xGrupoDeEventos = xEventGroupCreate();
/* Establecer bits desde una tarea (ej: señalizar un evento) */
EventBits_t uxBits = xEventGroupSetBits(xGrupoDeEventos, (1 << 0));
/* Esperar por bits con lógica AND y limpieza automática */
EventBits_t uxBitsEsperados = xEventGroupWaitBits(
xGrupoDeEventos,
(1 << 0) | (1 << 1), /* Máscara: esperar bit 0 Y bit 1 */
pdTRUE, /* Limpiar estos bits al salir */
pdTRUE, /* Requiere TODOS los bits (AND) */
pdMS_TO_TICKS(1000) /* Timeout de 1 segundo */
);
if ((uxBitsEsperados & ((1 << 0) | (1 << 1))) == ((1 << 0) | (1 << 1))) {
/* Ambos eventos ocurrieron */
}
Ejemplo Práctico: Sincronización de Múltiples Fuentes
El siguiente código ilustra un grupo de eventos (xEventGroupSistema) utilizado para coordinar una tarea principal con dos fuentes de eventos: una interrupción de recepción UART y una tarea de monitorización de red. La tarea principle espera hasta que ambas condiciones se cumplan.
/* Definiciones de bits para el grupo de eventos */
#define EVT_DATO_UART (1 << 0)
#define EVT_RED_OK (1 << 1)
#define EVT_TODOS_OK (EVT_DATO_UART | EVT_RED_OK)
EventGroupHandle_t xEventGroupSistema;
void vTareaPrincipal(void *pvParams) {
EventBits_t uxBitsEventos;
for (;;) {
/* Espera bloqueante hasta que ambos bits estén en alto */
uxBitsEventos = xEventGroupWaitBits(
xEventGroupSistema,
EVT_TODOS_OK,
pdTRUE, /* Limpiar EVT_DATO_UART y EVT_RED_OK al salir */
pdTRUE, /* Espera la condición AND */
portMAX_DELAY
);
/* Comprobación defensiva (en caso de timeout o cambio de bits) */
if ((uxBitsEventos & EVT_TODOS_OK) == EVT_TODOS_OK) {
/* Procesamiento conjunto de datos y estado de red */
ProcesarDatosRed();
}
}
}
/* Tarea que verifica la conexión a la red cada 2 segundos */
void vTareaMonitorRed(void *pvParams) {
for (;;) {
if (ConexionRedEstable()) {
xEventGroupSetBits(xEventGroupSistema, EVT_RED_OK);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
/* Rutina de Servicio de Interrupción (RSI) para el UART */
void UART_RSIDataReady(void) {
BaseType_t xSeRequiereCambioContexto = pdFALSE;
/* Establece el bit desde el contexto de interrupción */
xEventGroupSetBitsFromISR(
xEventGroupSistema,
EVT_DATO_UART,
&xSeRequiereCambioContexto
);
portYIELD_FROM_ISR(xSeRequiereCambioContexto);
}
/* Inicialización */
void main(void) {
xEventGroupSistema = xEventGroupCreate();
xTaskCreate(vTareaPrincipal, "Principal", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(vTareaMonitorRed, "RedMon", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
/* ... Configurar UART y su RSI ... */
vTaskStartScheduler();
}
Consiedraciones de Diseño y Limitaciones
Los grupos de eventos son inherentemente ligeros ya que no almacenan datos, solo estado. Sin embargo, tienen limitaciones importantes. No ofrecen herencia de prioridades, por lo que no son aptos para proteger secciones críticas compartidas entre tareas de diferentes prioridades. Además, en la espera con operación OR, si varios bits se establecen simultáneamente, la tarea será despertada una sola vez y verá todos los bits establecidos en la máscara devuelta. El bit xClearOnExit es crucial para evitar que una tarea consuma el mismo evento repetidamente si otras tareas también lo esperan.