What is Interrupt Priority Grouping Explained-Embedded C Training Institute in Hyderabad

Interrupt Priority Grouping Explained

The ARM specification allocates 8 bits for each interrupt priority field, but vendors implement only the top N bits. Understanding this asymmetry — and how PRIGROUP splits those bits between pre-empt priority and sub-priority — is essential for writing correct RTOS-aware firmware.

NVICPRIGROUPPre-empt PrioritySub-PrioritySHPRSTM32F4

IPR Vendor Implementation

The ARM Cortex-M architecture specifies 60 Interrupt Priority Registers (IPR0–IPR59), each 32 bits wide. Every 8-bit field inside those registers holds the priority for one IRQ:

NVIC Interrupt Priority Registers (60 × 32-bit)

Register
Bits [31:24]
Bits [23:16]
Bits [15:8]
Bits [7:0]
IPR0
PRI_3 (IRQ3)
PRI_2 (IRQ2)
PRI_1 (IRQ1)
PRI_0 (IRQ0)
IPRn
PRI_4n+3
PRI_4n+2
PRI_4n+1
PRI_4n
IPR59
PRI_239
PRI_238
PRI_237
PRI_236

4 IRQs per register. IRQ n → IPR[n/4], byte offset n%4.

Each 8-bit field is architecturally defined, but MCU vendors do not implement all 8 bits. They implement only the most-significant N bits and leave the lower bits as read-as-zero / write-ignored:

8-bit priority field — vendor MSB alignment

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Impl
Impl
Impl
—
—
—
—
—

Yellow = not implemented (write ignored, reads 0). Always write priority values left-shifted so the meaningful bits land in the implemented positions.

Vendor Comparison

Vendor XXX (3 bits implemented)

Bits [7:5] implemented → 8 priority levels.
Valid raw register values: 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0

To assign priority 3: write 3 << 5 = 0x60

Vendor YYY (4 bits implemented)

Bits [7:4] implemented → 16 priority levels.
Valid raw values: 0x00, 0x10, 0x20, ..., 0xF0

To assign priority 5: write 5 << 4 = 0x50

STM32F4 implements 4 bits (16 levels). TI TM4C123Gx implements 3 bits (8 levels). CMSIS NVIC_SetPriority() handles the shift automatically.

Priority Levels Visualised

The ARM exception model defines a continuous priority number line from very negative (highest priority, fixed) to 0xFF (lowest programmable priority):

Exception Priority Number Line

Priority
Exception
Configurable?
−3
RESET
No (fixed)
−2
NMI
No (fixed)
−1
HardFault
No (fixed)
0x00
Highest programmable priority
0x20
Programmable exceptions (3-bit MCU)
0x10
Programmable exceptions (4-bit MCU)
0xFF
Lowest programmable priority

Smaller number = higher urgency. RESET/NMI/HardFault can never be out-prioritised by programmable exceptions.

With a 3-bit implementation, programmable exceptions can only occupy 8 slots (0x00, 0x20, 0x40, … 0xE0). With 4 bits they occupy 16 slots (0x00 to 0xF0 in steps of 0x10). The lower bits are always ignored by hardware.

Common mistake: Writing priority 5 as the literal byte 0x05 to an IPR register. With 4 bits implemented in the top nibble, the hardware reads this as priority 0 (the highest!). Always write 5 << 4 = 0x50 for a 4-bit implementation, or use NVIC_SetPriority() which shifts correctly.

SHPR: System Exception Priorities

External IRQs use NVIC_IPR registers. But the configurable system exceptions (MemManage, BusFault, UsageFault, SVC, DebugMonitor, PendSV, SysTick) use a separate set of registers inside the System Control Block:

System Handler Priority Registers (SCB)

Address
Register
Bytes [3:0] → Exceptions
0xE000ED18
SHPR1
[31:24]=UsageFault [23:16]=BusFault [15:8]=MemManage [7:0]=reserved
0xE000ED1C
SHPR2
[31:24]=SVC [23:16]=reserved [15:8]=reserved [7:0]=reserved
0xE000ED20
SHPR3
[31:24]=SysTick [23:16]=PendSV [15:8]=DebugMonitor [7:0]=reserved

All SHPR fields follow the same vendor MSB-alignment rule as NVIC_IPR fields.

/* CMSIS — set SysTick to priority 15, PendSV to priority 14 */
NVIC_SetPriority(SysTick_IRQn, 15);   /* writes SHPR3 byte [31:24] */
NVIC_SetPriority(PendSV_IRQn,  14);   /* writes SHPR3 byte [23:16] */

/* Direct register access for STM32F4 (4-bit, top nibble) */
SCB->SHP[11] = (15U << 4);  /* SysTick  — SHP index 11 */
SCB->SHP[10] = (14U << 4);  /* PendSV   — SHP index 10 */
SCB->SHP[ 7] = ( 5U << 4);  /* SVC      — SHP index 7  */
RTOS Convention

FreeRTOS on STM32F4 always sets PendSV to the lowest priority (15) and SysTick to the lowest priority (15). This ensures the scheduler tick and context-switch never pre-empt application interrupts with higher priority. SVC priority is set to the lowest value so that vPortSVCHandler can be pre-empted by all application ISRs.

Pre-empt vs Sub-Priority

ARM divides each interrupt’s priority field into two sub-fields:

Pre-empt Priority

Determines whether a new interrupt can pre-empt (interrupt) a currently running ISR. When a new IRQ arrives while the CPU is in an ISR, the NVIC compares pre-empt priority values. The new IRQ is allowed to nest only if its pre-empt priority value is strictly less than (numerically smaller = higher urgency) the active ISR’s pre-empt priority.

Sub-Priority

Used only as a tie-breaker when two or more interrupts with identical pre-empt priority arrive at the same time. The interrupt with the lower sub-priority value is served first. Sub-priority does not cause nesting — it just determines which of the simultaneous pending interrupts runs first.

Decision tree for two pending IRQs:
1. Compare pre-empt priorities → lower value wins and can nest.
2. If pre-empt priorities are equal → compare sub-priorities → lower sub-priority value runs first.
3. If both are equal → lowest IRQ number (hardware tie-break) runs first.

Priority Grouping (PRIGROUP)

The PRIGROUP field (bits [10:8]) in the Application Interrupt and Reset Control Register (AIRCR, address 0xE000ED0C) controls how the implemented priority bits are divided between pre-empt priority and sub-priority:

AIRCR — Application Interrupt and Reset Control Register

Bits [31:16]
Bits [15:8]
Bits [10:8]
Bits [7:0]
VECTKEY (0x05FA)
Various
PRIGROUP
SYSRESETREQ etc.

Must write 0x05FA to bits [31:16] as a key any time AIRCR is written — otherwise the write is ignored.

The PRIGROUP value selects the split point within the 8-bit priority field. The ARM spec defines the split in terms of the 8-bit field, but only the implemented bits matter:

PRIGROUP Split Table (ARM Architecture Spec)

PRIGROUP
Pre-empt priority bits
Sub-priority bits
STM32F4 pre-empt levels
STM32F4 sub levels
0 (default)
Bit[7:1] (7 bits)
Bit[0] (1 bit)
8 levels (4 impl bits → bit0 not impl)
0 (bit0 not impl)
1
Bit[7:2] (6 bits)
Bit[1:0] (2 bits)
8 levels
0 (bits[1:0] not impl)
2
Bit[7:3] (5 bits)
Bit[2:0] (3 bits)
8 levels
0 (bits[2:0] not impl)
3
Bit[7:4] (4 bits)
Bit[3:0] (4 bits)
16 levels
0 (bits[3:0] not impl)
4 (FreeRTOS)
Bit[7:5] (3 bits)
Bit[4:0] (5 bits)
8 levels (pre-empt only)
0 (bits[3:0] not impl)
5
Bit[7:6] (2 bits)
Bit[5:0] (6 bits)
4 levels
8 levels
6
Bit[7] (1 bit)
Bit[6:0] (7 bits)
2 levels
8 levels
7
None
Bit[7:0] (8 bits)
0 pre-empt levels
16 levels

STM32F4 levels calculated assuming 4 implemented bits in top nibble. “Not impl” sub-priority bits always read 0 — only implemented bits provide real levels.

/* Set PRIGROUP = 4 (FreeRTOS default — all bits are pre-empt) */
SCB->AIRCR = (0x05FAU << 16) | (4U << 8);

/* CMSIS equivalent */
NVIC_SetPriorityGrouping(4U);

/* Verify */
uint32_t pg = NVIC_GetPriorityGrouping();  /* should return 4 */

Worked Case Studies

Case 1: PRIGROUP = 0 (default), 3-bit MCU

With PRIGROUP = 0 the architecture says the pre-empt field occupies bits [7:1] (7 bits) and the sub-priority field occupies bit [0] (1 bit). However, the MCU only implements the top 3 bits (bits [7:5]).

PRIGROUP=0, 3-bit implementation

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Pre
Pre
Pre
Pre*
Pre*
Pre*
Pre*
Sub

Green = implemented pre-empt bits. Yellow = pre-empt field but not implemented (ignored). Red = sub-priority bit, but also not implemented → effectively 0 sub-priority levels. Result: 8 pre-empt levels, 0 sub-priority levels.

Case 2: PRIGROUP = 4, 4-bit MCU (STM32F4 + FreeRTOS)

FreeRTOS requires PRIGROUP = 4 so that all implemented bits are pre-empt bits and there are zero sub-priority bits. This is mandatory because FreeRTOS’s taskENTER_CRITICAL() uses the BASEPRI register, which compares only pre-empt priority — sub-priority is irrelevant and must not exist.

PRIGROUP=4, 4-bit STM32F4

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Pre
Pre
Pre
Pre
Sub*
Sub*
Sub*
Sub*

Green = 4 implemented pre-empt bits → 16 pre-empt levels. Red Sub* = sub-priority field bits, but none are implemented → 0 effective sub-priority levels. Result: 16 pre-empt levels, 0 sub-priority levels — exactly what FreeRTOS needs.

Case 3: PRIGROUP = 5, 4-bit MCU

PRIGROUP = 5 splits at bit 6: pre-empt = bits [7:6], sub = bits [5:0]. With 4 bits implemented ([7:4]):

  • Pre-empt: bits [7:6] — 2 bits implemented → 4 pre-empt levels (0, 1, 2, 3)
  • Sub-priority: bits [5:4] of the implemented region — 2 bits → 4 sub-priority levels

This is sometimes used in bare-metal systems where you want a coarse nesting hierarchy but fine-grained ordering within each tier.

Practical Code

Reading the Priority of an Active ISR

#include "stm32f4xx.h"

/* Return the raw 8-bit IPR byte for any IRQn (including system exceptions) */
uint8_t get_raw_priority(IRQn_Type irq)
{
    if (irq >= 0) {
        /* External IRQ: NVIC_IPR byte array */
        return NVIC->IP[(uint32_t)irq];
    } else {
        /* System exception: SCB->SHP[] */
        /* SHP index = irq + 16 - 4 (SHP starts at exception 4) */
        return SCB->SHP[(uint32_t)(irq & 0xF) - 4];
    }
}

/* Return the logical priority level (right-aligned) for STM32F4 (4-bit) */
uint32_t get_logical_priority(IRQn_Type irq)
{
    return get_raw_priority(irq) >> 4;
}

Configuring PRIGROUP + Priorities (Bare Metal)

#include "stm32f4xx.h"

void nvic_priority_init(void)
{
    /*
     * Step 1: Set PRIGROUP = 4
     *   All 4 implemented bits are pre-empt priority.
     *   Zero sub-priority levels.
     */
    NVIC_SetPriorityGrouping(4U);

    /*
     * Step 2: Assign priorities.
     *   NVIC_SetPriority() takes the logical (right-aligned) value;
     *   CMSIS shifts it left by (8 - __NVIC_PRIO_BITS) internally.
     *   On STM32F4, __NVIC_PRIO_BITS = 4, so shift = 4.
     */
    NVIC_SetPriority(USART3_IRQn, 5);   /* 0x50 in IPR */
    NVIC_SetPriority(TIM2_IRQn,   3);   /* 0x30 in IPR — higher urgency */
    NVIC_SetPriority(ADC_IRQn,    7);   /* 0x70 in IPR — lower urgency  */

    /* System exceptions */
    NVIC_SetPriority(SysTick_IRQn, 15); /* 0xF0 in SHPR3 — lowest */
    NVIC_SetPriority(PendSV_IRQn,  15); /* 0xF0 in SHPR3 — lowest */
    NVIC_SetPriority(SVCall_IRQn,  15); /* 0xF0 in SHPR2 */
}

FreeRTOS Priority Constants Explained

/*
 * FreeRTOS uses PRIGROUP=4 on STM32F4 (4 implemented pre-empt bits = 16 levels).
 * Logical priority 15 → raw 0xF0 (lowest).
 * Logical priority  0 → raw 0x00 (highest, same as HardFault range — avoid!).
 *
 * configKERNEL_INTERRUPT_PRIORITY:
 *   The tick (SysTick) and yield (PendSV) use the LOWEST priority.
 *   This ensures every application ISR can pre-empt the RTOS tick.
 *
 * configMAX_SYSCALL_INTERRUPT_PRIORITY:
 *   ISRs that call FreeRTOS API functions (e.g. xQueueSendFromISR) must have
 *   a priority NUMERICALLY GREATER THAN OR EQUAL TO this value (i.e. lower urgency).
 *   ISRs with priority LOWER VALUE than this cannot call any FreeRTOS API.
 */

#define configKERNEL_INTERRUPT_PRIORITY      (15 << 4)  /* 0xF0 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 5 << 4)  /* 0x50 */

/*
 * Safe zone: priorities 5–15 (raw 0x50–0xF0) can call FreeRTOS API.
 * Unsafe zone: priorities 0–4 (raw 0x00–0x40) CANNOT call FreeRTOS API.
 *
 * taskENTER_CRITICAL() sets BASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY.
 * This masks all interrupts with priority value >= 0x50 but allows those
 * with priority value < 0x50 (i.e. higher urgency) to still fire.
 */

Verifying IPR Values at Runtime

/* Debug helper: dump first 16 IRQ priorities over SWO/UART */
void dump_nvic_priorities(void)
{
    uint32_t pg = NVIC_GetPriorityGrouping();
    uint32_t pre_bits  = 7U - pg;   /* approximate for fully-implemented field */
    (void)pre_bits;

    for (int i = 0; i < 16; i++) {
        uint32_t raw     = NVIC->IP[i];
        uint32_t logical = raw >> (8 - __NVIC_PRIO_BITS);  /* right-align */
        printf("IRQ%2d: raw=0x%02X  logical=%lu\r\n", i, raw, logical);
    }
}

Frequently Asked Questions

Why does FreeRTOS require PRIGROUP = 4?

FreeRTOS uses the BASEPRI register to implement critical sections. BASEPRI masks interrupts by comparing only the pre-empt priority portion of the IPR field. If any sub-priority bits exist, the comparison becomes ambiguous — an interrupt that should be masked might slip through. PRIGROUP = 4 on a 4-bit MCU ensures the entire implemented field is pre-empt priority with zero sub-priority bits, making BASEPRI masking deterministic.

Can I change PRIGROUP at runtime after FreeRTOS starts?

No. Changing PRIGROUP after the scheduler starts will corrupt the priority comparisons that FreeRTOS relies on. Set PRIGROUP once, before any NVIC priority assignments and before vTaskStartScheduler() is called.

What happens if I write a priority value to the lower (unimplemented) bits?

The write is silently ignored by hardware. The bits read back as zero. This is why writing 0x05 instead of 0x50 to an IPR byte on STM32F4 results in the interrupt getting priority 0 (the highest possible) rather than priority 5. Always left-shift your logical priority value by (8 - __NVIC_PRIO_BITS), or use NVIC_SetPriority() which does this automatically.

Can sub-priority cause interrupt nesting?

No. Sub-priority is purely a tie-breaker for simultaneously pending interrupts. Even if interrupt A has sub-priority 0 and interrupt B has sub-priority 15, if both have the same pre-empt priority, B cannot pre-empt A once A is running — B simply waits until A finishes.

How do I know how many priority bits my MCU implements?

The CMSIS header for your device defines __NVIC_PRIO_BITS. For STM32F4 it is 4. You can also detect it at runtime: write 0xFF to an IPR byte, then read it back — the number of set bits in the result equals the number of implemented bits.

Do SHPR registers follow the same MSB-alignment rule?

Yes. SHPR1, SHPR2, and SHPR3 hold system exception priorities in exactly the same 8-bit format as NVIC_IPR bytes. The same shift rule applies: logical priority × 2^(8 − implemented bits). NVIC_SetPriority(SysTick_IRQn, 15) handles this for you.

Suggested Diagrams for This Lecture

  • IPR register layout: 4×8-bit fields per 32-bit word, annotated with IRQ indices
  • 8-bit priority byte with top N bits highlighted (implemented) vs lower bits greyed out
  • PRIGROUP split visualisation for all 8 settings on a 4-bit MCU
  • AIRCR register bit-field layout with PRIGROUP highlighted at bits [10:8]
  • FreeRTOS priority zones: safe (5–15) vs forbidden (0–4) on STM32F4

EmbeddedPathashala — Embedded Systems Programming on ARM Cortex-M3/M4

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *