5933 字
30 分钟
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)未使用
0X18IRQ 中断(IRQ Interrupt)外部中断模式(IRQ)
0X1CFIQ 中断(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控制器#

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中声明:

/*
* 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的汇编代码为:

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函数
/* 复位中断 */
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 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中,如:

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

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

  1. 进入IRQ模式,保存上下文。ARM在进入IRQ模式会自动切换LR和SPSR,但不会自动保存其他寄存器,因此需手动保存r0-r3和r12寄存器(后续操作可能修改)。自动切换时,LR保存被中断指令的下一条指令-4
/* IRQ中断 */
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
  1. 保存现场。SPSR存储被中断前CPU的模式状态,必须保存SPSR以正确恢复中断前的执行环境。
mrs r0, spsr /* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
  1. 获取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 */
  1. 切换至SVC模式并调用C函数。IAR模式栈空间有限,切换至SVC模式(特权模式)以获得更大的栈空间,并允许C函数处理更复杂的逻辑,如中断嵌套。
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) 写入中断号,告知中断控制器该中断已处理完毕。
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:中断恢复
0x00MOV R1, R0准备执行已执行
0x04MOV R2, R1LRLR(恢复为状态A时的PC值-4)
0x08MOV R3, R2PCLR(已入栈)
0x0cMOV R4 ,R3
PC值0x080x12由LR值决定
LR值0x040x080x04

再通俗一点: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函数)#

  1. 中断初始化函数。向量表基地址设置为程序存放开始地址。
void int_init(void) {
GIC_Init(); // 初始化通用中断控制器,该函数由SDK提供
system_irqtable_init(); // 初始化中断函数表
__set_VBAR((uint32_t)0x87800000); // 设置向量表基地址
}
  1. 中断向量表初始化。将所有中断初始化为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);
}
}
  1. 中断注册函数。用于绑定中断服务函数和中断源。
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) {
irqTable[irq].irqHandler = handler; // 注册处理函数
irqTable[irq].userParam = userParam; // 绑定用户参数
}
  1. 中断分发器。默认允许中断嵌套。
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. 初始化定时器
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); /* 打开定时器 */
}
IMX6ULL外部中断配置
https://arkmostima.pages.dev/posts/imx6ull外部中断配置/
作者
Aki
发布于
2025-03-04
许可协议
CC BY-NC-SA 4.0