Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

NVIC Priority Configuration and Preemption

Recommended resources

Hello! Welcome to the third lesson in our module on Interrupts and Exception Handling.

In our last lesson, we explored the architecture of the Nested Vectored Interrupt Controller (NVIC) and learned how to configure it. We saw how to enable/disable interrupts and, crucially, how to assign a priority to each one using the NVIC_IPR registers. We also noted the two "gotchas" of ARM's priority system: lower numerical values mean higher urgency, and the priority bits are left-aligned.

Today, we will build directly on that knowledge to address the learning outcome: Configure interrupt priorities and priority grouping in the NVIC to manage preemption. We will answer the question, "What do these priority numbers actually do?" You'll learn how the NVIC uses priorities to manage which interrupts can interrupt others—a concept known as preemption or nesting. We will then dive into the powerful technique of priority grouping, which gives you finer control over this real-time behavior. For a role focused on low-level driver development, mastering these concepts is essential for writing robust and predictable code.

1. Preemption: The "Nested" in NVIC

The core function of the NVIC's priority system is to handle preemption. An interrupt is said to preempt another if it interrupts the execution of the other's Interrupt Service Routine (ISR).

The fundamental rule is simple:
An interrupt can preempt a currently running ISR if and only if its priority is strictly higher (i.e., its numerical priority value is strictly lower) than the active interrupt.

Nested Interrupts and Preemption
This diagram illustrates preemption. The main program is interrupted by a lower-priority interrupt (ISR1). While ISR1 is running, a higher-priority interrupt occurs, causing the processor to pause ISR1 and execute ISR2. Once ISR2 completes, execution returns to finish ISR1 before finally returning to the main program.

This nesting capability is critical in real-time systems. For example, a high-priority "motor emergency stop" interrupt must be able to preempt a lower-priority "update display" interrupt.

Let's see a practical demonstration of this.

ARM Cortex-M, Interrupts and FreeRTOS: Part 2

The article 'ARM Cortex-M, Interrupts and FreeRTOS: Part 2' by Erich Styger provides an excellent, practical demonstration of interrupt nesting.

Please read the section 'Nested Interrupts'. Pay attention to how two timer interrupts (ISR #58 and ISR #59) are configured with different priorities using NVIC_SetPriority. The screenshots from the Segger SystemView trace tool clearly show the higher-priority ISR interrupting the lower-priority one.

What if two interrupts have the same priority?
If two interrupts with the same priority level are pending at the same time, they cannot preempt each other. The tie is broken by their fixed IRQ number: the interrupt with the lower IRQ number is serviced first.


2. The Need for Finer Control: Priority Grouping

The simple preemption model works well, but sometimes you need more nuanced control. Consider a scenario where you have two interrupts related to the same driver (e.g., UART TX and RX interrupts). You might want them to have different urgencies (if both are pending, handle RX first), but you might also want to ensure they never preempt each other to simplify the driver logic and avoid re-entrancy issues.

This is where priority grouping comes in. The ARM Cortex-M architecture allows you to split the bits of the 8-bit priority field into two parts:

  1. Preemption Priority: The upper bits, which determine preemption eligibility as described above.
  2. Sub-priority: The lower bits, which act as a tie-breaker only for pending interrupts that have the same preemption priority.

Crucially, a higher sub-priority does not allow an interrupt to preempt another with the same preemption priority. This is the key to solving our UART driver problem. By placing both TX and RX interrupts in the same preemption priority group but giving RX a higher sub-priority, we ensure that if both are pending, RX runs first, but neither can interrupt the other while it's running.

Interrupts on Cortex M - NVIC (demonstrated on STM32) | VIDEO 34

This video from Matej Blagšič gives a good conceptual overview of why we use priorities and introduces the idea of splitting them into preemptive and sub-priority.

Please watch from 12:28 to 15:45. The video discusses assigning priorities and then introduces the concept of priority grouping, explaining the distinction between preemptive priority and the new sub-priority.


3. How to Configure Priority Grouping

The division of priority bits is a system-wide setting configured in the PRIGROUP field (bits [10:8]) of the Application Interrupt and Reset Control Register (AIRCR). You set this once, typically during system initialization.

The value written to PRIGROUP determines how the implemented priority bits are split.

Interrupt Priority Grouping in ARM Cortex-M NVIC

The article 'Interrupt Priority Grouping in ARM Cortex-M NVIC' provides the definitive reference tables for this configuration.

Please read the section 'Interrupt Priority Groups in ARM Cortex-M3'. Study the 'master table' carefully. Then, look at the example table for STM32 devices, which implement only 4 priority bits. This shows how the master table is adapted for specific hardware, a vital practical detail.

For convenience, here is a similar table for a system with 4 implemented priority bits (like many STM32 devices):

Table 51. Priority grouping
This table shows the 5 possible priority groupings for a microcontroller implementing 4 priority bits (e.g., bits [7:4] of the priority register). A `PRIGROUP` value of `3` splits the 4 bits evenly, giving 4 preemption levels and 4 sub-priority levels. A `PRIGROUP` value of `4` gives 3 bits to preemption priority (8 levels) and 1 bit to sub-priority (2 levels).

Configuration with CMSIS

Manually calculating bit shifts and register values for AIRCR and IPR is error-prone. The CMSIS-Core library provides standard functions to handle this for you.

Interrupt Priority Grouping in ARM Cortex-M NVIC

Let's look at the standard API for this task.

Now read the section 'Assigning/Programming Priority Levels using CMSIS'. Focus on the purpose of NVIC_SetPriorityGrouping(), NVIC_EncodePriority(), and NVIC_SetPriority(). Look at the C code examples to see how they are used together.

The typical workflow is:

  1. Set the grouping scheme once at startup:
    // Use 3 bits for preemption priority, 1 bit for sub-priority.
    // This corresponds to 8 preemption levels and 2 sub-priority levels.
    NVIC_SetPriorityGrouping(4); 
    
  2. Encode and set the priority for each interrupt:
    uint32_t priority;
    
    // Set UART RX interrupt: Preemption Prio=2, Sub-Prio=0 (higher urgency)
    priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0);
    NVIC_SetPriority(UART_RX_IRQn, priority);
    
    // Set UART TX interrupt: Preemption Prio=2, Sub-Prio=1 (lower urgency)
    priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 1);
    NVIC_SetPriority(UART_TX_IRQn, priority);
    

4. The Rules of Sub-Priority in Action

Understanding the rules of how preemption and sub-priorities interact is crucial. Let's make them explicit.

ARM Cortex-M, Interrupts and FreeRTOS: Part 2

The article 'ARM Cortex-M, Interrupts and FreeRTOS: Part 2' provides a perfect, concise list of the rules that govern sub-priority behavior.

Please read the list of 4 rules in the 'Sub-Priorities' section. Commit these to memory; they are the heart of this topic.

Let's recap those rules here:

  1. Rule 1 (Preemption): An ISR can be preempted by an interrupt with a strictly higher preemption priority. (e.g., an ISR with priority 2.1 can be preempted by an interrupt with priority 1.0).
  2. Rule 2 (No Preemption within a Group): An ISR cannot be preempted by an interrupt with the same preemption priority, regardless of sub-priority. (e.g., a pending interrupt of 2.0 cannot preempt a running ISR of 2.1).
  3. Rule 3 (Sub-Priority Tie-Break): If multiple interrupts with the same preemption priority are pending, the one with the higher sub-priority (lower numerical value) is executed first. (e.g., if 2.1 and 2.0 are pending, 2.0 will run first).
  4. Rule 4 (IRQ Number Tie-Break): If multiple pending interrupts have the same preemption and sub-priority, the one with the lower IRQ number is executed first.

Rule #2 is the most important one for our design goal. By putting related interrupts in the same preemption group, we create a "non-nesting zone" for them, simplifying driver design.

Test your understanding!

You are working on an STM32 microcontroller with 4 priority bits. Your system uses priority grouping 5 (PRIGROUP=5). You have two interrupts:

  • CAN RX (IRQn = 20, Priority = 0x40)
  • SPI TX (IRQn = 35, Priority = 0x60)

The CAN RX ISR is currently running. The SPI TX interrupt is triggered and becomes pending. Will the SPI TX ISR preempt the CAN RX ISR?

Show answer

First, let's decode the setup. With 4 priority bits and PRIGROUP=5, the table tells us we have:

  • 2 bits for preemption priority ([7:6]).
  • 2 bits for sub-priority ([5:4]).

Now, let's decode the priorities (remember they are left-shifted in the 8-bit register):

  • CAN RX Priority: 0x40 = 0b01000000. The preemption priority bits [7:6] are 01 (decimal 1).
  • SPI TX Priority: 0x60 = 0b01100000. The preemption priority bits [7:6] are also 01 (decimal 1).

Since both interrupts have the same preemption priority (1), the SPI TX interrupt cannot preempt the currently running CAN RX ISR (Rule #2). It will remain pending until the CAN RX ISR completes.


Interview Preparation

This is a favorite topic for interviewers to probe depth of knowledge.

  • Question: "What is interrupt nesting, and how is it controlled on an ARM Cortex-M processor?"

    • Your Answer: "Interrupt nesting, or preemption, is when a higher-priority interrupt pauses a currently executing lower-priority ISR. It's controlled by the priority values assigned to each interrupt in the NVIC's priority registers. An interrupt can only preempt another if its preemption priority is strictly higher—meaning its numerical priority value is strictly lower."
  • Question: "Explain what priority grouping is. Why would you use it?"

    • Your Answer: "Priority grouping allows you to split the priority bits for all interrupts into a preemption priority field and a sub-priority field. The main reason to use it is to create non-nesting groups of interrupts. By assigning several related interrupts the same preemption priority but different sub-priorities, you can order their execution when they are pending simultaneously, but prevent them from preempting each other. This is very useful for simplifying driver code where re-entrancy would be complex to manage, for example, between the TX and RX ISRs of a single peripheral."
  • Question: "Does a higher sub-priority allow an interrupt to preempt another with the same preemption priority?"

    • Your Answer: "No, it does not. That is a common misconception. Sub-priority is only used as a tie-breaker to decide which pending interrupt runs first if multiple interrupts with the same preemption priority are pending. Once an ISR is running, it cannot be preempted by any other interrupt from the same preemption group, regardless of sub-priority."

Conclusion

You have now mastered one of the most important and nuanced aspects of the ARM interrupt system. You not only know how to set a priority but also how to architect a system's real-time response using preemption and priority grouping.

Key Takeaways:

  • Preemption is governed by preemption priority: a higher priority (lower value) interrupt can preempt a lower priority one.
  • Priority Grouping splits priority bits into a preemption field and a sub-priority field. This is a system-wide setting.
  • The primary use of grouping is to create non-nesting groups of interrupts that share the same preemption priority.
  • Sub-priority only serves to break ties between pending interrupts within the same group; it does not enable preemption.
  • The CMSIS functions NVIC_SetPriorityGrouping, NVIC_EncodePriority, and NVIC_SetPriority are the standard, portable way to manage this configuration.

Next Lesson Preview:
We have covered the what, why, and how of interrupt configuration and prioritization. In the next lesson, we will move inside the ISR itself. We'll learn how to write a robust Interrupt Service Routine, covering the necessary steps for context preservation, interacting with the hardware to clear interrupt flags, and best practices to keep your ISRs short and efficient.