← Writing

Interrupts (ISR, IVT, NVIC)

2025-07-15 embedded · basic · interrupts


🟠 Interrupts allow the microcontroller to instantly respond to important events without wasting resources on constantly polling the peripherals.

When an interrupt occurs, the CPU suspends the current task, goes to the Interrupt Service Routine (ISR), performs the required action, and returns back.


Interrupts

An interrupt is a signal that tells the microcontroller’s processor to temporarily suspend execution of the current task and go to a special function called an interrupt handler (ISR). After the ISR completes, the processor returns to the point where the program was interrupted and continues executing it.


Interrupt reasons:


Interrupt types

By source

By processing method

By priority


ISR and IVT

Interrupts are handled by Interrupt Service Routines.


How the CPU handles interrupts

At a high level:

  1. When an interrupt is triggered, the CPU receives a signal to handle it. The CPU will probably be busy with some other work. However, we want it to continue where it left off after handling the interrupt.
  2. To do this, the CPU saves the current execution state, which includes the registers and the current address of the current instruction.
  3. It then looks up the IVT to find the memory address of the ISR, and then executes it.
  4. It then restores the interrupted execution state from RAM and continues.

Interrupt Priorities

What happens if we have multiple interrupts firing at the same time? We handle them by priority.

Interrupts have a priority associated with them. When multiple interrupts fire, the interrupt with the highest priority is handled first. Furthermore, a higher priority interrupt can even interrupt a lower priority interrupt.

This ensures that critical interrupts are handled in a timely manner. This is especially useful in hard real-time systems where they are the upper bounds of worst-case performance.


How to set up an interrupt in an MCU

There are several steps to using interrupts in an MCU:

  1. Enabling interrupt: Enable the interrupt for the desired source (for example, in the timer or GPIO settings).
  2. Setting priority: If the MCU supports programmable priorities, set the priority level for this interrupt.
  3. Writing a handler: Create an ISR function that will be executed when an interrupt occurs.
  4. Registering a handler: In some architectures (for example, ARM Cortex-M), specify the handler address in the vector table.

NVIC (Nested Vectored Interrupt Controller)

Many modern MCUs are based on the ARM Cortex-M architecture, where interrupt management is performed via the NVIC (Nested Vectored Interrupt Controller). Let’s consider its features:


🧪 Example in Rust (STM32)

use cortex_m_rt::interrupt;

#[interrupt]
fn TIM2() {
    // TIM2 timer interrupt handling logic
    // For example, clearing the interrupt flag or performing an action
}

Safety: Critical Sections

Interrupts can interfere with the main code. To protect shared data:

A simple way is to temporarily disable interrupts

use cortex_m::interrupt;

interrupt::free(|_| {
    // Critical Section: Safe Access
});

Mutex and RefCell

use cortex_m::interrupt::Mutex;
use core::cell::RefCell;

static SHARED_DATA: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));

#[interrupt]
fn EXTI0() {
    interrupt::free(|cs| {
        let mut data = SHARED_DATA.borrow(cs).borrow_mut();
        *data += 1;
    });
}

Interrupt Handling Guidelines