In short: An interrupt is a hardware or software signal that pauses the CPU's normal flow to run a short handler called an ISR. Mastering interrupts in embedded systems means understanding the NVIC, interrupt latency, priority levels, nesting, and the discipline of keeping ISRs tiny and deterministic.
At its core, an interrupt is an asynchronous event that tells the processor "stop what you are doing and attend to me." Instead of the CPU repeatedly polling a peripheral to ask "are you ready yet?", the peripheral raises a signal when it genuinely needs service. The processor finishes the current instruction, saves enough context to resume later, and jumps to a dedicated routine. Understanding interrupts in embedded systems is fundamental because almost every responsive, low-power, real-time device depends on them rather than on wasteful busy-wait loops.
The routine that services the event is the Interrupt Service Routine (ISR), sometimes called an interrupt handler. Each interrupt source — a timer overflow, a UART byte received, a GPIO edge, an ADC conversion complete — maps to an entry in the vector table, a list of handler addresses the hardware consults to find the right ISR. When the event fires, the CPU looks up the vector, pushes state, and begins executing the handler.
Polling wastes CPU cycles and energy, and it reacts only as often as the loop revisits a check. Interrupts invert the model: the CPU sleeps or does useful work until an event demands attention, then responds within microseconds. This makes interrupts the backbone of real-time behaviour, power management, and clean software architecture.
The mechanism on a Cortex-M follows a precise sequence. A peripheral asserts its interrupt line, which reaches the NVIC (Nested Vectored Interrupt Controller). The NVIC checks whether the interrupt is enabled and whether its priority is high enough to preempt whatever is running. If so, it signals the core, which completes the current instruction, pushes the register frame, loads the handler address from the vector table, and executes the ISR. On return, a special exception-return value in the Link Register triggers automatic unstacking, and the interrupted code resumes exactly where it left off.
Interrupt latency is the time between the event asserting and the first useful instruction of the ISR running. It is the sum of several contributions: the time to finish the current instruction, hardware stacking cycles, vector fetch, and any higher-priority activity or critical sections that temporarily mask interrupts. On Cortex-M this is famously deterministic — typically 12 cycles to enter — and features like tail-chaining skip redundant unstack/restack when a second interrupt is already pending, cutting latency between back-to-back ISRs.
Not all events are equally urgent. A motor fault must be handled before a routine housekeeping timer. Priorities let the system express this ranking. On Cortex-M the NVIC gives each interrupt a configurable priority number, where a lower numeric value means higher urgency — a common source of confusion for beginners.
Priority is split into two fields: preemption priority (group priority) and sub-priority. A running ISR can be interrupted only by another whose preemption priority is higher. This is nesting: an ISR runs, a more urgent event arrives, its handler preempts and runs to completion, then control returns to the first ISR. Sub-priority does not cause preemption; it only decides the order when two interrupts of equal preemption level are pending at the same instant.
A frequent bug is a priority inversion or an unintended lockup where a low-priority ISR holds a resource a high-priority path needs. Careful priority assignment and short critical sections keep the system predictable.
The single most important rule: keep ISRs short and bounded. An ISR runs with other interrupts possibly masked and steals time from the whole system, so it should do the minimum — clear the hardware flag, grab the data, and defer the heavy work.
volatile so the compiler does not cache stale values, and protect multi-byte updates with a short critical section or atomic access.In an RTOS such as FreeRTOS, ISRs communicate with tasks using the FromISR API variants (for example xQueueSendFromISR) and may request a context switch on exit so a freshly-unblocked high-priority task runs immediately.
Vendor tooling makes interrupt configuration far less error-prone than hand-writing register values. STMicroelectronics' STM32CubeMX and HAL generate the NVIC enable calls, priority-group settings, and weak ISR stubs for you, so you fill in only the handler body. Silicon Labs, NXP, and Microchip ship equivalent configurators. The CMSIS standard from Arm provides a uniform API — NVIC_EnableIRQ(), NVIC_SetPriority() — so code ports across Cortex-M vendors.
On the debugging side, tools like SEGGER SystemView and Percepio Tracealyzer visualise exactly when each ISR fired and how long it ran, exposing latency spikes and overlong handlers. Logic analysers with GPIO toggling at ISR entry and exit remain a cheap, precise way to measure real latency and execution time on the bench. RTOS kernels add interrupt-safe primitives and a documented maximum priority at which their API may be called.
Real projects and industry tool flows, with certification.
Explore CourseTron Courses →Polling has the CPU actively and repeatedly check a status register, consuming cycles even when nothing changes. An interrupt is event-driven: the peripheral signals the CPU only when service is needed, giving lower latency, lower power, and better CPU utilisation. Polling can be simpler and more deterministic for very fast, tightly bounded loops, but interrupts scale far better across many sources.
While an ISR runs it can block equal- or lower-priority interrupts and steals time from the main program, hurting overall responsiveness and worst-case latency. A long ISR risks missing other time-critical events. Best practice is to do only the urgent minimum — clear the flag, capture data, signal a task — and defer heavy processing to the main loop or a dedicated task.
volatile tells the compiler the variable can change outside the normal program flow, so it must read it from memory each time rather than caching it in a register or optimising the access away. Without it, a flag set inside an ISR might never appear to change in the main loop. Note that volatile guarantees visibility, not atomicity — multi-byte values still need a critical section or atomic access.
Nesting means a higher-priority interrupt can preempt an ISR that is already executing, run to completion, and then return to the original ISR. The Cortex-M NVIC supports this natively through configurable preemption-priority levels: an interrupt preempts only if its preemption priority is higher (numerically lower) than the one currently running. Sub-priority merely orders simultaneously-pending interrupts of equal preemption level.
Related: a related topic · the VLSI Glossary · all courses.