Stream Buffers e Message Buffers para Fluxos de Eventos
Em arquiteturas orientadas a eventos mais sofisticadas, especialmente aquelas que lidam com fluxos contínuos de dados, como áudio, comunicação serial, rede ou DMA, o uso de queues tradicionais pode se tornar ineficiente. Para esses cenários, o FreeRTOS oferece dois mecanismos especializados: Stream Buffers e Message Buffers.
Ambos foram projetados para lidar com sequências de dados de tamanho variável, reduzindo cópias, latência e overhead de memória. Do ponto de vista arquitetural, eles representam um nível mais baixo de abstração em relação às queues, mas são extremamente eficazes quando bem aplicados.
Stream Buffers — Eventos como fluxo contínuo
Um Stream Buffer representa um canal unidirecional de bytes, onde o conceito de “evento” não é uma mensagem isolada, mas sim a disponibilidade de dados no fluxo. Ele é ideal para cenários como:
- Áudio contínuo
- UART RX/TX
- SPI em modo streaming
- DMA circular
O evento, nesse caso, é “há dados suficientes para processar”.
Exemplo 1 — Stream Buffer com UART
StreamBufferHandle_t uartStream;
void vUartConsumerTask(void *pvParameters)
{
uint8_t buffer[64];
size_t received;
for (;;)
{
received = xStreamBufferReceive(
uartStream,
buffer,
sizeof(buffer),
portMAX_DELAY
);
processUartData(buffer, received);
}
}
ISR produtora:
void USART_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint8_t byte = USART_Read();
xStreamBufferSendFromISR(
uartStream,
&byte,
1,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Aqui, o Stream Buffer atua como um pipe de eventos, onde cada byte recebido gera implicitamente um evento de disponibilidade de dados. A tarefa permanece bloqueada até que dados suficientes estejam presentes.
Thresholds e controle de latência
Stream Buffers permitem definir um nível mínimo de dados para desbloquear a tarefa. Isso é crucial para reduzir wake-ups excessivos.
xStreamBufferSetTriggerLevel(uartStream, 32);
Esse ajuste transforma o buffer em um mecanismo orientado a eventos por volume, ideal para processamento em blocos.
Message Buffers — Eventos com mensagens variáveis
Enquanto Stream Buffers trabalham com bytes contínuos, Message Buffers trabalham com mensagens delimitadas, onde cada envio é tratado como um evento completo.
Eles são ideais para:
- Protocolos de comunicação
- Comandos textuais
- Pacotes de rede
- Logs estruturados
Exemplo 2 — Message Buffer para comandos
MessageBufferHandle_t cmdBuffer;
void vCommandTask(void *pvParameters)
{
char cmd[64];
size_t len;
for (;;)
{
len = xMessageBufferReceive(
cmdBuffer,
cmd,
sizeof(cmd),
portMAX_DELAY
);
processCommand(cmd, len);
}
}
Produtor:
void vShellTask(void *pvParameters)
{
const char *msg = "STATUS\n";
xMessageBufferSend(
cmdBuffer,
msg,
strlen(msg) + 1,
portMAX_DELAY
);
}
Nesse modelo, cada mensagem enviada é um evento autocontido, preservando fronteiras e facilitando parsing.
Comparação arquitetural
- Queues: eventos discretos com tamanho fixo
- Stream Buffers: fluxo contínuo de dados
- Message Buffers: mensagens variáveis e delimitadas
Uma arquitetura orientada a eventos madura utiliza cada mecanismo no contexto correto, evitando sobrecarga desnecessária e mantendo clareza semântica.
Armadilhas comuns
- Usar Stream Buffer como fila de mensagens (perda de fronteiras)
- Usar Message Buffer para streaming contínuo (fragmentação)
- Ignorar thresholds, causando wake-ups excessivos
- Criar buffers grandes demais, mascarando gargalos