由于不同的 Cortex-M 系列,其中断优先级是不一样的,所以事先要搞清楚所用 MCU 的中断优先级有几种。在 CMSIS 库中,头文件中的 __NVIC_PRIO_BITS
为中断优先级的比特位数,也就表示了 MCU 使用的中断的数量。
FreeRTOS 的中断嵌套结构被分为两组,一组为被屏蔽的一组为不屏蔽的。 FreeRTOSConfig.h
中的configMAX_SYSCALL_INTERRUPT_PRIORITY
定义了这种分组。而这个最优值取决于上面提到的 MCU 的优先级位数,实际 CMSIS 中只要定义了 __NVIC_PRIO_BITS
,移植时需要在 FreeRTOSConfig.h
引入即可。
数以千计的 FreeRTOS 应用运行在 ARM Cortex-M 内核上。令人惊奇的是,RTOS 与 Cortex-M 内核组合使用,使得技术支持请求变得如此的少。大多数的问题点是由不正确的优先级设置引起的。这个问题也是在意料之中的,因为尽管 Cortex-M 内核的中断模式是非常强大的,但对于那些使用传统中断优先级架构的工程师来说, Cortex-M 内核中断机制也有点笨拙(或者是说使用比较繁琐),并且违反直觉(这个主要是因为 Cortex-M 优先级数越大代表的优先级反而越小)。
说明:虽然 Cortex-M 内核的优先级方案看上去比较复杂,但每一个官方发布的 FreeRTOS 接口包(在 .\ FreeRTOS \Source\portable文件夹中,一般为 port.c)内都会有正确配置的演示例程,可以以此为参考。
有效优先级
Cortex-M 硬件详述
首先需要清楚有效优先级的总数,这取决于微控制器制造商怎么使用 Cortex-M 内核。所以,并不是所有的 Cortex-M 内核微处理器都具有相同的中断优先级级别。
Cortex-M 构架自身最多允许 256 级可编程优先级(优先级配置寄存器最多 8 位,所以优先级范围从 0x00 ~ 0xFF),但是绝大多数微控制器制造商只是使用其中的一部分优先级。比如,TI Stellaris Cortex-M3 和 Cortex-M4 微控制器使用优先级配置寄存器的 3 个位,能提供 8 级优先级。再比如,NXP LPC17xx Cortex-M3 微控制器使用优先级配置寄存器的 5 个位,能提供 32 级优先级。STM32F1、F4 使用了其中的 4 位,能提供 16 级优先级。
如果你的工程包含 CMSIS 库头文件,则头文件中的宏 __NVIC_PRIO_BITS
定义使用多少优先级寄存器的位(默认是 4 位)。
应用到 FreeRTOS
RTOS 中断嵌套方案将有效的中断优先级分成两组:一组可以通过 RTOS 临界区屏蔽,另一组不受 RTOS 影响,永远都是使能的。在 FreeRTOSConfig.h
中配置的 configMAX_SYSCALL_INTERRUPT_PRIORITY
,定义两组中断优先级的边界。逻辑优先级高于此值的中断不受 RTOS 影响。最优值取决于微控制器使用的优先级配置寄存器的位数。
与数值相反的优先级值和逻辑优先级设置
Cortex-M 硬件详述
有必要先解释一下优先级值和逻辑优先级:在 Cortex-M 内核中,假如有 8 级优先级,我们说优先级值是 0 ~ 7,但数值最大的优先级 7 却代表着最低的逻辑优先级。很多使用传统传统中断优先级架构的工程师会觉得这样比较绕,违反直觉。以下内容提到的优先级要仔细区分是优先级数值还是逻辑优先级。
接下来需要清楚的是,在 Cortex-M 内核中,一个中断的优先级数值越低,逻辑优先级却越高。比如,中断优先级为 2 的中断可以抢占中断优先级为 5 的中断,但反过来就不行。换句话说,中断优先级 2 比中断优先级 5 的优先级更高。这是 Cortex-M 内核最容易让人犯错之处,因为大多数的非 Cortex-M 内核微控制器的中断优先级表述是与之相反的。
应用到 FreeRTOS
以 FromISR
结尾的 FreeRTOS 函数是具有中断调用保护的(执行这些函数会进入临界区),但是就算是这些函数,也不可以被逻辑优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY
的中断服务函数调用。(宏 configMAX_SYSCALL_INTERRUPT_PRIORITY
定义在头文件 FreeRTOSConfig.h
中)。
因此,任何使用 RTOS API 函数的中断服务例程的中断优先级数值必须要大于等于 configMAX_SYSCALL_INTERRUPT_PRIORITY
宏的值。这样就能保证中断的逻辑优先级等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY
。
Cortex-M 中断默认情况下有一个数值为 0 的优先级。大多数情况下 0 代表最高级优先级。因此,绝对不可以在优先级为 0 的中断服务例程中调用 RTOS API 函数。
Cortex-M 内部优先级概述
Cortex-M 硬件详述
Cortex-M 优先级寄存器最多有 8 位,并且是以最高位(MSB)对齐的。比如,如果使用了 3 位来表达优先级,则这 3 个位位于中断优先级寄存器的 bit5、bit6、bit7 位。剩余的 bit0 ~ bit4 可以设置成任何值,但为了兼容,最好将他们设置成 1。见下图:
某微控制器只使用了优先级寄存器中的 3 位,下图展示了优先级数值 5(二进制 101)是怎样在优先级寄存器中存储的。如果优先级寄存器中未使用的位置 1,下图也展示了为什么数值 5(二进制00000101)可以看成数值 191(二进制10111111)。
某微控制器只使用了优先级寄存器中的 4 位,下图展示了优先级数值 5(二进制101)是怎样在优先级寄存器中存储的。如果优先级寄存器中未使用的位置 1,下图也展示了为什么数值 5(二进制00000101)可以看成数值 95(二进制01011111)。
注意:转载的原文中上图有误,查找英文出处修订改过
应用到 FreeRTOS
上文中已经描述,那些在中断服务例程中调用 RTOS API 函数的中断逻辑优先级必须低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY
(低逻辑优先级意味着高优先级数值)。
CMSIS 以及不同的微控制器供应商提供了可以设置某个中断优先级的库函数。一些库函数的参数使用最低位对齐,另一些库函数的参数可能使用最高位对齐,所以,使用时应该查阅库函数的应用手册进行正确设置。
移植 FreeRTOS 时,必须在 FreeRTOSConfig.h
中设置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY
和 configKERNEL_INTERRUPT_PRIORITY
的值。这两个宏需要根据 Cortex-M 内核自身的情况进行设置,要以最高有效位对齐。比如某微控制器使用中断优先级寄存器中的 3 位,设置 configKERNEL_INTERRUPT_PRIORITY
的值为 5,则代码为:#define configKERNEL_INTERRUPT_PRIORITY (5<<(8-3))
(关于这两个宏可以参考参数设置一章,网址:http://open MCU.net/post/kernel-config.html)。
对于每一个官方 FreeRTOS 演示例程,这也是在 FreeRTOSConfig.h
中要设置宏 configKERNEL_INTERRUPT_PRIORITY
为最低优先级时,为什么要将它设置为 255(1111 1111B)的原因。使用这种方式指定这个值的原因是: FreeRTOS 内核是直接在 Cortex-M 内核硬件上运行的(没有使用第三方接口库函数),要比大多数库函数先运行。
临界区
Cortex-M 硬件详述
RTOS 内核使用 Cortex-M 内核的 BASEPRI
寄存器来实现临界区(注:BASEPRI
为优先级屏蔽寄存器,优先级数值大于或等于该寄存器的中断都会被屏蔽,优先级数值越大,逻辑优先级越低,但是为零时不屏蔽任何中断)。这允许 RTOS 内核可以只屏蔽一部分中断,因此可以提供一个灵活的中断嵌套模式。
那些需要在中断调用时保护的 API 函数, FreeRTOS 使用寄存器 BASEPRI
实现中断保护临界区。当进入临界区时,将寄存器BASEPRI
的值设置成 configMAX_SYSCALL_INTERRUPT_PRIORITY
,当退出临界区时,将寄存器 BASEPRI
的值设置成 0。
很多 Bug 反馈都提到,当退出临界区时不应该将寄存器设置成 0,应该恢复它之前的状态(之前的状态不一定是 0)。但是 Cortex-M NVIC 决不会允许一个低优先级中断,去打断当前正在执行的高优先级中断,不管 BASEPRI 寄存器中是什么值。与进入临界区前先保存 BASEPRI 的值,退出临界区再恢复的方法相比,退出临界区时将 BASEPRI 寄存器设置成 0 的方法可以获得更快的执行速度。
应用到RTOS kernel
RTOS 内核通过写 configMAX_SYSCALL_INTERRUPT_PRIORITY
的值到 BASEPRI
寄存器的方法创建临界区。中断优先级 0(具有最高的逻辑优先级)不能被 BASEPRI 寄存器屏蔽,因此,configMAX_SYSCALL_INTERRUPT_PRIORITY
绝不可以设置成 0。