注意:以下关于中断嵌套的信息适用于Cortex-M3、Cortex-M4、Cortex-M4F和Cortex-M7,不适用于Cortex-M0或者Cortex-M+等不包括BASEPRI寄存器的内核。
介绍
数以千计的应用在ARM Cortex-M内核上运行FreeRTOS。令人吃惊的是,很少有关于这个RTOS和ARM Cortex内核组合的技术支持请求。但是,确实存在大量由于不正确设置中断优先级导致的问题。这可能是意料之中的,因为尽管ARM Cortex-M内核使用的中断模型很强大,但是对于那些习惯于更传统的中断优先级方案的工程师来说,它也有些复杂和违反直觉。本文的目的是讲述ARM Cortex-M的中断优先级机制及其在RTOS内核中的使用。
虽然ARM Cortex-M3内核的重点优先级机制可能看起来很复杂,但是FreeRTOS官方提供的每个port都带有一个正确配置的演示例程作为参考。此外,FreeRTOS V7.5.0版引入了configASSERT()函数以捕获错误配置的ARM Cortex-M重点控制器(NVIC)。在开发中请确认是否定义了configASSERT()。
可用的优先级
Cortex-M硬件详情
首先必须知道,可用的优先级总数是由(具体的MCU芯片)实现定义的,也就是说,取决于实现ARM Cortex-M内核的微控制器厂商。因此,并不是所有的ARM Cortex-M内核的微控制器都提供了相同数量的中断优先级。
ARM Cortex-M架构允许最多256和不同的优先级(最多8个优先级位,因此优先级0到优先级0xff都是可能的),但是大多数使用ARM Cortex-M内核的微控制器只允许访问这8个优先级位中的一部分位。例如,TI公司的Stellaris Cortex-M3 and ARM Cortex-M4微控制器实现了3个优先级位。这提供了8个不同的优先级数值。再比如,NXP LPC17xx ARM Cortex-M3微控制器实现了5个优先级位。这提供了32个不同的优先级数值。
如果你的工程包含了CMSIS库的头文件,可以通过宏定义__NVIC_PRIO_BITS查看你使用的微控制器有多少个优先级位。
使用RTOS时的相关性
RTOS中断嵌套机制把可用的中断优先级划分位两组,一组中的中断被RTOS的临界区(critical sections)屏蔽,另一组中的中断不会被临界区屏蔽,因此总是使能的。在头文件FreeRTOSConfig.h中的宏
configMAX_SYSCALL_INTERRUPT_PRIORITY定义了这两个分组的边界。此设置的最优值取决于在微控制器中实现的优先级位数。
抢占优先级和子优先级
Cortex-M硬件详情
8位中断优先级寄存器被划分为2个部分:抢占优先级和子优先级。这两个部分分别包含的中断优先级位数是可配置的。抢占优先级定义了一个中断是否可以中断一个已经处于执行中的中断。子优先级定义了当两个具有相同抢占优先级的中断同时发生时,微控制器先响应哪个中断。
使用RTOS时的相关性
建议把所有的优先级位分配给抢占优先级,不要把任何优先级位用于指定子优先级。任何其他配置都会使宏configmax_syscall_interrupt_priority的设置与分配给各外设的中断的优先级之间的直接关系变得复杂化。
大多数系统默认都是这种配置,但是STM32驱动程序库除外。如果你使用的是STM32和STM32驱动程序库,必须在RTOS启动之前调用函数NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ),从而使所有的优先级位被分配给抢占优先级。
中断优先级数字值和逻辑优先级设置之间的相反关系
Cortex-M硬件详情
下一件需要了解的事情是,在ARM Cortex-M内核中,优先级数字值较小意味着逻辑优先级越高。例如,中断优先级数值为2的中断比中断优先级数值为5的中断优先级更高。换句话说,中断优先级2比中断优先级5的优先级高,即使2比5小。更明确的说,中断优先级数值为2的中断可以中断中断优先级数值为5的中断的运行,反之则不行。
这是ARM Cortex-M架构中断优先级机制最反直觉的部分,因为它和绝大多数非ARM Cortex-M架构的微控制器的重点优先级机制相反。
使用RTOS时的相关性
FreeRTOS中以FromISR结尾的函数是中断安全的,但是,即使是这些函数也不能被逻辑优先级比宏configMAX_SYSCALL_INTERRUPT_PRIORITY所定义的优先级高的中断中调用。因此,
任何使用RTOS API函数的中断服务程序必须被设置为优先级数字大于或等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY定义的数值。这样就保证了中断的逻辑优先级小于或等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY定义的优先级。
Cortex-M中断默认具有中断数值0,这是最高的优先级。因此,绝对不要让调用RTOS API的中断使用其默认的优先级。
Cortex-M内部优先级表示
Cortex-M硬件详情
ARM Cortex-M内核把中断优先级数值从其8位中断优先级寄存器的最高位开始存储。例如,如果一个ARM Cortex-M微控制器只实现了3位优先级位,那么这三个优先级位被移位到优先级寄存器的第5、6、7位。第0到4位可以保存任意值,但是,为了保证未来的最大兼容性,这5位应当被设置为全1。
ARM Cortex-M的内部优先级表示如图所示。
ARM Cortex-M的优先级寄存器提供了最多8个优先级位。如果一个微控制器只实现了3位,那么只有优先级寄存器的最高三位被使用。
上图显示了优先级数值5(二进制的101)是如何存储在实现了3个优先级位的微控制的优先级寄存器中的。上图说明了为什么数值5(二进制的00000101)可以被认为是191(二进制的10111111),因为这3个优先级位(101)被向左移位5位,然后低5位被填充1。
上图显示了优先级数值5(二进制的101)是如何存储在实现了4个优先级位的微控制的优先级寄存器中的。上图说明了为什么数值5(二进制的00000101)可以被认为是95(二进制的01011111),因为这4个优先级位(0101)被向左移位4位,然后低4位被填充1。
使用RTOS时的相关性
如上文所述,必须把使用RTOS API的中断服务程序的逻辑中断优先级设置为小于或等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY定义的优先级(低的逻辑优先级意味着大的数值)。
CMSIS和不同的微控制器厂商提供了用于设置中断优先级的库函数。一些库函数期望在8位字节的最低有效位中指定中断优先级,而另一些库函数期望使用已经移位到最高有效位的8bit字节指定优先级。请查看函数文档,以确定你所调用的库函数属于哪种情形,否则将导致意外的行为。
在头文件FreeRTOSConfig.h中的宏configMAX_SYSCALL_INTERRUPT_PRIORITY 和configKERNEL_INTERRUPT_PRIORITY的值应当被设置为已经移位到最高有效位上的形式。这就是为什么configKERNEL_INTERRUPT_PRIORITY在官方发布的头文件FreeRTOSConfig.h中被设置为255(二进制的11111111),因为它需要最高优先级。这两个值采用移位到最高有效位的形式指定有以下几个原因:RTOS内核直接访问ARM Cortex-M3硬件(不通过任何第三方库函数),RTOS内核的实现比大多数库函数的实现早,这是第一个ARM Cortex-M3库上市时使用的方案。
临界区
Cortex-M硬件详情
RTOS内核使用ARM Cortex-M内核的BASEPRI寄存器实现临界区。这允许RTOS内核只屏蔽所有中断的一个子集,因此提供了一种灵活的中断嵌套模型。
BASEPRI是一个位掩码。把BASEPRI设置为某个值,将会屏蔽所有逻辑优先级小于或等于该值的中断。因此,不可能使用BASEPRI屏蔽中断优先级为0的中断。
可以在中断中安全调用的FreeRTOS API函数使用BASEPRI实现中断安全临界区。当进入临界区时,BASEPRI被设置成configMAX_SYSCALL_INTERRUPT_PRIORITY的值,当退出临界区时被设置成0。很多bug报告要求FreeRTOS在退出临界区时把BASEPRI恢复成其原来的值,而不是仅仅设置成0。但是Cortex-M的NVIC从不接受比当前正在执行的中断优先级低的中断,无论BASEPRI被设置成什么值。相对在进入临界区时先存储BASEPRI的原始值,然后退出时再恢复原始值的实现方式,采用退出临界区时将BASEPRI设置为0的实现方式代码将会执行的更快(当编译器优化打开时)。
使用RTOS时的相关性
RTOS内核通过把configMAX_SYSCALL_INTERRUPT_PRIORITY的值写入ARM Cortex-M的BASEPRI寄存器创建临界区。优先级为0的重点(最高优先级的中断)不能使用BASEPRI屏蔽,
因此configMAX_SYSCALL_INTERRUPT_PRIORITY一定不能设置为0。