MCU & FPGA RTOS Arquitetura Orientada a Eventos no FreeRTOS

Arquitetura Orientada a Eventos no FreeRTOS

Event Groups: Coordenação de Múltiplos Eventos

Enquanto as Task Notifications são ideais para eventos direcionados a uma única tarefa, existem cenários em que múltiplas tarefas precisam observar ou sincronizar-se com o mesmo conjunto de eventos. Para isso, o FreeRTOS oferece os Event Groups, que funcionam como um registro global de flags binárias, permitindo coordenação entre diversas tarefas e ISRs.

Um Event Group é essencialmente um conjunto de bits (normalmente 24 bits utilizáveis) onde cada bit representa um evento lógico. Tarefas podem aguardar por um ou vários desses bits, de forma bloqueante, até que as condições desejadas sejam satisfeitas. Esse mecanismo é especialmente útil para barreiras de sincronização, estados globais do sistema e coordenação entre subsistemas independentes.

No contexto de uma arquitetura orientada a eventos, os Event Groups devem ser usados com cuidado. Embora poderosos, eles introduzem um acoplamento indireto, já que várias tarefas passam a depender do mesmo estado compartilhado. Por isso, seu uso deve ser intencional e bem documentado.


Modelo de uso correto para Event Groups

O modelo mental adequado para Event Groups é pensar neles como sinais de estado, e não como filas de eventos. Um bit indica que uma condição é verdadeira naquele momento, e não quantas vezes ela ocorreu. Isso significa que Event Groups não acumulam eventos, apenas refletem o estado atual.

Alguns exemplos clássicos de uso adequado:

  • Sistema inicializado e pronto
  • Rede conectada
  • Sensor calibrado
  • Módulo em modo de erro
  • Sincronização de múltiplas tarefas na inicialização

Exemplo 1 — Aguardando múltiplos eventos simultâneos

#define EVT_NET_READY   (1 << 0)
#define EVT_SENSOR_OK  (1 << 1)

EventGroupHandle_t systemEventGroup;

void vAppTask(void *pvParameters)
{
    EventBits_t bits;

    for (;;)
    {
        bits = xEventGroupWaitBits(
            systemEventGroup,
            EVT_NET_READY | EVT_SENSOR_OK,
            pdFALSE,
            pdTRUE,
            portMAX_DELAY
        );

        startApplication();
    }
}

Neste exemplo, a tarefa só prossegue quando ambos os eventos estiverem ativos. O parâmetro pdTRUE indica que todos os bits especificados devem estar setados. Isso cria uma barreira lógica extremamente útil em fases de inicialização complexas.


Exemplo 2 — Sinalização parcial e limpeza automática

void vMaintenanceTask(void *pvParameters)
{
    EventBits_t bits;

    for (;;)
    {
        bits = xEventGroupWaitBits(
            systemEventGroup,
            EVT_NET_READY | EVT_SENSOR_OK,
            pdTRUE,
            pdFALSE,
            portMAX_DELAY
        );

        if (bits & EVT_NET_READY)
            handleNetworkEvent();

        if (bits & EVT_SENSOR_OK)
            handleSensorEvent();
    }
}

Aqui, qualquer um dos eventos acorda a tarefa. O uso de pdTRUE no terceiro parâmetro limpa automaticamente os bits consumidos, criando um comportamento próximo a um evento consumível, embora sem contagem.


Uso em ISRs

Event Groups também podem ser manipulados de forma segura em interrupções:

void DMA_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    xEventGroupSetBitsFromISR(
        systemEventGroup,
        EVT_SENSOR_OK,
        &xHigherPriorityTaskWoken
    );

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

Esse padrão é comum em drivers que precisam informar o sistema sobre mudanças de estado sem executar lógica complexa dentro da ISR.


Armadilhas comuns no uso de Event Groups

Do ponto de vista arquitetural, os principais erros ao usar Event Groups são:

  • Usá-los como filas de eventos (perda de eventos)
  • Criar grupos “genéricos demais”
  • Misturar estados permanentes com eventos transitórios
  • Criar dependências ocultas entre tarefas

Em arquiteturas bem desenhadas, Event Groups costumam ser poucos, bem nomeados e associados a estados globais claros. Quando usados corretamente, eles aumentam a clareza do sistema; quando usados indiscriminadamente, tornam o comportamento difícil de prever.


Na próxima seção, avançaremos para Queues e Message Passing, analisando como filas se encaixam em uma arquitetura orientada a eventos, quando elas são indispensáveis e como evitar problemas clássicos de latência e consumo de memória.

Seguimos adiante.


Related Post