嵌入式FreeRTOS操作系统中断优先级配置(重要)

时间:2021-08-12 19:52:14

本章节为大家讲解FreeRTOS中断优先级配置,此章节非常重要,初学者经常在这里犯迷糊。对于初学者来说,本章节务必要整明白。

12.1 NVIC基础知识

12.2 使用FreeRTOS时如何配置外设NVIC

12.3 FreeRTOS配置选项中NVIC相关配置

12.4 不受FreeRTOS管理中的的深入讨论

12.5总结

 

12.1 NVIC基础知识

NVIC的全称是Nested vectored interrupt controller,即嵌套向量中断控制器。

对于M3M4内核的MCU,每个中断的优先级都是用寄存器中的8位来设置的。8位的话就可以设置2^8 = 256级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如STSTM32F1xxF4xx只使用了这个8位中的高四位[7:4],低四位取零,这样2^4=16,只能表示16级中断嵌套。

对于这个NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级,下面就以STM32为例进行介绍,STM32F1xxF4xx都是只使用了这个8位寄存器的高四位[7:4]

嵌入式FreeRTOS操作系统中断优先级配置(重要) 

12.1 优先级分组

从上面的表格可以看出,STM32支持5种优先级分组,系统上电复位后,默认使用的是优先级分组0,也就是没有抢占式优先级,只有子优先级,关于这个抢占优先级和这个子优先级有几点一定要说清楚。

l  具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。

l  在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。

l  在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。

l  ResetNMIHard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。

l  对于初学者还有一个比较纠结的问题就是系统中断(比如:PendSVSVCSysTick)是不是一定比外部中断(比如SPI,USART)要高,答案:不是的,它们是在同一个NVIC下面设置的。

 

掌握了这些基础知识基本就够用了。另外特别注意一点,配置抢占优先级和子优先级,他们合并成的4bit数字的数值越小,优先级越高,这一点千万不要搞错了,下面通过12.2小节举一个实例。

12.2 使用FreeRTOS时如何配置外设NVIC

强烈推荐用户将Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407以及STM32F429NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。这个也是官方强烈建议的。此函数在bsp_Init中第一个被调用:

 

void bsp_Init(void)

  

    

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

    

     bsp_InitUart();   

     bsp_InitLed();    

     bsp_InitKey();    

    

}

(注意:一旦初始化好NVIC的优先级分组后,切不可以在应用中再次更改。)

设置NVIC的优先级分组为4表示支持0-15级抢占优先级(注意,0-15级是16个级别,包含0级),不支持子优先级。反映在STM32标准库的配置上就是如下:

 

static void TIM_Config(void)

{

     NVIC_InitTypeDef  NVIC_InitStructure;

 

     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;

 

     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

 

     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;      

     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

     NVIC_Init(&NVIC_InitStructure);

}

在这里继续强调下这一点,在NVIC分组为4的情况下,抢占优先级可配置范围是0-15,那么数值越小,抢占优先级的级别越高,即0代表最高优先级,15代表最低优先级。

12.3FreeRTOS配置选项中NVIC相关配置

FreeRTOSConfig.h配置文件中设置到NVIC中断的有如下几个选项:

 

#ifdef __NVIC_PRIO_BITS

    

     #define configPRIO_BITS              __NVIC_PRIO_BITS

#else

     #define configPRIO_BITS              4       

#endif

 

 

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f

 

 

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    0x01

 

 

#define configKERNEL_INTERRUPT_PRIORITY        ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

 

#define configMAX_SYSCALL_INTERRUPT_PRIORITY   ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

u  #define configPRIO_BITS              4

此宏定义用于配置STM328位优先级设置寄存器实际使用的位数。STM32F103STM32F407STM32F429都是使用的4位。另外注意一点,这里使用了一个条件编译,用户可以选择将条件编译删掉,直接定义一个#define configPRIO_BITS  4即可。使用条件编译的好处就是方便与系统统一。这个__NVIC_PRIO_BITSSTM32F103标准库的头文件stm32f10x.h中以及STM32F407/439的标准库的头文件stm32f4xx.h中分别有定义。如果用户在FreeRTOSConfig.h文件里面包含了这个标准库的头文件,那么就会执行条件编译选项:

#define configPRIO_BITS                    __NVIC_PRIO_BITS

 

l  __NVIC_PRIO_BITS

关于这个__NVIC_PRIO_BITS有必要跟大家深入的说明下,我们要刨根问底。在CMSIS软件包的M3内核头文件core_cm3.hM4内核的头文件core_cm4.h文件里面已经进行了定义:

  #ifndef __NVIC_PRIO_BITS

    #define __NVIC_PRIO_BITS          4U

  #endif

也就是说,如果用户没有定义的话,这里的定义就会起作用。而STM32F103标准库的头文件stm32f10x.hSTM32F407/439的标准库的头文件stm32f4xx.h有定义了,而且相应的头文件core_cm3.hcore_cm4.h是放在了宏定义#define __NVIC_PRIO_BITS  4的后面,如此一来,头文件core_cm3.hcore_cm4.h的定义就会被屏蔽掉。STM32F103标准库的头文件stm32f10x.hSTM32F407/439的标准库的头文件stm32f4xx.h里面的定义是有效的。

 

u  #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY                     0x0f

此宏定义是用来配置FreeRTOS用到的SysTick中断和PendSV中断的优先级。在NVIC分组设置为4的情况下,此宏定义的范围就是0-15,即专门配置抢占优先级。这里配置为了0x0f,即SysTickPendSV都是配置为了最低优先级,实际项目中也建议大家配置最低优先级即可。

 

l  SVC中断

FreeRTOS的移植文件ports.c中有用到SVC中断的0号系统服务,即SVC 0。此中断在FreeRTOS中仅执行一次,用于启动第一个要执行的任务。另外,由于FreeRTOS没有配置SVC的中断优先级,默认没有配置的情况下,SVC中断的优先级就是最高的0。如果用户在不清楚自己配置的PendSVSysTick中断是否跟实际情况一致时,可以进行硬件调试。比如MDK,我们可以在硬件调试的状态下,先点击全速运行,然后查看如下调试组件:

嵌入式FreeRTOS操作系统中断优先级配置(重要)

 

打开后可以看到如下状态,其中SysTickPendSV中断的优先级240就是0x0f左移4位的结果。这里为什么要左移四位呢,前面我们已经多次强调了,STM32的优先级设置仅使用高4位。而SVC的优先级就是0,可以理解为0左移4位还是0

嵌入式FreeRTOS操作系统中断优先级配置(重要)

 

u  #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY   0x01

此宏定义比较重要,定义了受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOSAPI的最高优先级。设置NVIC的优先级分组为4的情况下。配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。不受FreeRTOS管理的中断有什么深层的含义吗?且看12.4小节继续为大家讲解。

u  #define configKERNEL_INTERRUPT_PRIORITY

宏定义configLIBRARY_LOWEST_INTERRUPT_PRIORITY的数值经过4bit偏移后得到一个8bit的优先级数值,即宏定义configKERNEL_INTERRUPT_PRIORITY的数值。这个8bit的数值才可以实际赋值给相应中断的优先级寄存器。

也许初学者有疑问了,为什么前面NVIC配置的时候不是8bit的方式进行配置?这是因为ST的库函数NVIC_Init()已经为我们做好了。这里的宏定义数值是供PendSVSysTick中断进行优先级配置的。比如:我们这里配置宏定义configLIBRARY_LOWEST_INTERRUPT_PRIORITY0x0f,经过4bit偏移后就是0xf0,即SysTickPendSV的中断优先级就是240

u  #define configMAX_SYSCALL_INTERRUPT_PRIORITY

宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的数值经过4bit偏移后得到一个8bit的优先级数值,即宏定义configMAX_SYSCALL_INTERRUPT_PRIORITY的数值。这个数值是赋值给寄存器basepri使用的,8bit的数值才可以实际赋值给相应中断的优先级寄存器。

这里的宏定义数值赋给寄存器basepri后就可以实现全局的开关中断操作了。比如:我们这里配置宏定义configLIBRARY_LOWEST_INTERRUPT_PRIORITY0x01,经过4bit偏移后就是0x10,即16。调用了FreeRTOS的关中断后,所有优先级数值大于等于16的中断都会被关闭。优先级数值小于16的中断不会被关闭,对寄存器basepri寄存器赋值0,那么被关闭的中断会被打开。

12.4不受FreeRTOS管理中断的深入讨论

讲解不受FreeRTOS管理的中断之前要说一个小知识点----中断延迟。中断延迟时间是衡量RTOS实时操作系统的一项重要指标,那什么又是中断延迟呢?从中断触发到执行中断服务程序的第一条指令这段时间就是中断延迟时间。

FreeRTOS内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。比如在源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。如果这是一个紧急的中断事件,得不到及时执行的话,后果是可想而知的。

针对这种情况,FreeRTOS就专门做了一种新的开关中断实现机制。关闭中断时仅关闭受FreeRTOS管理的中断,不受FreeRTOS管理的中断不关闭,这些不受管理的中断都是高优先级的中断,用户可以在这些中断里面加入需要实时响应的程序。FreeRTOS能够实现这种功能的奥秘就在于FreeRTOS开关中断使用的是寄存器basepri,而像uCOS这种使用的是primask,详情请看下面整理的表格:

嵌入式FreeRTOS操作系统中断优先级配置(重要)

对寄存器basepri我们举一个例子,帮助大家理解,比我们配置寄存器basepri的数值为16,所有优先级数值大于等于16的中断都会被关闭,优先级数值小于16的中断不会被关闭。对寄存器basepri寄存器赋值0,那么被关闭的中断会被打开。这个就是FreeRTOS开关中断的实现方案。