STM32 NVIC: Master Interrupt Priorities, SysTick, and PendSV
Abstract
In real-time embedded systems, responding to external events with minimal latency is critical. The Nested Vector Interrupt Controller (NVIC) is the heart of interrupt management in ARM Cortex-M based microcontrollers like the STM32. This article explores the internal mechanisms of the NVIC, including its priority schemes and hardware-level optimizations that ensure high-performance execution. We also provide practical “Hands-On” examples using the STM32 HAL (Hardware Abstraction Layer).
1. Introduction
In the world of embedded systems, a microcontroller must be able to multitask—not by doing everything at once, but by reacting to critical events the microsecond they occur. The Nested Vector Interrupt Controller (NVIC) is the specialized hardware unit within the ARM Cortex-M core that makes this possible.
While the processor is busy executing your main loop, dozens of peripherals (like Timers, UART, or GPIOs) may signal that they need attention. The NVIC evaluates these requests, determines their urgency based on your configuration, and decides whether to pause the current task to handle the new event.
Why the NVIC is Essential
Without the NVIC, the processor would have to “poll” (manually check) every peripheral, which wastes power and introduces massive delays. The NVIC provides:
- Deterministic Performance: Calculate exactly how long it takes to respond to an event.
- Prioritization: Ensure a safety-critical “Emergency Stop” overrides a low-priority “Blink LED” task.
- Hardware-Managed Efficiency: It handles saving processor states (stacking) and jumping to the correct code address (vectoring) in hardware, requiring zero software overhead to initiate.
What “Nested” and “Vectored” Mean
- Nested: An interrupt can be interrupted. If a “High Priority” event occurs while a “Medium Priority” routine is running, the NVIC will “nest” the high-priority task on top.
- Vectored: Every interrupt has a specific “vector”—a dedicated memory address in a Vector Table. The NVIC automatically points the CPU to the exact function needed for that event.
2. Prerequisites
- STM32 board with COMP peripheral (e.g., STM32G0, STM32G4 series)
- STM32CubeIDE installed
- Basic understanding of MCUs
3. NVIC Basics: Priority vs. Sub-priority
STM32 microcontrollers typically use 4 bits for interrupt priority, providing 16 levels (0 to 15). Smaller cores, such as the M0+, has fewer bits. These bits are split into two categories:
- Preemption Priority: If an interrupt with a higher preemption priority occurs while a lower one is running, the core stops the current ISR to run the new one.
- Sub-priority: A “tie-breaker.” If two interrupts with the same preemption priority arrive simultaneously, the one with the higher sub-priority (lower numerical value) runs first. A sub-priority cannot preempt an active ISR.
Note: In the NVIC, a lower numerical value equals a higher priority. Priority 0 is the highest possible.
Priority Grouping Table
Priority Group | Preemption Bits | Sub-priority Bits | Total Levels |
Group 0 | 0 | 4 | 1 Preemption, 16 Sub |
Group 2 | 2 | 2 | 4 Preemption, 4 Sub |
Group 4 | 4 | 0 | 16 Preemption, 0 Sub |
CMSIS Example:
HAL_NVIC_SetPriority(SysTick_IRQn, 1, 0); // High Priority
HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0); // Low Priority
4. Advanced Hardware Features:
4.1 Nesting and Tail Chaining:
In traditional processors, exiting one interrupt and entering another requires a full “pop” of the stack followed by an immediate “push.” This is inefficient.
Nesting: When a higher-priority request arrives during an active ISR, the current exception is preempted. The previous handler resumes execution only after the higher-priority task is complete.
Tail Chaining: If a second interrupt is pending when the current ISR finishes, the NVIC skips the pop/push phase. This reduces the latency between consecutive interrupts to just 6 cycles.
4.2 Late Arrival
Late Arrival occurs when a high-priority interrupt is requested while the processor is still saving the context (stacking) for a lower-priority interrupt. Instead of finishing the entry for the low-priority ISR, the NVIC switches the “Vector Fetch” to the high-priority ISR. Since the stack state is identical, the high-priority task starts sooner without extra overhead.
5. The Interrupt Vector Table
The Interrupt Vector Table is a memory-mapped array of addresses pointing to the start of each ISR. In STM32, it is typically located at the start of Flash memory (0x0800 0000).
- Entry 0: Initial Main Stack Pointer (MSP).
- Entry 1: Reset Handler.
- System Exceptions: NMI, HardFault, SysTick, etc.
- External Interrupts: Peripheral-specific interrupts.
All Cortex-M cores (except the base Cortex-M0) allow the VTOR (Vector Table Offset Register) to be changed. This facilitates bootloader integration by isolating interrupt management between the bootloader and the application. In STM32CubeIDE, this table is usually found in the startup_stm32xxxx.s assembly file.
The same table can be found in the reference manual:
6. CubeMX Configuration
We will configure 3 interrupts, SysTick (highest), EXTI13 (medium priority) and PendSV (low priority) to demonstrate how the NVIC manages execution flow.
Create a new project with STMCubeMX and select the STM32G071RBT6:
This will be a trivial project, that can leverage the PC13 push button on the NUCLEO-G071RB board, as well as the USART2, located on PA2/PA3, for printf functionality. Alternatively, configure PA5 as output push pull for the LED.
Configure PC13 as EXTI13:
Configure USART2 with default settings on PA2/PA3:
Under [Sytem Core], open the NVIC to evaluate the priority and enable the EXTI:
Change the priorities in the same tab:
Generate the code and proceed to the Code Edition portion of the hands on.
6.1 Understanding the firmware
Step 1: Locate the NVIC priority settings
Depending on the CubeMX settings, you might have everything in the main.c or split between main.c and the peripheral/resources name, such as gpio.c.
This is the main function responsible for setting the interrupt priority:
/**
* @brief Sets the priority of an interrupt.
* @param IRQn External interrupt number .
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to stm32g0xx.h file)
* @param PreemptPriority The preemption priority for the IRQn channel.
* This parameter can be a value between 0 and 3.
* A lower priority value indicates a higher priority
* @param SubPriority the subpriority level for the IRQ channel.
* with stm32g0xx devices, this parameter is a dummy value and it is ignored, because
* no subpriority supported in Cortex M0+ based products.
* @retval None
*/
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
Step 2: Define ISR Handlers
In your stm32g0xx_it.c file, the function handlers are created:
void SysTick_Handler(void) {
HAL_IncTick();
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggles LED every 1ms
}
void PendSV_Handler(void) {
// Executes only when no higher-priority IRQs are active
// Note: Avoid blocking functions like printf in production!
printf("PendSV: Processing background task...\n");
}
void EXTI4_15_IRQHandler(void)
{
/* USER CODE BEGIN EXTI4_15_IRQn 0 */
/* USER CODE END EXTI4_15_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
/* USER CODE BEGIN EXTI4_15_IRQn 1 */
/* USER CODE END EXTI4_15_IRQn 1 */
}
7. Validation
Observations:
- SysTick preempts PendSV, ensuring the LED toggles reliably.
- A Button press (EXTI13) will preempt PendSV, but not SysTick.
- PendSV runs only when the processor is not busy with higher-priority interrupts.
Check this article for more details on how to use printf> – Hacker Embedded How to implement printf with STM32: Setup for STM32CubeIDE and VS Code
8. Common NVIC Pitfalls
| Issue | Cause | Solution |
|---|---|---|
| Interrupt not triggered | IRQ not enabled in NVIC | Use HAL_NVIC_EnableIRQ(). |
| PendSV never executes | Priority assigned is too high | Assign the lowest priority (3 or 15) for PendSV. |
| SysTick is delayed | Higher priority ISR is blocking it | Keep ISRs short; ensure SysTick has the highest priority. |
| Nesting fails | Priority groups misconfigured | Use HAL_NVIC_SetPriorityGrouping() correctly. |
9. Conclusion
The NVIC is what makes the STM32 a true “Real-Time” controller. By mastering preemption groups and understanding hardware optimizations like tail chaining, you can write firmware that is both responsive and efficient.


