5.5.7 · D5Embedded Systems & Real-Time Software
Question bank — Interrupts — ISR design, NVIC priority, interrupt latency
Ground-work: the words these traps rely on
Before the questions, two small pictures. If you already know them cold, skip — but every trap below leans on them.

The figure above is the 12-cycle deterministic hardware part referenced in the traps: on Cortex-M3/M4 with no wait states, lands at the famous 12 cycles. Everything else in the latency ( and especially ) is variable and depends on your code.
True or false — justify
A higher priority number on Cortex-M means a more urgent interrupt.
False — on the NVIC lower numeric value = higher urgency, so priority
0 is the most urgent. This is the reverse of everyday "level 10 beats level 1" intuition.Sub-priority can let one interrupt preempt another mid-execution.
False — sub-priority is only a tiebreaker when two equal-preemption interrupts are pending at the same instant; it never causes preemption. Only a higher preemption priority can interrupt a running ISR.
An ISR always executes to completion before any other interrupt can run.
False — that is only true for equal or lower priority. A higher preemption-priority interrupt will preempt (nest into) a running ISR; that nesting is the whole point of the NVIC.
Interrupts are strictly faster than polling in every situation.
False — for a device that is always ready (a tight, high-rate stream you must service continuously), polling can be lower-overhead because you skip the stacking and vector-fetch cost each event. Interrupts win when events are sporadic and you'd otherwise waste cycles checking.
Hardware stacking means you never need to worry about saving registers in an ISR.
False — hardware auto-stacks the caller-saved set (–, , , , ) defined above. If your ISR uses callee-saved registers (–), the compiler emits code to save/restore them; you rely on writing the ISR as a normal C function so the compiler does this.
The volatile keyword makes access to a shared variable atomic.
False —
volatile only forces the compiler to re-read from memory each time (defeating caching in a register). It gives you no atomicity; a multi-byte variable can still be torn mid-update. See Concurrency & Race Conditions.Tail-chaining changes the logical order in which two pending interrupts run.
False — it only removes the wasted unstack-then-restack between back-to-back ISRs. The order is still decided by priority; tail-chaining is a pure latency optimization, not a scheduling change.
The 12-cycle Cortex-M figure (see the diagram above) is the total latency you'll actually observe.
False — 12 cycles is only the deterministic hardware part (, no wait states). Real latency also adds and any (interrupts masked, or a higher/equal-priority ISR already running).
Spot the error
An engineer forgets to clear the peripheral's interrupt flag inside the ISR. What breaks?
The interrupt line stays asserted, so the moment the ISR returns the NVIC sees it still pending and re-enters immediately — an effectively infinite ISR loop that starves the main code.
Code does while(!flag); where flag is a plain int set by an ISR. Why can it hang forever?
The compiler caches
flag in a register on the first read and never revisits RAM, so it never sees the ISR's write. Mark it ==volatile== so each read hits memory.An ISR calls printf() to log the event. What's the design flaw?
printf is long-running, may block, and is often non-reentrant; running it in an ISR balloons latency for every equal/lower-priority interrupt and can corrupt shared library state. Set a flag and let the main loop log.The main loop reads a 32-bit counter that an ISR updates, on a chip where the read is one instruction. It's still marked volatile but read without disabling IRQs — safe?
If the read is a single atomic access it's fine for tearing, but if you read the counter twice (e.g. compare then use) the ISR can change it between reads. Atomicity is about the whole logical operation, not one instruction.
A designer gives the UART-receive IRQ a low urgency (large number) because "logging isn't critical," but bytes keep getting dropped. Where's the reasoning wrong?
Dropping bytes is a latency/deadline problem, not an importance-of-content problem. A slow-to-service receive IRQ overruns its hardware buffer; the fix is a high urgency short ISR that shovels bytes into a ring buffer.
Two interrupts share the same preemption priority; the designer expects the higher sub-priority one to interrupt the other. Why won't it?
Equal preemption priority means neither can preempt the other — sub-priority only decides who goes first when both are pending simultaneously. Once one is running, the other must wait for it to return.
Someone wraps a large data-processing block in __disable_irq() "to be safe." What does this cost?
Every interrupt is masked for the whole block, so — and therefore worst-case latency — grows to the length of that block. Critical sections must be short and bounded; disable IRQs only around the few instructions that actually touch shared state.
Why questions
Why does hardware stack registers automatically instead of leaving it to your ISR code?
To make the ISR a plain C function and to make latency deterministic — the CPU can begin stacking immediately in fixed hardware time () rather than waiting on variable software prologue.
Why split a priority value into preemption and sub-priority fields at all?
They answer two different questions: "who can interrupt whom" (preemption) and "who wins a tie without preemption" (sub-priority). Keeping ties non-preempting avoids needless context switches between equally-urgent handlers.
Why must a critical section that guards ISR-shared data be as short as possible?
Because inside it interrupts are masked, so any event arriving mid-section adds the entire section length to before stacking can even start — inflating latency and risking missed deadlines. See Real-Time Scheduling & Deadlines.
Why can a long division instruction increase latency even though your ISR is short?
Division is a multi-cycle instruction, so it isn't at an interruptible point partway through; the CPU must let it finish (or restart it) before taking the interrupt. That wait is , and it's independent of how short your ISR is.
Why do we defer heavy work to the main loop instead of doing it in the ISR?
A long ISR blocks every equal/lower-priority interrupt for its whole duration. Doing the minimum (grab data, set flag) keeps latency low and bounded for everyone else; the non-time-critical work then happens where preemption is cheap.
Why does tail-chaining save cycles when two IRQs are back-to-back?
The 8-register context already on the stack is still valid, so popping it just to push the identical set again is wasted work — hardware jumps handler-to-handler directly (~6 cycles instead of ~24).
Why can interrupts lower average power draw?
They let the CPU sleep and be woken by an event rather than spinning in a polling loop, so it spends more time in a low-power sleep mode instead of burning cycles checking a device that isn't ready.
Edge cases
What happens if an interrupt fires exactly while it is masked by a critical section?
It becomes pending in the NVIC and is remembered. The instant the mask is lifted it is taken — so you don't lose the event, but you do pay the full critical-section time as added .
Two interrupts of equal preemption and equal sub-priority arrive together — how is the tie broken?
There is no user-programmable tiebreaker left, so the NVIC falls back to a fixed rule: the one with the lower exception/IRQ number is serviced first.
If a higher-priority interrupt keeps firing rapidly, what happens to a lower-priority one?
It can be starved — perpetually preempted and never allowed to make progress. This is a real-time hazard; you avoid it with bounded high-priority ISRs or rate limiting on the fast source.
Can an interrupt preempt an ISR that has the same preemption priority as itself?
No — equal preemption priority means no nesting. It waits pending until the running ISR returns, even if its sub-priority is "higher."
What is the latency of an interrupt that arrives while nothing is running and nothing is masked?
Essentially the pure deterministic hardware minimum (, the ~12 cycles on Cortex-M3/M4 shown above), since is zero and there's no long instruction to finish.
If an ISR re-enables interrupts and then a higher-priority IRQ nests into it, whose stacked context is on the stack?
Both — the original interrupted code's context (–, , , , ) was stacked when the first ISR was entered, and the first ISR's context is stacked when the nested one is entered. Unstacking unwinds them in last-in-first-out order on each return.
Recall One-line self-check before you leave
Lower number = more urgent; ISRs short and bounded; shared vars volatile and atomic; latency = the deterministic 12-cycle hardware part plus and whatever you blocked (). Question ::: If all four feel obvious, you've beaten the traps.