嵌入式裸机开发- GPIO 中断

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、就这样,整个中断系统有条不紊的运行着。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值