Loading header...

Interrupts, Concurrency, and Critical Sections

Interrupts let firmware respond quickly to hardware events, but they also create concurrency. Main code and interrupt handlers can touch the same data at different times, so correctness depends on ownership, atomicity, and bounded latency.

Learning Objectives

By the end of this lesson, you should be able to explain interrupt latency, protect shared data, choose critical sections carefully, and debug common race conditions in bare-metal firmware.

Interrupt Flow

sequenceDiagram participant HW as Hardware participant NVIC as Interrupt controller participant ISR as ISR participant Main as Main loop HW->>NVIC: event flag NVIC->>ISR: vector entry ISR->>ISR: clear flag, capture data ISR->>Main: set event flag Main->>Main: process event later

A good ISR captures the event, clears the hardware condition, stores minimal data, and lets main code do the longer work.

Shared Data

If an ISR writes a variable read by main code, mark it volatile so the compiler reloads it.

static volatile bool rx_ready;

void UART_IRQHandler(void)
{
    rx_ready = true;
}

Volatile does not make multi-byte updates atomic and does not protect compound operations.

Critical Sections

uint32_t flags_snapshot;

disable_interrupts();
flags_snapshot = event_flags;
event_flags = 0;
enable_interrupts();

Keep critical sections short. Long interrupt masking increases latency and can lose hardware events.

Latency Budget

$$
T_{response}=T_{mask}+T_{higherISR}+T_{other}
$$

$$
T_{other}=T_{entry}+T_{handler}
$$

Compare response time to the hardware deadline. A UART receive interrupt must service data before the receive FIFO overflows.

Worked Example: Button Event

Bad design: debounce inside the GPIO ISR with a 20 ms delay. Better design: GPIO ISR records timestamp and sets button_edge; a main-loop or timer task waits for stable state and generates a debounced event.

Practical Checks

  • List every variable shared between ISR and main code.
  • Confirm which operations are atomic on the target MCU.
  • Measure longest ISR time with a GPIO toggle or trace.
  • Avoid blocking calls, logging, and dynamic allocation in ISRs.
  • Use ring buffers for byte streams.

Common Mistakes

  • Thinking volatile prevents races.
  • Clearing interrupt flags in the wrong order.
  • Calling slow protocol stacks from ISRs.
  • Disabling interrupts around large computations.
  • Forgetting priority inversion between interrupts and RTOS tasks.

Summary

Interrupts are necessary for responsive firmware, but they require clear rules. Keep ISRs short, protect shared data, understand atomicity, and measure latency against hardware deadlines.

Further Reading

  • Arm, "Cortex-M Interrupts and Exception Handling."
  • FreeRTOS documentation on ISRs and interrupt priorities.
  • Jack Ganssle, "Interrupts" and debounce articles.

Mind Map

mindmap root((Interrupt Concurrency)) Core concept Async hardware events Shared data races Bounded latency Applications UART receive Timer tick GPIO edge DMA complete Calculations Response time equals mask plus higher ISR plus entry plus handler FIFO service time Debounce interval ISR CPU load Design rules Keep ISR short Use volatile for visibility Protect compound access Clear flags correctly Practical checks Shared variable list Atomic width ISR timing Lost event tests Common mistakes Volatile as lock Delay in ISR Long critical section Wrong priority