Configuring Hardware Interrupt Priorities When Using RT-Thread and Other RTOS

When configuring peripheral interrupt priorities under RT-Thread (including Nano), the core idea is:

  1. First, let the RTOS layer take over the NVIC (set grouping and maximum preemption level);

  2. Then, let the driver layer assign specific priorities to peripherals in a “high to low” manner;

  3. All ISRs must call <span>rt_interrupt_enter/leave</span> to inform the kernel;

  4. 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

  1. 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

Configuring Hardware Interrupt Priorities When Using RT-Thread and Other RTOS

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.

Leave a Comment