EmbeddedRelated.com

NVIC

Category: Architecture

The Nested Vectored Interrupt Controller (NVIC) is a hardware interrupt controller integrated into ARM Cortex-M processors that manages exception and interrupt handling, including priority arbitration, preemption, and tail-chaining. It supports up to 240 external interrupts on ARMv7-M cores (Cortex-M3/M4/M7 and related), though actual device implementations vary and ARMv6-M cores (Cortex-M0/M0+) support far fewer; configurable priority levels; and low-latency interrupt entry and exit without software overhead.

In practice

In real embedded projects, the NVIC is configured at startup to assign priorities to every peripheral interrupt in use. Each interrupt source, such as a UART receive FIFO, a timer overflow, or an external GPIO edge, is given a numeric priority and enabled individually through NVIC registers. Getting priorities wrong is one of the most common sources of subtle bugs: two peripherals assigned the same priority that call shared data structures without a mutex, or a high-priority ISR that blocks a time-critical lower-priority one because preemption was not configured correctly.

Cortex-M implements priority as a bit field inside an 8-bit register, but most silicon vendors implement only the upper 2 to 8 bits. This means the effective number of priority levels varies by device, and the lower unused bits always read as zero. Always check your specific MCU's reference manual for the number of implemented priority bits, not just the Cortex-M architecture reference.

The NVIC also controls interrupt pending and active status bits, which are useful for debugging. For pulse-based or edge-triggered sources, an interrupt that fires faster than its ISR can run will set the NVIC pending bit, but only one pending event can be tracked per interrupt line at the NVIC level; any additional edges that arrive while the line is already pending are lost at the NVIC. Whether multiple events are captured or lost before reaching the NVIC depends on the source peripheral's own event logic (edge detectors, FIFOs, latches). Reading SCB->ICSR and the per-interrupt pending registers is an effective first step when diagnosing runaway or silent ISR problems.

CMSIS provides a portable C API (NVIC_SetPriority, NVIC_EnableIRQ, NVIC_DisableIRQ, and related functions) that abstracts the raw register writes. Using CMSIS calls rather than direct register manipulation improves portability across Cortex-M vendors such as STMicroelectronics, NXP, Nordic Semiconductor, and Microchip SAM.

Frequently asked

What is the difference between interrupt priority and subpriority in the NVIC?
The NVIC priority register is split into a preemption priority field and a subpriority field using the PRIGROUP setting in the Application Interrupt and Reset Control Register (AIRCR). Two ISRs can preempt each other only if they differ in preemption priority. Subpriority only determines which pending ISR runs first when both have the same preemption priority; neither can preempt the other. In common practice, many projects keep all bits in the preemption priority group and leave subpriority unused, though some RTOSes and vendor HALs do make use of the subpriority field.
How many interrupt priority levels does a Cortex-M device actually have?
The architecture reserves 8 bits per priority register, but silicon vendors typically implement 2 to 8 bits. An STM32F4, for example, implements 4 bits, giving 16 levels. An nRF52840 implements 3 bits, giving 8 levels. Unimplemented low-order bits always read zero, so writing a fine-grained value and reading it back will show a rounded result. Always verify in the device datasheet or reference manual.
What is tail-chaining and why does it matter?
Tail-chaining is a Cortex-M hardware optimization that skips the full exception exit and re-entry sequence when a second interrupt is pending as the first ISR finishes. Instead of restoring and then immediately saving the stack frame again, the processor pivots directly to the next ISR, saving approximately 6 to 12 cycles depending on the core and implementation. In interrupt-heavy designs such as high-speed communications stacks, tail-chaining measurably reduces CPU overhead.
Can the NVIC be used with an RTOS?
Yes, but the RTOS typically reserves certain priority levels for its own internal SVC and PendSV exceptions and imposes a priority ceiling on interrupts that are allowed to call RTOS API functions. In Cortex-M convention, a lower numeric priority value means higher urgency. In FreeRTOS, configMAX_SYSCALL_INTERRUPT_PRIORITY defines the highest-urgency (numerically smallest, after accounting for implemented priority bit shifts) interrupt level that may safely call RTOS API functions. ISRs assigned a numerically lower value than this ceiling (i.e., higher urgency) must not call RTOS API functions. Violating this constraint causes hard faults or silent data corruption in the RTOS kernel.
How do you disable all interrupts atomically on Cortex-M using the NVIC?
The preferred method is not to iterate through NVIC enable registers but to use the CPSID I instruction, which sets the PRIMASK bit and globally masks all configurable-priority interrupts in a single cycle. CMSIS exposes this as __disable_irq(). For critical sections that also need to block fault handlers, FAULTMASK (CPSID F / __disable_fault_irq()) can be used, though this is rarely needed in application code.

Differentiators vs similar concepts

The NVIC is sometimes confused with the SCB (System Control Block), which is a separate Cortex-M register block that handles reset, fault escalation, sleep control, and the PRIGROUP field that configures how NVIC priority bits are split. The NVIC handles enabling, disabling, prioritizing, and pending individual interrupt lines; the SCB handles system-level exception policy. They are accessed through different base addresses and CMSIS structs (NVIC and SCB respectively). Additionally, the NVIC should not be confused with the EXTI (External Interrupt/Event Controller) found on STM32 devices: EXTI routes GPIO pin-change signals and wakeup events into NVIC interrupt lines, but it is a vendor-specific peripheral that sits upstream of the NVIC, not part of the ARM architecture itself.