中断是一种由硬件或软件触发的机制,用于打断当前程序的执行流程,转而去处理更高优先级的任务。
中断执行流程
以下是一个典型的中断处理流程,通常以硬件中断为例:
- 触发中断
- 硬件设备产生中断信号,通知处理器有事件需要处理。
- 处理器检测到中断信号后,当前程序暂停,进入中断响应阶段。
- 中断优先级判断
- 如果有多个中断信号,处理器根据中断优先级选择需要响应的中断。
- 如果当前有更高优先级的任务正在运行,则可能推迟低优先级中断的响应。
- 保存上下文
- 处理器保存当前的执行上下文(包括程序计数器 PC、通用寄存器、状态寄存器等),以便中断处理完成后可以恢复。
- 在某些处理器中,这由硬件自动完成,或者需要软件显式完成。
- 跳转到中断向量表
- 处理器根据中断号查找中断向量表(Interrupt Vector Table, IVT),获取对应中断服务例程(ISR)的入口地址。
- 中断向量表是一个存储各中断服务例程地址的数组。
- 执行中断服务例程(ISR)
- 处理器跳转到中断服务例程的入口地址,执行中断处理代码。
- 中断服务例程是用户定义的程序,用于处理特定的中断事件。
- 中断结束与恢复
- 中断服务例程执行完成后,调用
IRET
(中断返回)或类似指令,恢复保存的上下文。 - 处理器返回到被中断的程序,继续执行。
ARM 中的中断优先级
ARM Cortex-M 架构自身允许最多 256 个不同的优先级(最多有 8 个 优先级位,因此从 0 到 0xff 的优先级都是可能的),但绝大多数使用 ARM Cortex-M 核心的微控制器仅允许使用其中一部分;
ARM Cortex-M4F 使用 4(低四位) 位优先级寄存器来配置中断的优先等级,共分为两个部分:抢占优先级和子优先级。分配给每个部分的位数是可配置的。抢占优先级定义了一个中断是否可以抢占另一个正在执行的中断。当两个抢占优先级相同的中断同时发生时,子优先级决定首先执行哪个中断。
在 ARM Cortex-M 核心中,优先级的数值越小, 则中断的逻辑优先级越高。
分组 | 抢占优先级 | 响应优先级 |
---|---|---|
分组0 | 0(可取值为0) | 4(可取值为0到15) |
分组1 | 1(可取值为0到1) | 3(可取值为0到7) |
分组2 | 2(可取值为0到3) | 2(可取值为0到3) |
分组3 | 3(可取值为0到7) | 1(可取值为0到1) |
分组4 | 4(可取值为0到15) | 0(可取值为0) |
在使用 FreeRTOS 时,建议将所有优先级位都指定为抢占优先级位, 不保留任何优先级位作为子优先级位。
任何其他配置都会使 configMAX_SYSCALL_interrupt_PRIORITY 设置与分配给各个外设中断之间的直接关系复杂化。
1 | nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0); |
FreeRTOS 中的中断优先级
FreeRTOS 的中断嵌套方案将可用的中断优先级分为两组:一组将被 RTOS 临界区屏蔽,另一组 永远不会被 RTOS 临界区屏蔽,因此始终处于启用状态。两个组之间的边界由 FreeRTOSConfig.h 中的 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置定义。此设置的最佳值将取决于微控制器中实现的优先级位数量。
临界区是指一段代码,它在执行过程中需要独占访问某些共享资源(如全局变量、外设寄存器等),以防止并发访问导致的数据不一致问题。中断中的临界区通过禁用某些中断来实现,这样可以保证这段代码不被中断打断,从而避免资源访问冲突。
简单来说 FreeRTOS 中的中断分为以下两类:
- 非 FreeRTOS 管理的中断(中断优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY):
- 这些中断不能调用 FreeRTOS 提供的 API 函数。
- 适用于对实时性要求非常高的事件。
- 它们的优先级设置必须高于 configMAX_SYSCALL_INTERRUPT_PRIORITY(即优先级数值小于)。
- FreeRTOS 管理的中断(中断优先级低于等于 configMAX_SYSCALL_INTERRUPT_PRIORITY):
- 可以调用 FreeRTOS 提供的中断安全 API。
- 中断优先级必须低于或等于 configMAX_SYSCALL_INTERRUPT_PRIORITY(即优先级数值大于或等于),否则可能导致系统行为异常。
数值优先级与逻辑优先级设置之间的反转关系
在 ARM Cortex-M 核心中,优先级的数值越小, 则中断的逻辑优先级越高
在 FreeRTOS 中以 “FromISR” 结尾的 FreeRTOS 函数是中断安全的,但前提是调用这些函数的中断的逻辑优先级不高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 定义的优先级;
因此,对于任何使用一个 RTOS API 函数的中断服务程序,必须为其手动设置为一个数值优先级, 这个值必须等于或大于 configMAX_SYSCALL_INTERRUPT_PRIORITY 设定的值。这确保了中断的逻辑优先级等于或小于 configMAX_SYSCALL_INTRUPT_PRIORITY 设置。
FreeRTOS 规定,调用 “FromISR” 函数的中断必须满足:
- 逻辑优先级 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY
- 数值优先级 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY
由于不同的 Cortex-M 微控制器使用的优先级位数可能有所不同(通常少于 8 位),为了统一处理这些差异,FreeRTOS 将硬件原生的优先级值映射到一个统一的 8 位优先级范围。这种映射机制确保了在不同 Cortex-M 微控制器上实现一致的中断优先级管理。configKERNEL_INTERRUPT_PRIORITY 和 configMAX_SYSCALL_INTERRUPT_PRIORITY;
对于仅实现 configKERNEL_INTERRUPT_PRIORITY 的移植:
configKERNEL_INTERRUPT_PRIORITY 设置 RTOS 内核自身使用的中断优先级。(SysTick 也需要一个中断优先级)
调用 API 函数的中断也必须在此优先级下执行。不调用 API 函数的中断可以在更高的优先级下执行,因此其执行不会因 RTOS 内核活动而延迟 (在硬件本身的限制范围内)。
如果微处理器本身与 FreeRTOS 一致,则可以不需要使用 configMAX_SYSCALL_INTERRUPT_PRIORITY 抽象一层;
对于同时实现 configKERNEL_INTERRUPT_PRIORITY 和 configMAX_SYSCALL_INTERRUPT_PRIORITY 的移植:
configKERNEL_INTERRUPT_PRIORITY 设置 RTOS 内核自身使用的中断优先级。
configMAX_SYSCALL_INTERRUPT_PRIORITY 设置可以调用中断安全 FreeRTOS API 函数的最高中断优先级。
宏介绍
1 | // 定义 MCU 最多支持几位优先级 Cortex-M 是 4 位,0~15 |
RTOS 内核的核心功能依赖于硬件的 SysTick 和其他可能需要的中断服务(例如上下文切换)。
中断优先级与任务优先级
实现
FreeRTOS 主要用于帮我们管理中断的关闭与开启
1 | // 屏蔽中断 |
FreeRTOS 的中断屏蔽与恢复是通过设置 BASEPRI 寄存器的值来实现的(写 0 屏蔽,写 1 恢复)
BASEPRI 是 ARM Cortex-M 处理器架构中的一个 8 位寄存器,逻辑为:
- 当 BASEPRI 设置为 0 时,所有中断都可以响应(没有屏蔽)。
- 当 BASEPRI 设置为某个非零值时,优先级低于该值的中断将被屏蔽。
1 |
|
问题
一、vTaskDelay 函数为什么会恢复中断
vTaskDelay 函数会调用 xTaskResumeAll 函数,在 xTaskResumeAll 这个函数中,他会执行 taskENTER_CRITICAL 和 taskEXIT_CRITICAL 函数,这两个函数分别起到了控制中断屏蔽的作用。
taskENTER_CRITICAL:这个宏会调用 portDISABLE_INTERRUPTS,通常是通过设置 BASEPRI 寄存器来屏蔽中断。
taskEXIT_CRITICAL:这个宏会恢复 BASEPRI 寄存器的值,解除屏蔽低优先级的中断。
二、如果在中断中执行while(1)会怎么样
中断无法结束:中断服务例程的目标是快速处理必要的操作并尽早返回,使得系统可以继续响应其他任务和中断。如果在 ISR 中执行 while(1),中断服务例程会永远处于活动状态,不会退出,这会阻塞中断的返回,系统无法恢复到正常的执行流。
如果系统允许中断嵌套(即优先级较高的中断可以中断较低优先级的中断),那么当 while(1)
导致当前中断服务例程无法退出时,其他待处理的中断会被挂起,直到当前 ISR 完成。这意味着新的中断不会得到及时处理,可能会丢失重要的数据或事件。
如果系统禁用了中断嵌套(通过设置更高的优先级屏蔽),则新的中断在 ISR 执行时将被禁止,导致该中断甚至不会被触发,直到当前 ISR 执行完毕。