FreeRTOS 中断配置和临界段

FreeRTOS 中断配置和临界段 ,是整个 FreeRTOS 移植和使用中最关键、最容易出错的部分之一。我们从 Cortex-M 硬件机制讲起,逐层深入到 FreeRTOS 的配置宏、开关中断实现、临界段保护,最后通过实验验证。


一、Cortex-M 中断基础(硬件层面)

要理解 FreeRTOS 的中断配置,首先得掌握 Cortex-M 内核(如 STM32)的中断管理机制。

1. NVIC 与中断优先级

  • NVIC(嵌套向量中断控制器) 管理所有外部中断和系统异常。
  • 每个中断有一个 8 位优先级寄存器 ,但实际使用多少位由芯片厂商决定。STM32 只使用了高 4 位 ,因此有 16 个优先级(0~15),数值越小优先级越高
  • 优先级分组(PRIGROUP) 将优先级分为"抢占优先级"和"子优先级"两部分。FreeRTOS 强制要求使用分组 4,即所有 4 位全部作为抢占优先级,没有子优先级。这样做的目的是简化中断管理,避免子优先级带来的复杂性。

ST 的 HAL 库在 HAL_Init() 中默认设置的就是 NVIC_PRIORITYGROUP_4(见图 5.1.2),所以使用 HAL 库时通常不需要修改。但如果从其他非 FreeRTOS 例程移植代码,务必检查并统一为分组 4。

2. 三个关键的中断屏蔽寄存器

寄存器 作用 FreeRTOS 使用情况
PRIMASK 屏蔽除 NMI 和 HardFault 外的所有中断 μC/OS 常用,FreeRTOS 不用
FAULTMASK 比 PRIMASK 更强,连 HardFault 都能屏蔽 FreeRTOS 不用
BASEPRI 屏蔽优先级低于某个阈值的中断(更精细的控制) FreeRTOS 使用,这是核心

重点理解 BASEPRI :向它写入一个数值(比如 5),则所有优先级数值大于等于 5 的中断都会被屏蔽,而优先级为 0~4 的高优先级中断不受影响。这种"选择性屏蔽"非常适合 RTOS------既要保护临界区,又不能耽误高优先级实时中断。


二、FreeRTOS 中断配置宏(软件层面)

FreeRTOS 通过 FreeRTOSConfig.h 中的几个宏来配置中断行为。这些宏容易混淆,我们逐一理清:

宏名称 示例值(STM32) 含义与用途
configPRIO_BITS 4 MCU 实际使用的优先级位数。STM32 用 4 位,所以为 4。
configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 用户视角的最低优先级(即数值 15)。
configKERNEL_INTERRUPT_PRIORITY 15 << (8-4) = 0xF0 写入寄存器时使用的值。因为优先级只存在高 4 位,需要左移 4 位。用于设置 PendSV 和 SysTick 的中断优先级(设为最低)。
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 用户视角的"系统调用最大优先级阈值"。优先级数值小于 5 的中断不受 FreeRTOS 屏蔽。
configMAX_SYSCALL_INTERRUPT_PRIORITY 5 << 4 = 0x50 写入 BASEPRI 寄存器时使用的值。用于开关中断时屏蔽"低于此优先级"的中断。

关键结论(图 5.1.3 的核心):

  • 优先级 0~4 :高于阈值,不会被 FreeRTOS 关中断屏蔽。这些中断绝对实时 ,但不能在 ISR 中调用任何 FreeRTOS API 函数(因为 API 可能被屏蔽,导致不可预知)。
  • 优先级 5~15 :低于或等于阈值,会被 FreeRTOS 关中断屏蔽。这些中断可以安全调用 FromISR 版本的 API(如 xQueueSendFromISR)。
  • PendSV 和 SysTick:被设为最低优先级 15,确保所有外设中断都能优先于内核服务。

为什么优先级值要左移 4 位?

因为 STM32 只使用了优先级寄存器的高 4 位,低 4 位不存在(读回 0)。所以写入寄存器时,必须将优先级值左移 4 位,让它对齐到高 4 位。例如,优先级 5 在寄存器中实际写入的是 0x50,优先级 15 写入的是 0xF0

PendSV 和 SysTick 优先级设置详解

xPortStartScheduler() 源码可以看到,FreeRTOS 通过直接操作地址 0xE000ED20(即 portNVIC_SYSPRI2_REG)一次性写入 32 位数据,其中:

  • SysTick 优先级对应最高 8 位(左移 24 位)
  • PendSV 优先级对应次高 8 位(左移 16 位)

两者都被设置为最低优先级(15),确保任务切换发生在所有外设中断之后。


三、FreeRTOS 开关中断的实现机制

有了上面的基础,开关中断的实现就一目了然了。

1. 关中断:portDISABLE_INTERRUPTS()

实际执行的是:

c 复制代码
ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;  // 0x50
msr basepri, ulNewBASEPRI

效果:所有优先级 ≥ 5 的中断被屏蔽,优先级 0~4 的中断继续响应。

2. 开中断:portENABLE_INTERRUPTS()

实际执行的是:

c 复制代码
vPortSetBASEPRI(0);  // 向 BASEPRI 写入 0

效果:取消所有屏蔽,所有中断恢复正常。

为什么不用 PRIMASK? 因为 BASEPRI 更灵活,允许高优先级中断(如电机控制、紧急停机的 IO 中断)在临界区中依然得到及时响应,这是 RTOS 实时性的重要保障。


四、临界段代码保护(应用层面)

临界段是"必须完整执行、不能被中断打断"的代码区域。FreeRTOS 提供了两种保护方式,区别在于调用上下文

1. 任务级临界段:taskENTER_CRITICAL() / taskEXIT_CRITICAL()

  • 在任务代码中使用(不能在中断中使用)。
  • 支持嵌套 :通过全局变量 uxCriticalNesting 记录嵌套深度。只有最外层的 EXIT 才会真正开中断,内层的 EXIT 只减少计数。这在复杂函数调用中非常有用,避免过早开中断破坏内层临界区的保护。
  • 使用原则 :临界区代码要尽可能短,因为关中断会影响系统实时性(即使只屏蔽低优先级中断)。

典型用法

c 复制代码
taskENTER_CRITICAL();
// 操作共享资源(如全局变量、外设配置)
total_num += 0.01f;
printf("total_num = %.4f\n", total_num);
taskEXIT_CRITICAL();

2. 中断级临界段:taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()

  • 在中断服务函数中使用 (且该中断的优先级必须 低于 configMAX_SYSCALL_INTERRUPT_PRIORITY)。
  • 用法有区别 :进入函数时会返回当前 BASEPRI 的值 ,退出时必须将返回值作为参数传入,以恢复之前的中断屏蔽状态。这保证了嵌套中断场景下,中断屏蔽状态能正确还原。
  • 为什么返回值很重要:假如中断 A 被更高优先级的中断 B 抢占,B 中又调用了临界段,那么退出 B 时必须恢复到 A 的中断屏蔽状态,而不是简单打开中断。返回值机制确保了这个恢复过程的正确性。

正确用法

c 复制代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    uint32_t status_value;
    status_value = taskENTER_CRITICAL_FROM_ISR();   // 保存当前 BASEPRI
    // 操作共享资源
    taskEXIT_CRITICAL_FROM_ISR(status_value);       // 恢复保存的 BASEPRI
}

五、中断测试实验深度解析(验证理论)

实验设计

  • TIM3:优先级设为 4(高于阈值),每秒打印 "TIM3 output..."。
  • TIM5:优先级设为 5(等于阈值),每秒打印 "TIM5 output..."。
  • 任务 interrupt_task :运行 5 次后,调用 portDISABLE_INTERRUPTS() 关闭中断,延时 5 秒,再调用 portENABLE_INTERRUPTS() 打开中断。

实验结果与结论

  1. 关中断前:两个定时器都正常输出。
  2. 关中断期间(5 秒)
    • TIM3(优先级 4)继续输出 ------ 因为它不受 BASEPRI 屏蔽。
    • TIM5(优先级 5)停止输出 ------ 因为它被 BASEPRI 屏蔽了。
  3. 重新开中断后:TIM5 恢复输出。

这个实验完美验证了configMAX_SYSCALL_INTERRUPT_PRIORITY 这个阈值确实将中断分成了"不可屏蔽的高实时通道"和"可屏蔽的普通通道"。这为实际项目中选择中断优先级提供了清晰的依据。


六、核心要点总结与实践建议

要点 说明
优先级分组必须是 4 HAL_Init() 中已设置,不要改动。
阈值 configMAX_SYSCALL_INTERRUPT_PRIORITY 是分水岭 高于它的中断可以响应但不能调用 API ;低于或等于它的中断可以调用 API,但会被临界段屏蔽。
PendSV 和 SysTick 优先级必须最低 FreeRTOS 内核依赖它们进行任务切换和时间管理。
临界区代码要极短 进入临界区会关中断(即使只关一部分),过长会影响系统实时性。
任务级与中断级临界段函数不可混用 在任务中用 taskENTER_CRITICAL(),在 ISR 中用 taskENTER_CRITICAL_FROM_ISR(),并注意返回值传递。
中断优先级设置要避开临界区 如果你有一个极端重要的中断(比如故障保护),把它设为 0~4,并确保它的 ISR 不调用任何 FreeRTOS API。

七、与 SVC 和内核态/用户态的关联

  • 在标准 FreeRTOS(无 MPU)中:所有中断和任务都运行在特权态,SVC 只用于启动第一个任务。开关中断(BASEPRI 操作)是全局的,影响所有低于阈值的任务和中断。
  • 在启用 MPU 的 FreeRTOS 中 :用户态任务通过 SVC 陷入内核态调用 API。此时,中断配置逻辑不变,但 configMAX_SYSCALL_INTERRUPT_PRIORITY 阈值同样决定了哪些中断可以"穿透"临界区,哪些会被屏蔽。高优先级中断(0~4)即使在用户态任务执行时也能响应,但它们的 ISR 仍然不能调用 FreeRTOS API(因为可能访问内核数据,触发 MPU 错误)。

相关推荐
2301_801434981 小时前
STM32使用rtthread studio内Cubemx开发
stm32·单片机·嵌入式硬件
辰哥单片机设计2 小时前
STM32档案室环境监测系统(机智云版)
stm32·单片机·嵌入式硬件
emdoorrugged2 小时前
产线数字化管理三防平板怎么选品牌?不同场景推荐不同厂家
单片机·嵌入式硬件
Hello_Damon_Nikola2 小时前
SS6285L从入门到精通
单片机·嵌入式硬件
雾削木3 小时前
STM32 HAL + FreeRTOS(CMSIS-RTOS V1)完整开发攻略
stm32·单片机·嵌入式硬件
wuyk55511 小时前
第2章:六步换相原理全解+STM32工程实战
c语言·开发语言·stm32·单片机·嵌入式硬件
小僧景贤16 小时前
单片机全方位调试实战指南:串口/RTT/逻辑分析仪/示波器/内核排错分层避坑教程
单片机·嵌入式硬件·物联网
2401_8275012820 小时前
【无标题】
arm开发·嵌入式硬件
LeoZY_20 小时前
LinkScope 使用笔记:基于 OpenOCD 的通用硬件芯片调试助手
笔记·单片机·嵌入式硬件·开源软件