IMX6ULL外部中断配置

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)

中断向量表

1
2
3
4
5
6
7
8
9
10
11
.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控制器

GIC v2框图

类似于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上去)。分发器收集所有的中断源,可控制每个中断的优先级,具体功能有:
    1. 全局中断使能控制
    2. 控制每个中断的使能或关闭
    3. 设置中断优先级
    4. 设置每个中断的目标处理器列表
    5. 设置每个外部中断的触发模式:电平触发或边沿触发
    6. 设置每个中断属于组 0 还是组 1
  • CPU Interface:CPU接口端,具体功能有:
    1. 使能或者关闭发送到 CPU Core 的中断请求信号
    2. 应答中断
    3. 通知中断处理完成
    4. 设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core
    5. 定义抢占策略
    6. 当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core

GIC控制器的所有寄存器在core_ca7.h中声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* 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寄存器为例:

CP15 c0寄存器参数搭配

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

对于c15寄存器:
CP15 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的汇编代码为:

1
2
3
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_ISENABLERnGICD_ISENABLER0的bit[15:0]对应ID15 - 0的SGI中断,bit[31:16]对应ID31 - 16的PPI中断
  • GICD_ICENABLERnGICD_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

总结一下,中断优先级设置分三部分:

  1. 设置GICC_PMR,配置优先级个数,IMX6U为32级
  2. 设置抢占优先级和子优先级位数,一般默认所有位数都为抢占优先级
  3. 设置指定中断ID的优先级

复位中断服务函数

整个启动文件在设置完中断向量表后,要进行复位中断的设置,其分为以下步骤:

  1. 设置复位中断:关闭IRQ,然后关闭I/D Cache、MMU、对齐检测和分支预测
  2. 进行中断向量表重映射
  3. 设置IRQ模式、SYS模式和SVC模式下的栈指针,栈大小均为2MB
  4. 打开IRQ中断
  5. 跳转至main函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* 复位中断 */	
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函数 */

其他中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 未定义中断 */
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 r0

IRQ中断服务函数

所有的外部中断都会触发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中,如:

1
2
mrs r0, spsr                /* 读取spsr寄存器 */
msr spsr_cxsf, r0 /* 恢复spsr */

IRQ中断服务函数的编写大致分为以下步骤:

  1. 进入IRQ模式,保存上下文。ARM在进入IRQ模式会自动切换LR和SPSR,但不会自动保存其他寄存器,因此需手动保存r0-r3和r12寄存器(后续操作可能修改)。自动切换时,LR保存被中断指令的下一条指令-4
1
2
3
4
/* IRQ中断 */
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
  1. 保存现场。SPSR存储被中断前CPU的模式状态,必须保存SPSR以正确恢复中断前的执行环境。
1
2
mrs r0, spsr				/* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
  1. 获取GIC中断号。通过MRC向cp15读取寄存器以获取GIC基址,从而计算GICC_IAR地址并读取,读取后自动标记该中断为active状态,获取到的中断号r0作为参数传递给C语言ISR。
1
2
3
4
5
6
7
8
9
10
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 */
  1. 切换至SVC模式并调用C函数。IAR模式栈空间有限,切换至SVC模式(特权模式)以获得更大的栈空间,并允许C函数处理更复杂的逻辑,如中断嵌套。
1
2
3
4
5
cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */

push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有中断号参数,保存在R0寄存器中 */
  1. 恢复模式并发送中断结束信号。向向GIC的End of Interrupt Register (GICC_EOIR) 写入中断号,告知中断控制器该中断已处理完毕。
1
2
3
4
pop {lr}					/* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
  1. 恢复中断前状态并返回。

这里是一个很容易搞错的点。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。

1
2
3
4
5
6
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函数)

  1. 中断初始化函数。向量表基地址设置为程序存放开始地址。
1
2
3
4
5
void int_init(void) {
GIC_Init(); // 初始化通用中断控制器,该函数由SDK提供
system_irqtable_init(); // 初始化中断函数表
__set_VBAR((uint32_t)0x87800000); // 设置向量表基地址
}
  1. 中断向量表初始化。将所有中断初始化为default_irqhandler(死循环),强制开发者显式注册有效ISR,避免未处理中断导致不可控行为。
1
2
3
4
5
6
7
8
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);
}
}
  1. 中断注册函数。用于绑定中断服务函数和中断源。
1
2
3
4
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) {
irqTable[irq].irqHandler = handler; // 注册处理函数
irqTable[irq].userParam = userParam; // 绑定用户参数
}
  1. 中断分发器。默认允许中断嵌套。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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--; // 嵌套计数减少
}

中断处理全流程

  1. 硬件触发:外设触发中断,GIC将中断请求发送至CPU。
  2. 汇编入口:CPU跳转至IRQ_Handler,保存上下文并调用system_irqhandler(giccIar)。
  3. 中断分发:
    • 解析giccIar获取有效中断号
    • 查表irqTable[intNum]获取处理函数和参数
  4. ISR执行:执行用户注册的函数(如uart_isr),处理具体中断任务。
  5. 中断结束:汇编代码写GICC_EOIR通知GIC处理完成。

示例(定时器按键消抖)

实现功能:

  1. 按下按键,进入外部中断,在中断中开启定时器
  2. 定时器中断中完成消抖延时,中断周期即为延时时间。如果定时中断触发,表示消抖完成,执行按键处理函数

按键初始化

  1. 设置GPIO复用和Config
  2. 填充gpio结构体,初始化gpio
  3. 使能中断,注册ISR(绑定中断源和对应的ISR)
  4. 初始化定时器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 */
}

定时器初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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函数

1
2
3
4
5
6
7
8
void gpio1_16_31_irqhandler(void)
{
/* 开启定时器 */
filtertimer_restart(66000000/100);

/* 清除中断标志位 */
gpio_clearintflags(GPIO1, 18);
}

定时器 ISR函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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; /* 清除中断标志位 */
}

辅助函数

1
2
3
4
5
6
7
8
9
10
11
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); /* 打开定时器 */
}

IMX6ULL外部中断配置
http://akichen891.github.io/2025/03/04/IMX6ULL外部中断配置/
作者
Aki
发布于
2025年3月4日
更新于
2025年3月5日
许可协议