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 |
|
指定中断发生后调用对应的中断复位函数。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
中声明:
1 |
|
不过,这里只给出了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的汇编代码为:
1 |
|
总结:
- 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函数
1 |
|
其他中断
1 |
|
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 |
|
IRQ中断服务函数的编写大致分为以下步骤:
- 进入IRQ模式,保存上下文。ARM在进入IRQ模式会自动切换LR和SPSR,但不会自动保存其他寄存器,因此需手动保存r0-r3和r12寄存器(后续操作可能修改)。自动切换时,LR保存被中断指令的下一条指令
-4
。
1 |
|
- 保存现场。SPSR存储被中断前CPU的模式状态,必须保存SPSR以正确恢复中断前的执行环境。
1 |
|
- 获取GIC中断号。通过MRC向cp15读取寄存器以获取GIC基址,从而计算GICC_IAR地址并读取,读取后自动标记该中断为active状态,获取到的中断号
r0
作为参数传递给C语言ISR。
1 |
|
- 切换至SVC模式并调用C函数。IAR模式栈空间有限,切换至SVC模式(特权模式)以获得更大的栈空间,并允许C函数处理更复杂的逻辑,如中断嵌套。
1 |
|
- 恢复模式并发送中断结束信号。向向GIC的End of Interrupt Register (GICC_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 |
|
中断驱动文件(C函数)
- 中断初始化函数。向量表基地址设置为程序存放开始地址。
1 |
|
- 中断向量表初始化。将所有中断初始化为default_irqhandler(死循环),强制开发者显式注册有效ISR,避免未处理中断导致不可控行为。
1 |
|
- 中断注册函数。用于绑定中断服务函数和中断源。
1 |
|
- 中断分发器。默认允许中断嵌套。
1 |
|
中断处理全流程
- 硬件触发:外设触发中断,GIC将中断请求发送至CPU。
- 汇编入口:CPU跳转至IRQ_Handler,保存上下文并调用system_irqhandler(giccIar)。
- 中断分发:
- 解析giccIar获取有效中断号
- 查表irqTable[intNum]获取处理函数和参数
- ISR执行:执行用户注册的函数(如uart_isr),处理具体中断任务。
- 中断结束:汇编代码写GICC_EOIR通知GIC处理完成。
示例(定时器按键消抖)
实现功能:
- 按下按键,进入外部中断,在中断中开启定时器
- 定时器中断中完成消抖延时,中断周期即为延时时间。如果定时中断触发,表示消抖完成,执行按键处理函数
按键初始化
- 设置GPIO复用和Config
- 填充gpio结构体,初始化gpio
- 使能中断,注册ISR(绑定中断源和对应的ISR)
- 初始化定时器
1 |
|
定时器初始化
1 |
|
GPIO ISR函数
1 |
|
定时器 ISR函数
1 |
|
辅助函数
1 |
|