APIs FromISR: por que elas existem e como funcionam
Um dos erros mais comuns de quem começa a usar FreeRTOS é tentar chamar, a partir de uma ISR, as mesmas funções usadas dentro de uma task. Essa tentativa normalmente resulta em comportamentos imprevisíveis, porque o kernel do FreeRTOS não foi projetado para ser acessado de forma arbitrária a partir de uma interrupção. É exatamente para resolver esse problema que existem as chamadas conhecidas como APIs FromISR.
Motivação conceitual
Dentro de uma task, o FreeRTOS assume que:
- O código pode ser interrompido a qualquer momento pelo tick ou por outra interrupção.
- É permitido bloquear (block) a task, aguardando eventos.
- O escalonador pode ser invocado imediatamente.
Em uma ISR, nenhuma dessas premissas é válida. Uma interrupção:
- Não pode bloquear.
- Não pode chamar o escalonador diretamente de forma arbitrária.
- Executa em um contexto especial de hardware, com restrições severas de tempo e reentrância.
Por isso, as funções xQueueSend, xSemaphoreGive, xEventGroupSetBits e similares não podem ser usadas dentro de ISRs. Em seu lugar, o FreeRTOS fornece versões específicas, como:
xQueueSendFromISRxSemaphoreGiveFromISRxEventGroupSetBitsFromISRxTaskNotifyFromISR
Essas funções são cuidadosamente implementadas para operar com seções críticas reduzidas, sem bloqueio e com lógica explícita para decidir se uma troca de contexto deve ocorrer ao final da interrupção.
O parâmetro xHigherPriorityTaskWoken
Todas as APIs FromISR seguem um padrão muito importante, que costuma gerar dúvidas:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
Esse parâmetro funciona como um indicador de preempção. Ele informa se a ação executada dentro da ISR (por exemplo, liberar um semáforo) fez com que uma task de prioridade maior do que a task atual se tornasse pronta para executar.
Internamente, o FreeRTOS:
- Executa a operação solicitada (ex: liberar semáforo).
- Verifica se alguma task desbloqueada possui prioridade maior que a task interrompida.
- Se sim, seta
xHigherPriorityTaskWoken = pdTRUE.
A ISR não realiza a troca de contexto diretamente. Em vez disso, ao final da ISR, esse indicador é usado para solicitar ao kernel que execute um context switch imediatamente após o retorno da interrupção.
O padrão correto de uso é:
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Limpa a flag da interrupção de hardware */
xSemaphoreGiveFromISR(xButtonSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
A macro portYIELD_FROM_ISR() é dependente da arquitetura e:
- Solicita ao kernel uma troca de contexto
- Garante que, ao sair da ISR, a task mais prioritária execute imediatamente
Por que não chamar o escalonador diretamente?
O FreeRTOS separa claramente:
- Decisão de escalonamento
- Execução da troca de contexto
Essa separação é essencial para manter a portabilidade do kernel entre arquiteturas (Cortex-M, RISC-V, AVR, etc.). A ISR apenas sinaliza a necessidade de troca, e o port layer cuida da implementação específica do hardware.
Essa abordagem evita:
- Trocas de contexto aninhadas
- Corrupção de pilha
- Estados inconsistentes do kernel
Resumo prático desta seção
Do ponto de vista arquitetural:
- ISR nunca deve chamar APIs normais do FreeRTOS.
- Sempre utilize versões
FromISR. - Sempre avalie corretamente
xHigherPriorityTaskWoken. - Sempre finalize a ISR com
portYIELD_FROM_ISR()quando aplicável.
Esse padrão garante:
- Baixa latência
- Determinismo temporal
- Escalonamento correto
- Sistema estável e previsível