NVIC 的全称是 Nested vectored interrupt controller,即嵌套向量中断控制器。
对于 M3 和 M4 内核的 MCU,每个中断的优先级都是用寄存器中的 8 位来设置的。 8 位的话就可以
设置 2^8 = 256 级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如 ST
的 STM32F1xx 和 F4xx 只使用了这个 8 位中的高四位[7:4],低四位取零,这样 2^4=16,只能表示 16
级中断嵌套。
对于这个 NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级,下面就以 STM32 为
例进行介绍,STM32F1xx 和 F4xx 都是只使用了这个 8 位寄存器的高四位[7:4]。
从上面的表格可以看出,STM32 支持 5 种优先级分组,系统上电复位后,默认使用的是优先级分组
0,也就是没有抢占式优先级,只有子优先级,关于这个抢占优先级和这个子优先级有几点一定要说清楚。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中
断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。
在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优
先被响应。
在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被
响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。
Reset、 NMI、 Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。
对于初学者还有一个比较纠结的问题就是系统中断(比如:PendSV,SVC,SysTick)是不是一
定比外部中断(比如 SPI,USART)要高,答案:不是的,它们是在同一个 NVIC 下面设置的。
强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407 以及
STM32F429 的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这
样中断优先级的管理将非常方便。 这个也是官方强烈建议的。
(注意:一旦初始化好 NVIC 的优先级分组后,切不可以在应用中再次更改。)
设置 NVIC 的优先级分组为 4 表示支持 0-15 级抢占优先级 (注意, 0-15 级是 16 个级别,包含 0 级), 不支持子优先级。
在这里继续强调下这一点,在 NVIC 分组为 4 的情况下,抢占优先级可配置范围是 0-15,那么数值越小,抢占优先级的级别越高,即 0 代表最高优先级,15 代表最低优先级。
FreeRTOS 配置选项中 NVIC 相关配置
FreeRTOSConfig.h 配置文件中设置到 NVIC 中断的有如下几个选项:
/* * Cortex-M内核使用8bit来配置优先级,但是STM32只使用了高4bit,数值越小,优先级越高。 * 在往寄存器里面写数值配置的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为: * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级 */ #ifdef __NVIC_PRIO_BITS /* __NVIC_PRIO_BITS 已经在stm32f4xx.h里面定义为4 */ #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 #endif /*============================================== SysTick中断优先级配置 ============================================*/ /* * 在往寄存器里面写数值配置的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为: * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级。经过这个公式之后得到的是 * 下面的这个宏:configKERNEL_INTERRUPT_PRIORITY * SysTick的优先级我们一般配置为最低,即0xf 。这样可以提高系统的实时响应能力,即其他的外部中断可以及时的得到响应。 */ #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /*===========================================可屏蔽的中断优先级配置====================================================*/ /* * 用于配置STM32的特殊寄存器basepri寄存器的值,用于屏蔽中断,当大于basepri值的优先级的中断将被全部屏蔽。basepri只有4bit有效, * 默认只为0,即全部中断都没有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置为:5,意思就是中断优先级大于5的中断都被屏蔽。 * 当把配置好的优先级写到寄存器的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为: * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级。经过这个公式之后得到的是下面的这个宏: * configMAX_SYSCALL_INTERRUPT_PRIORITY * * 在FreeRTOS中,关中断是通过配置basepri寄存器来实现的,关掉的中断由配置的basepri的值决定,小于basepri值的 * 中断FreeRTOS是关不掉的,这样做的好处是可以系统设计者可以人为的控制那些非常重要的中断不能被关闭,在紧要的关头必须被响应。 * 而在UCOS中,关中断是通过控制PRIMASK来实现的,PRIMASK是一个单1的二进制位,写1则除能除了NMI和硬 fault的所有中断。当UCOS关闭 * 中断之后,即使是你在系统中设计的非常紧急的中断来了都不能马上响应,这加大了中断延迟的时间,如果是性命攸关的场合,那后果估计挺严重。 * 相比UCOS的关中断的设计,FreeRTOS的设计则显得人性化很多。 * */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configPRIO_BITS 4
此宏定义用于配置 STM32 的 8 位优先级设置寄存器实际使用的位数。 STM32F103,STM32F407
和 STM32F429 都是使用的 4 位。 另外注意一点,这里使用了一个条件编译,用户可以选择将条件编
译删掉,直接定义一个#define configPRIO_BITS 4 即可。使用条件编译的好处就是方便与系统统
一。这个__NVIC_PRIO_BITS 在 STM32F103 标准库的头文件 stm32f10x.h 中以及 STM32F407/439
的标准库的头文件 stm32f4xx.h 中分别有定义。如果用户在 FreeRTOSConfig.h 文件里面包含了这个
标准库的头文件,那么就会执行条件编译选项:
#define configPRIO_BITS __NVIC_PRIO_BITS
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f
此宏定义是用来配置 FreeRTOS 用到的 SysTick 中断和 PendSV 中断的优先级。在 NVIC 分组设置为
4 的情况下,此宏定义的范围就是 0-15,即专门配置抢占优先级。这里配置为了 0x0f,即 SysTick
和 PendSV 都是配置为了最低优先级,实际项目中也建议大家配置最低优先级即可。
SVC 中断
在 FreeRTOS 的移植文件 ports.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS
中仅执行一次, 用于启动第一个要执行的任务。 另外, 由于 FreeRTOS 没有配置 SVC 的中断优先级,
默认没有配置的情况下, SVC 中断的优先级就是最高的 0。 如果用户在不清楚自己配置的 PendSV 和
SysTick 中断是否跟实际情况一致时,可以进行硬件调试。 比如 MDK,我们可以在硬件调试的状态下,
先点击全速运行,然后查看如下调试组件:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01
此宏定义比较重要,定义了受 FreeRTOS 管理的最高优先级中断。简单的说就是允许用户在这个中断
服务程序里面调用 FreeRTOS 的 API 的最高优先级。 设置 NVIC 的优先级分组为 4 的情况下。 配置
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 为 0x01 表示用户可以在抢占式优先级为 1
到 15 的中断里面调用 FreeRTOS 的 API 函数,抢占式优先级为 0 的中断里面是不允许调用的。 不受
FreeRTOS 管理的中断有什么深层的含义吗?
不受 FreeRTOS 管理中断的深入讨论
讲解不受 FreeRTOS 管理的中断之前要说一个小知识点----中断延迟。 中断延迟时间是衡量 RTOS 实
时操作系统的一项重要指标,那什么又是中断延迟呢?从中断触发到执行中断服务程序的第一条指令这段
时间就是中断延迟时间。
FreeRTOS 内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。 比如在
源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全
局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。
如果这是一个紧急的中断事件,得不到及时执行的话,后果是可想而知的。
针对这种情况,FreeRTOS 就专门做了一种新的开关中断实现机制。 关闭中断时仅关闭受 FreeRTOS
管理的中断,不受 FreeRTOS 管理的中断不关闭,这些不受管理的中断都是高优先级的中断,用户可以在
这些中断里面加入需要实时响应的程序。 FreeRTOS 能够实现这种功能的奥秘就在于 FreeRTOS 开关中断
使用的是寄存器 basepri,而像 uCOS 这种使用的是 primask,详情请看下面整理的表格:
#define configKERNEL_INTERRUPT_PRIORITY
宏定义 configLIBRARY_LOWEST_INTERRUPT_PRIORITY的数值经过 4bit偏移后得到一个 8bit
的优先级数值,即宏定义 configKERNEL_INTERRUPT_PRIORITY 的数值。这个 8bit 的数值才可以
实际赋值给相应中断的优先级寄存器。
也许初学者有疑问了,为什么前面 NVIC 配置的时候不是 8bit 的方式进行配置?这是因为 ST 的
库函数 NVIC_Init()已经为我们做好了。 这里的宏定义数值是供 PendSV 和 SysTick 中断进行优先级
配置的。 比如:我们这里配置宏定义 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 是 0x0f,经
过 4bit 偏移后就是 0xf0,即 SysTick 和 PendSV 的中断优先级就是 240。
#define configMAX_SYSCALL_INTERRUPT_PRIORITY
宏定义 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 的数值经过 4bit 偏移后得到一
个 8bit 的优先级数值,即宏定义 configMAX_SYSCALL_INTERRUPT_PRIORITY 的数值。 这个数值
是赋值给寄存器 basepri 使用的,8bit 的数值才可以实际赋值给相应中断的优先级寄存器。
这里的宏定义数值赋给寄存器 basepri 后就可以实现全局的开关中断操作了。 比如:我们这里配
置宏定义 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 是 0x01,经过 4bit 偏移后就是 0x10,
即 16。 调用了 FreeRTOS 的关中断后,所有优先级数值大于等于 16 的中断都会被关闭。优先级数值
小于 16 的中断不会被关闭,对寄存器 basepri 寄存器赋值 0,那么被关闭的中断会被打开。
上面的优先级数值,240和16,是十进制数,是真正8bit的数值,但是ST给我们了库函数,我们只配置4-7位,就可以了,其实就在配置这4-7位为0xf,表示优先级为15,实际十进制为240,配置这4-7位为 0x1,表示优先级为1,实际十进制为16.不要混淆了。
NOTE:
这里configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级是个8bit的,之前的configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY是4bit,注意有没有LIBRARY。