(1)实验平台:普中STM32F103朱雀、玄武开发板
STM32 中断非常强大, 几乎每个外设都可以产生中断, 因此这里我们单独使用一章来介绍它, 为后面介绍外设中断做铺垫。 学习本章可以参考《STM32F10x中文参考手册》-9 中断和事件章节,《Cortex M3 权威指南(中文)》-chpt08 NVIC与中断控制章节。 本章分为如下几部分内容:
[17.1 中断介绍](#17.1 中断介绍)
[17.1.1 中断概念](#17.1.1 中断概念)
[17.1.2 NVIC 介绍](#17.1.2 NVIC 介绍)
[17.1.3 中断优先级](#17.1.3 中断优先级)
[17.2 中断配置](#17.2 中断配置)
17.1 中断介绍
17.1.1 中断概念
在学习 51 单片机时, 我们就接触过中断, 中断其实就是当 CPU 执行程序时,由于发生了某种随机的事件(外部或内部), 引起 CPU 暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序), 以处理该事件, 该事件处理完后又返回被中断的程序继续执行, 这一过程就称为中断, 引发中断的称为中断源。 比如: 看电视时突然门铃响, 那么门铃响就相当于中断源。有些中断还能够被其他高优先级的中断所中断, 那么这种情况又叫做中断的嵌套。 中断示意图如下图所示:

17.1.2 NVIC 介绍
NVIC 英文全称是 Nested Vectored Interrupt Controller, 中文意思就是嵌套向量中断控制器, 它属于 M3 内核的一个外设, 控制着芯片的中断相关功能。由于 ARM 给 NVIC 预留了非常多的功能, 但对于使用 M3 内核设计芯片的公司可能就不需要这么多功能, 于是就需要在 NVIC 上裁剪。 ST 公司的 STM32F103 芯片内部中断数量就是 NVIC 裁剪后的结果。
Crotex-M3 内核支持 256 个中断, 其中包含了 16 个内核中断和 240 个外部中断。 但 STM32 并没有使用 M3 内核的全部东西, 而是只用了它的一部分。STM32F10x 芯片有 84 个中断通道, 包括 16 个内核中断和 68 个可屏蔽中断,对于STM32F103系列芯片只有60个可屏蔽中断, 在 STM32F107 系列才有 68 个。除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的。 这些中断通道已按照不同优先级顺序固定分配给相应的外部设备。 从 STM32F10x 中文参考手册的中断向量表可以知道具体分配到那些外设, 这里只截取一部分, 如需了解更详细可参考《STM32F10x 中文参考手册》 -9 中断和事件章节内容, 中断向量表如下图所示:




上面说到 NVIC 控制着芯片的中断相关功能, 那么肯定有很多对应的寄存器,在固件库 core_cm3.h 文件内定义了一个 NVIC 结构体, 里面定义了相关寄存器,如下:
cpp
typedef struct
{
__IO uint32_t ISER[8]; //中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; //中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; //中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; //中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; //中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; //中断优先级寄存器
uint32_t RESERVED5[644];
__IO uint32_t STIR; //软件触发中断寄存器
} NVIC_Type;
在配置中断时, 我们通常使用的只有 ISER、 ICER 和 IP 这三个寄存器, ISER 是中断使能寄存器, ICER 是中断清除寄存器, IP 是中断优先级寄存器。
在固件库 core_cm3.h 文件后面, 还提供了一些对 NVIC 操作的库函数, 这些函数都是遵循 CMSIS 标准, 所以只要是基于 Cortex-M3 内核的芯片都可以用这些函数来操作 NVIC, 只不过我们很少这样做, 甚至不使用这些函数, 因为在后面
我们会有更简单的办法来配置中断。 至于这些函数内容, 大家如果有兴趣的话,可以打开我们库函数版本任意程序, 找到 core_cm3.h 文件查看即可。
17.1.3 中断优先级
前面说了 STM32F103 芯片支持 60 个可屏蔽中断通道, 每个中断通道都具备自己的中断优先级控制字节(8 位, 理论上每个外部中断优先级可以设置为0-255, 数值越小, 优先级越高。 但是 STM32F103 中只使用 4 位, 高 4 位有效),用于表达优先级的高 4 位又被分组成抢占式优先级和响应优先级, 通常也把响应优先级称为"亚优先级" 或"副优先级" , 每个中断源都需要被指定这两种优先级。
高抢占式优先级的中断事件会打断当前的主程序或者中断程序运行, 俗称中断嵌套。 在抢占式优先级相同的情况下, 高响应优先级的中断优先被响应。
当两个中断源的抢占式优先级相同时, 这两个中断将没有嵌套关系, 当一个中断到来后, 如果正在处理另一个中断, 这个后到来的中断就要等到前一个中断处理完之后才能被处理。 如果这两个中断同时到达, 则中断控制器根据他们的响应优先级高低来决定先处理哪一个; 如果他们的抢占式优先级和响应优先级都相等, 则根据他们在中断表中的排位顺序决定先处理那一个, 越靠前的先执行。
STM32F103 中指定中断优先级的寄存器位有 4 位, 这 4 位的分组方式如下图所示:

第 0 组: 所有 4 位用于指定响应优先级
第 1 组: 最高 1 位用于指定抢占式优先级, 最低 3 位用于指定响应优先级第 2 组: 最高 2 位用于指定抢占式优先级, 最低 2 位用于指定响应优先级第 3 组: 最高 3 位用于指定抢占式优先级, 最低 1 位用于指定响应优先级第 4 组: 所有 4 位用于指定抢占式优先级
设置优先级分组可调用库函数 HAL_NVIC_SetPriorityGrouping()实现, 有关NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中, 所以当使用到中断时, 一定要记得把 misc.c 和 misc.h 添加到工程组中。
NVIC_PriorityGroupConfig()函数代码如下:
cpp
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;
}
NVIC_PriorityGroupConfig 函数带一个形参用于中断优先级分组, 该值范围可以是 NVIC_PriorityGroup_0-NVIC_PriorityGroup_4, 对应优先级与占用的位数信息如下:

函数内最终将分组值给 SCB->AIRCR, 说明控制中断优先级寄存器是内核外设SCB 的 AIRCR 寄存器的 PRIGROUP[10:8]位。
17.2 中断配置
前面讲解了那么多中断知识, 如果大家不理解也没有关系, 我们会应用即可,等到后面 STM32 熟练了, 再回过头深入了解自然就会明白。 要使用中断我们就需要先配置它, 通常都需经过这几步:
(1) 使能外设中断, 这个具体是由外设相关中断使能位来控制, 比如定时器有溢出中断, 这个可由定时器的控制寄存器中相应中断使能位来控制。
(2) 设置中断优先级分组, 初始化 NVIC_InitTypeDef 结构体, 设置抢占优先级和响应优先级, 使能中断请求。
NVIC_InitTypeDef 结构体如下:
cpp
typedef struct
{
uint8_t NVIC_IRQChannel; //中断源
uint8_t NVIC_IRQChannelPreemptionPriority; //抢占优先级
uint8_t NVIC_IRQChannelSubPriority; //响应优先级
FunctionalState NVIC_IRQChannelCmd; //中断使能或失能
} NVIC_InitTypeDef;
下面我们对 NVIC_InitTypeDef 结构体成员进行一下简单介绍。
NVIC_IRQChannel: 中断源的设置, 不同的外设中断, 中断源不一样, 自然名字也不一样, 所以名字不能写错, 否则不会进入中断。 中断源放在 stm32f10x.h文件的 IRQn_Type 结构体内, 由于内容太多, 这里就不复制所有中断源, 只截取一部分, 如下:
cpp
typedef enum IRQn
{
//Cortex-M3 处理器异常编号
NonMaskableInt_IRQn = -14,
MemoryManagement_IRQn = -12,
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
//STM32 外部中断编号
WWDG_IRQn = 0,
PVD_IRQn = 1,
TAMP_STAMP_IRQn = 2,
// 限于篇幅, 中间部分代码省略, 具体的可查看库文件 stm32f10x.h
DMA2_Channel2_IRQn = 57,
DMA2_Channel3_IRQn = 58,
DMA2_Channel4_5_IRQn = 59
}IRQn_Type;
NVIC_IRQChannelPreemptionPriority: 抢占优先级, 具体的值要根据优先级分组来确定, 可以参考前面中断优先级分组内容。
NVIC_IRQChannelSubPriority: 响应优先级, 具体的值要根据优先级分组来确定, 可以参考前面中断优先级分组内容。
NVIC_IRQChannelCmd: 中断使能/失能设置, 使能配置为 ENABLE, 失能配置为 DISABLE。
(3) 编写中断服务函数
配置好中断后如果有触发, 即会进入中断服务函数, 那么中断服务函数也有固定的函数名, 可以在 startup_stm32f10x_hd.s 启动文件查看, 启动文件提供的只是一个中断服务函数名, 具体实现什么功能还需要我们自己编写, 可以将中断服务函数放在 stm32f10x_it.c 文件内, 也可以放在自己的应用程序中。 通常我们把中断函数放在应用程序中。 这里提醒一下大家, 不要任意修改中断服务函数名, 因为启动文件内中断服务函数名已经固定, 如果要修改, 你还必须在启动文件内把原中断函数修改。 启动文件内中断服务函数名如下:

课后作业
(1) 打开之前编写好的库函数程序, 查看 misc.c、 misc.h、 core_cm3.h 及startup_stm32f10x_hd.s 文件内容, 对照本章提到的函数及结构体等加深下印象。