← Back to Embedded C series
🔌
STM32
STM32 · Prereq: lesson 17

18. NVIC Interrupts and EXTI

Polling a button ties up the CPU and can miss short inputs. With interrupts, main does its work and jumps immediately to a handler (ISR) on an event, then returns. The NVIC governs interrupts on Cortex-M, and EXTI turns external pin changes into interrupts. We take a PA0 button as an EXTI0 interrupt to toggle an LED, and learn to always clear the pending flag (PR) in the ISR.

interruptNVICEXTIAFIOEXTICRpending
Duration
~1.5–2 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 17
OUTCOME
Map a pin to an EXTI line via AFIO->EXTICR, enable interrupts via RTSR/IMR·NVIC_EnableIRQ, and clear PR in the ISR to react instantly to pin changes.

What you'll learn

  • 1Explain polling vs interrupts and their trade-offs
  • 2Understand NVIC's role and NVIC_EnableIRQ()
  • 3Turn a pin edge (rising/falling) into an interrupt via EXTI
  • 4Map a pin to an EXTI line via AFIO->EXTICR
  • 5Write an ISR and clear the pending flag (PR) correctly

Introduction

The NVIC (Nested Vectored Interrupt Controller) manages each interrupt's enable·priority·pending, and EXTI detects rising/falling edges on 16 lines (EXTI0–15) to raise interrupts.

Key concepts

1) NVIC and EXTI registers

RegisterRole
EXTI->IMRinterrupt mask (line enable)
EXTI->RTSR/FTSRrising/falling edge trigger
EXTI->PRpending flag — write 1 to clear
c
NVIC_EnableIRQ(EXTI0_IRQn);   /* CMSIS: enable in NVIC */

2) AFIO->EXTICR — pin→line mapping

c
/* EXTI line n is at EXTICR[n/4], (n%4)*4 bits. 0=PA, 1=PB, 2=PC... */
AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0;   /* EXTI0 ← PA0 */
/* mapping needs the AFIO clock (RCC_APB2ENR_AFIOEN) */

3) ISR and clearing pending

c
void EXTI0_IRQHandler(void) {
    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;        /* write 1 to clear! else infinite re-entry */
        GPIOC->ODR ^= (1u << 13);
    }
}
⚠️

The handler name must match the startup vector table (EXTI0_IRQHandler). Without clearing PR, the same interrupt fires again the moment you leave the handler.

Core example

c
#include "stm32f10x.h"
void EXTI0_IRQHandler(void){
    if (EXTI->PR & EXTI_PR_PR0) { EXTI->PR = EXTI_PR_PR0; GPIOC->ODR ^= (1u<<13); }
}
int main(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
    GPIOA->CRL &= ~0xFu; GPIOA->CRL |= 0x8u; GPIOA->ODR &= ~1u;  /* PA0 input pull-down */
    GPIOC->CRH &= ~(0xFu<<20); GPIOC->CRH |= (0x2u<<20);          /* PC13 output */
    AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0;   /* EXTI0 ← PA0 */
    EXTI->RTSR |= EXTI_RTSR_TR0;              /* rising edge */
    EXTI->IMR  |= EXTI_IMR_MR0;               /* unmask */
    NVIC_EnableIRQ(EXTI0_IRQn);
    while (1) { /* main is free */ }
}

In the simulator's Command window, `PORTA |= 0x0001` (PA0 rising edge) enters EXTI0_IRQHandler and toggles PC13.

Common mistakes

Q. The interrupt fires once and never leaves the handler.

A. You didn't clear EXTI->PR. PR is cleared by writing 1 (EXTI->PR = EXTI_PR_PR0;). Skip it and it re-enters immediately.

Q. The handler is never called.

A. Check ① the AFIO clock (AFIOEN), ② unmask via EXTI->IMR, ③ NVIC_EnableIRQ, and the handler name EXTI0_IRQHandler.

Q. Can I do long work inside the ISR?

A. No. Keep ISRs short; set only a flag and handle heavy work in main (used in lesson 22 ring buffer).

Summary

  • Interrupts auto-call on events, using the CPU efficiently
  • The NVIC governs interrupts; enable with NVIC_EnableIRQ
  • EXTI turns pin edges into interrupts via IMR/RTSR/FTSR/PR
  • Map pin→line via AFIO->EXTICR (needs AFIO clock)
  • Always clear pending (PR) in the ISR and keep handlers short

Exercises

  1. Switch to falling-edge trigger via FTSR and observe the difference
  2. Configure EXTI0 for a PB0 button (map EXTICR to PB)
  3. Refactor so the ISR only sets a volatile flag and main handles the LED
Example code / lecture materials

All lecture materials and example code (with homework and answers) are openly available on GitHub.

View on GitHub ↗