Bare Metal STM32 · Part 3 of 7
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 3 / 7
⚙️ Bare Metal STM32 Series · 7 Parts
Part 3 of 7
Part 01
HAL vs Bare Metal: Which Should You Use?
38:22
Part 02
Clock Configuration with RCC: From HSI to PLL
22:10
Part 03
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 04
SPI DMA Transfers: No CPU Overhead
33:05
Part 05
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 06
Debugging with SWO Trace & ITM Printf
18:30
Part 07
Low-Power Stop Mode with Wake-Up on EXTI
34:12
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
volatilecorrectly - 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 setupnvic_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 patterncritical.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.