When configuring peripheral interrupt priorities under RT-Thread (including Nano), the core idea is:
-
First, let the RTOS layer take over the NVIC (set grouping and maximum preemption level);
-
Then, let the driver layer assign specific priorities to peripherals in a “high to low” manner;
-
All ISRs must call
<span>rt_interrupt_enter/leave</span>to inform the kernel; -
High real-time peripherals can bypass global interrupt disabling (super interrupts), but must never call kernel APIs.
Below is a general process from “zero to mass production,” applicable to the Cortex-M (NVIC) family, which can be directly copied.
1. RTOS Level – First, lock down the NVIC framework
-
Select the priority grouping (one-time setup) in
<span>board.c</span>or<span>rt_hw_board_init()</span>:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); /* 4 bits for preemption, 0-15 levels */
-
Group4 → 16 levels of preemption, 0 is highest, 15 is lowest;
-
Do not set sub-priorities to avoid ambiguity of “priority inversion.”
2. Inform RT-Thread of the maximum preemption level
#define RT_THREAD_PRIORITY_MAX 32 /* Total number of thread priorities */
#define RT_TICK_PER_SECOND 1000 /* 1 kHz tick */
/* The following line lets the kernel know “how high-level interrupts can preempt me” */
#define RT_INTERRUPT_PRIORITY_MAX (configLIBRARY_LOWEST_INTERRUPT_PRIORITY)
/* configLIBRARY_LOWEST_INTERRUPT_PRIORITY = 15 in FreeRTOS compatibility */
2. Peripheral Level – Rank by real-time requirements

Example:
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = TIM1_UP_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 1; /* High real-time */
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 5; /* Medium */
NVIC_Init(&nvic);
nvic.NVIC_IRQChannel = EXTI15_10_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 10; /* Lowest */
NVIC_Init(&nvic);
3. ISR Level – Must wrap kernel entry and exit
void TIM1_UP_IRQHandler(void)
{
rt_interrupt_enter(); /* ← Inform RT-Thread of entering interrupt */
/* User processing code */
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
…
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}
rt_interrupt_leave(); /* ← Inform RT-Thread of leaving interrupt */
}
Not wrapping → kernel statistics error, may trigger hard lock or assertion.
4. Super Interrupt (Optional) – Bypass global interrupt disabling
RT-Thread by default <span>rt_hw_interrupt_disable()</span> will disable all interrupts with priority ≤ a certain threshold. If a certain peripheral absolutely cannot be masked (e.g., safety shutdown, encoder zero position), it can be made into a “super interrupt”:
/* Set threshold to 6 → IRQs with priority 0-5 can penetrate rt_hw_interrupt_disable() */
#define RT_INTERRUPT_PRIORITY_MAX 6 /* 0-5 levels = super interrupt */
/* In board.c */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
SCB->AIRCR |= (0x5FA << 16) | (6 << 8); /* Write BASEPRI threshold = 6 */
Inside super interrupts, do not call any RT-Thread API (including rt_sem_release/printf), only set flags or write FIFO.
5. Quick Checklist
✅ Startup Phase
-
[ ] NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
-
[ ] RT_INTERRUPT_PRIORITY_MAX = 15 (or 6);
✅ Peripheral Phase
-
[ ] SysTick/PendSV = 15 (lowest)
-
[ ] High real-time timer = 0-2
-
[ ] Serial = 3-5
-
[ ] Human-machine = 9-12
✅ ISR Phase
-
[ ] All ISRs must wrap rt_interrupt_enter()/leave()
-
[ ] Inside super interrupts, never call RT-Thread API
✅ Testing Phase
-
[ ] When high-priority IRQ is triggered, systick still counts → priority is correct
-
[ ] Printing/releasing semaphore inside super interrupt → should trigger assertion (if enabled)
With this configuration, RT-Thread can ensure hard real-time response without losing interrupts due to priority inversion.