Composição Arquitetural de Eventos no FreeRTOS
Até aqui, analisamos isoladamente cada mecanismo de evento do FreeRTOS. Porém, sistemas reais raramente usam apenas um deles. O verdadeiro ganho arquitetural surge quando combinamos conscientemente esses mecanismos, criando uma malha de eventos clara, previsível e de baixo acoplamento. Esta seção trata exatamente de como orquestrar múltiplos tipos de eventos sem comprometer o determinismo do sistema.
O princípio fundamental é simples: cada mecanismo deve ter um papel bem definido. Task Notifications sinalizam eventos direcionados, Event Groups representam estados globais, Queues transportam dados estruturados, Stream/Message Buffers lidam com fluxo, e Timers geram eventos temporais. Misturar responsabilidades é o caminho mais curto para arquiteturas frágeis.
Padrão 1 — ISR mínima + Evento Direcionado
Este é o padrão mais importante em sistemas orientados a eventos.
void DMA_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(dmaTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
A tarefa:
void vDmaTask(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
processDmaBuffer();
}
}
Aqui, a ISR não carrega lógica, apenas converte um evento físico em evento lógico. Esse padrão reduz latência, evita jitter e simplifica análise de tempo real.
Padrão 2 — Evento acorda, fila transporta
Um erro comum é usar filas apenas para acordar tarefas. Em arquiteturas maduras, eventos acordam tarefas; filas entregam dados.
void vProducerTask(void *pvParameters)
{
Data_t data;
for (;;)
{
data = acquireData();
xQueueSend(dataQueue, &data, 0);
xTaskNotifyGive(consumerTaskHandle);
}
}
A tarefa consumidora:
void vConsumerTask(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
drainQueueAndProcess();
}
}
Esse padrão reduz wake-ups redundantes e melhora eficiência energética.
Padrão 3 — Event Group como estado global, não evento
#define EVT_SYSTEM_READY (1 << 0)
void vInitTask(void *pvParameters)
{
initializeSubsystems();
xEventGroupSetBits(systemEventGroup, EVT_SYSTEM_READY);
vTaskDelete(NULL);
}
Tarefas dependentes:
void vAppTask(void *pvParameters)
{
xEventGroupWaitBits(
systemEventGroup,
EVT_SYSTEM_READY,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
runApplication();
}
Aqui, o Event Group representa um estado permanente, não um evento transitório. Essa distinção é crucial para evitar perda de sinalização.
Padrão 4 — Timer → Evento → Task
void vPeriodicTimerCallback(TimerHandle_t xTimer)
{
xTaskNotifyGive(periodicTaskHandle);
}
A tarefa:
void vPeriodicTask(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
performPeriodicAction();
}
}
Esse padrão elimina delays ativos e centraliza controle temporal.
Padrão 5 — Máquina de estados orientada a eventos
Arquiteturas orientadas a eventos se integram naturalmente com State Machines.
typedef enum {
EVT_START,
EVT_STOP,
EVT_ERROR
} Event_t;
void vControllerTask(void *pvParameters)
{
Event_t evt;
for (;;)
{
xQueueReceive(eventQueue, &evt, portMAX_DELAY);
stateMachineDispatch(evt);
}
}
Esse modelo é extremamente poderoso para sistemas complexos, como comunicação, controle e supervisão.