什么是中断?
打断CPU执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行,就叫中断
中断的作用和意义
中断的意义:高效处理紧急程序,不会一直占用CPU资源
STM32 GPIO外部中断简图
NVIC
什么是 NVIC?NVIC 即嵌套向量中断控制器,全称 Nested vectored interrupt controller。它
是内核的器件。
M3/M4/M7 内核都是支持 256 个中
断,其中包含了 16 个系统中断和 240 个外部中断,并且具有 256 级的可编程中断设置。然而芯
片厂商一般不会把内核的这些资源全部用完,如 STM32F407 的系统中断有 10 个,外部中断有
82 个。
关于 82 个外部中断部分在《STM32F4xx 参考手册_V4(中文版).pdf》的 10.2 小节有详细
的列表,这里就不列出来了。STM32F407 的中断向量表在 stm32f407xx.h 文件中被定义。
NVIC 寄存器
NVIC 相关的寄存器定义了可以在 core_cm4.h 文件中找到。我们直接通过程序的定义来分
析 NVIC 相关的寄存器,其定义如下:
typedef struct
{
__IOM uint32_t ISER[8U]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[24U];
__IOM uint32_t ICER[8U]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RSERVED1[24U];
__IOM uint32_t ISPR[8U]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[24U];
__IOM uint32_t ICPR[8U]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[24U];
__IOM uint32_t IABR[8U]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */
uint32_t RESERVED4[56U];
__IOM uint8_t IP[240U]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644U];
__OM uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */
} NVIC_Type;
STM32F407 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能
方便的使用 STM32F407 的中断。下面重点介绍这几个寄存器:
ISER[8]:ISER 全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组。上面
说了 CM4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是
STM32F407 的可屏蔽中断最多只有 82 个,所以对我们来说,有用的就是两个(ISER[0~3]),总
共可以表示 128 个中断。而 STM32F407 只用了其中的 82 个。ISER[0]的 bit0~31 分别对应中断
0~31;ISER[1]的 bit0~31 对应中断 32~63; ISER[2]的 bit0~16 对应中断 64~81,这样总共 82 个
中断就可以分别对应上了。你要使能某个中断,必须设置相应的 ISER 位为 1,使该中断被使能
(这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。
具体每一位对应哪个中断,请参考 stm32f407xx.h 里面的第 68 行。
ICER[8]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组
与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ISER 一样。
这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄
存器都是写 1 有效的,写 0 是无效的。
ISPR[8]:全称是:Interrupt Set Pending Registers,是一个中断使能挂起控制寄存器组。每
个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高
级别的中断。写 0 是无效的。
ICPR[8]:全称是:Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。其作
用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。
IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位
所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄
存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP [240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄
存器组相当重要!STM32F407 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit
的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32F407
只用到了其中的 82 个。IP[81]~IP[0]分别对应中断 81~0。而每个可屏蔽中断占用的 8bit 并没有
全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子
优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。关
于中断优先级控制的寄存器组我们下面详细讲解。
中断优先级
STM32 中的中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先级,
每个中断源都需要被指定这两种优先级。抢占式优先级和响应优先级的区别:
抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
还有一种情况就是当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循
自然优先级,看中断向量表的中断排序,数值越小,优先级越高。
在 NVIC 中由寄存器 NVIC_IPR0-NVIC_IPR59 共 60 个寄存器控制中断优先级,每个寄存器的每 8 位又分为一组,可以分 4 组,所以就有了 240 组宽度为 8bit 的中断优先级控制寄存器,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是实际上 M3 /M4/M7 芯片为了精简设计,只使用了高四位[7:4],低四位取零,这样以至于最多只有 16 级中断嵌套,即 2^4=16。
通过这个表,我们就可以清楚的看到组 0~4 对应的配置关系,例如优先级分组设置为 3,
那么此时所有的 82 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,
低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优
先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
结合实例说明一下:假定设置中断优先级分组为 2,然后设置中断 3(RTC_WKUP 中断)的
抢占优先级为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。
中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:
中断 7>中断 3>中断 6。
上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互
打断!
NVIC 相关函数
ST 公司把 core_cm4.h 文件的 NVIC 相关函数封装到 stm32f4xx_hal_cortex.c 文件中,下面
列出我们较为常用的函数进行.
1. HAL_NVIC_SetPriorityGrouping 函数
HAL_NVIC_SetPriorityGrouping 是设置中断优先级分组函数。其声明如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
⚫ 函数描述:
用于设置中断优先级分组。
⚫ 函数形参:
形参 1 是中断优先级分组号,可以选择范围:NVIC_PRIORITYGROUP_0 到
NVIC_PRIORITYGROUP_4(共 5 组)。
⚫ 函数返回值:无
⚫ 注意事项:
这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调
用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面
设置好的抢占优先级和响应优先级不匹配。
2. HAL_NVIC_SetPriority 函数
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority,
uint32_t SubPriority);
⚫ 函数描述:
用于设置中断的抢占优先级和响应优先级(子优先级)。
⚫ 函数形参:
形参 1 是中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
形参 2 是抢占优先级,可以选择范围:0 到 15。
形参 3 是响应优先级,可以选择范围:0 到 15。
3. HAL_NVIC_EnableIRQ 函数
id HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
⚫ 函数描述:
用于使能中断。
⚫ 函数形参:
形参 1 是中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
⚫ 函数返回值:无
4. HAL_NVIC_DisableIRQ 函数
HAL_NVIC_DisableIRQ 是中断失能函数。其声明如下:
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
⚫ 函数描述:
用于中断除能。
⚫ 函数形参:无形参
⚫ 函数返回值:无
5. HAL_NVIC_SystemReset 函数
HAL_NVIC_SystemReset 是系统复位函数。其声明如下:
void HAL_NVIC_SystemReset(void);
⚫ 函数描述:
用于软件复位系统。
⚫ 函数形参:无形参
⚫ 函数返回值:无
EXTI 简介
EXTI 即是外部中断和事件控制器,它是由 20 个产生事件/中断请求的边沿检测器组成。每
一条输入线都可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或
者双边沿都触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
从 EXTI 功能框图可以看到有两条主线,一条是由输入线到 NVIC 中断控制器,一条是由
输入线到脉冲发生器。这就恰恰是 EXTI 的两大部分功能,产生中断与产生事件,两者从硬件
上就存在不同。
下面让我们看一下 EXTI 功能框图的产生中断的线路,最终信号是流入 NVIC 控制器中。输入线是线路的信息输入端,它可以通过配置寄存器设置为任何一个 GPIO 口,或者是一些外设的事件。输入线一般都是存在电平变化的信号。
标号①:====是一个边沿检测电路,包括边沿检测电路,上升沿触发选择寄存器(EXTI_RTSR)和
下降沿触发选择寄存器(EXTI_FTSR)。边沿检测电路以输入线作为信号输入端,如果检测到有
边沿跳变就输出有效信号‘1’,就输出有效信号‘1’到标号②部分电路,否则输入无效信号‘0’。
边沿跳变的标准在于开始的时候对于上升沿触发选择寄存器或下降沿触发选择寄存器对应位的
设置,对应位的设置可以参照一下表 16.1.2.1。
标号②:是一个或门电路,它的两个信号输入端分别是软件中断事件寄存器(EXTI_SWIER)和边沿检测电路的输入信号。或门电路只要输入端有信号‘1’,就会输出‘1’,所以就会输出‘1’到标号③电路和标号④电路。通过对软件中断事件寄存器的读写操作就可以启动中断/事
件线,即相当于输出有效信号‘1’到或门电路输入端。
标号③:是一个与门电路,它的两个信号输入端分别是中断屏蔽寄存器(EXTI_IMR)和标号②电路输出信号。与门电路要求输入都为‘1’才输出‘1’,这样子的情况下,如果中断屏蔽寄存器(EXTI_IMR)设置为 0 时,不管从标号②电路输出的信号特性如何,最终标号③电路输出的信
号都是 0;假如中断屏蔽寄存器(EXTI_IMR)设置为 1 时,最终标号③电路输出的信号才由标号
②电路输出信号决定,这样子就可以简单控制 EXTI_IMR 来实现中断的目的。标号④电路输出
‘1’就会把请求挂起寄存器(EXTI_PR)对应位置 1。
最后,请求挂起寄存器(EXTI_PR)的内容就输出到 NVIC 内,实现系统中断事件的控制。
接下来我们看看 EXTI 功能框图的产生事件的线路。产生事件线路是从标号 2 之后与中断线路有所不用,之前的线路都是共用的。标号④是一个与门,输入端来自标号 2 电路以及来自于事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR 寄存器设置为 0,那不管标号 2 电路输出的信号是‘0’还是‘1’,最终标号 4 输出的是‘0’;如果 EXTI_EMR 寄存器设置为 1,最终标号④电路输出信号就由标号③电路输出的信号决定,这样子就可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
标号④:电路输出有效信号 1 就会使脉冲发生器电路产生一个脉冲,而无效信号就不会使其产生脉冲信号。脉冲信号产生可以给其他外设电路使用,例如定时器,模拟数字转换器等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换。
产生中断线路目的使把输入信号输入到 NVIC,进一步运行中断服务函数,实现功能。而产
生事件线路目的是传输一个脉冲信号给其他外设使用,属于硬件级功能。
EXTI 支持 23 个外部中断/事件请求,这些都是信息输入端,也就是上面提及到了输入线
具体如下:
EXTI 线 0~15:对应外部 IO 口的输入中断
EXTI 线 16:连接到 PVD 输出
EXTI 线 17:连接到 RTC 闹钟事件
EXTI 线 18:连接到 USB 唤醒事件
EXTI 线 19:连接到以太网唤醒事件
EXTI 线 20:连接到 USB OTG HS(在 FS 中配置)唤醒事件
EXTI 线 21:连接到 RTC 入侵和时间戳事件
EXTI 线 22:连接到 RTC 唤醒事件
从上面可以看出,STM32F407 供给 IO 口使用的中断线只有 16 个,但是 STM32F407 的 IO
口却远远不止 16 个,所以 STM32 把 GPIO 管脚 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应
中断线 0~15。这样子每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、
GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0 和 GPIOG.0。而中断线每次只能连接到 1 个
IO 口上,这样就需要通过配置决定对应的中断线配置到哪个 GPIO 上了。