Interrupts and the kinds you meet
An interrupt lets a pin, a peripheral, an instruction or an error pause the CPU, run a short handler and resume exactly where it left off, so nothing has to be polled. Interrupts differ by source and by behaviour: maskable or not, edge or level, own handler or shared.
What happens when one fires
Without interrupts, firmware polls: it checks a status flag in a loop and burns CPU time while nothing happens. With interrupts, the source raises a request and an interrupt controller passes it to the CPU.
The sequence is much the same on every chip. The CPU finishes its current instruction, saves its context (at least the program counter and status), finds the handler's address, usually in a vector table, and runs the interrupt service routine (ISR). Afterwards it restores the context and carries on.
On an Arm Cortex-M, for example, the core itself stacks the registers a C function may change, so an ISR is a plain C function; other cores leave more of that to the compiler. The delay from request to handler ranges from a few cycles to a few dozen, depending on the core: its manual gives the figure.
Kinds by source
- External: a pin changes, such as a button or a sensor's data-ready line.
- Peripheral: a UART received a byte, a timer overflowed, or an ADC conversion or DMA transfer finished.
- Software: an instruction raises one on purpose (
SVCon Arm,ECALLon RISC-V,INTon x86), typically so code can call an OS or RTOS kernel. - Fault: the CPU itself hits an error, such as an undefined instruction or a bad memory access.
Arm calls all of these exceptions. On a Cortex-M, pin and peripheral interrupts reach the core as IRQs through the NVIC, while SVCall, PendSV and HardFault are the core's own system exceptions.
Kinds by behaviour
- Maskable or not: most interrupts can be disabled, globally or one by one. A non-maskable interrupt (NMI) is kept for events that must never be ignored, such as a failed external crystal on STM32 parts.
- Edge or level: an edge fires once on a transition; a level stays requested until the source is cleared, so a handler that forgets the flag runs again at once.
- Own or shared handler: a vectored source has its own handler; a shared one reads a status register to see what fired, like one UART handler for receive, transmit and errors.
- Priority: a more urgent interrupt can pre-empt a running handler. How many levels exist depends on the chip; an STM32F4 has 16.
Whatever the kind, keep handlers short: clear the flag, do the minimum and leave the real work to the main loop. A button on EXTI line 0 of an STM32F4, as an example:
volatile bool button_pressed; // shared with the main loop
void EXTI0_IRQHandler(void) { // name from the startup file
EXTI->PR = EXTI_PR_PR0; // clear the flag: write 1
button_pressed = true; // do the minimum
}
// in the main loop
if (button_pressed) { button_pressed = false; handle_button(); }