Interrupt Service Routine
, , , , , , ,

Interrupt Service Routine (ISR): Definition, Examples, Best Practices | Embedded Interview Guide (2025)

Learn what an Interrupt Service Routine (ISR) is, how it works, with C examples, NVIC priorities, latency tips, Linux top/bottom halves, and common pitfalls.An Interrupt Service Routine (ISR) is a short, high-priority function that runs automatically when hardware or software triggers an interrupt. Its job is to respond fast, clear the source, and defer heavy work, so the system stays responsive and deterministic.

What Is an Interrupt Service Routine?

An Interrupt Service Routine (ISR) is a special function invoked by the CPU when an interrupt occurs—an asynchronous event like a timer tick, GPIO edge, UART byte received, or a fault condition. The CPU pauses the main thread, saves minimal context, jumps to the ISR address from the interrupt vector table, executes the routine, then returns to the pre-empted code.

Why ISRs Exist

  • Responsiveness: Process time-critical events immediately.
  • Efficiency: Avoid constant polling loops.
  • Determinism: Bound the delay (latency) to meet real-time deadlines.

Core Concepts You Should Know

Interrupt Vector Table (IVT)

A table of addresses that map each interrupt source to its ISR entry point. On ARM Cortex-M, the IVT (vector table) starts at a fixed address (often 0x00000000 or relocated) and includes reset and exception vectors.

Maskable vs Non-Maskable

  • Maskable interrupts: Can be disabled (masked) by the CPU or software.
  • Non-Maskable Interrupt (NMI): Highest priority, cannot be masked, used for critical faults.

Priority and Preemption (NVIC on ARM Cortex-M)

The Nested Vectored Interrupt Controller (NVIC) assigns priorities. A higher-priority interrupt can preempt a lower one (nested interrupts). Proper priority design reduces worst-case latency for critical sources.

Latency vs Response vs Service Time

  • Interrupt latency: Time from event to ISR start.
  • Response time: Latency plus any hardware pipeline delays.
  • Service time: Time spent inside the ISR.
    Goal: Minimize latency and service time to protect real-time behavior.

Golden Rules for Writing ISRs

  1. Keep ISRs short and deterministic.
  2. Clear the interrupt source early (status/flag register).
  3. Never block: no delays, no busy-waits, avoid printf.
  4. Avoid dynamic allocation and heavy computations.
  5. Use volatile for shared variables modified in ISR and main/threads.
  6. Defer work to a main loop, RTOS task, or bottom half.
  7. Minimize critical sections (time with interrupts disabled).
  8. Make ISRs reentrant-safe or explicitly non-reentrant via hardware.
  9. Respect priority scheme; test worst-case nesting.
  10. Instrument and measure latency/jitter with GPIO toggles + scope/logic analyzer.

Minimal C Example (Bare-Metal, Cortex-M Style)

#include <stdint.h>
#include "stm32f4xx.h" // device header (example)

volatile uint8_t button_event = 0;

void EXTI0_IRQHandler(void) {
    // Check pending flag for EXTI line 0
    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;   // 1) Clear interrupt source early
        button_event = 1;         // 2) Set a small flag (volatile)
        // 3) Do not debounce here; defer heavy work to main/task
    }
}

int main(void) {
    // ... GPIO/EXTI/NVIC init: configure PA0 as input, EXTI0 on rising edge
    while (1) {
        if (button_event) {
            button_event = 0;
            // Handle button: debounce in main context or schedule a task
        }
        // other non-blocking work
    }
}

Why this is good: short ISR, clears the flag, uses a volatile flag, defers the work.

With an RTOS (FreeRTOS) — Deferring Work Correctly

// Assume a task waits on a notification or queue
extern TaskHandle_t buttonTaskHandle;

void EXTI0_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;
        vTaskNotifyGiveFromISR(buttonTaskHandle, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // request context switch if needed
    }
}

Notes: Use the FromISR variants only. They are designed to be ISR-safe and avoid locking issues.

Linux Driver Perspective: Top Half vs Bottom Half

In the Linux kernel, the top half is the fast interrupt handler (ISR) that acknowledges the device and schedules a bottom half (e.g., tasklet or workqueue) to complete longer processing in process context.

static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct mydev *d = dev_id;
    u32 status = readl(d->mmio + STATUS);

    if (!(status & IRQ_OCCURRED))
        return IRQ_NONE;

    writel(status, d->mmio + STATUS); // Ack/clear quickly
    schedule_work(&d->work);          // Defer heavy work
    return IRQ_HANDLED;
}

This pattern mirrors the embedded “set a flag and get out quickly” philosophy.

Designing Priorities and Handling Nested Interrupts

  • Put hard real-time sources (e.g., motor control, high-rate ADC, safety signals) at higher priority.
  • Keep their ISRs ultra-short; move computations to lower priority tasks.
  • Test nesting: Use synthetic interrupt storms to verify the system remains stable and meets deadlines.
  • Beware of priority inversion with shared resources — use lock-free queues or ISR-safe ring buffers.

Reducing Interrupt Latency and Jitter

  • Disable interrupts for the shortest possible time.
  • Avoid large critical sections (__disable_irq() / __enable_irq() sparingly).
  • Use branch-free, cache-friendly code in hot paths.
  • Ensure vector table is in fast memory (when relocatable).
  • Tune compiler optimization for ISR sections (inline, -O2/-O3 judiciously).
  • Prefer DMA + ISR for completion events instead of byte-wise ISRs.

Common Mistakes (and Fixes)

  • Forgetting volatile: Variables changed in ISR must be volatile to avoid compiler reordering/optimization issues.
  • Not clearing the interrupt flag: Causes immediate retrigger or lockup.
  • Work inside ISR too heavy: Leads to missed deadlines; always defer.
  • Calling non-reentrant APIs (like printf, malloc) in ISR: avoid or provide ISR-safe alternatives.
  • Priority misconfiguration: A low-priority critical ISR getting delayed.
  • Debouncing in ISR: Use timers or software debouncing in main/task context instead.

Testing & Debugging ISRs

  • Oscilloscope method: Toggle a GPIO at ISR entry/exit to measure latency and service time.
  • Logic analyzer: Correlate multiple events (e.g., RX line vs ISR start).
  • Fault handlers: Implement HardFault/NMI handlers with minimal logging hooks.
  • Stress tests: Burst interrupts, nested paths, and DMA completion storms.
  • Static analysis: Check for race conditions, missing volatile, and reentrancy issues.

Quick ISR Checklist (Pin or print)

  • ISR is short and clears the source early
  • Uses volatile for shared flags/counters
  • No blocking calls, no dynamic allocation
  • Work deferred to main/task/bottom half
  • Priorities documented and tested for nesting
  • Latency/jitter measured with GPIO or tracing
  • Minimal time with interrupts disabled
  • Safe access to shared peripherals/buffers

FAQ: Interrupt Service Routine

Q1. What exactly is an Interrupt Service Routine?
An ISR is a high-priority function that automatically runs when an interrupt occurs, handles the event quickly, and returns control to normal code.

Q2. What should never be inside an ISR?
Blocking calls, long loops, printf, dynamic memory allocation, or any heavy computation. Defer to a task/bottom half.

Q3. How do I share data between ISR and main code safely?
Use volatile for simple flags/counters. For larger data, use lock-free ring buffers or RTOS queues with FromISR APIs.

Q4. What is interrupt latency and how do I reduce it?
Latency is the delay from the event to ISR start. Reduce by shortening disabled-interrupt regions, optimizing priorities, and keeping ISRs minimal.

Q5. Are nested interrupts good or bad?
They’re essential for critical events but must be used carefully. Keep higher-priority ISRs ultra-short and validate worst-case timing.

Conclusion

An Interrupt Service Routine is the backbone of a responsive embedded and real-time system. Design for speed, simplicity, and determinism: acknowledge the event, clear the source, and defer the heavy work. With disciplined priorities, careful sharing of data, and solid testing, your ISRs will meet deadlines and keep the whole system stable.

Interrupt Service Routine
Interrupt Service Routine (ISR) Definition, Examples, Best Practices Embedded Interview Guide (2025)

Leave a Reply

Your email address will not be published. Required fields are marked *