STM32及ARM中断详解

HarderHeng Lv5

ARM体系的中断和异常

当一个中断或者异常触发时,先看到这个中断源有没有被启动,以及它的优先级如何,然后进入中断向量表,如果允许进行中断或者异常的处理,就会根据中断向量表进入中断服务程序

在中断服务程序中进行关中断、保存现场、开中断、处理中断和异常、关中断、恢复现场、开中断

在ARM体系中和异常统一由中断向量表管理。

中断向量表

中断向量表有低端和高端向量表两种,低端向量表就是从0x00000000开始,高端向量表处在0xFFFF0000位置。

下图中的中断向量表基本对应着ArmV7-A架构,而CortexM架构都可以使用自定义的中断向量表,不用遵循下表。如果是更新的Arm指令集版本,和下表也有较大区别

image-20240508162736933

中断向量表和ResetHandler

ARM中断向量表的位置默认为0x00000000,中断向量表如下。

1
2
3
4
5
6
7
8
9
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义指令中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断*/
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 保留中断 */
ldr pc, =IRQ_Handler /* IRQ 中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断) */

当机器上电启动时会从这个代码开始,先进入ResetHandler进行系统的初始化。当进入中断时,会从中断向量表的起始地址(也就是上面_start)开始,根据中断的类型进行偏移,例如如果偏移量为0x08则会执行SVC中断的语句,pc寄存器直接被装入中断向量的地址,跳转到对应的中断处理。

如果要防止中断嵌套,需要在中断服务程序中加入屏蔽中断的代码。

ResetHandler中是机器上电启动后或者复位后进行初始化的代码,初始化后会进入**_main**函数最后才进入main函数。

可以手动设置中断向量表的地址,一般的做法是在ResetHandler中设置。

STM32(Cortex-M)的中断服务程序

对Cortex-M系列来说,并不遵循arm所规定的普通的中断向量表。

1
2
3
4
5
6
7
__Vectors       DCD     __initial_sp              ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler

可以看到,中断向量表的第一行规定着栈初始化的地址,也就是说开机后会先初始化栈,然后才进行ResetHandler。

中断和异常触发流程(Cortex-M)

  • 保存现场(硬件):将一些寄存器的值自动的保存到SP指向的栈中。
  • 分辨异常/中断(硬件):根据异常/中断号在中断向量表中找到中断服务程序的地址并进行跳转。
  • 中断服务程序结束后,从栈中恢复现场(软件)

1. 保存现场

不管是触发中断还是异常,硬件都会自动保存R0到R3、R12、LR、xPSR寄存器。

剩下的寄存器将在中断服务程序中进行保存。

根据ATPCS标准,被调用的函数如果要是用R4到R11寄存器,则需要在被调用函数的内部进行压栈。

  • 调用C语言的中断服务程序:编译器会自动生成保存现场的汇编代码,不需要程序员手动的保存。
  • 调用汇编编写的中断服务程序:需要在中断服务程序中手动编写保存现场的程序。

2. 根据中断号进入中断

寻找中断向量表,再根据中断号,在中断向量表中进行偏移。

3. 恢复现场。。。。

NVIC中断优先级

不同系列的芯片有不同位数的中断优先级,这个中断优先级一般是在BASEPRI寄存器的最高几位。比如CortexM系列的CPU,中断优先级位数有4位,就是BASEPRI寄存器的高四位。

优先级数字越小,中断越优先。

中断优先级又被NVIC分成抢占优先级和响应优先级,可以配置对应的优先级分组。比如4位优先级里面,有两位是抢占优先级,有两位是响应优先级。我们发现如果中断优先级的设置只有4位的话,也就是只有16个不同的优先级,注定了一些中断必须要在同一个优先级中。对抢占优先级和响应优先级的处理是NVIC负责的,抢占优先级更高的可以中断嵌套其他更低的中断,而相同的抢占优先级的任务里,顺序执行响应优先级更高的。而对于整个优先级都完全一样的中断,则会通过自然优先级,也就是在中断向量表中的位置来判断。越靠前的自然优先级就越高。

本身抢占优先级和响应优先级这两个东西配合在一起,才是BASEPRI看到的真正的任务优先级,但是NVIC把他们拆成了两部分来使用。对BASEPRI寄存器来说,对优先级进行屏蔽,不在乎抢占还是响应,会直接屏蔽掉比寄存器的数值更大的那些中断。

NVIC可以管理的是中断,一般来源于外设或者内核异常。而有一些异常,比如HardFault或者NMI,他们在中断向量表的最前面,但是不通过NVIC进行中断的管理,并且其优先级也不能够被配置。他们的优先级默认被配置为最高,任何情况下发生这几个异常都是最先进行处理。而在这之后的MemManage和BusFault等等异常,包括操作系统常用到的SysTickPenSV都是NVIC管理的内核异常。再往后,就是NVIC管理的外设的中断。

在操作系统中,一般使用到的中断就是SysTickPendSV。SysTick用于提供操作系统时钟,并且在合适的时候进行唤起任务的切换。PendSV则用于进行任务的切换。PendSV的可配置优先级一般为最低,然后SysTick为其次。

  • Title: STM32及ARM中断详解
  • Author: HarderHeng
  • Created at : 2024-05-08 15:59:41
  • Updated at : 2025-03-21 16:56:19
  • Link: https://harderheng.life/2024/05/08/STM32及ARM中断详解/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments