Queues e Message Passing em Arquiteturas Orientadas a Eventos
Em uma arquitetura orientada a eventos, nem todo evento pode ser representado apenas por um sinal ou flag. Muitos eventos carregam dados associados, e esses dados precisam ser entregues de forma segura, ordenada e determinística. É exatamente nesse ponto que as Queues do FreeRTOS tornam-se fundamentais, funcionando como o principal mecanismo de message passing entre tarefas e entre ISRs e tarefas.
Uma Queue no FreeRTOS é uma estrutura FIFO (First In, First Out) que permite o envio de mensagens tipadas, copiadas por valor, com sincronização automática. Diferente das Task Notifications e Event Groups, as filas acumulam eventos, preservam ordem e garantem que nenhum evento seja perdido enquanto houver espaço disponível.
Do ponto de vista arquitetural, queues são o elo ideal entre produtores rápidos e consumidores mais lentos, criando um desacoplamento temporal que é essencial em sistemas reativos.
Modelo conceitual de filas orientadas a eventos
Em arquiteturas bem projetadas, cada queue deve ter uma responsabilidade clara. Normalmente, uma fila representa um canal de eventos de um tipo específico, como:
- Amostras de sensores
- Pacotes de comunicação
- Comandos de controle
- Eventos de log ou telemetria
Misturar tipos de mensagens diferentes na mesma fila é um erro comum e prejudica a legibilidade e a segurança do sistema.
Exemplo 1 — Evento com payload (ISR → Task)
typedef struct
{
uint32_t timestamp;
uint16_t value;
} SensorEvent_t;
QueueHandle_t sensorQueue;
void vSensorTask(void *pvParameters)
{
SensorEvent_t evt;
for (;;)
{
if (xQueueReceive(sensorQueue, &evt, portMAX_DELAY) == pdPASS)
{
processSensor(evt.value, evt.timestamp);
}
}
}
A ISR produtora:
void ADC_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
SensorEvent_t evt;
evt.timestamp = getSystemTime();
evt.value = ADC_Read();
xQueueSendFromISR(sensorQueue, &evt, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Aqui, o evento é a conclusão de uma conversão ADC, e o payload contém os dados associados. A arquitetura permanece orientada a eventos, mas agora com conteúdo estruturado.
Exemplo 2 — Fila como buffer de desacoplamento
void vProducerTask(void *pvParameters)
{
uint32_t data = 0;
for (;;)
{
xQueueSend(dataQueue, &data, portMAX_DELAY);
data++;
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void vConsumerTask(void *pvParameters)
{
uint32_t received;
for (;;)
{
xQueueReceive(dataQueue, &received, portMAX_DELAY);
consumeData(received);
}
}
Esse padrão cria um pipeline orientado a eventos, onde a produção e o consumo ocorrem em ritmos distintos, sem polling ou busy-wait. A fila atua como amortecedor, absorvendo variações temporais do sistema.