Cortex-A7中断系统
中断向量表
Cortex-A7的中断向量表共有8个异常中断,其中一个未使用,有效中断为7个:
| 向量地址 | 中断类型 | 中断模式 |
|---|---|---|
| 0X00 | 复位中断(Rest) | 特权模式(SVC) |
| 0X04 | 未定义指令中断(Undefined Instruction) | 未定义指令中止模式(Undef) |
| 0X08 | 软中断(Software Interrupt,SWI) | 特权模式(SVC) |
| 0X0C | 指令预取中止中断(Prefetch Abort) | 中止模式 |
| 0X10 | 数据访问中止中断(Data Abort) | 中止模式 |
| 0X14 | 未使用(Not Used) | 未使用 |
| 0X18 | IRQ 中断(IRQ Interrupt) | 外部中断模式(IRQ) |
| 0X1C | FIQ 中断(FIQ Interrupt) | 快速中断模式(FIQ) |
不同于Cortex-M在中断向量表中列出了所有的中断向量,Cortex-A将所有属于内核CPU的外部中断放在0x18的IRQ中断中,任意外部中断都会触发IRQ中断,然后在IRQ中断中通过读取寄存器的方式来判断具体的中断类型,然后做出相应处理。
汇编代码(启动文件start.S)
中断向量表
.global _start /* 全局标号 */
_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(快速中断)未定义中断 */指定中断发生后调用对应的中断复位函数。pc为程序计数器,用来指出下一条指令在主存储器中的地址。中断发生后,主程序的下一条指令地址会自动更新为中断服务函数的地址。
GIC控制器

类似于STM32的NVIC,Cortex-A的中断控制器名字叫GIC,GIC负责处理输入的所有中断,然后生成一个IRQ信号上报给ARM内核。GIC将众多的中断源分为三类:
- SPI,共享中断,常见外部中断均属于SPI中断
- PPI,私有中断,指定核心处理
- SGI,软件中断,通过向寄存器
GICR_SGIR写入数据触发,常用于多核通信
中断ID
I.MX6UL的1020个中断ID分配如下:
- ID0 - ID15:分配至SGI
- ID16 - ID31:分配至PPI
- ID32 - ID1019:分配至SPI
I.MX6U的中断源由官方SDK在MCIMX6Y2.h中给出,共有160个,每个中断源有独属的中断ID。
GIC逻辑分块
GIC架构分为两个逻辑块:
- Distributor:分发器端,负责处理中断事件分发问题(中断事件该发送到哪个CPU Interface上去)。分发器收集所有的中断源,可控制每个中断的优先级,具体功能有:
- 全局中断使能控制
- 控制每个中断的使能或关闭
- 设置中断优先级
- 设置每个中断的目标处理器列表
- 设置每个外部中断的触发模式:电平触发或边沿触发
- 设置每个中断属于组 0 还是组 1
- CPU Interface:CPU接口端,具体功能有:
- 使能或者关闭发送到 CPU Core 的中断请求信号
- 应答中断
- 通知中断处理完成
- 设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core
- 定义抢占策略
- 当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core
GIC控制器的所有寄存器在core_ca7.h中声明:
/* * GIC寄存器描述结构体, * GIC分为分发器端和CPU接口端 */typedef struct{ uint32_t RESERVED0[1024]; __IOM uint32_t D_CTLR; /*!< Offset: 0x1000 (R/W) Distributor Control Register */ __IM uint32_t D_TYPER; /*!< Offset: 0x1004 (R/ ) Interrupt Controller Type Register */ __IM uint32_t D_IIDR; /*!< Offset: 0x1008 (R/ ) Distributor Implementer Identification Register */ uint32_t RESERVED1[29]; __IOM uint32_t D_IGROUPR[16]; /*!< Offset: 0x1080 - 0x0BC (R/W) Interrupt Group Registers */ uint32_t RESERVED2[16]; __IOM uint32_t D_ISENABLER[16]; /*!< Offset: 0x1100 - 0x13C (R/W) Interrupt Set-Enable Registers */ uint32_t RESERVED3[16]; __IOM uint32_t D_ICENABLER[16]; /*!< Offset: 0x1180 - 0x1BC (R/W) Interrupt Clear-Enable Registers */ uint32_t RESERVED4[16]; __IOM uint32_t D_ISPENDR[16]; /*!< Offset: 0x1200 - 0x23C (R/W) Interrupt Set-Pending Registers */ uint32_t RESERVED5[16]; __IOM uint32_t D_ICPENDR[16]; /*!< Offset: 0x1280 - 0x2BC (R/W) Interrupt Clear-Pending Registers */ uint32_t RESERVED6[16]; __IOM uint32_t D_ISACTIVER[16]; /*!< Offset: 0x1300 - 0x33C (R/W) Interrupt Set-Active Registers */ uint32_t RESERVED7[16]; __IOM uint32_t D_ICACTIVER[16]; /*!< Offset: 0x1380 - 0x3BC (R/W) Interrupt Clear-Active Registers */ uint32_t RESERVED8[16]; __IOM uint8_t D_IPRIORITYR[512]; /*!< Offset: 0x1400 - 0x5FC (R/W) Interrupt Priority Registers */ uint32_t RESERVED9[128]; __IOM uint8_t D_ITARGETSR[512]; /*!< Offset: 0x1800 - 0x9FC (R/W) Interrupt Targets Registers */ uint32_t RESERVED10[128]; __IOM uint32_t D_ICFGR[32]; /*!< Offset: 0x1C00 - 0xC7C (R/W) Interrupt configuration registers */ uint32_t RESERVED11[32]; __IM uint32_t D_PPISR; /*!< Offset: 0x1D00 (R/ ) Private Peripheral Interrupt Status Register */ __IM uint32_t D_SPISR[15]; /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared Peripheral Interrupt Status Registers */ uint32_t RESERVED12[112]; __OM uint32_t D_SGIR; /*!< Offset: 0x1F00 ( /W) Software Generated Interrupt Register */ uint32_t RESERVED13[3]; __IOM uint8_t D_CPENDSGIR[16]; /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI Clear-Pending Registers */ __IOM uint8_t D_SPENDSGIR[16]; /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI Set-Pending Registers */ uint32_t RESERVED14[40]; __IM uint32_t D_PIDR4; /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 Register */ __IM uint32_t D_PIDR5; /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 Register */ __IM uint32_t D_PIDR6; /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 Register */ __IM uint32_t D_PIDR7; /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 Register */ __IM uint32_t D_PIDR0; /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 Register */ __IM uint32_t D_PIDR1; /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 Register */ __IM uint32_t D_PIDR2; /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 Register */ __IM uint32_t D_PIDR3; /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 Register */ __IM uint32_t D_CIDR0; /*!< Offset: 0x1FF0 (R/ ) Component ID0 Register */ __IM uint32_t D_CIDR1; /*!< Offset: 0x1FF4 (R/ ) Component ID1 Register */ __IM uint32_t D_CIDR2; /*!< Offset: 0x1FF8 (R/ ) Component ID2 Register */ __IM uint32_t D_CIDR3; /*!< Offset: 0x1FFC (R/ ) Component ID3 Register */
__IOM uint32_t C_CTLR; /*!< Offset: 0x2000 (R/W) CPU Interface Control Register */ __IOM uint32_t C_PMR; /*!< Offset: 0x2004 (R/W) Interrupt Priority Mask Register */ __IOM uint32_t C_BPR; /*!< Offset: 0x2008 (R/W) Binary Point Register */ __IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */ __OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */ __IM uint32_t C_RPR; /*!< Offset: 0x2014 (R/ ) Running Priority Register */ __IM uint32_t C_HPPIR; /*!< Offset: 0x2018 (R/ ) Highest Priority Pending Interrupt Register */ __IOM uint32_t C_ABPR; /*!< Offset: 0x201C (R/W) Aliased Binary Point Register */ __IM uint32_t C_AIAR; /*!< Offset: 0x2020 (R/ ) Aliased Interrupt Acknowledge Register */ __OM uint32_t C_AEOIR; /*!< Offset: 0x2024 ( /W) Aliased End Of Interrupt Register */ __IM uint32_t C_AHPPIR; /*!< Offset: 0x2028 (R/ ) Aliased Highest Priority Pending Interrupt Register */ uint32_t RESERVED15[41]; __IOM uint32_t C_APR0; /*!< Offset: 0x20D0 (R/W) Active Priority Register */ uint32_t RESERVED16[3]; __IOM uint32_t C_NSAPR0; /*!< Offset: 0x20E0 (R/W) Non-secure Active Priority Register */ uint32_t RESERVED17[6]; __IM uint32_t C_IIDR; /*!< Offset: 0x20FC (R/ ) CPU Interface Identification Register */ uint32_t RESERVED18[960]; __OM uint32_t C_DIR; /*!< Offset: 0x3000 ( /W) Deactivate Interrupt Register */} GIC_Type;不过,这里只给出了GIC各寄存器的偏移地址,基地址需要通过CP15协处理器获取。
CP15协处理器
CP15协处理器共有16个32位寄存器。对CP15协处理器的访问可通过下列指令完成:
- MRC:将CP15的寄存器数据读取到ARM寄存器中
- MCR:将ARM寄存器数据写入到CP15寄存器中。格式:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>- cond:指令执行的条件码。若忽略,表示无条件执行
- opc1:CP15要执行的操作码
- Rt:ARM源寄存器
- CRn:CP15的目标寄存器
- CRm:CP15中附加的目标寄存器或源操作数寄存器。如不需要附加信息,该位应设置为
c0 - opc2:可选的CP15特定操作码,不需要时设置为0
CP15的16个寄存器在被MRC和MCR指令访问时,指令中的参数搭配若不同,得到的寄存器含义也不同。以c0寄存器为例:

例如,当opc1 = 0, CRm = c0, opc2 = 0时,此时c0是MIDR寄存器,即主ID寄存器,包含厂商编号、主版本号、架构代码等信息。
对于c15寄存器:

可见GIC基地址保存在CBAR中,因此可通过MRC p15, 4, r1, c15, c0, 0指令获取GIC基地址至r1中。现在就可以设置GIC的寄存器了,比如:读取当前中断ID,将ID保存在GICC_IAR中,SDK中已经给出GICC_IAR的偏移地址__IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */为0x200C,因此获取当前中断ID的汇编代码为:
MRC p15, 5, r1, c15, c0 ,0 ;获取GIC基地址ADD r1, r1, #0x2000 ;基地址+0x2000得到CPU接口端寄存器起始地址LDR r0, [r1, #0xC] ;读取GIC_IAR的值总结:
- c0寄存器可获取到处理器内核信息
- c1寄存器可使能或禁止MMU、I/D Cache
- c12寄存器可设置中断向量偏移
- c15可获取GIC基地址
中断使能
IRQ和FIQ总中断使能
汇编下支持以下快捷指令:
| 指令 | 描述 |
|---|---|
| cpsid i | 禁止IRQ中断 |
| cpsie i | 使能IRQ中断 |
| cpsid f | 禁止FIQ中断 |
| cpsie f | 使能FIQ中断 |
ID0 - ID1019 中断使能和禁止
由以下寄存器完成:
GICD_ISENABLERn:GICD_ISENABLER0的bit[15:0]对应ID15 - 0的SGI中断,bit[31:16]对应ID31 - 16的PPI中断GICD_ICENABLERn:GICD_ISENABLER1-GICD_ISENABLER15控制SPI中断
中断优先级
优先级数量配置
GICC_PMR寄存器用于配置优先级数量,寄存器低8位有效。I.MX6U是Cortex-A7内核,支持32个优先级,因此GICC_PMR设置为0b11111000.
抢占优先级和子优先级位数配置
抢占优先级和子优先级各占多少位由寄存器GICC_BPR决定,低3位有效,配置如下所示:
| Binary Point | 抢占优先级域 | 子优先级域 | 描述 |
|---|---|---|---|
| 0 | [7:1] | [0] | 7 级抢占优先级,1 级子优先级。 |
| 1 | [7:2] | [1:0] | 6 级抢占优先级,2 级子优先级。 |
| 2 | [7:3] | [2:0] | 5 级抢占优先级,3 级子优先级。 |
| 3 | [7:4] | [3:0] | 4 级抢占优先级,4 级子优先级。 |
| 4 | [7:5] | [4:0] | 3 级抢占优先级,5 级子优先级。 |
| 5 | [7:6] | [5:0] | 2 级抢占优先级,6 级子优先级。 |
| 6 | [7:7] | [6:0] | 1 级抢占优先级,7 级子优先级。 |
| 7 | 无 | [7:0] | 0 级抢占优先级,8 级子优先级。 |
一般而言所有的中断优先级位都配置为抢占优先级。如IMX6U的优先级位数为5(32个优先级),可设置Binary point为2,表示5个优先级位全部为抢占优先级。
优先级设置
某个中断ID的中断优先级设置由寄存器GICD_IPRIORITYR完成,每个中断ID配有一个该寄存器,共512个。如果优先级个数为32,即使用GICD_IPRIORITYR的bit[7:4]来设置优先级(实际优先级左移3位)。例如,要设置ID == 40中断优先级为5:GICD_IPRIORITYR[40] = 5 << 3
总结一下,中断优先级设置分三部分:
- 设置
GICC_PMR,配置优先级个数,IMX6U为32级 - 设置抢占优先级和子优先级位数,一般默认所有位数都为抢占优先级
- 设置指定中断ID的优先级
复位中断服务函数
整个启动文件在设置完中断向量表后,要进行复位中断的设置,其分为以下步骤:
- 设置复位中断:关闭IRQ,然后关闭I/D Cache、MMU、对齐检测和分支预测
- 进行中断向量表重映射
- 设置IRQ模式、SYS模式和SVC模式下的栈指针,栈大小均为2MB
- 打开IRQ中断
- 跳转至main函数
/* 复位中断 */Reset_Handler:
cpsid i /* 关闭全局中断 */
/* 关闭I,DCache和MMU * 采取读-改-写的方式。 */ mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */ bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */ bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */ bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */ bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */ bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */ mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
#if 0 /* 汇编版本设置中断向量表偏移 */ ldr r0, =0X87800000
dsb isb mcr p15, 0, r0, c12, c0, 0 dsb isb#endif
/* 设置各个模式下的栈指针, * 注意:IMX6UL的堆栈是向下增长的! * 堆栈指针地址一定要是4字节地址对齐的!!! * DDR范围:0X80000000~0X9FFFFFFF */ /* 进入IRQ模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */
/* 进入SYS模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */
/* 进入SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */
cpsie i /* 打开全局中断 */#if 0 /* 使能IRQ中断 */ mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */ bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */ msr cpsr, r0 /* 将r0重新写入到cpsr中 */#endif
b main /* 跳转到main函数 */其他中断
/* 未定义中断 */Undefined_Handler: ldr r0, =Undefined_Handler bx r0
/* SVC中断 */SVC_Handler: ldr r0, =SVC_Handler bx r0
/* 预取终止中断 */PrefAbort_Handler: ldr r0, =PrefAbort_Handler bx r0
/* 数据终止中断 */DataAbort_Handler: ldr r0, =DataAbort_Handler bx r0
/* 未使用的中断 */NotUsed_Handler:
ldr r0, =NotUsed_Handler bx r0
/* FIQ中断 */FIQ_Handler:
ldr r0, =FIQ_Handler bx r0IRQ中断服务函数
所有的外部中断都会触发IRQ中断,因此IRQ中断服务函数的工作就是获取当前发生的中断ID以确定中断来源,然后根据不同的外部中断来进行不同的处理。这里会涉及到几个ARM中常用的寄存器:
- IP寄存器:内部程序调用暂存寄存器,子程序的连接text段中常使用该规则
- SP寄存器:栈指针寄存器,用于存储当前栈顶地址。程序执行过程中,栈是用来存储临时变量、函数调用返回地址等数据的重要数据结构,SP寄存器的值会随着栈的变化而变化
- LR寄存器:连接寄存器,程序跳转(子程序调用,中断跳转)后,arm自动在该寄存器中存入原程序(未跳转)的下一条指令的地址,也叫函数调用返回地址。当一个函数被调用时,LR寄存器会存储调用该函数的下一条指令的地址,当函数执行完毕后,程序会跳转到LR寄存器中存储的地址继续执行。
- PC寄存器:程序计数器,保存的是当前正在取指的指令的地址(arm采用2级流水线,因此是当前正在执行指令的地址+8)。PC寄存器是ARM中的程序计数器,用于存储下一条将要执行的指令的地址。
此外,还有两个比较重要的状态寄存器:
- CPSR:程序状态寄存器,在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。CPSR在用户级编程时用于存储条件码。
- SPSR:程序状态保存寄存器,每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
二者常用于MRS或MSR指令,用于spsr中的值转移到寄存器或把寄存器的内容加载到spsr中,如:
mrs r0, spsr /* 读取spsr寄存器 */msr spsr_cxsf, r0 /* 恢复spsr */IRQ中断服务函数的编写大致分为以下步骤:
- 进入IRQ模式,保存上下文。ARM在进入IRQ模式会自动切换LR和SPSR,但不会自动保存其他寄存器,因此需手动保存r0-r3和r12寄存器(后续操作可能修改)。自动切换时,LR保存被中断指令的下一条指令
-4。
/* IRQ中断 */IRQ_Handler: push {lr} /* 保存lr地址 */ push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */- 保存现场。SPSR存储被中断前CPU的模式状态,必须保存SPSR以正确恢复中断前的执行环境。
mrs r0, spsr /* 读取spsr寄存器 */ push {r0} /* 保存spsr寄存器 */- 获取GIC中断号。通过MRC向cp15读取寄存器以获取GIC基址,从而计算GICC_IAR地址并读取,读取后自动标记该中断为active状态,获取到的中断号
r0作为参数传递给C语言ISR。
mrc p15, 4, r1, c15, c0, 0 /* 读取CP15的C0寄存器内的值到R1寄存器中 * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49 * Cortex-A7 Technical ReferenceManua.pdf P68 P138 */ add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */ ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器, * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据 * 这个中断号来绝对调用哪个中断服务函数 */ push {r0, r1} /* 保存r0,r1 */- 切换至SVC模式并调用C函数。IAR模式栈空间有限,切换至SVC模式(特权模式)以获得更大的栈空间,并允许C函数处理更复杂的逻辑,如中断嵌套。
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */ ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/ blx r2 /* 运行C语言中断处理函数,带有中断号参数,保存在R0寄存器中 */- 恢复模式并发送中断结束信号。向向GIC的End of Interrupt Register (GICC_EOIR) 写入中断号,告知中断控制器该中断已处理完毕。
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */ cps #0x12 /* 进入IRQ模式 */ pop {r0, r1} str r0, [r1, #0X10] /* 中断执行完成,写EOIR */- 恢复中断前状态并返回。
这里是一个很容易搞错的点。ARM使用三级流水线机制:取指->译指->执行,而我们总以执行位置作为参考点,因此PC永远是当前执行位置+8;LR存放函数调用结束后返回继续执行的地址,也就是当前执行指令的下一条(+4)。进入IRQ中断时,中断总是在执行一条指令后再进入,此时PC更新为+12,相应的LR变为+8,然后LR被入栈保存。如果中断结束后,直接将LR出栈,程序会从+8处开始运行,那么+4处的指令就直接被跳过了。因此,中断结束后将LR出栈时,要将LR-4。
用表格可以解释为:
| 状态A:中断前 | 状态B:进入中断后 | 状态C:中断恢复 | ||
|---|---|---|---|---|
| 0x00 | MOV R1, R0 | 准备执行 | 已执行 | |
| 0x04 | MOV R2, R1 | LR | LR(恢复为状态A时的PC值-4) | |
| 0x08 | MOV R3, R2 | PC | LR(已入栈) | |
| 0x0c | MOV R4 ,R3 | |||
| PC值 | 0x08 | 0x12 | 由LR值决定 | |
| LR值 | 0x04 | 0x08 | 0x04 |
再通俗一点:PC始终指向当前执行指令+8,发生中断时,入栈保存的LR实际上是PC的地址,如果返回时将LR直接赋给PC,中间就跳过了一个指令,因此LR出栈后要-4才能赋给PC。
pop {r0} msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */ pop {lr} /* lr出栈 */ subs pc, lr, #4 /* 将计算后的地址(lr-4)赋给pc */中断驱动文件(C函数)
- 中断初始化函数。向量表基地址设置为程序存放开始地址。
void int_init(void) { GIC_Init(); // 初始化通用中断控制器,该函数由SDK提供 system_irqtable_init(); // 初始化中断函数表 __set_VBAR((uint32_t)0x87800000); // 设置向量表基地址}- 中断向量表初始化。将所有中断初始化为default_irqhandler(死循环),强制开发者显式注册有效ISR,避免未处理中断导致不可控行为。
void system_irqtable_init(void) { unsigned int i = 0; irqNesting = 0; // 重置嵌套计数器
for(i = 0; i < NUMBER_OF_INT_VECTORS; i++) { system_register_irqhandler((IRQn_Type)i, default_irqhandler, NULL); }}- 中断注册函数。用于绑定中断服务函数和中断源。
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; // 注册处理函数 irqTable[irq].userParam = userParam; // 绑定用户参数}- 中断分发器。默认允许中断嵌套。
void system_irqhandler(unsigned int giccIar) { uint32_t intNum = giccIar & 0x3FFUL; // 提取中断号
/* 校验中断号有效性 */ if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)) { return; // 非法中断号(1023伪中断或中断号不在表内)直接返回 }
irqNesting++; // 嵌套计数增加
/* 调用注册的ISR */ irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
irqNesting--; // 嵌套计数减少}中断处理全流程
- 硬件触发:外设触发中断,GIC将中断请求发送至CPU。
- 汇编入口:CPU跳转至IRQ_Handler,保存上下文并调用system_irqhandler(giccIar)。
- 中断分发:
- 解析giccIar获取有效中断号
- 查表irqTable[intNum]获取处理函数和参数
- ISR执行:执行用户注册的函数(如uart_isr),处理具体中断任务。
- 中断结束:汇编代码写GICC_EOIR通知GIC处理完成。
示例(定时器按键消抖)
实现功能:
- 按下按键,进入外部中断,在中断中开启定时器
- 定时器中断中完成消抖延时,中断周期即为延时时间。如果定时中断触发,表示消抖完成,执行按键处理函数
按键初始化
- 设置GPIO复用和Config
- 填充gpio结构体,初始化gpio
- 使能中断,注册ISR(绑定中断源和对应的ISR)
- 初始化定时器
void filterkey_init(void){ gpio_pin_config_t key_config;
/* 1、初始化IO复用 */ IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */
/* 2、、配置GPIO1_IO18的IO属性 *bit 16:0 HYS关闭 *bit [15:14]: 11 默认22K上拉 *bit [13]: 1 pull功能 *bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出 *bit [7:6]: 10 速度100Mhz *bit [5:3]: 000 关闭输出 *bit [0]: 0 低转换率 */ IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
/* 3、初始化GPIO为中断 */ key_config.direction = kGPIO_DigitalInput; /* 输入 */ key_config.interruptMode = kGPIO_IntFallingEdge; /* 下降沿触发 */ key_config.outputLogic = 1; gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
/* 注册中断服务函数 */ system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_16_31_irqhandler, NULL);
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
filtertimer_init(66000000/100); /* 初始化定时器,10ms */}定时器初始化
void filtertimer_init(unsigned int value){ EPIT1->CR = 0; //先清零
/* * CR寄存器: * bit25:24 01 时钟源选择Peripheral clock=66MHz * bit15:4 0 1分频 * bit3: 1 当计数器到0的话从LR重新加载数值 * bit2: 1 比较中断使能 * bit1: 1 初始计数值来源于LR寄存器值 * bit0: 0 先关闭EPIT1 */ EPIT1->CR = (1<<24 | 1<<3 | 1<<2 | 1<<1);
/* 计数值 */ EPIT1->LR = value;
/* 比较寄存器,当计数器值和此寄存器值相等的话就会产生中断 */ EPIT1->CMPR = 0;
GIC_EnableIRQ(EPIT1_IRQn); /* 使能GIC中对应的中断 */
/* 注册中断服务函数 */ system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)filtertimer_irqhandler, NULL);}GPIO ISR函数
void gpio1_16_31_irqhandler(void){ /* 开启定时器 */ filtertimer_restart(66000000/100);
/* 清除中断标志位 */ gpio_clearintflags(GPIO1, 18);}定时器 ISR函数
void filtertimer_irqhandler(void){ static unsigned char state = OFF;
if(EPIT1->SR & (1<<0)) /* 判断比较事件是否发生 */ { filtertimer_stop(); /* 关闭定时器 */ if(gpio_pinread(GPIO1, 18) == 0) /* KEY0 */ { state = !state; beep_switch(state); /* 反转蜂鸣器 */ } }
EPIT1->SR |= 1<<0; /* 清除中断标志位 */}辅助函数
void filtertimer_stop(void){ EPIT1->CR &= ~(1<<0); /* 关闭定时器 */}
void filtertimer_restart(unsigned int value){ EPIT1->CR &= ~(1<<0); /* 先关闭定时器 */ EPIT1->LR = value; /* 计数值 */ EPIT1->CR |= (1<<0); /* 打开定时器 */}