STM32学习笔记5——中断
本文最后更新于:2024年11月6日 晚上
0x00 Reference
0x01 中断简介
打断CPU执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行,就叫中断

中断的作用和意义
实时控制 在确定时间内对相应事件作出响应,如:温度监控
故障处理 检测到故障,需要第一时间处理,如:电梯门夹人了
数据传输 不确定数据何时会来,如:串口数据接收
中断的意义:高效处理紧急程序,不会一直占用CPU资源
0x02 Cortex-M3的中断
-1- 中断类型
CM3内核的NVIC最高支持256个中断,也就是有0~255个中断号,其中前16个(0~15)为内核中断,也叫系统中断,后面的240个中断号全部都为外部中断。芯片厂商可以精简设计,所以做成芯片后,支持的外部中断数量往往不到240个

在 NVIC 的中断控制及状态寄存器中,有一个 VECTACTIVE 位段;另外,还有一个特殊功能寄存器 IPSR。在它们二者的里面,都记录了当前正服务中断号
当一个中断触发了但是不能被立即响应时,就处于“悬起”状态(pending)。出现“悬起”的原因可能是CPU正在执行更高优先级的中断,也可能是该中断被除能了。处于pending状态的中断,会有一个对应的 悬起状态寄存器 来保存这个中断请求,直到这个中断请求能够被响应。
-2- 中断优先级
当有多个中断同时请求响应时,哪一个应该被优先响应由中断的优先级决定。
CM3 支持中断嵌套,使得高优先级异常可以抢占(preempt)低优先级异常。比如CPU正在响应一个低优先级的中断时,又触发了一个高优先级的中断,那CPU将会暂时放弃响应此低优先级中断,转而先去响应高优先级中断,这就是中断嵌套。
有 3 个系统中断复位,NMI 以及硬 fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它中断。所有其它中断的优先级则都是可编程的
CM3用8位来表达优先级,所以最高支持2^8=256级优先级,并且将优先级分为了 抢占优先级 和 响应优先级(也叫亚优先级/子优先级)。
- 抢占优先级:高抢占优先级可以打断正在执行的低抢占优先级中断
- 响应优先级:两个抢占优先级相同的中断同时触发时,响应优先级高的先执行,但是不能互相打断
CM3规定至少需要用一位来表示响应优先级,所以抢占优先级最多只能有128个
优先级的数量是可裁剪的,几位表示抢占优先级,几位表示响应优先级是由芯片厂商决定的
-3- 向量表
当中断发生时,CPU需要知道如何响应中断,也就是需要找到中断服务函数。中断服务函数的入口地址就被记录在向量表中。
默认情况下,向量表被放在0地址处,并且每个地址占4个字节。但是CM3支持向量表重定向。为了实现这个功能,NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08 处),通过修改它的值就能重新定位向量表。
如果需要动态地更改向量表,则对于任何器件来说,向量表的起始处都必须包含以下向量:
- 主堆栈指针(MSP)的初始值
- 复位向量
- NMI
- 硬 fault 服务例程
可以在 SRAM 中开出一块用于存储向量表。然后在引导完成后,就可以启用内存中的向量表,从而实现向量可动态调整的功能
-4- SVC 和 PendSV
SVC(SuperVisor Call,系统调用中断)和 PendSV(Pendable Service Call,可悬起系统调用)多用于带操作系统的软件开发中。
4-1 SVC的用法
操作系统抽象了抽象硬件,程序无法直接操作硬件,当用户程序想操作硬件时,就会生成SVC中断 ,CPU响应SVC中断服务函数,在这个中断服务函数里再去执行相应的操作系统函数,完成用户程序的请求。
SVC异常是必须立即得到响应的,如果因为CPU在响应更高优先级的中断而导致SVC中断无法立即响应,则会抛出一个硬fault异常中断

4-2 PendSV的用法
PendSV中断不要求被立即响应,它可以被“悬起”。PendSV通常被用在OS的上下文切换中。
在OS的时间片轮转调度中,通常会有个SysTick中断用来切换任务,在没有PendSV的情况下,如果在响应中断服务时,触发了SysTick中断抢占了ISR进行任务切换,OS 在某中断活跃时尝试切入线程模式,将抛出用法 fault 异常。

为了处理此问题,在早期的OS中,大多会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick 在执行后不得作上下文切换,只能等待下一次 SysTick 异常)
有了PendSV就可以完美解决此问题,PendSV 异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的中断。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,
以便缓期执行上下文切换。
- 任务 A 呼叫 SVC 来请求任务切换(例如,等待某些工作完成)
- OS 接收到请求,做好上下文切换的准备,并且挂起一个 PendSV 中断。
- 当 CPU 退出 SVC 后,它立即进入 PendSV,从而执行上下文切换。
- 当 PendSV 执行完毕后,将返回到任务 B,同时进入线程模式。
- 发生了一个中断,并且中断服务程序开始执行
- 在 ISR 执行过程中,发生 SysTick 异常,并且抢占了该 ISR。
- OS 执行必要的操作,然后挂起 PendSV 中断以作好上下文切换的准备。
- 当 SysTick 退出后,回到先前被抢占的 ISR 中,ISR 继续执行
- ISR 执行完毕并退出后,PendSV 服务例程开始执行,并且在里面执行上下文切换
- 当 PendSV 执行完毕后,回到任务 A,同时系统再次进入线程模式。
0x03 NVIC
CM3实现中断的核心是NVIC(Nested Vectored Interrupt Controller),嵌套向量中断控制器。NVIC 的寄存器以存储器映射的方式来访问,除了包含控制寄存器和中断处理的控制逻辑之外,NVIC 还包含了 MPU的控制寄存器、SysTick 定时器以及调试控制。
NVIC 的访问地址是 0xE000_E000。所有 NVIC 的中断控制/状态寄存器都只能在特权级下访问。不过有一个例外——软件触发中断寄存器可以在用户级下访问以产生软件中断。
-1- 中断寄存器
每个外部中断都在NVIC的下列寄存器中“挂号”:
- 使能与除能寄存器
- 悬起与“解悬”寄存器
- 优先级寄存器
- 活动状态寄存器
此外,下列寄存器也对中断处理有重大影响:
- 异常掩蔽寄存器(PRIMASK, FAULTMASK 以及 BASEPRI)
- 向量表偏移量寄存器
- 软件触发中断寄存器
- 优先级分组位段
1-1 使能与除能中断寄存器
SETENAs:0xE000_E100 - 0xE000_E11C
CLRENAs:0xE000_E180 - 0xE000_E19C
中断的使能与除能分别使用各自的寄存器来控制,而不是使用一个位的0和1来分别代表关闭和打开。欲使能一个中断,你需要写 1 到对应 SETENA 的位中;欲除能一个中断,你需要写 1 到对应的 CLRENA 位中;如果往它们中写 0,不会有任何效果。
中断使能寄存器SETENA也叫ISER,中断除能寄存器CLRENA也叫ICER
如果将一个向一个中断的使能寄存器和除能寄存器都写1会怎么样呢?
正是因为使能和除能是不同的寄存器,所以不可能同时向两个寄存器中写值,必然存在一个先后顺序,具体的结果就与最后写入的值有关了
SETENA 位和 CLRENA 位可以有 240 对,对应的 32 位寄存器可以有 8 对。但是在特定的芯片中,只有该芯片实现的中断,其对应的位才有意义。因此,如果你使用的芯片只支持 32 个中断,则只有 SETENA0/CLRENA0 才需要使用。SETENA/CLRENA 可以按字/半字/字节的方式来访问。又因为前 16 个异常已经分配给系统异常,外部中断的中断号是从16开始的,所以寄存器控制的中断号也是从16开始的

1-2 悬起与解悬中断寄存器
SETPENDs:0xE000_E200 - 0xE000_E21C
CLRPENDs:0xE000_E280 - 0xE000_E29C
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。
悬起寄存器和“解悬”寄存器也可以有 8 对,其用法和用量都与前面的使能/除能中断寄存器完全相同

1-3 优先级寄存器
外部中断优先级寄存器IPR:0xE000_E400 - 0xE000_E4EF
系统中断优先级寄存器SHPR:0xE000_ED18 - 0xE000_ED23
每个中断有都有一个8位的优先级寄存器来存储此中断的优先级。每个寄存器占用 8 位,但是允许最少只使用最高 3 位。4 个相临的优先级寄存器拼成一个 32 位寄存器。按照优先级位数的配置,会将8位分为抢占优先级和响应优先级。
优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。有意义的优先级寄存器数目由芯片厂商实现的中断数目决定


1-4 活动状态寄存器
ACTIVE 寄存器族: 0xE000_E300 - 0xE000_E31C
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。然而,哪怕一个中断被抢占,其活动状态也依然为 1

1-5 PRIMASK寄存器
PRIMASK寄存器用来屏蔽除了NMI和硬fault之外的其他中断,PRIMASK寄存器只有1位,写1就是屏蔽其他中断,写0就是恢复
1 |
|
为什么要使用MSR指令而不是MOV指令写入PRIMASK寄存器呢?
因为PRIMASK寄存器是一个特殊寄存器(系统寄存器),所以MOV不能直接操作PRIMASK寄存器。对于特殊寄存器,ARM提供了专门的指令MSR(Move to Special Register)和MRS(Move from Special Register),所以往PRIMASK写值要使用MSR指令。
通过CPS指令可以快速完成上述指令
1 |
|
1-6 FAULTMASK寄存器
FAULTMASK的作用与PRIMASK的相似,它会连同硬FAULT一起屏蔽,只剩下NMI不可屏蔽。但要注意的是,FAULTMASK会在异常退出时自动清零。
既然FAULTMASK屏蔽了异常,那异常怎么会退出呢?
FAULTMASK
的屏蔽作用是临时的,它不会影响已经发生的异常。如果一个异常在FAULTMASK
被设置之前已经发生,那么异常处理程序会按照正常的流程执行。在异常处理程序执行完毕后,处理器会返回到异常发生前的状态,继续执行程序。
1-7 BASEPRI寄存器
如果需要对中断屏蔽进行更精细的控制——只屏蔽优先级低于某一阈值的中断,那就需要用到BASEPRI寄存器。优先级在数字上大于BASEPRI寄存器的中断会被屏蔽,如果往BASEPRI寄存器中写0,那将表示关闭此功能,BASEPRI将停止屏蔽任何中断
1-8 其他特殊寄存器
系统Handler控制及状态寄存器SHCSR:0xE000_ED24

中断控制及状态寄存器ICSR:0xE000_ED04

-2- NVIC工作原理

0x04 STM32的中断
-1- STM32的中断个数
CM3支持240个外部中断,而STM32F1系列只支持了其中的60个

具体参考《STM32F10xxx 参考手册_V10(中文版).pdf》的 9.1.2 小节
-2- STM32的优先级分组
STM32支持16个优先级,具体由AIRCR寄存器控制
前面CM3中断中要求至少要有一位分配给响应优先级,而STM32却可以全部为抢占优先级,是因为STM32没有沾满所有的优先级分组
-3- STM32 GPIO外部中断简图

-4- 相关函数
STM32 NVIC相关函数被封装在 stm32f1xx_hal_cortex.c 文件中
4-1 HAL_NVIC_SetPriorityGrouping
用于设置中断优先级分组
1 |
|
唯一参数是优先级分组情况,也就是写入AIRCR[10:8]的数,可选
1
2
3
4
5
6
7
8
9#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
4-2 HAL_NVIC_SetPriority
用于设置中断的抢占优先级和响应优先级
1 |
|
第一个参数是中断号,IRQn_Type 定义的枚举类型,定义在 stm32f103xe.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24typedef enum
{
/****** Cortex-M3 Processor Exceptions Numbers ***************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
HardFault_IRQn = -13, /*!< 3 Cortex-M3 Hard Fault Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M3 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M3 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M3 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers *********************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMPER_IRQn = 2, /*!< Tamper Interrupt */
RTC_IRQn = 3, /*!< RTC global Interrupt */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
...
DMA2_Channel4_5_IRQn = 59, /*!< DMA2 Channel 4 and Channel 5 global Interrupt */
} IRQn_Type;第二个参数为抢占优先级,可选0~15
第三个参数为响应优先级,可选0~15
4-3 HAL_NVIC_EnableIRQ
用于使能中断
1 |
|
- 唯一参数是中断号,IRQn_Type 定义的枚举类型,定义在 stm32f103xe.h
4-4 HAL_NVIC_disableIRQ
用于除能中断
1 |
|
- 唯一参数是中断号,IRQn_Type 定义的枚举类型,定义在 stm32f103xe.h
4-5 HAL_NVIC_SystemReset
用于实现软复位
1 |
|