中断与异常
中断与异常这个概念其实可以说是一回事。最早出现中断概念的时候就管它就叫中断。外设某些时候需要中断(Interrupt)CPU的运行,来处理自己的请求。此时中断用于外设响应。但是系统内部也会出现错误,必须要中止CPU的运行。为了区分这两者所引发的中断,便有了内中断与外中断概念。内中断指的是系统内部的错误,外中断是外设的请求。为了区分两者,内中断就被称为异常(Exception)了。从此有了中断和异常的概念,外设请求叫中断,内部错误叫异常。外中断这个概念就有了新的含义,就是芯片外部发来的中断请求。在STM32芯片中,由GPIO管脚产生的中断,称为EXTI(External Interrupts 外部中断)。
NVIC
NVIC英文全称是Nested Vectored Interrupt Controller,中文意思就是嵌套向量中断控制器,它属于M3内核的一个外设,控制着芯片相关的功能。Cortex-M3内核可以支持256个中断,其中有16个内核中断和240个外部中断。STM32在此基础上进行了一定的裁剪。例如STM32F10系列芯片提供了84个中断,103包含16个内核中断和60个外部中断(可屏蔽中断),107包含16个内核中断和68个外部中断。103属于基础型芯片,不含有以太网接口,中断数量就少一些,而107芯片含有以太网接口,中断数量就多些。下图是STM32F1系列芯片中断向量表(部分),其中灰色部分表示Cortex-M3异常,白色部分表示中断,从上至下优先级依次降低。第一列数字与"stm32f10x.h"中定义的IRQn一致,表示对应的中断编号。每个中断都有惟一编号。这些中断统一由NVIC来管理,可以设置中断使能,优先级等相关参数。

既然NVIC是用来控制中断向量的,那就避免不了需要很多相关的寄存器。下面一段代码出自“core_cm3.h”,定义了NVIC寄存器组。地址范围是0xE000 0000~0xE010 0000。这个结构体很特别,里面定义的寄存器地址并不连续,有很多的保留区(RESERVED)。保留区就是没有使用到的地址空间,定义它们的原因是需要保证ISER、ICER这样的成员地址可以和寄存器真实地址相对应。
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;
/*@}*/ /* end of group CMSIS_CM3_NVIC */
- ISER[8]
ISER全称是Interrupt Set-Enable Registers,这是一个中断使能寄存器组。前面提到CM3内核支持256个中断,这里用8个32位来控制,每一位控制一个中断。但是STM32F103可屏蔽中断只有60个,所以对我们来说,能用的就两个(ISER[0]和ISER[1]),一共可以表示60个中断。而STM32F103只用了前60位。ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~bit27对应中断32~59;这样总共60个中断就分别对上了。要使某个中断,必须设置相应的ISER位为1,才能使中断使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO映射才是一个完整的中断设置)。 - ICER[8]
ICER全称是Interrupt Clear-Enable Registers,这是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除中断使能的。其对应的功能为也和ICER一样。这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为这些寄存器都是写1有效,写0是无效的。
-ISPR[8]
ISPR全称是Interrupt Set-Pending Registers,这是一个中断挂起寄存器组。每个位对应的中断和ISER是一样的。通过置1可以将对应的中断挂起,而执行更高级别的中断。写0是无效的。 - ICPR[8]
ICPR全称是Interrupt Clear-Pending Registers,这是一个中断解挂寄存器组。该寄存器组与ICPR的作用恰好相反,通过置1来将挂起的中断解挂。写0是无效的。 - IABR[8]
IABR全称是Interrupt Active Bit Registers,这是一个中断激活标志寄存器组。对应位代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器。通过它可以知道当前执行的中断时哪一个。在中断执行完之后由硬件自动清零。 - IP[240]
IP全称是Interrupt Priority Registers,这是一个中断激活标志寄存器组。这个寄存器组相当重要,STM32的中断分组要这个寄存器组密切相关。IP寄存器由240个8bit寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。STM32只用到前60个。IP[0]~IP[59]分别对应中断0~59。而每个可屏蔽中断占用的8bit并没有全部使用,而是只使用了高4位。这4位又被分为抢占优先级和响应优先级。抢占优先级在前,响应优先级在后。这两个优先级占几个位又要根据SCB->AIRCR中的中断分组来决定。
优先级设置
IP寄存器里面的高四位用于设置中断优先级,分为抢占优先级和响应优先级,数字越小,优先级越高。这样以来就有了下面5种组合。优先级组别和抢占优先级的位数一一对应。这4位如何设置,需要由SCB->AIRCRbit10~8决定的。
| 优先级组别 | AIRCR[10:8] | IP[7:4分配] | 抢占优先级 | 响应优先级 |
|---|---|---|---|---|
| 0 | 111 | 0:4 | 0位 | 4位 |
| 1 | 110 | 1:3 | 1位 | 3位 |
| 2 | 101 | 2:2 | 2位 | 2位 |
| 3 | 100 | 3:1 | 3位 | 1位 |
| 4 | 011 | 4:0 | 4位 | 0位 |
高抢占优先级的中断可以中断正在执行的低抢占优先级的中断服务程序,这体现了NVIC“嵌套”的含义。
若两个中断同时发生,高抢占优先级的中断优先处理,抢占优先级相同的,响应优先级高的优先处理。
如果抢占优先级和响应优先级都相同的,按照STM32数据手册提供的优先级顺序判别哪一个先处理。
正在执行的响应优先级高的中断,不能中断正在被处理的同一抢占优先级的低响应优先级中断服务程序。
NVIC相关库函数
STM32或者说CM3内核的中断管理,主要依靠NVIC进行设置,而在项目开发中,如果要使用一个外设的外部中断,需要使能其中断,还设置中断优先级,最后编写中断服务函数(ISR)。ISR函数名是固定的。
- 优先级管理函数
管理函数主要在misc.c里面( miscellaneous 杂项)。这个函数的作用是对中断优先级进行分组,这个函数在系统中只会被调用一次,一旦分组确定就不好更改。
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
重点在SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;这一句,就是在给SCB->AIRCRbit[10:8]赋值,确定优先分组情况。由于我们只需要修改bit[10:8],为了不影响到其他位,需要与AIRCR_VECTKEY_MASK按位或,这个量通常被称为“掩码”。掩码的值为0x05FA0000。
- 中断初始函数
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
其中NVIC_initTypeDef 是一个结构体,成员及其注释如下:
typedef struct
{
uint8_t NVIC_IRQChannel; //定义初始化哪个中断,我们key在stm32f10x.h中找到,每个中断的名字。例如USART1_IRQn
uint8_t NVIC_IRQChannelPreemptionPriority; //定义抢占优先级级别
uint8_t NVIC_IRQChannelSubPriority; //定义响应优先级级别
FunctionalState NVIC_IRQChannelCmd; //是否使能中断
} NVIC_InitTypeDef
例如要使能串口1中断,同时使用中断分组2、设置抢占优先级为1、响应优先级为2,初始化方法为:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组2
NVIC_InitStucture.NVIC_IRQChannel = USART1_IRQn; //串口1中断
NVIC_InitStucture.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级为1 由于使用分组2 只有两位表示抢占优先级,这里的取值范围只能是0~3
NVIC_InitStucture.NVIC_IRQChannelSubPriority = 2; //响应优先级为2
NVIC_InitStucture.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure)