FreeRTOS中断管理

FreeRTOS 中断管理

1. 先理解什么是中断

中断是外设向 CPU 发出的"有事情需要处理"的请求。例如定时器到时、GPIO 检测到边沿时,CPU 会暂时停下正在执行的任务,转去运行对应的中断服务函数(ISR)。处理完后,CPU 再返回被打断的位置继续执行。

复制代码
任务正在运行
    ↓ 外设发出中断请求
CPU 暂停任务,保存必要现场
    ↓
运行 ISR,尽快处理紧急工作
    ↓
退出 ISR,恢复原现场或切换到更合适的任务

ISR 不是一个 FreeRTOS 任务。它运行在中断上下文中,因此不能随意调用任务上下文 API,也不应做耗时工作。

2. 中断优先级:数值方向和任务相反

在 Cortex-M / STM32 中,中断优先级数值通常是越小越紧急 :优先级 0 比优先级 6 更紧急。FreeRTOS 任务优先级则通常是数值越大越高。

对象 数值更大意味着
Cortex-M 中断优先级 紧急程度更低
FreeRTOS 任务优先级 运行优先级更高

Cortex-M 的优先级寄存器宽度为 8 位,但芯片实际实现的位数可能少于 8 位。课件中的 STM32 实现了高 4 位,因此可配置 16 个有效优先级等级(0--15)。不能把 STM32 的 4 位实现直接套用到所有 Cortex-M 芯片。

2.1 抢占优先级和子优先级

部分 STM32 HAL/CMSIS 配置把中断优先级位分成:

  • 抢占优先级:数值更小的中断可以打断抢占优先级数值更大的中断。

  • 子优先级:抢占优先级相同时,多个中断同时等待时用于决定先处理谁;子优先级本身不能让一个中断抢占另一个。

3. 哪些中断可以调用 FreeRTOS API?

这是本章最重要的规则:

只有优先级处于 FreeRTOS 允许范围内的中断,才能调用 FreeRTOS 的 ISR 专用 API。

在 Cortex-M 上,中断数值越小越紧急。FreeRTOS 使用 configMAX_SYSCALL_INTERRUPT_PRIORITY(或相关配置)设定一条边界:比边界更紧急的中断不会被 FreeRTOS 临界区屏蔽,因此不能调用 FreeRTOS API;边界及更低紧急度的中断,才可在遵循移植层要求的前提下调用 ...FromISR() API。

以 STM32 只实现 4 个优先级位、使用逻辑优先级 5 作为边界为例:

中断逻辑优先级 紧急程度 能否调用 FreeRTOS API
0--4 更紧急 不可以
5--15 较低或等于边界 可以调用允许的 ISR 专用 API

这里的 5 是 CMSIS/HAL 常用的未移位逻辑优先级。FreeRTOS 底层配置 configMAX_SYSCALL_INTERRUPT_PRIORITY 时,通常需要填写按芯片优先级位数编码后的寄存器值,例如 4 个有效位时逻辑优先级 5 对应 0x50。许多工程也会定义便于阅读的 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY。务必按项目 FreeRTOSConfig.h 和官方移植层的要求填写,不要把逻辑值 5 与编码值 0x50 混用。

不同厂商 HAL、芯片实现位数和移植版本的宏定义可能不同;以工程配置及对应端口文档为准。优先级设错可能导致断言失败、临界区行为异常或难以复现的系统故障。

4. 为什么高紧急度中断不能调用内核 API?

FreeRTOS 在 Cortex-M 上利用 BASEPRI 实现可管理中断的屏蔽。进入临界区时,内核提高 BASEPRI 阈值,屏蔽一部分较低紧急度的中断;优先级更高的紧急中断仍可立即响应。

因此,高紧急度中断可以用于时限严格的硬件响应,但它可能在内核修改列表、队列或其他内部状态时插入执行。内核无法保证这些操作对它安全,所以这类中断不能调用 FreeRTOS API。

可把中断分成两类理解:

  1. 高紧急度、内核不可管理:响应快,但 ISR 中不能调用 FreeRTOS API。若要把数据交给任务,可先写入硬件缓冲区或受控的无锁缓冲,再由可调用内核 API 的中断或任务转交。

  2. 可由内核管理的中断 :优先级数值处于允许范围,可以调用匹配的 ...FromISR() API。

5. 中断中调用任务 API 的规则

在 ISR 中需要使用 FreeRTOS 功能时,调用对应的中断版本 API,通常函数名带 FromISR 后缀。例如:

任务上下文 API ISR 中对应 API
xQueueSend() xQueueSendFromISR()
xSemaphoreGive() xSemaphoreGiveFromISR()
vTaskNotifyGive() vTaskNotifyGiveFromISR()
vTaskResume() xTaskResumeFromISR()

实际可用函数取决于 FreeRTOS 版本和组件配置。不能只因为名称相似就从 ISR 调用普通任务版本。

5.1 请求在中断退出时切换任务

ISR API 通常通过输出参数告诉程序,操作是否唤醒了一个比当前任务更高优先级的任务。ISR 退出前再调用端口提供的切换宏,让调度器决定是否立即切到新任务:

复制代码
void UART_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
​
    /* 读取并清除 UART 中断状态 */
    xQueueSendFromISR(xRxQueue, &receivedByte,
                      &xHigherPriorityTaskWoken);
​
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

如果队列操作唤醒了更高优先级任务,通常在 ISR 退出时立即切换;否则先返回原任务。切换发生在中断退出路径中,而不是让 ISR 直接"运行一个任务"。

不同端口可能使用 portEND_SWITCHING_ISR() 等其他宏名,参数类型也可能不同,应以当前移植层提供的写法为准。

6. 临界区和中断屏蔽

临界区用于保护一小段不能被相关中断打断的代码。Cortex-M FreeRTOS 端口通常通过 BASEPRI 屏蔽可管理的中断,而不是无条件屏蔽所有中断。因此更高紧急度的中断仍可能执行。

任务上下文中的典型形式:

复制代码
taskENTER_CRITICAL();
/* 受保护的短小操作 */
taskEXIT_CRITICAL();

规则:

  • 进入和退出应成对,临界区必须尽可能短。

  • 不要在临界区中调用会阻塞的 API、延时、等待队列或执行耗时操作。

  • 临界区不等同于"所有中断都停了";具体屏蔽范围由端口和配置决定。

  • 需要在 ISR 中保护代码时,应使用当前端口明确提供的 ISR 临界区接口;不要在 ISR 中直接照搬任务上下文的进入/退出宏。

  • PRIMASK、FAULTMASK 与 BASEPRI 的作用范围不同。应用层通常优先使用 FreeRTOS 临界区 API,不要随意直接改内核依赖的屏蔽寄存器。

7. Cortex-M 系统异常优先级

课件提到 SHPR1、SHPR2、SHPR3 是系统处理程序优先级寄存器。FreeRTOS Cortex-M 端口通常把 SysTick 和 PendSV 配为最低优先级:

  • SysTick 产生系统节拍,推进延时和时间管理。

  • PendSV 执行任务上下文切换。

把它们放在较低优先级,可让更紧急的外设中断先处理,减少任务切换对高紧急度中断的影响。普通应用一般由 FreeRTOS 端口配置这些异常,不应随意覆盖其优先级。

8. 本章使用要点

  1. 中断优先级和任务优先级方向相反:前者数值越小越紧急,后者通常数值越大优先级越高。

  2. 只有处于 configMAX_SYSCALL_INTERRUPT_PRIORITY 允许范围内的中断才能调用 FreeRTOS ISR API;更紧急的中断不能调用任何 FreeRTOS API。

  3. ISR 要短小。耗时处理放到任务中,ISR 负责确认硬件事件并通知任务。

  4. ISR 中只调用明确标记为 FromISR 的接口,并按返回值或唤醒标志使用端口切换宏。

  5. 中断优先级寄存器的有效位数、逻辑优先级和编码值要区分清楚;按芯片和移植层配置,不照抄其他工程的常数。

  6. 临界区只保护短小的共享数据操作,不能在其中阻塞,也不能假设所有中断都被屏蔽。

9. 本章函数与宏汇总

函数或宏 中文含义 主要用法
taskENTER_CRITICAL() 进入任务临界区 任务上下文中保护短小的共享数据操作;必须与退出宏配对
taskEXIT_CRITICAL() 退出任务临界区 恢复进入临界区前的调度/中断屏蔽状态
taskENTER_CRITICAL_FROM_ISR() 从 ISR 进入临界区 仅在移植层支持时用于 ISR;保存宏返回的状态,供退出时恢复
taskEXIT_CRITICAL_FROM_ISR() 从 ISR 退出临界区 使用 taskENTER_CRITICAL_FROM_ISR() 返回的状态恢复;具体签名查当前版本
xQueueSendFromISR() 从中断向队列发送数据 传入队列、数据地址和唤醒标志;必要时在 ISR 退出时请求切换
xSemaphoreGiveFromISR() 从中断释放信号量 常用于 ISR 通知等待任务;检查唤醒标志并在退出时按端口要求切换
vTaskNotifyGiveFromISR() 从中断向任务发送通知 传入目标任务句柄和唤醒标志;适合轻量级事件通知
xTaskResumeFromISR() 从中断恢复挂起任务 仅在任务确实使用挂起/恢复语义时使用;它不是可靠的事件缓存机制
portYIELD_FROM_ISR() 根据条件请求 ISR 退出切换 通常传入 ISR API 设置的高优先级任务唤醒标志;宏名依端口而异
portEND_SWITCHING_ISR() 部分端口的 ISR 切换宏 某些移植层使用此名称;按当前端口示例调用
HAL_NVIC_SetPriorityGrouping() 设置 NVIC 优先级分组 STM32 HAL 初始化时设置抢占/子优先级位分配;FreeRTOS 项目按移植层建议配置
HAL_NVIC_SetPriority() 设置外设中断优先级 STM32 HAL 中为指定 IRQ 配置逻辑抢占优先级和子优先级;确保其符合 FreeRTOS API 调用范围
portDISABLE_INTERRUPTS() / portENABLE_INTERRUPTS() 移植层屏蔽/允许中断 端口相关的底层宏;应用代码通常使用临界区 API,避免不成对调用
configMAX_SYSCALL_INTERRUPT_PRIORITY(配置宏) FreeRTOS API 可调用中断的优先级边界 在 FreeRTOSConfig.h 中按处理器实现位数填写移位后的编码值;更紧急的中断不得调用内核 API
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY(部分工程配置宏) 以逻辑优先级表示的 API 边界 有些工程用它生成上面的编码值;遵循项目配置,避免混淆逻辑值和寄存器值
configPRIO_BITS(配置宏) 芯片实现的中断优先级位数 移植配置使用,必须匹配 MCU 实际实现的优先级位数
相关推荐
天吾cc2 小时前
FreeRTOS任务挂起和恢复
freertos
中文英文-我选中文4 天前
8.2 学习探索
freertos
qq_401700419 天前
FreeRTOS 堆内存管理源码剖析:释放一块内存,空闲块数量反而变少了?
单片机·freertos
List<String> error_P11 天前
使用FreeRTOS搭建工程项目(一)
freertos
@嵌入式扫地僧19 天前
RTOS 面试实战:任务状态切换原理
freertos·rtos·时间片轮转·任务状态切换·抢占式调度
WWJA王文举1 个月前
Bootloader、App 和升级分区之间是什么关系?
stm32·freertos·嵌入式开发·在线升级
怀民民民1 个月前
嵌入式软件学习路线与项目实践记录:STM32、FreeRTOS、Linux 与 OpenMV
stm32·freertos·嵌入式软件·嵌入式linux·openmv
长江后浪博客1 个月前
ESP32-SOLO-1 在 ESP-IDF v5.5 下启动不断重启问题分析与解决
单片机·嵌入式硬件·嵌入式·esp32·freertos·esp-idf
WWJA王文举1 个月前
FreeRTOS动态创建任务和静态创建任务详解:xTaskCreate与xTaskCreateStatic区别
操作系统·freertos·嵌入式开发