FreeRTOS 的中断管理,本质上是在 Cortex-M 的硬件中断机制之上,规定一套“哪些中断可以被内核临时屏蔽、哪些中断可以调用 FreeRTOS API、任务切换相关中断应该放在哪个优先级”的规则。
理解这部分内容时,最容易混淆的是两套优先级:
- 中断优先级:数值越小,优先级越高。
- FreeRTOS 任务优先级:数值越大,优先级越高。
这两个概念不是一套东西。中断优先级由 Cortex-M/NVIC 硬件管理,任务优先级由 FreeRTOS 调度器管理。
什么是中断
中断就是 CPU 在正常执行程序时,被外设或系统事件打断,转而去处理更紧急的事件。
一个完整的中断执行过程可以简单分成三步:
- 外设产生中断请求,比如 GPIO 外部中断、定时器中断。
- CPU 响应中断,暂停当前程序,进入对应的中断服务函数,也就是 ISR。
- ISR 执行完毕后退出中断,CPU 回到被打断的位置继续执行。
在裸机程序里,中断通常用于快速响应外设事件;在 FreeRTOS 中,中断仍然由硬件触发,但如果中断中要和任务交互,就必须遵守 FreeRTOS 的中断管理规则。
STM32 的中断优先级
ARM Cortex-M 使用 8 位宽的寄存器配置中断优先级,理论范围是 0~255。STM32 只使用其中的高 4 位 [7:4],所以最多提供 16 个中断优先级,范围是 0~15。
注意:中断优先级数值越小,优先级越高。
例如:
- 优先级 0 最高。
- 优先级 15 最低。
- 优先级 4 可以抢占优先级 6。
- 优先级 6 不能抢占优先级 4。
STM32 的中断优先级又可以分成两部分:
- 抢占优先级:决定一个中断能不能打断另一个正在执行的中断。
- 子优先级:当两个中断同时发生,并且抢占优先级相同时,决定谁先执行。
FreeRTOS 官方建议把所有优先级位都分配给抢占优先级,避免子优先级带来额外复杂度。在 STM32 HAL 中通常这样设置:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
NVIC_PRIORITYGROUP_4 表示 4 bit 都用于抢占优先级,0 bit 用于子优先级。这样 STM32 的 0~15 就都可以直接作为抢占优先级使用。
FreeRTOS 对中断优先级的要求
FreeRTOS 中有一个非常关键的配置项:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
它表示:允许调用 FreeRTOS API 的最高中断优先级。
因为 STM32 中断优先级是“数值越小越高”,所以当该值配置为 5 时,可以这样理解:
- 优先级 0~4:硬件优先级更高,不受 FreeRTOS 管理,不能调用 FreeRTOS 的 API。
- 优先级 5~15:处于 FreeRTOS 可管理范围内,可以调用带
FromISR后缀的 API。
因此,如果某个中断服务函数里需要释放信号量、发送队列、通知任务等,就要满足两个条件:
- 该中断的优先级必须在 FreeRTOS 可管理范围内,比如 5~15。
- 在 ISR 中调用 FreeRTOS API 时,必须使用带
FromISR后缀的函数。
例如:
xSemaphoreGiveFromISR();
xQueueSendFromISR();
xTaskNotifyFromISR();
不要在中断服务函数里调用普通任务上下文版本的 API,比如 xSemaphoreGive()、xQueueSend()。
系统异常优先级寄存器 SHPR
Cortex-M 中有 3 个系统异常优先级配置寄存器:
| 寄存器 | 地址 |
|---|---|
| SHPR1 | 0xE000ED18 |
| SHPR2 | 0xE000ED1C |
| SHPR3 | 0xE000ED20 |
FreeRTOS 任务切换相关的两个系统异常是:
- PendSV
- SysTick
FreeRTOS 会把 PendSV 和 SysTick 的中断优先级设置为最低优先级。
这样做的目的很明确:任务切换不能阻塞系统中更重要的外设中断响应。也就是说,调度器可以慢一点执行,但不能影响真正紧急的硬件中断。
BASEPRI:FreeRTOS 中断屏蔽的核心
Cortex-M 中常见的中断屏蔽寄存器有 3 个:
PRIMASKFAULTMASKBASEPRI
FreeRTOS 的中断管理主要依赖 BASEPRI。
BASEPRI 的作用是屏蔽某个阈值及其以下优先级的中断。这里的“以下”要结合 Cortex-M 的优先级规则理解:数值越大,逻辑优先级越低。
例如,当 BASEPRI = 0x50 时,表示屏蔽优先级 5~15 的中断,而优先级 0~4 的中断仍然可以正常响应。
为什么是 0x50?因为 STM32 只使用中断优先级寄存器的高 4 位:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
当:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configPRIO_BITS 4
那么:
configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 << 4 = 0x50
也就是说,FreeRTOS 关中断时并不是把所有中断都关掉,而是只屏蔽自己能管理的那一部分中断。
FreeRTOS 的关中断和开中断
FreeRTOS 中关闭可管理中断的宏如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
这段代码把 BASEPRI 设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY,从而屏蔽 FreeRTOS 可管理范围内的中断。
对应的开中断宏如下:
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
__asm
{
msr basepri, ulBASEPRI
}
}
当 BASEPRI 被设置为 0 时,就不再屏蔽任何中断。
这里要注意:portDISABLE_INTERRUPTS() 不等于屏蔽全部中断。优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,比如 0~4,仍然可以响应。
ISR 中调用 FreeRTOS API 的规则
在中断服务函数中使用 FreeRTOS API 时,可以记住三句话:
- 中断优先级必须在 FreeRTOS 管理范围内。
- ISR 中必须使用带
FromISR后缀的 API。 - 建议优先级分组设置为
NVIC_PRIORITYGROUP_4。
以 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 5 为例:
- 优先级 4 的中断不能调用 FreeRTOS API,因为它不受 FreeRTOS 管理。
- 优先级 6 的中断可以调用
FromISRAPI,因为它在 FreeRTOS 管理范围内。
这也是很多 FreeRTOS 初学者容易踩坑的地方:不是“中断优先级越高越能调用 API”,恰恰相反,优先级太高的中断不能被 FreeRTOS 内核屏蔽,所以也不能安全地调用依赖内核临界区保护的 API。
实验设计
本节实验的目标是观察 FreeRTOS 中断管理的效果。
实验中使用两个定时器中断:
- 一个定时器中断优先级为 4。
- 一个定时器中断优先级为 6。
假设系统配置为:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
那么:
- 优先级 4 高于 FreeRTOS 管理范围,不会被
BASEPRI = 0x50屏蔽。 - 优先级 6 位于 FreeRTOS 管理范围内,会被
BASEPRI = 0x50屏蔽。
实验设计两个任务:
| 任务 | 作用 |
|---|---|
start_task | 创建 task1 任务 |
task1 | 调用关中断和开中断函数,观察两个定时器中断的打印现象 |
实验现象是:两个定时器每 1 秒打印一段字符串。当 FreeRTOS 关闭可管理中断时,优先级 6 的定时器中断会停止响应,而优先级 4 的定时器中断仍然可以响应;当重新开中断后,被管理的中断继续恢复。
通过这个实验可以直观看到:FreeRTOS 的“关中断”并不是全局关闭所有中断,而是通过 BASEPRI 屏蔽一部分低逻辑优先级的中断。
Github:Hui404/FreeRTOS_4
小结
FreeRTOS 中断管理可以归纳为以下几点:
- STM32 中断优先级使用高 4 位,所以通常有 0~15 共 16 级。
- 中断优先级数值越小越高,任务优先级数值越大越高。
- 建议使用
NVIC_PRIORITYGROUP_4,把所有优先级位都作为抢占优先级。 - PendSV 和 SysTick 通常被设置为最低优先级,避免任务切换影响外设中断响应。
- FreeRTOS 使用
BASEPRI实现中断屏蔽。 BASEPRI = 0x50时,通常表示屏蔽优先级 5~15,优先级 0~4 仍可响应。- ISR 中调用 FreeRTOS API 时,中断优先级必须在可管理范围内,并且必须使用
FromISR后缀函数。
掌握这几个规则后,再看 FreeRTOS 的临界区、任务切换、队列和信号量的中断用法,就会清晰很多。
参考资料
FreeRTOS 官方 Cortex-M3/M4 说明:https://www.freertos.org/RTOS-Cortex-M3-M4.html
《Cortex-M3 权威指南》









