1. Today’s topic
We examine:
interrupt latency
NVIC priorities on STM32
configMAX_SYSCALL_INTERRUPT_PRIORITY
ESP32 interrupt levels
IRAM-safe ISR
ISR-safe FreeRTOS API
deferred interrupt processing
DMA half/full callbacks
ISR timing measurementThe central idea: an ISR should quickly record the hardware event, clear the interrupt source, pass minimal information to a task, and exit. Parsing, filtering, recovery, logging, and networking are performed outside the ISR.
2. Why this matters
EC25 UART
A UART/DMA interrupt must not parse +QMTRECV, run strstr(), send MQTT, or restart EC25. Correct flow:
UART/DMA interrupt
→ record the DMA/ring buffer position
→ notify modem_task
→ modem_task collects bytes
→ AT parser
→ modem state machinePhase GPIO
GPIO edge:
GPIO edge
→ timestamp + raw level
→ input_task
→ debounce / AC-window / confidence
→ phase_detector
→ phase_eventDMA callbacks
DMA half/full callbacks indicate that half of the buffer is ready. ADC filtering, CRC, and UART parsing belong in a task.
3. Theory
Top half and bottom half
ISR:
read status
clear flag
save minimal data
notify task
exitTask:
parse UART
debounce
filter ADC
CRC
state machine
recovery
logsNVIC priority
On Cortex-M, a smaller number means higher priority. Priority 0 is the highest. configMAX_SYSCALL_INTERRUPT_PRIORITY separates ISRs:
0..4:
FreeRTOS API forbidden
5..15:
...FromISR() allowedExample with 4 priority bits:
UART DMA: priority 6 → RTOS API allowed
ADC DMA: priority 7 → RTOS API allowed
FDCAN: priority 8 → RTOS API allowed
precise capture: priority 3 → RTOS API forbiddenOnly ...FromISR()
Do not call ordinary functions from an ISR:
xQueueSend();
xSemaphoreGive();
xTaskNotify();
vTaskDelay();Use:
xQueueSendFromISR();
xSemaphoreGiveFromISR();
xTaskNotifyFromISR();
vTaskNotifyGiveFromISR();If a higher-priority task is unblocked, portYIELD_FROM_ISR() is required.
ESP32 interrupts
ESP32 has interrupt levels and core affinity. An external interrupt is allocated on the core that calls the allocation function. Prefer allocation from a pinned task. ESP_INTR_FLAG_IRAM requires the entire ISR code chain and its data to reside in internal memory, not merely the first function marked IRAM_ATTR.
4. Common mistakes
- All IRQs have priority 0.
- Ordinary RTOS API is called from ISR.
- portYIELD_FROM_ISR() is omitted.
- EC25 parsing runs in a UART callback.
- printf or ESP_LOGI in ISR.
- ESP_INTR_FLAG_IRAM is set without a fully IRAM-safe chain.
- An interrupt is allocated from a migratable ESP32 task.
- A shared ISR does not check status.
- An event bit is used as a counter.
- A DMA callback processes the entire buffer.
5. Practical assignment
Create INTERRUPT_POLICY.md.
# Interrupt policy
## Main rule
ISR acknowledges hardware, records minimal data,
notifies the owner task and exits.
## Forbidden in ISR
- blocking calls
- delays
- printf/full logging
- MQTT/TCP operations
- NVS/Flash writes
- modem recovery
- large CRC/parsing
- mutex waits
- malloc/free unless explicitly proven ISR-safeAdd an IRQ table:
UART EC25 RX/DMA: priority 6, calls RTOS, owner modem_task
GPIO phase: priority 9, owner input_task
ADC DMA: priority 7, owner adc_task
FDCAN RX: priority 8, owner can_task
Timer capture: priority 3, no RTOS API6. What to try next
- Measure latency through a debug GPIO.
- Add an irq stats CLI.
- Check STM32 configPRIO_BITS == __NVIC_PRIO_BITS.
- Verify that all IRQs using RTOS API have priority at or below the permitted urgency boundary.
Exercise
Explain the assumptions behind the source example 0..4 forbidden and 5..15 allowed. How would you validate that mapping for a different Cortex-M configuration?
Self-check criteria: Distinguish logical library priority numbers from shifted register encoding; lower numerical priority has higher urgency. This is a conditional example, not a universal IRQ map.
Show the supplied answer
The example assumes 4 implemented priority bits and a configured library syscall threshold of 5. Check the target’s implemented priority bits, priority grouping, FreeRTOS port, and encoded configMAX_SYSCALL_INTERRUPT_PRIORITY; apply the target’s actual boundary rather than copying the example universally.
Exercise
A UART callback parses a large response and logs it. Redesign the path so ISR latency stays bounded and no input bytes are silently lost.
Self-check criteria: Keep ISR work bounded; choose ...FromISR() where allowed and explain the yield decision and buffer-lifetime contract.
Show the supplied answer
The ISR records the ready region or position, clears the source, and signals the owning task through an ISR-safe primitive. The task owns parsing and logging; define buffer ownership and overflow detection so delayed processing is observable.