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.
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
| Register | Role |
|---|---|
| EXTI->IMR | interrupt mask (line enable) |
| EXTI->RTSR/FTSR | rising/falling edge trigger |
| EXTI->PR | pending flag — write 1 to clear |
NVIC_EnableIRQ(EXTI0_IRQn); /* CMSIS: enable in NVIC */2) AFIO->EXTICR — pin→line mapping
/* 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
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
#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
- Switch to falling-edge trigger via FTSR and observe the difference
- Configure EXTI0 for a PB0 button (map EXTICR to PB)
- Refactor so the ISR only sets a volatile flag and main handles the LED
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗