STM32笔记之 NVIC(嵌套向量中断控制器)
写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
在 STM32笔记之 EXIT(外部中断)篇章中,已经了解到 EXIT的配置,但是,就像我们刚学 C51的中断一样,中断是有等级区分的,实验中的 STM32在同一时间上,只能执行一个线程(因为是单核,这里稍微带入了点操作系统的知识,不了解就跳过吧),反正就相当于两辆汽车同时在单通道隧道入口处的大圆盘路口的等待(有交警指挥),但是谁先进入这单通道隧道到达目的地呢?那么就要人为的(交警)去控制哪台车先行;然后把思维转换一下,变成现在讲解的 NVIC控制
一、NVIC简单解释
1、在 STM32(Cortex-M3)中有两个优先级控制(这与学 C51时只有一个中断等级不同):
- 抢占式优先级(NVIC_IRQChannelPreemptionPriority)
- 子优先级 也叫响应优先级(NVIC_IRQChannelSubPriority)
2、NVIC是在 M3内核处理器上的,在 M3的所有中断机制都由NVIC实现,看下图
3、Cortex-M3支持大量异常,包括16 -4 -1=11个系统异常,和最多 240个外部中断——简称IRQ。具体使用了这 240个中断源中的多少个,则由芯片制造商决定。由外设产生的中断信号,除了 SysTick的之外,全都连接到 NVIC的中断输入信号线。典型情况下,处理器一般支持 16到 32个中断,当然也有在此之外的。
4、CM3 除了支持 240条中断之外,NVIC还支持16 -4 -1 = 11个内部异常源,可以实现 fault管理机制。所以,CM3一共有 256个预定义的异常类型,根据上面的图可知
5、然后 Cortex-M3在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中,编号为 1-15的对应系统异常,大于等于16的则全是外部中断。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。也就是说我们用的外设中断都是基于 15之后的中断
二、抢占优先级与子优先级
CM3把 256级优先级按位分成高低两段,分别称为抢占优先级和子优先级
NVIC中有一个寄存器是 “ 应用程序中断及复位控制寄存器 ”,它里面有一个位段名为 “ 优先级组 ” 。
该位段的值对每一个优先级可配置的异常都有影响——把其优先级分为 2个位段:MSB所在的位段(左边的)对应抢占优先级,而 LSB所在的位段(右边的)对应子优先级
在 Cortex-M3中定义了 8个 bit用于设置中断源的优先级,这 8个 bit可以有 7处分组的分配方式,当然还有凌驾于法律之上的三位老大:复位,NMI和硬 fault。它们无论何时出现,都立即无条件抢占所有优先级可编程的“平民异常”。那么这 7处分组可以如下分配:
- 最高 7位用于指定抢占优先级,最低 1位用于指定子优先级
- 最高 6位用于指定抢占优先级,最低 2位用于指定子优先级
- 最高 5位用于指定抢占优先级,最低 3位用于指定子优先级
- 最高 4位用于指定抢占优先级,最低 4位用于指定子优先级
- 最高 3位用于指定抢占优先级,最低 5位用于指定子优先级
- 最高 2位用于指定抢占优先级,最低 6位用于指定子优先级
- 最高 1位用于指定抢占优先级,最低 7位用于指定子优先级
了解完 CM3所分布的等级后,我们再去了解 ST把它所封装的优先级
/**
@code
The table below gives the allowed values of the pre-emption priority and subpriority according
to the Priority Grouping configuration performed by NVIC_PriorityGroupConfig function
============================================================================================================================
NVIC_PriorityGroup | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority | Description
============================================================================================================================
NVIC_PriorityGroup_0 | 0 | 0-15 | 0 bits for pre-emption priority
| | | 4 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 1 bits for pre-emption priority
| | | 3 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 2 bits for pre-emption priority
| | | 2 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 3 bits for pre-emption priority
| | | 1 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_4 | 0-15 | 0 | 4 bits for pre-emption priority
| | | 0 bits for subpriority
============================================================================================================================
@endcode
*/
- 第0组:所有 4位用于指定响应优先级
- 第1组:最高 1位用于指定抢占式优先级,最低3位用于指定响应优先级
- 第2组:最高 2位用于指定抢占式优先级,最低2位用于指定响应优先级
- 第3组:最高 3位用于指定抢占式优先级,最低1位用于指定响应优先级
- 第4组:所有 4位用于指定抢占式优先级
上面的注释代码可以在 misc.h文件中查到
由于 Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此 ST把指定中断优先级的寄存器位减少到 4位,所以就造成了我们所看到的上面给出的 code注释一样的等级分布
然后配置的时候,他们所对应的宏如下:
/** @defgroup Preemption_Priority_Group
* @{
*/
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
三、抢占优先级和子优先级的区分(白话文)
在作对比之前我们假设配置了 EXIT0和 EXIT1的线路,毕竟对比至少需要两个,这里用两个就够了,再多也是一样的,弄懂下面的例程就会举一反三了;
继续用开篇的那个例子,只不过,我们需要补充一些情节,在来到隧道之前有 n条一样路程的独立公路可以通往隧道入口的大圆盘处(有交警指挥的),每条公路在到达隧道之前都受红绿灯影响,并且这些红绿灯比较奇葩,其中一条公路的绿灯亮起,其他公路的灯都会变红色,这些红绿灯受总部调整;好了,现在基本完善了,下面的诠释例子就各靠本事脑补了
eg 1:抢占优先级的通俗理解
假设我们使用了 NVIC_PriorityGroup_4这组优先级分配,则所有 4位用于指定抢占式优先级,共 16个(0 - 15)抢占式优先级;然后指定 EXTI0_IRQn抢占优先级为 3,EXTI1_IRQn的抢占优先级为 1,因为子优先级没有得设置就不用管了;那么根据上面的例子可以形象化理解为:
有 16条(个抢占式优先级)附带红绿灯的路程一样的公路,然后这时有两辆(EXTI0_IRQn和 EXTI1_IRQn)同时出发警车需要通往隧道,而在到达隧道之前他们需要经过各自的路程,也就是所说的独立公路;由于 EXTI1_IRQn有重大任务在身(抢占优先级越小,优先级越高),于是 EXTI1_IRQn把警灯开起来,总部得知情况就优先帮 EXTI1_IRQn所在的公路打开绿灯(),从而使 EXTI1_IRQn优先通过隧道到达目的地(优先被执行),而 EXTI0_IRQn在 EXTI1_IRQn通过隧道后继续走没走完的路(因为 EXTI1_IRQn通过了,此时 EXTI0_IRQn的道路就变回绿灯了)
eg 2:接着再继续在抢占优先级上理解区分子优先级
这次我们使用 NVIC_PriorityGroup_3这组优先级分配,则最高 3位用于指定抢占式优先级,最低1位用于指定响应优先级,这样就变成了共 8个(0 - 7)抢占式优先级,2个(0 - 1)子优先级;指定 EXTI0_IRQn抢占优先级为 3,EXTI1_IRQn的抢占优先级为 3,而子优先级 EXTI0_IRQn为 0,EXTI1_IRQn为 1,(因为引入了子优先级,再把这些车贴上标号,对应子优先级)EXTI0_IRQn为 0号车,EXTI1_IRQn为 1号车;那么根据上面的例子可以形象化理解为:
现在只有 8条(个抢占式优先级)附带红绿灯的路程一样的公路,然而这两辆(EXTI0_IRQn和 EXTI1_IRQn)同时出发的警车在通过了独立公路后,同时到达隧道入口处的大圆盘(因为抢占式优先级相同),这时,由于该隧道是单通道,只能一辆一辆的通过,那么谁先通过啊,这下可烦倒了这位交警;于是他为了方便,直接利用车号顺序来作为通过顺序,这样肯定是 EXTI0_IRQn(0号车)先通行(因为抢占式优先级已经不能比较了,所以只能根据子优先级判断,是 EXTI0_IRQn先通过的,再者才是 EXTI1_IRQn通过;而且算数也是从 0先算的嘛)
扩展一下:如果这两辆(EXTI0_IRQn和 EXTI1_IRQn)在通过交警指挥的地方(大圆盘)之前,再来一台 USART1_IRQn(他的抢占优先级为 2、子优先级为 0);那么因为 USART1_IRQn的抢占优先级比上面的两个优先级高一个等级,按照第一个例子演化,肯定会出现 EXTI0_IRQn和 EXTI1_IRQn被红灯限制所停止,而 USART1_IRQn率先通过,后面就按照上面第二个例子一样继续;这种情况就是所谓的中断嵌套
可以简短的看成,在公路这段路程是红绿灯(抢占式优先级)控制的,而进入隧道是由交警(子优先级)控制的
四、NVIC配置分析
继续根据之前的 STM32笔记之 GPIO引脚中设置的 Key输入配置代码分析 NVIC的配置
/* Enable the EXTI1 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 选择需配置的中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 设置抢占等级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 设置子优先级(响应等级)
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道
NVIC_Init(&NVIC_InitStructure);
↓↓ 函数实现 ↓↓
/**
* @brief Initializes the NVIC peripheral according to the specified
* parameters in the NVIC_InitStruct.
* @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains
* the configuration information for the specified NVIC peripheral.
* @retval None
*/
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
/* Check the parameters */
assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));
assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
{
/* Compute the Corresponding IRQ Priority --------------------------------*/
tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
tmppre = (0x4 - tmppriority);
tmpsub = tmpsub >> tmppriority;
tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
tmppriority = tmppriority << 0x04;
NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
/* Enable the Selected IRQ Channels --------------------------------------*/
NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
else
{
/* Disable the Selected IRQ Channels -------------------------------------*/
NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
}
这里值得注意的是要先确定优先级的分组等级,也就要先调用 NVIC_PriorityGroupConfig()函数配置好
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 选择中断优先级分组等级为 2,即:最高的抢占优先级和子优先级(响应优先级)皆为 3
↓↓ 函数实现 ↓↓
/**
* @brief Initializes the NVIC peripheral according to the specified
* parameters in the NVIC_InitStruct.
* @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains
* the configuration information for the specified NVIC peripheral.
* @retval None
*/
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
/* Check the parameters */
assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));
assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
{
/* Compute the Corresponding IRQ Priority --------------------------------*/
tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
tmppre = (0x4 - tmppriority);
tmpsub = tmpsub >> tmppriority;
tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
tmppriority = tmppriority << 0x04;
NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
/* Enable the Selected IRQ Channels --------------------------------------*/
NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
else
{
/* Disable the Selected IRQ Channels -------------------------------------*/
NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
}
要注意的几点是:
- 如果指定的抢占式优先级别或响应优先级别超出了选定的优先级分组所限定的范围,将可能得到意想不到的结果(坏的比较多);
- 抢占式优先级别相同的中断源之间没有嵌套关系;
- 如果某个中断源被指定为某个抢占式优先级别,又没有其它中断源处于同一个抢占式优先级别,则可以为这个中断源指定任意有效的响应优先级别。
- 抢占优先级越小,优先级越高;相同抢占优先级的中断不能嵌套;
- 相同抢占优先级 N个中断发生时,响应优先级越小的中断首先执行(不能嵌套),如果响应优先级也均相同,则根据各中断对应向量表的位置来确定,向量表中越靠前的中断先响应。