Padrões de Comunicação e Sincronização em RTOS
Em FreeRTOS, concorrência mal projetada é a principal fonte de bugs: deadlocks, starvation, jitter temporal e corrupção de dados. Os padrões desta seção existem para substituir variáveis globais, flags soltas e volatile mal utilizados por mecanismos determinísticos e auditáveis.
Aqui falamos de comunicação, não apenas de sincronização.
2.1 Event Queue (Fila de Eventos)
Problema resolvido:
Múltiplos produtores gerando eventos assíncronos para um único consumidor.
Ideia do padrão:
Eventos são encapsulados em estruturas e enviados por uma fila RTOS. O consumidor processa eventos em ordem temporal.
Quando usar:
- Interface homem-máquina
- Eventos de sensores
- Drivers desacoplados da lógica de aplicação
Estrutura típica do evento:
typedef enum {
EVT_BUTTON,
EVT_SENSOR,
EVT_TIMEOUT
} EventType;
typedef struct {
EventType type;
uint32_t data;
} AppEvent;
Fila global (criada na inicialização):
QueueHandle_t EventQueue;
EventQueue = xQueueCreate(10, sizeof(AppEvent));
Produtor:
void ButtonISR(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
AppEvent evt = {
.type = EVT_BUTTON,
.data = 1
};
xQueueSendFromISR(EventQueue, &evt, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Consumidor:
void EventTask(void *pvParameters)
{
AppEvent evt;
for (;;)
{
if (xQueueReceive(EventQueue, &evt, portMAX_DELAY))
{
switch (evt.type)
{
case EVT_BUTTON:
HandleButton(evt.data);
break;
case EVT_SENSOR:
HandleSensor(evt.data);
break;
default:
break;
}
}
}
}
Vantagens:
- Desacoplamento total
- Ordem garantida
- Fácil instrumentação
Erro comum:
Usar fila para stream contínuo de dados (para isso existem buffers).
2.2 Mailbox (Fila de Mensagem Única)
Problema resolvido:
Apenas o último valor importa, não o histórico.
Ideia do padrão:
Uma fila de tamanho 1 funciona como mailbox sobrescrevível.
QueueHandle_t Mailbox;
Mailbox = xQueueCreate(1, sizeof(uint32_t));
Produtor:
uint32_t temperature = ReadTemperature();
xQueueOverwrite(Mailbox, &temperature);
Consumidor:
uint32_t temp;
if (xQueuePeek(Mailbox, &temp, 0))
{
UseTemperature(temp);
}
Quando usar:
- Telemetria
- Estados globais observáveis
- Última leitura válida
Vantagem-chave:
Evita backlog artificial.
2.3 Publish–Subscribe (Pub-Sub) com FreeRTOS
Problema resolvido:
Múltiplos consumidores interessados no mesmo evento.
Ideia do padrão:
Uma tarefa “dispatcher” recebe eventos e os redistribui para múltiplas filas.
Arquitetura:
ISR / Producers
↓
Event Queue
↓
Dispatcher Task
↓ ↓ ↓
Task A Task B Task C
Dispatcher:
void DispatcherTask(void *pvParameters)
{
AppEvent evt;
for (;;)
{
if (xQueueReceive(EventQueue, &evt, portMAX_DELAY))
{
xQueueSend(QueueA, &evt, 0);
xQueueSend(QueueB, &evt, 0);
}
}
}
Vantagens:
- Extensível
- Isola produtores de consumidores
- Facilita testes
Custo:
Mais RAM e latência — aceitável em troca de clareza.
2.4 Task Notification como Padrão de Sinalização Rápida
Problema resolvido:
Overhead de filas quando apenas sinalização simples é necessária.
Ideia do padrão:
Usar Task Notifications como semáforos ultraleves.
Produtor:
xTaskNotify(TaskHandle, 0x01, eSetBits);
Consumidor:
uint32_t flags;
xTaskNotifyWait(0, 0xFFFFFFFF, &flags, portMAX_DELAY);
if (flags & 0x01)
{
ProcessEvent();
}
Vantagens:
- Zero alocação dinâmica
- Extremamente rápido
- Ideal para ISR → Task
Limitação:
Apenas 32 bits por tarefa → não serve para dados complexos.
2.5 Stream Buffer e Message Buffer
Problema resolvido:
Troca eficiente de fluxos contínuos de dados.
Stream Buffer
- Dados binários contínuos
- Sem fronteira de mensagem
StreamBufferHandle_t sb;
sb = xStreamBufferCreate(128, 1);
Message Buffer
- Mensagens delimitadas
- Ideal para pacotes
MessageBufferHandle_t mb;
mb = xMessageBufferCreate(128);
Uso típico:
- UART
- SPI
- Áudio
- Protocolos binários
Boa prática:
Preferir buffers a filas para dados “em volume”.