Comunicação entre Soft Timers e Tarefas no FreeRTOS
Como vimos, callbacks de soft timers não podem bloquear nem executar lógica complexa. No entanto, em sistemas reais, é muito comum que o disparo de um timer precise acionar uma tarefa, sinalizar um evento ou iniciar um processamento mais pesado. Para isso, o FreeRTOS oferece mecanismos seguros e determinísticos de comunicação entre o contexto do Timer Service Task e tarefas do sistema.
O princípio arquitetural aqui é simples e fundamental:
O soft timer apenas sinaliza.
A tarefa executa o trabalho pesado.
Comunicação usando Task Notifications
O mecanismo mais leve e eficiente para essa comunicação é a notificação direta de tarefa (Task Notification). Ela funciona como um semáforo binário, contador ou até um valor de 32 bits, sem a necessidade de criar objetos adicionais no heap.
Callback do timer notificando uma tarefa
TaskHandle_t xWorkerTaskHandle = NULL;
void vTimerCallback(TimerHandle_t xTimer)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(
xWorkerTaskHandle,
0,
eNoAction,
&xHigherPriorityTaskWoken
);
}
Apesar do nome FromISR, essa função é segura para uso no callback de soft timer, pois o kernel trata corretamente o contexto.
Tarefa aguardando a notificação
void vWorkerTask(void *pvParameters)
{
for (;;)
{
xTaskNotifyWait(
0,
0,
NULL,
portMAX_DELAY
);
/* Processamento pesado */
}
}
Essa abordagem é extremamente eficiente:
- não consome heap adicional,
- não envolve cópia de dados,
- possui latência mínima.
Comunicação usando Filas (Queues)
Quando é necessário transferir dados estruturados, como medições ou estados, as queues são mais apropriadas.
Callback enviando dados para a fila
typedef struct
{
uint32_t timestamp;
uint16_t value;
} SensorData_t;
QueueHandle_t xQueue;
void vTimerCallback(TimerHandle_t xTimer)
{
SensorData_t data = {
.timestamp = xTaskGetTickCount(),
.value = 123
};
xQueueSendFromISR(xQueue, &data, NULL);
}
Tarefa consumindo a fila
void vConsumerTask(void *pvParameters)
{
SensorData_t data;
for (;;)
{
if (xQueueReceive(xQueue, &data, portMAX_DELAY) == pdPASS)
{
/* Processa os dados */
}
}
}
Essa estratégia é segura, mas:
- envolve cópia de memória,
- consome heap para criação da fila,
- possui maior latência que notificações diretas.
Comunicação usando Event Groups
Event Groups são úteis quando vários eventos independentes precisam ser sinalizados para uma ou mais tarefas.
Callback setando um evento
EventGroupHandle_t xEvents;
#define EVT_TIMER_EXPIRED (1 << 0)
void vTimerCallback(TimerHandle_t xTimer)
{
xEventGroupSetBitsFromISR(
xEvents,
EVT_TIMER_EXPIRED,
NULL
);
}
Tarefa aguardando o evento
void vEventTask(void *pvParameters)
{
for (;;)
{
xEventGroupWaitBits(
xEvents,
EVT_TIMER_EXPIRED,
pdTRUE,
pdFALSE,
portMAX_DELAY
);
/* Ação associada ao evento */
}
}
Event Groups são excelentes para sincronizar múltiplos eventos, mas não transportam dados, apenas estados lógicos.
Comparação entre mecanismos
| Mecanismo | Latência | Heap | Transporte de dados | Complexidade |
|---|---|---|---|---|
| Task Notification | Muito baixa | Não | Não | Baixa |
| Queue | Média | Sim | Sim | Média |
| Event Group | Baixa | Sim | Não | Média |
Padrão arquitetural recomendado
Em sistemas bem estruturados, o padrão mais comum é:
- Soft timer expira
- Callback sinaliza uma tarefa
- Tarefa acorda
- Processamento é executado no contexto da tarefa
Esse modelo mantém o sistema responsivo, previsível e fácil de escalar.
Vamos concluir o artigo com a parte mais prática e integradora.