Comunicação entre ISR e Tasks: qual mecanismo usar e quando
No FreeRTOS, a ISR não processa eventos, ela sinaliza eventos. A forma como essa sinalização ocorre define diretamente o desempenho, a latência e a robustez do sistema. O kernel oferece múltiplos mecanismos para comunicação ISR → Task, cada um com características muito distintas. Escolher o mecanismo errado é uma das principais causas de sistemas instáveis ou com desempenho ruim.
Nesta seção, vamos analisar quando e por que usar cada mecanismo, sempre do ponto de vista prático.
4.1 Semáforos binários: sinalização simples de eventos
O semáforo binário é o mecanismo mais direto para sinalizar que “algo aconteceu”. Ele não carrega dados, apenas um evento.
Uso típico:
- Botão pressionado
- Interrupção de fim de conversão do ADC
- Evento externo simples
Exemplo clássico: botão gerando interrupção e acordando uma task.
ISR:
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Limpa flag do EXTI */
xSemaphoreGiveFromISR(xButtonSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Task:
void vButtonTask(void *pvParameters)
{
for (;;)
{
if (xSemaphoreTake(xButtonSemaphore, portMAX_DELAY) == pdTRUE)
{
/* Trata o evento do botão */
}
}
}
Vantagens:
- Simples
- Baixíssima sobrecarga
- Ideal para eventos pontuais
Limitações:
- Não transporta dados
- Eventos podem ser perdidos se ocorrerem rapidamente em sequência
4.2 Filas (Queues): eventos com dados
Quando a ISR precisa entregar dados para uma task, o mecanismo correto é a fila. A fila permite armazenar múltiplos eventos, mantendo ordem FIFO (First In, First Out).
Uso típico:
- Recepção de bytes via UART
- Amostras de ADC
- Eventos estruturados
ISR (exemplo UART RX):
void USART1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint8_t rxByte;
rxByte = USART1->DR;
xQueueSendFromISR(uartRxQueue, &rxByte, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Task:
void vUartTask(void *pvParameters)
{
uint8_t byte;
for (;;)
{
if (xQueueReceive(uartRxQueue, &byte, portMAX_DELAY) == pdTRUE)
{
/* Processa byte recebido */
}
}
}
Vantagens:
- Transporta dados
- Mantém ordem
- Suporta burst de eventos
Desvantagens:
- Mais custo de memória
- Overhead maior que notificações
4.3 Notificações diretas de task: o mecanismo mais eficiente
As Task Notifications são o mecanismo mais rápido e leve do FreeRTOS para comunicação ISR → Task. Elas utilizam um campo interno da estrutura da task, evitando alocação dinâmica ou buffers intermediários.
Uso típico:
- Sinalização de eventos frequentes
- Sincronização rápida
- Substituição de semáforo binário
ISR:
void TIM2_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xControlTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Task:
void vControlTask(void *pvParameters)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
/* Executa rotina de controle */
}
}
Vantagens:
- Extremamente rápido
- Baixíssimo uso de memória
- Ideal para sistemas tempo real estritos
Limitações:
- Um canal por task
- Não serve para múltiplos consumidores
4.4 Event Groups: múltiplos eventos e sincronização complexa
Os Event Groups permitem sinalizar múltiplos eventos simultaneamente usando bits. São úteis quando uma task precisa aguardar combinações de eventos.
Uso típico:
- Estado de múltiplos periféricos
- Sincronização entre ISR e várias tasks
ISR:
xEventGroupSetBitsFromISR(
systemEventGroup,
EVENT_ADC_DONE,
&xHigherPriorityTaskWoken
);
Task:
xEventGroupWaitBits(
systemEventGroup,
EVENT_ADC_DONE | EVENT_UART_DONE,
pdTRUE,
pdTRUE,
portMAX_DELAY
);
Vantagens:
- Expressivo
- Bom para estados complexos
Desvantagens:
- Não transporta dados
- Overhead maior
Resumo arquitetural
| Mecanismo | Dados | Velocidade | Uso típico |
|---|---|---|---|
| Semáforo binário | Não | Alta | Evento simples |
| Queue | Sim | Média | Comunicação com dados |
| Notificação | Limitado | Muito alta | Sinalização rápida |
| Event Group | Não | Média | Estados múltiplos |
Na próxima seção, vamos tratar de prioridades de interrupção, configMAX_SYSCALL_INTERRUPT_PRIORITY e erros clássicos em Cortex-M, um dos pontos mais críticos e mal compreendidos do FreeRTOS.