参考资料:STM32F1开发指南(精英版)-库函数版本 4.5小节 中断优先级分组管理
STM32中文参考手册_V10.pdf"
一.NVIC中断优先级分组
CM3 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256
级的可编程中断设置。
但 STM32 并没有使用 CM3 内核的全部东西,而是只用了它的一部分。
STM32 有 84 个中断,包括 16 个内核中断和 68 个可屏蔽中断,具有 16 级可编程的中断优先级。
而我们常用的就是这 68 个可屏蔽中断,但是 STM32 的 68 个可屏蔽中断,在 STM32F103 系列上面,又只有 60 个(在 107 系列才有 68 个)。因为我们开发板选择的芯片是 STM32F103 系列的所以我们就只针对 STM32F103 系列这 60 个可屏蔽中断进行介绍。
1.中断管理方法:
这里简单介绍一下 STM32 的中断分组:STM32 将中断分为 5 个组,组 0~4。该分组的设
置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表 4.5.1 所示
通过这个表,我们就可以清楚的看到组 0~4 对应的配置关系,例如组设置为 3,那么此时
所有的 60 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是
响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的
级别高于响应优先级。 而数值越小所代表的优先级就越高。
2.抢占优先级和响应优先级的区别
1)高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
2)抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
3)抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
4)如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
假定设置中断优先级组为 2,然后设置
中断 3(RTC 中断)的抢占优先级为 2,响应优先级为 1。
中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。
中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。
那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。
上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互
打断!
特别说明:
一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。
3.中断优先级分组函数
接下来我们介绍如何使用库函数实现以上中断分组设置以及中断优先级管理,使得我们以后的中断设置简单化。NVIC 中断管理函数主要在 misc.c 文件里面。
首先要讲解的是中断优先级分组函数 NVIC_PriorityGroupConfig,其函数申明如下:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分
组确定就最好不要更改。这个函数我们可以找到其实现:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
例如:分组范围为 0-4。比如我们设置整个系统的中断优先级分组值为 2,那么方法是:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这样就确定了一共为“2 位抢占优先级,2 位响应优先级”。
二.NVIC中断优先级设置
1.中断设置相关寄存器
中断设置相关寄存器
_lO uint8_t IP[240]: //中断优先级控制的寄存器组
_lO uint32_t ISER[8]; //中断使能寄存器组
_lO uint32_t ICER[8]; //中断失能寄存器组
_lO uint32_t ISPR[8]; //中断挂起寄存器组
_lO uint32_t ICPR[8]; //中断解挂寄存器组
_lO uint32_t IABR[8]; //中断激活标志位寄存器组
2.MDK中NVIC寄存器结构体
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
3.中断相关的寄存器
1) 中断优先级控制的寄存器组: IP[240]全称是: Interrupt Priority Registers
240个8位寄存器,每个中断使用一个寄存器来确定优先级STM32F10x系列一共60个可屏蔽中断,使用IP[59]~IP[0]。
每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。
2) 中断使能寄存器组: ISER[8] 作用:用来使能中断
32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]
ISER[0]的bit0~bit31分别对应中断0-31。ISER[1]的bit0~27对应中断32~59;
3) 中断失能寄存器组: ICER[8] 作用:用来失能中断
32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICERI1]
ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断32~59;
配置方法跟ISER一样
4) 中断挂起控制寄存器组: ISPR[8] 作用:用来挂起中断
5) 中断解挂控制寄存器组: ICPRI8] 作用:用来解挂中断
6) 中断激活标志位寄存器组: IABR[8]
作用:只读,通过它可以知道当前在执行的中断是哪一个
如果对应位为1,说明该中断正在执行。
4. 中断参数初始化函数
设置好了系统中断分组,那么对于每个中断我们又怎么确定他的抢占优先级和响应优先级
呢?下面我们讲解一个重要的函数为中断初始化函数 NVIC_Init,其函数申明为:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
其中 NVIC_InitTypeDef 是一个结构体,我们可以看看结构体的成员变量:
typedef struct
{
uint8_t NVIC_IRQChannel; 设置中断通道
uint8_t NVIC_IRQChannelPreemptionPriority; 设置抢占优先级
uint8_t NVIC_IRQChannelSubPriority; 设置响应优先级
FunctionalState NVIC_IRQChannelCmd; 使能
} NVIC_InitTypeDef;
例如
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure. NVIC_IRQChannel = USART1_IRQn; //串口1中断
NVIC_InitStructure. NVIC_IRQChannelPreemptionPriority = 1 ;//抢占优先级为1
NVIC_InitStructure. NVIC_IRQChannelSubPriority = 2 ; //设置响应优先级为2
NVIC_InitStructure. NVIC_IRQChannelCmd = ENABLE ; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure) //初始化NVIC寄存器
三.NVIC总结
1. 系统运行后先设置中断优先级分组。调用函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
注意:整个系统执行过程中,只设置一次中断分组
2. 针对每个中断,设置对应的抢占优先级和响应优先级
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
3. 如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。