Bare Metal STM32 · Part 3 of 7

Interrupts & NVIC: Complete Configuration Guide

STM32 Interrupts NVIC Intermediate Source Code
Bare Metal STM32 Part 3
Bare Metal STM32 — Part 3
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 3 / 7
47:22
Intermediate
STM32F4 · Cortex-M4
01

Overview

The NVIC (Nested Vectored Interrupt Controller) is the ARM Cortex-M hardware that makes real-time response possible. Understanding it fully — priorities, preemption, subpriority groups, EXTI routing, and ISR best practices — is the difference between a system that works and one that deadlocks under load. This part covers every layer: from EXTI line to vector table to CMSIS API.

  • Configure EXTI lines to trigger on GPIO rising/falling edges
  • Set NVIC preemption priority groups and understand the priority encoding
  • Write ISRs that are safe, short, and use volatile correctly
  • Implement critical sections with __disable_irq() / __enable_irq()
02

NVIC Priority Model

🎯
Preemption Priority
A higher-priority ISR (lower number) can interrupt a running lower-priority ISR. This is the main priority that matters for real-time response. Set with NVIC_SetPriority().
Lower number = higher priority
🔢
Subpriority
Breaks ties when two ISRs of the same preemption level are pending simultaneously. Does NOT allow preemption. Configured by NVIC_SetPriorityGrouping().
Tie-breaker only, no preemption
🔀
EXTI Routing
GPIO pins share EXTI lines by pin number — PA0 and PB0 both use EXTI0. Only one port can be active per line. Route via SYSCFG_EXTICRx before enabling the interrupt.
SYSCFG→EXTICR[n]
03

Code

01
EXTI GPIO interrupt — full bare-metal setup
nvic_exti.c
C
void EXTI0_Init(void) {
    /* 1. Enable clocks for GPIOA and SYSCFG */
    RCC->AHB1ENR  |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR  |= RCC_APB2ENR_SYSCFGEN;

    /* 2. Configure PA0 as input with pull-up */
    GPIOA->MODER  &= ~(0x3 << 0);   // Input mode
    GPIOA->PUPDR  &= ~(0x3 << 0);
    GPIOA->PUPDR  |=  (0x1 << 0);   // Pull-up

    /* 3. Route EXTI0 to port A (SYSCFG_EXTICR1 bits [3:0] = 0000) */
    SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;  // 0000 = PA

    /* 4. Configure EXTI: unmask line 0, falling-edge trigger */
    EXTI->IMR  |= EXTI_IMR_MR0;    // Unmask interrupt line 0
    EXTI->RTSR &= ~EXTI_RTSR_TR0; // No rising-edge
    EXTI->FTSR |= EXTI_FTSR_TR0;  // Falling-edge trigger

    /* 5. Configure NVIC: enable, set priority */
    // Priority group 4: 4 bits preemption, 0 subpriority
    NVIC_SetPriorityGrouping(0);
    NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(0, 5, 0));
    NVIC_EnableIRQ(EXTI0_IRQn);
}

/* ISR — keep it short. Set a flag, post to queue, give a semaphore. */
volatile uint32_t g_button_count = 0;

void EXTI0_IRQHandler(void) {
    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;   // Clear pending — write 1 to clear
        g_button_count++;
    }
}
02
Critical section pattern
critical.c
C
/* Save/restore pattern — safer than bare disable/enable */
void Update_SharedBuffer(void) {
    uint32_t primask = __get_PRIMASK();
    __disable_irq();        // Mask all exceptions (sets PRIMASK)

    // --- critical section ---
    shared_buf[write_idx] = new_data;
    write_idx = (write_idx + 1) % BUF_SIZE;
    // ------------------------

    if (!primask) __enable_irq(); // Restore only if was enabled
}

/* For RTOS use: basepri masks below a threshold, not all IRQs */
void RTOS_CriticalSection(void) {
    __set_BASEPRI(5 << (8 - 4));  // Mask priority < 5 (FreeRTOS style)
    __DSB(); __ISB();              // Instruction/data sync barriers
    // ... critical work ...
    __set_BASEPRI(0);             // Re-enable all
}
⚠️
Clear the pending flag first in your ISR
For EXTI, write 1 to the PR register to clear the pending bit. If you forget, the ISR re-enters immediately after returning — creating an infinite interrupt storm that locks the CPU.

Continue the Series

Work through all 7 parts of Bare Metal STM32 to master low-level embedded development from the ground up.