Prioridades de interrupção no Cortex-M e regras do FreeRTOS
Grande parte dos problemas graves em sistemas FreeRTOS com Cortex-M não está no código das tasks, mas sim em uma configuração incorreta das prioridades de interrupção. Esses erros são difíceis de depurar, muitas vezes não geram faults explícitos e se manifestam como travamentos aleatórios, perda de eventos ou comportamento não determinístico.
Para evitar isso, é essencial compreender como o NVIC do Cortex-M funciona e como o FreeRTOS impõe regras sobre ele.
5.1 Como o NVIC trata prioridades de interrupção
Nos microcontroladores Cortex-M, as prioridades de interrupção:
- São representadas por números menores = maior prioridade
- Nem todos os bits do campo de prioridade são implementados
- O número de bits válidos é definido por
__NVIC_PRIO_BITS
Exemplo típico:
- STM32F4: 4 bits de prioridade (0 a 15)
- STM32F1: 4 bits
- STM32H7: 4 bits (com subprioridades opcionais)
Ou seja:
| Prioridade | Nível |
| 0 | mais alt |
| 15 | mais baixa |
O NVIC permite que:
- Uma interrupção de prioridade maior interrompa outra de prioridade menor
- ISRs de prioridade muito alta nunca sejam interrompidas pelo kernel
5.2 O papel de configMAX_SYSCALL_INTERRUPT_PRIORITY
O FreeRTOS não permite que qualquer ISR chame suas APIs. Apenas interrupções com prioridade igual ou inferior (numericamente maior) a um determinado limite podem interagir com o kernel.
Esse limite é definido pela macro:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY
Ela define a maior prioridade de interrupção (menor número) que ainda pode chamar APIs FromISR.
Exemplo prático em um STM32 com 4 bits de prioridade:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - __NVIC_PRIO_BITS))
Isso significa:
- ISRs com prioridade 0 a 4 → NÃO podem chamar FreeRTOS
- ISRs com prioridade 5 a 15 → PODEM chamar APIs
FromISR
Essa distinção é obrigatória e não opcional.
5.3 Erro clássico: “funciona até parar de funcionar”
Um erro muito comum é configurar uma interrupção com prioridade máxima (0) e, dentro dela, chamar:
xQueueSendFromISR(...)
O código compila.
O sistema inicializa.
Às vezes funciona.
Mas o comportamento é indefinido.
Por quê?
Porque:
- O FreeRTOS desabilita interrupções até
configMAX_SYSCALL_INTERRUPT_PRIORITY - Uma ISR com prioridade mais alta ignora esse bloqueio
- O kernel pode ser acessado em estado inconsistente
Resultado:
- Corrupção de estruturas internas
- Deadlocks silenciosos
- HardFaults intermitentes
5.4 Regra de ouro para Cortex-M + FreeRTOS
Se uma interrupção chama qualquer API do FreeRTOS, ela NÃO pode ter prioridade maior do que
configMAX_SYSCALL_INTERRUPT_PRIORITY.
Ou, dito de forma prática:
- ISRs de tempo crítico absoluto (ex: controle PWM, safety)
→ prioridade alta, não usam FreeRTOS - ISRs que sinalizam tasks
→ prioridade mais baixa, usam APIsFromISR
5.5 Configuração correta no código
Exemplo de configuração segura:
HAL_NVIC_SetPriority(USART1_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
E no FreeRTOSConfig.h:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
Nesse cenário:
- USART1 pode chamar
xQueueSendFromISR - Timer de controle crítico pode rodar em prioridade 2 sem interferência
5.6 Por que o FreeRTOS faz isso?
Essa regra existe para garantir:
- Determinismo temporal
- Escalonamento previsível
- Integridade das estruturas internas
- Portabilidade entre arquiteturas
Sem esse modelo, o kernel teria que:
- Suportar preempção total dentro de ISRs
- Aumentar drasticamente a complexidade
- Perder desempenho e previsibilidade