What Are Interrupts in Embedded Systems and How Do They Work?
Last updated 26 June 2026 · 6 min read
Direct Answer
An interrupt is a hardware signal that causes a microcontroller to immediately suspend its current code and execute a dedicated interrupt service routine (ISR) to handle the event. When the ISR completes, execution resumes exactly where it was interrupted. Interrupts enable embedded systems to respond to hardware events — timer expiry, received byte, GPIO edge, ADC conversion complete — with deterministic, low-latency responses that polling a register in a main loop cannot match.
Detailed Explanation
A microcontroller running firmware has two fundamental ways to detect and respond to a hardware event: polling and interrupts.
Polling means the main loop repeatedly reads a status register to check whether an event has occurred. It is simple but wasteful — the CPU spends cycles checking a flag that is usually false, and if an event occurs between checks, the response is delayed by however long it takes for the loop to come around again.
Interrupts invert this model. The peripheral hardware raises an interrupt line when the event occurs; the CPU hardware detects the signal, finishes its current instruction, saves the processor state, and immediately jumps to the ISR. The response latency is deterministic — on ARM Cortex-M, the hardware overhead is 12 clock cycles — regardless of what the main code was doing.
The interrupt vector table
Every MCU has an interrupt vector table (IVT) — a fixed array of function pointers, one per possible interrupt source, placed at a known address in flash memory. When an interrupt fires, the hardware reads the appropriate entry from the vector table and jumps to that address. The compiler and linker toolchain populates the vector table with the addresses of the ISR functions defined in firmware.
On ARM Cortex-M, the vector table also contains the initial stack pointer and the reset handler (the first code that runs on power-up), and exception handlers for hard faults, bus faults, and usage faults.
The NVIC (Nested Vectored Interrupt Controller)
ARM Cortex-M MCUs include a hardware block called the NVIC that manages all interrupt sources. The NVIC provides:
- Enable/disable per interrupt: each interrupt source can be individually enabled or disabled.
- Priority levels: each interrupt is assigned a numeric priority. Lower numerical value = higher priority. The NVIC automatically selects the highest-priority pending interrupt to serve first.
- Pre-emption and nesting: a higher-priority interrupt can pre-empt a currently running ISR. The interrupted ISR's state is pushed to the stack; after the higher-priority ISR returns, the original ISR resumes.
- Pending state: if an interrupt fires while masked or while a same/higher-priority ISR is running, the NVIC holds it pending and serves it as soon as the blocking condition clears.
Priority grouping on Cortex-M
Cortex-M implements priority as a configurable split between pre-emption priority (determines whether one ISR can interrupt another) and sub-priority (used to break ties between two pending interrupts at the same pre-emption level). The number of bits available for each is configurable and varies by MCU family. STM32 typically uses 4 bits of priority with a 4/0 pre-emption/sub split by default.
Interrupt latency sources
The total time from event to ISR first instruction includes:
- Hardware NVIC latency: ~12 cycles on Cortex-M (state saving + vector fetch).
- Interrupt masking: if
__disable_irq()or a critical section is held, the interrupt is delayed until interrupts are re-enabled. - Higher-priority ISR in progress: lower-priority interrupts wait until any currently running higher-priority ISR finishes.
- Flash wait states: on MCUs with flash wait states, vector fetch and ISR instruction fetch may be slower than for code running from cache.
ISRs and the RTOS
When using an RTOS, ISRs run at interrupt priority — above all RTOS tasks — and should return as quickly as possible. An ISR should not call RTOS blocking functions (e.g. standard xQueueSend), but can use ISR-safe variants (e.g. xQueueSendFromISR in FreeRTOS) to signal a waiting task, which wakes on the next RTOS scheduler tick.
Practical Examples
UART receive ISR: a UART peripheral raises an interrupt each time a byte is received. The ISR reads the byte from the data register and writes it into a ring buffer. The main loop (or an RTOS task) reads from the ring buffer when it has time. This decouples the byte arrival (hardware-speed, unpredictable) from the main loop processing (application-speed).
Timer compare-match ISR: a timer peripheral is set to fire an interrupt every 1 ms. The ISR increments a tick counter and sets a flag if a 100 ms interval has elapsed. The main loop reads the flag to know when to sample a sensor. This is the basis of almost all periodic timing in embedded firmware.
GPIO edge interrupt: a motion sensor drives a GPIO pin high when motion is detected. The GPIO edge interrupt wakes the MCU from sleep, the ISR sets a flag, and the main loop powers up the radio and transmits an alert. Without the interrupt, the MCU would either poll the pin constantly (wasting power) or miss a fast event between polls.
Design Considerations
- Keep ISRs short: an ISR should do the minimum necessary — read the hardware register, copy the value to a buffer, set a flag — and return. Long ISRs block all lower-priority interrupts and increase overall system latency.
- Volatile variables shared with ISRs: any variable written by an ISR and read by the main code must be declared
volatile, or the compiler may cache the value in a register and miss ISR updates. For multi-byte values, a critical section (disable interrupts, read, re-enable) is also needed on non-atomic architectures. - Stack usage in ISRs: each nested ISR consumes stack space for the CPU state-save frame (~32 bytes on Cortex-M) plus local variable usage. Deep nesting with large local ISR variables can overflow the stack silently.
- Interrupt storms: if a peripheral fires interrupts faster than the ISR can service them (e.g. a UART at a high baud rate without DMA), the CPU can become interrupt-bound, spending most of its time in ISRs with no time for application logic. Use DMA to buffer high-throughput peripherals where possible.
- STM32 reset causes: unhandled hard faults (which often result from a corrupt stack caused by an ISR running with insufficient stack space) are a common cause of unexpected MCU resets. If your firmware crashes unpredictably, check the fault handler and reset-cause register.
- STM32 NVIC priority configuration: STM32-specific NVIC configuration — preemption priority vs sub-priority,
HAL_NVIC_SetPriority, priority grouping, and the FreeRTOSconfigMAX_SYSCALL_INTERRUPT_PRIORITYconstraint — is covered in detail in How Do You Configure STM32 NVIC Interrupt Priorities?. - Production interrupt handling: designing interrupt priority schemes and ISR-to-task handoff patterns for production embedded systems is a core competency of Zeus Design's embedded software team.
Common Mistakes
- Declaring ISR variables without
volatile: the compiler may optimise away reads of a variable it cannot see being modified in an ISR, producing bugs that disappear under debug builds (which disable many optimisations). - Calling blocking functions in ISRs: any function that waits —
HAL_Delay, RTOS blocking calls,printf(if it waits on UART transmit complete) — must not be called from an ISR. These functions either spin-poll or call RTOS APIs that assume a task context. - Forgetting to clear the interrupt flag: most peripherals require the firmware to explicitly clear the interrupt pending flag in the ISR. Failing to clear it causes the ISR to fire repeatedly in a tight loop immediately on return, stalling the entire CPU.
- Setting all interrupts to the same priority: if every interrupt has the same priority, none can pre-empt another. Time-critical ISRs (comms, motor control) should have higher priority than non-critical ones (logging, LED control).
Frequently Asked Questions
- What is interrupt latency and why does it matter?
- Interrupt latency is the time between the hardware event that triggers an interrupt and the first instruction of the ISR executing. On ARM Cortex-M, hardware latency is 12 clock cycles for a standard interrupt with no pre-emption delay. If interrupts are masked (disabled) anywhere in the code, events that arrive during that window are delayed until interrupts are re-enabled. Minimising interrupt latency is critical for hard real-time systems where a slow response to a hardware event causes a functional failure.
- What is the difference between a hardware interrupt and a software interrupt?
- A hardware interrupt is triggered by a peripheral or external signal — a GPIO edge, a timer overflow, a UART receive, a DMA completion. A software interrupt is triggered by executing a specific instruction (e.g. SVC on ARM, used for system calls). For most embedded firmware, 'interrupt' means hardware interrupt unless specifically qualified.
- Can interrupts interrupt each other?
- Yes, if nested interrupts are enabled and a new interrupt has a higher priority than the currently running ISR. The ARM Cortex-M NVIC supports full nesting — a high-priority interrupt will pre-empt a lower-priority ISR. Interrupts of equal or lower priority are held pending until the current ISR finishes. The maximum depth of nesting is limited by the available stack space.
References
Related Questions
What Is a Microcontroller (MCU)?
A microcontroller (MCU) combines a CPU, flash, RAM, and peripherals on one chip. Learn how MCUs work and how they differ from microprocessors and FPGAs.
Bare-Metal vs RTOS: Which Should You Use for Your Firmware?
Bare-metal firmware and RTOS suit different embedded projects. Learn the trade-offs — timing, RAM overhead, complexity — and how to choose.
How Do You Create and Schedule Tasks in FreeRTOS?
Learn how to create FreeRTOS tasks with xTaskCreate, configure task priorities, size stacks safely, and start the scheduler on ARM Cortex-M MCUs.
How Do GPIO Pins Work on a Microcontroller?
GPIO (General Purpose Input/Output) pins let a microcontroller read digital signals and drive outputs. Learn how push-pull, open-drain, and pull resistors work.
Why Does My STM32 Keep Resetting Unexpectedly?
STM32 unexpected resets are caused by watchdog timeout, brown-out, hard fault, or power decoupling issues. Use the RCC reset flags to identify the root cause.
How Do You Configure STM32 NVIC Interrupt Priorities?
Learn how to configure STM32 NVIC interrupt priorities using HAL, priority grouping, and the FreeRTOS configMAX_SYSCALL_INTERRUPT_PRIORITY constraint.