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 实际实现的优先级位数
相关推荐
La Pulga1 天前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga2 天前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga4 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
La Pulga4 天前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
天吾cc9 天前
FreeRTOS 事件标志组
freertos
编码追梦人10 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
天吾cc14 天前
FreeRTOS 队列集
freertos
硅基手札14 天前
操作系统设计思想系列 06 · FreeRTOS 深度 — 极简可移植范本
架构·操作系统·freertos·实时操作系统·嵌入式开发·设计思想
天吾cc19 天前
FreeRTOS 列表和列表项
freertos
qq_4017004120 天前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos