中断
Cortex-A7 中断系统详解
中断系统分为四部分:中断向量表、中断服务函数、中断控制器、中断使能。
中断向量表
Cortex-A7 内核有 8 个异常中断
和Cortex-M不同,
Cortex-A7的外设中断(I2C、 SPI、定时器等)都在IRQ中断中。
主要的7个中断:
①、
复位中断(Rest)
, CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR 等等。
②、
未定义指令中断(Undefined Instruction)
,如果指令不能识别的话就会产生此中断。
③、
软中断(Software Interrupt,SWI)
,由 SWI 指令引起的中断, Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。
④、
指令预取中止中断(Prefetch Abort)
,预取指令的出错的时候会产生此中断。
⑤、
数据访问中止中断(Data Abort)
,访问数据出错的时候会产生此中断。
⑥、
IRQ 中断(IRQ Interrupt)
,外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
⑦、
FIQ 中断(FIQ Interrupt)
,快速中断,如果需要快速处理中断的话就可以使用此中。
这样使用中断就可以写在start.S启动函数中。
中断控制器
GIC
(Generic Interrupt Controller) 是 ARM 公司给 Cortex-A/R 内核提供的一个
中断控制器
,类似 Cortex-M 内核中的NVIC。
目前
GIC 有 4 个版本:V1~ V4
, V1 是最老的版本,已经被废弃了。V2~V4 目前正在大量的使用。 GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。
不同版本对应不同的IP核。
I.MX6U 是 Cortex-A 内核的,因此主要学习
GIC V2版本
,最多支持 8 个核。 ARM 针对 GIC V2 就开发出了 GIC400 这个
中断控制器 IP 核
。GIC中断控制器负责管理各个中断,通过四个信号给 ARM 内核汇报中断情况。
其中,VFIQ:虚拟快速 FIQ、VIRQ:虚拟外部 IRQ、FIQ:快速中断 IRQ、IRQ:外部中断 IRQ。
1、中断源分类
GIC 将众多的中断源分为分为三类:
①、
SPI(Shared Peripheral Interrupt),共享中断
,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、
PPI(Private Peripheral Interrupt),私有中断
,我们说了 GIC 是支持多核的,每个核肯定
有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、
SGI(Software-generated Interrupt),软件中断
,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
每个中断源分配一个唯一
中断ID
,每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。
ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断。
I.MX6U的中断:128 个SPI中断 ID+32 个PPI 和 SGI 中断ID=
160个中断。
具体查看
《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节
。NXP 官方 SDK中的文件 MCIMX6Y2C.h已经枚举enum了各个中断的中断ID号。
2、GIC 逻辑分块
GIC 架构分为了两个逻辑块:
分发器端
(Distributor) 和
CPU 接口端
(CPU Interface)。
Distributor(分发器端):分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下:
①、全局中断使能控制。
②、控制每一个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组 0 还是组 1。
CPU Interface(CPU 接口端): CPU 接口端就是分发器和 CPU Core 之间的桥梁, CPU 接口端主要工作如下:
①、使能或者关闭发送到 CPU Core 的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
core_ca7.h 定义了 GIC 结构体,里边儿有好多寄存器。
/*
* GIC 寄存器描述结构体,
* GIC 分为分发器端和 CPU 接口端
*/
1 typedef struct
2 {
3 /* 分发器端寄存器 */
4 uint32_t RESERVED0[1024];
5 __IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */
6 __IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */
7 __IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */
8 uint32_t RESERVED1[29];
9 __IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */
10 uint32_t RESERVED2[16];
11 __IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */
12 uint32_t RESERVED3[16];
13 __IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */
14 uint32_t RESERVED4[16];
15 __IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */
16 uint32_t RESERVED5[16];
17 __IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */
18 uint32_t RESERVED6[16];
19 __IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */
20 uint32_t RESERVED7[16];
21 __IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */
22 uint32_t RESERVED8[16];
23 __IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */
24 uint32_t RESERVED9[128];
25 __IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */
26 uint32_t RESERVED10[128];
27 __IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */
28 uint32_t RESERVED11[32];
29 __IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */
30 __IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */
31 uint32_t RESERVED12[112];原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
425
I.MX6U 嵌入式 Linux 驱动开发指南
32 __OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */
33 uint32_t RESERVED13[3];
34 __IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */
35 __IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */
36 uint32_t RESERVED14[40];
37 __IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */
38 __IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */
39 __IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */
40 __IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */
41 __IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */
42 __IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */
43 __IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */
44 __IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */
45 __IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */
46 __IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */
47 __IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */
48 __IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */
49
50 /* CPU 接口端寄存器 */
51 __IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */
52 __IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */
53 __IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */
54 __IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */
55 __OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */
56 __IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */
57 __IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */
58 __IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */
59 __IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */
60 __OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */
61 __IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */
62 uint32_t RESERVED15[41];
63 __IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */
64 uint32_t RESERVED16[3];
65 __IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */
66 uint32_t RESERVED17[6];
67 __IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */
68 uint32_t RESERVED18[960];
69 __OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */
70 } GIC_Type;
C_IAR寄存器存储了中断号
,其地址=GIC基地址+0x2000+0x0C。
当一个中断处理完成以后必须向
GICC_EOIR 寄存器
写入其中断号表示中断处理完成。
CP15 协处理器
参考下面两份文 档: 《ARMArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》 第 1469 页“B3.17 Oranizationof the CP15 registers in a VMSA implementation”。《Cortex-A7 Technical ReferenceManua.pdf》 第55 页“Capter 4 System Control”。
CP15 协处理器一般
用于存储系统管理
,但是在中断中也会使用到, CP15 协处理器一共有
16 个 32 位寄存器,c0~c15
。
CP15 协处理器的访问通过如下另个指令完成:
MRC
: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR
: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
cond:指令执行的条件码,如果忽略的话就表示无条件执行。
opc1:协处理器要执行的操作码。
Rt: ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
CRn: CP15 协处理器的目标寄存器。
CRm: 协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
CRm 设置为 C0,否则结果不可预测。
opc2: 可选的协处理器特定操作码,当不需要的时候要设置为 0。
一般的
MRC p15, 0, r0, c0, c0, 0 ;将 CP15 中 C0 寄存器的值读取到 R0 寄存器中
MCR p15, 0, r0, c0, c0, 0 ;将RO寄存器值写到CP15 中 C0 寄存器
指令中的 CRn、 opc1、 CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是不同的。
1、 c0 寄存器
CRn=c0, opc1=0, CRm=c0, opc2=0 的时候就表示此时的 c0 就是
MIDR 寄存器
,也就是主 ID 寄存器,这个也是 c0 的基本作用。
bit31:24:厂商编号, 0X41, ARM。
bit23:20:内核架构的主版本号, ARM 内核版本一般使用 rnpn 来表示,比如 r0p1,其中 r0后面的 0 就是内核架构主版本号。
bit19:16:架构代码, 0XF, ARMv7 架构。
bit15:4:内核版本号, 0XC07, Cortex-A7 MPCore 内核。
bit3:0:内核架构的次版本号, rnpn 中的 pn,比如 r0p1 中 p1 后面的 1 就是次版本号。
2、 c1 寄存器
CRn=c1, opc1=0, CRm=c0, opc2=0 的时候就表示此时的 c1 就是
SCTLR 寄存器
,也就是系统控制寄存器,这个是 c1 的基本用。
bit13: V ,
中断向量表基地址选择位
,**为 0 的话中断向量表基地址为 0X00000000,软件可以使用 VBAR 来重映射此基地址,也就是中断向量表重定位。**为 1 的话中断向量表基地址为0XFFFF0000,此基地址不能被重映射。
bit12: I, I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache。
bit11: Z,分支预测使能位,如果开启 MMU 的话,此位也会使能。
bit10: SW, SWP 和 SWPB 使能位,当为 0 的话关闭 SWP 和 SWPB 指令,当为 1 的时候就使能 SWP 和 SWPB 指令。
bit9:3:未使用,保留。
bit2: C, D Cache 和缓存一致性使能位,为 0 的时候禁止 D Cache 和缓存一致性,为 1 时使能。
bit1: A,内存对齐检查使能位,为 0 的时候关闭内存对齐检查,为 1 的时候使能内存对齐检查。
bit0: M, MMU 使能位,为 0 的时候禁止 MMU,为 1 的时候使能 MMU。
3、 c12 寄存器
CRn=c12, opc1=0, CRm=c0, opc2=0 的时候就表示此时 c12 为
VBAR 寄存器
,也就是
向量表基地址寄存器
,将向量表写入该寄存器。
ldr r0, =0X87800000 ; r0=0X87800000
MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中,即 c12=0X87800000
4、 c15 寄存器
CRn=c15, opc1=4, CRm=c0, opc2=0 的时候就表示此时 c15 为
CBAR 寄存器
,也就是
GIC 基地址寄存器
,从该寄存器读取GIC基地址。
MRC p15, 4, r1, c15, c0, 0 ;获取 GIC 基地址
ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址
LDR r0, [r1, #0XC] ;读取 CPU 接口端起始地址+0XC 处的寄存器值,也就是寄存器;GIC_IAR 的值
中断使能
中断使能包括两部分:
IRQ 或者 FIQ 总中断使能
和
ID0~ID1019 这 1020个中断源的使能。
IRQ 和 FIQ 总中断使能
IRQ 和 FIQ 分别是外部中断和快速中断的总开关,类似于51单片机里总中断使能EA。
CPSR寄存器中I、F位
控制总中断使能/禁止。
case 1:
使用指令MRS、MSR间接控制。
case 2:
使用特殊指令。
ID0~ID1019 中断使能和禁止
总中断开启后,还需要各个中断源自己开启中断。 Cortex-A7 内核来说中断 ID 只使用了 512 个(512/32=16 ),需要GIC中断控制中的
16 个
GICD_ISENABLER 寄存器
来完成中断的使能,
16 个
GICD_ICENABLER 寄存器
来完成中断的禁止。
中断优先级设置
Cortex-A7 的中断优先级也可以分为
抢占优先级
和
子优先级
。最多可以支持 256 个优先级,数字越小,优先级越高!
GICC_PMR 寄存器
GICC_PMR 寄存器来决定使用几级优先级,GICC_PMR 寄存器只有低 8 位有效。
而I.MX6U 只选择了32 个优先级
。
GICC_BPR寄存器
GICC_BPR寄存器来决定抢占优先级和子优先级各占多少位,只有低 3 位有效,
共8种可能
。
I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。
D_IPRIORITYR寄存器
Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有
512 个 D_IPRIORITYR 寄存器
。
如果优先级个数为 32 的话,使用
寄存器 D_IPRIORITYR 的 bit7:3
来设置优先级,也就是说实际的优先级要左移 3 位。
优先级设置总结
①、设置寄存器 GICC_PMR,配置优先级个数。
②、设置抢占优先级和子优先级位数。
③、设置指定中断 ID 的优先级,也就是设置外设优先级。
驱动编写
移植 SDK 包中断相关文件
core_ca7.h 中
用到了GIC相关的API函数,可以很容易的控制寄存器。
编写 start.S 文件
.global _start /*全局标号 */
/*中断向量表 */
_start:
LDR PC,=Reset_Handler
LDR PC,=Undefine_Handler
LDR PC,=SVC_Handler
LDR PC,=PrefAbort_Handler
LDR PC,=DataAbort_Handler
LDR PC,=NotUsed_Handler
LDR PC,=IRQ_Handler
LDR PC,=FIQ_Handler
/*复位中断 */
Reset_Handler:
cpsid i ;关闭IRQ总中断
/* 关闭 I,DCache 和 MMU 采取读-改-写的方式。 */
MRC p15,0,R0,C1,C0,0
BIC R0,R0,#(1<<12)
BIC R0,R0,#(1<<2)
BIC R0,R0,#(1<<1)
BIC R0,R0,#(1<<11)
BIC R0,R0,#0x1
MCR p15,0,R0,C1,C0,0
/* 设置各个模式下的栈指针,
* 注意: IMX6UL 的堆栈是向下增长的!
* 堆栈指针地址一定要是 4 字节地址对齐的!!!
* DDR 范围:0X80000000~0X9FFFFFFF 或者 0X8FFFFFFF
*/
/* 进入 IRQ 模式 */
MRS R0,CPSR
BIC R0,R0,#0x1f
ORR R0,R0,#0x12
MSR CPSR,R0
LDR SP,=0x80600000 ;2M
/* 进入 SYS 模式 */
MRS R0,CPSR
BIC R0,R0,#0x1f
ORR R0,R0,#0x1f
MSR CPSR,R0
LDR SP,=0x80400000 ;2M
/* 进入 SVC 模式 */
MRS R0,CPSR
BIC R0,R0,#0x1f
ORR R0,R0,#0x13
MSR CPSR,R0
LDR SP,=0x80200000 ;2M
cpsie i ;关闭IRQ总中断
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 R,=DataAbort_Handler
BX R0
/* 未使用的中断 */
NotUsed_Handler:
LDR R0,=NotUsed_Handler
BX R0
/* IRQ 中断!重点!!!!! */
IRQ_Handler:
/*IRQ模式 */
/*保护现场 */
PUSH {LR}
PUSH {R0-R3,R12}
MRS R0,SPSR ;保险点儿,把备份的也入栈
PUSH {R0}
/*获取当前中断号并保存 */
MRC p15,4,R1,C15,C0,0 ;GIC 基地址
ADD R1,R1,#0x2000 ;CPU 接口端基地址
LDR R0,[R1,0x0C] ;GICC_IAR 保存着当前发生中断的中断号
PUSH {R0,R1}
/*进入SVC模式,执行中断服务函数 */
CPS #0x13
PUSH {LR} ;将返回地址存到LR中,这里先保护起来
LDR R2,=system_irqhandler ;进入C语言中断服务函数,利用R0寄存器传参数。
BLX R2
POP {LR}
/*回到IRQ模式 */
/*恢复现场 */
CPS #0x12
POP {R0,R1}
STR R0,[R1,#0x10] ;将中断号存给GICC_EOIR 寄存器
POP {R0}
MSR SPSR_CXSF,R0
POP {R0-R3,R12}
POP {LR}
SUBS PC,LR,#4 ;ARM 的指令是三级流水线,LR地址的前一条地址的指令刚翻译指令,还没执行呢,所以要-4字节,退回一条指令开始取指令,一条指令也不拉下。
/* FIQ 中断 */
FIQ_Handler:
LDR R0,=FIQ_Handler
BX R0
这里学到了,汇编中调用 C 函数如何实现参数传递呢?根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在
汇编调用 C 函数的时候建议形参不要超过 4 个, 形参可以由 r0~r3 这四个寄存器来传递
,如果形参大于 4 个, 那么大于 4 个的部分要使用堆栈进行传递。所以进入 C函数 system_irqhandler 时候,
通过R0将中断号传入中断函数中
。
通用中断驱动文件编写
中断服务函数 IRQ_Handler 中调用了
C 函数 system_irqhandler 来处理具体的中断,内部160 个中断处理函数
,我们可以将这些
中断处理函数放到一个数组里面
,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后
函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行
即可。
bsp_int.h
#ifndef _BSP_INT_H
#define _BSP_INT_H
#include "imx6ul.h"
/* 中断服务函数形式 */
typedef void (*system_irq_handler_t) (unsigned int giccIar,void *param); //定义一个函数指针别名
/* 中断服务函数结构体*/
typedef struct
{
system_irq_handler_t irqHandler; // 中断处理函数 函数指针
void *userParam; // 中断处理函数参数
}sys_irq_handler_t;
void default_irqhandler(unsigned int giccIar,void *param);
void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler,void *usrParam);
void system_irqtable_init(void);
void int_init(void);
void system_irqhandler(unsigned int giccIar);
#endif
bsp_int.c
#include "bsp_int.h"
static unsigned int irqNesting; /* 中断嵌套计数器 */
static sys_irq_handler_t irqTable[NUMBER_OF_INT_VECTORS]; //中断服务函数表
//中断初始化函数
void int_init(void)
{
GIC_Init(); //初始化GIC
system_irqtable_init(); //中断服务函数表初始化
__set_VBAR((uint32_t)0x87800000); //写入中断向量表偏移
}
//中断服务函数表初始化
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);
}
}
/*
* @description : 给指定的中断号注册中断服务函数
* @param - irq : 要注册的中断号
* @param - handler : 要注册的中断处理函数
* @param - usrParam : 中断服务处理函数参数
* @return : 无
*/
void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler,void *usrParam)
{
irqTable[irq].irqHandler=handler;
irqTable[irq].userParam=usrParam;
}
//默认中断服务函数
void default_irqhandler(unsigned int giccIar,void *param)
{
while(1)
{
}
}
/*
* @description : C 语言中断服务函数, irq 汇编中断服务函数会
调用此函数,此函数通过在中断服务列表中查
找指定中断号所对应的中断处理函数并执行。
* @param - giccIar : 中断号
* @return : 无
*/
void system_irqhandler(unsigned int giccIar)
{
uint32_t intNum=giccIar & 0x3ffUL;
//检查中断号是否符合要求
if((intNum==1020)||(intNum>NUMBER_OF_INT_VECTORS)) return;
//调用对应的中断服务函数
irqNesting++;/* 中断嵌套计数器加一 */
irqTable[giccIar].irqHandler(giccIar,irqTable[giccIar].userParam);
irqNesting--;/* 中断执行完成,中断嵌套寄存器减一 */
}
这次的中断驱动文件用到了函数指针的概念,不是很熟悉,将
中断服务函数指针类型
和用户参数放到
中断服务函数结构体
中,再创建一个
中断服务函数结构体类型的表
,即可很好通过中断号的管理各个中断服务函数。
从汇编中
IRQ中断
进入决定性C
函数system_irqhandler()
,再根据中断号确定进入哪个
中断源的中断服务函数
。
GPIO 驱动文件
bsp_gpio.h
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件头文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
V2.0 2019/1/4 左忠凯修改
添加GPIO中断相关定义
***************************************************************/
/*
* 枚举类型和结构体定义
*/
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0U, /* 输入 */
kGPIO_DigitalOutput = 1U, /* 输出 */
} gpio_pin_direction_t;
/*
* GPIO中断触发类型枚举
*/
typedef enum _gpio_interrupt_mode
{
kGPIO_NoIntmode = 0U, /* 无中断功能 */
kGPIO_IntLowLevel = 1U, /* 低电平触发 */
kGPIO_IntHighLevel = 2U, /* 高电平触发 */
kGPIO_IntRisingEdge = 3U, /* 上升沿触发 */
kGPIO_IntFallingEdge = 4U, /* 下降沿触发 */
kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */
} gpio_interrupt_mode_t;
/*
* GPIO配置结构体
*/
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
gpio_interrupt_mode_t interruptMode; /* 中断方式 */
} gpio_pin_config_t;
/* 函数声明 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode);
void gpio_enableint(GPIO_Type* base, unsigned int pin);
void gpio_disableint(GPIO_Type* base, unsigned int pin);
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
#endif
bsp_gpio.c
#include "bsp_gpio.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
V2.0 2019/1/4 左忠凯修改:
修改gpio_init()函数,支持中断配置.
添加gpio_intconfig()函数,初始化中断
添加gpio_enableint()函数,使能中断
添加gpio_clearintflags()函数,清除中断标志位
***************************************************************/
/*
* @description : GPIO初始化。
* @param - base : 要初始化的GPIO组。
* @param - pin : 要初始化GPIO在组内的编号。
* @param - config : GPIO配置结构体。
* @return : 无
*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
base->IMR &= ~(1U << pin);
if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
{
base->GDIR &= ~( 1 << pin);
}
else /* 输出 */
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic); /* 设置默认输出电平 */
}
gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */
}
/*
* @description : 读取指定GPIO的电平值 。
* @param - base : 要读取的GPIO组。
* @param - pin : 要读取的GPIO脚号。
* @return : 无
*/
int gpio_pinread(GPIO_Type *base, int pin)
{
return (((base->DR) >> pin) & 0x1);
}
/*
* @description : 指定GPIO输出高或者低电平 。
* @param - base : 要输出的的GPIO组。
* @param - pin : 要输出的GPIO脚号。
* @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平
* @return : 无
*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
if (value == 0U)
{
base->DR &= ~(1U << pin); /* 输出低电平 */
}
else
{
base->DR |= (1U << pin); /* 输出高电平 */
}
}
/*
* @description : 设置GPIO的中断配置功能
* @param - base : 要配置的IO所在的GPIO组。
* @param - pin : 要配置的GPIO脚号。
* @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
* @return : 无
*/
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
{
volatile uint32_t *icr;
uint32_t icrShift;
icrShift = pin;
base->EDGE_SEL &= ~(1U << pin);
if(pin < 16) /* 低16位 */
{
icr = &(base->ICR1);
}
else /* 高16位 */
{
icr = &(base->ICR2);
icrShift -= 16;
}
switch(pin_int_mode)
{
case(kGPIO_IntLowLevel):
*icr &= ~(3U << (2 * icrShift));
break;
case(kGPIO_IntHighLevel):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
break;
case(kGPIO_IntRisingEdge):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
break;
case(kGPIO_IntFallingEdge):
*icr |= (3U << (2 * icrShift));
break;
case(kGPIO_IntRisingOrFallingEdge):
base->EDGE_SEL |= (1U << pin);
break;
default:
break;
}
}
/*
* @description : 使能GPIO的中断功能
* @param - base : 要使能的IO所在的GPIO组。
* @param - pin : 要使能的GPIO在组内的编号。
* @return : 无
*/
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{
base->IMR |= (1 << pin);
}
/*
* @description : 禁止GPIO的中断功能
* @param - base : 要禁止的IO所在的GPIO组。
* @param - pin : 要禁止的GPIO在组内的编号。
* @return : 无
*/
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{
base->IMR &= ~(1 << pin);
}
/*
* @description : 清除中断标志位(写1清除)
* @param - base : 要清除的IO所在的GPIO组。
* @param - pin : 要清除的GPIO掩码。
* @return : 无
*/
void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
base->ISR |= (1 << pin);
}
不用重复造轮子,看懂了直接用,里边儿设置寄存器某几位时,可先将几位清零,在或上。
按键中断驱动文件
bsp_key.h
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "imx6ul.h"
#include "bsp_delay.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_delay.h"
void key_init(void);
void gpio1_io18_irqhandler(void);
int key_getvalue(void);
#endif
bsp_key.c
#include "bsp_key.h"
//按键初始化
void key_init(void)
{
//IO复用
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
//属性配置
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
//GPIO1_IO18配置 输入 中断
gpio_pin_config_t key_config;
key_config.direction=kGPIO_DigitalInput;
key_config.outputLogic=1;
key_config.interruptMode=kGPIO_IntFallingEdge;
gpio_init(GPIO1,18,&key_config);
//使能 GIC 中断、注册中断服务函数、使能 GPIO 中断
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_io18_irqhandler,NULL);
gpio_enableint(GPIO1,18);
}
//GPIO1_IO18 最终的中断处理函数
void gpio1_io18_irqhandler(void)
{
static unsigned char state = 0;
delay_ms(10);
if(gpio_pinread(GPIO1,18)==0)
{
state=!state;
beep_switch(state);
}
gpio_clearintflags(GPIO1,18);
}
//延时读取按键值
int key_getvalue(void)
{
int ret=1;
static unsigned char release=1; //考虑按键释放松开
if(release&&((((GPIO1->DR)>>18) & 0x01 )== 0))
{
release=0;
delay_ms(10);
if((((GPIO1->DR)>>18) & 0x01 )== 0)
{
ret=0;
}
}
else if((((GPIO1->DR)>>18) & 0x01 )== 1)
{
release=1;
ret=1;
}
return ret;
}
注意每次要手动清除中断标志,还有
汇编的注释要用“@”
不能“;”。代码太多了,嗨~,要求不要太高,知道那个原理和架构,知道怎么配置就行,起码是能看懂。
总结
总结一下这个中断,
1、start.S汇编文件需要有中断向量表(8个中断)和2个主要中断(复位中断、IRQ中断)
2、int.c文件初始化GIC中断控制器(获取GIC基地址等)、中断服务函数表、向CP15协处理器C12写中断向量表偏移地址。虽说GIC管理中断,但也需要看CP15的脸色。
3、中断源需要中断的时候,配置完自己中断规则并写了自己的中断服务函数之后,
自己开启一下自己的
GIC中断源中断
;
再把自己的中断服务函数到表中
注册一下
;
最后开启这个
外设自己的中断
(如GPIO中断使能)。
4、中断过程,程序正慢悠悠的运行着,
突然出发了中断,进入了IRQ中断,开启IRQ总中断,获取中断号;
进入C语言管理的system中断函数,根据中断号查表,找到对应的中断服务函数,告诉那个中断源:“老哥,你的中断来了,来进你的中断服务函数吧”,运行完回归正常。
5、就这样,整个中断系统有条不紊的运行着。