将 SDK 包中的文件 core_ca7.h 拷贝到本章试验工程中的“imx6ul”文件夹中,参考试验“9_int”中 core_ca7.h 进行修改。主要留下和GIC 相关的内容,我们重点是需要 core_ca7.h 中的 10 个API 函数,这 10 个函数如表所示:
GIC 相关 API 操作函数
移植好 core_ca7.h 以后,修改文件 imx6ul.h,在里面加上如下一行代码:
#include "core_ca7.h"
重新在 start.S 中输入如下内容:
1 .global _start /* 全局标号 */ 2 3 /* 4 * 描述: _start 函数,首先是中断向量表的创建 5 */ 6 _start: 7 ldr pc, =Reset_Handler /* 复位中断 */ 8 ldr pc, =Undefined_Handler /* 未定义指令中断 */ 9 ldr pc, =SVC_Handler /* SVC(Supervisor)中断*/ 10 ldr pc, =PrefAbort_Handler /* 预取终止中断 */ 11 ldr pc, =DataAbort_Handler /* 数据终止中断 */ 12 ldr pc, =NotUsed_Handler /* 未使用中断 */ 13 ldr pc, =IRQ_Handler /* IRQ 中断 */ 14 ldr pc, =FIQ_Handler /* FIQ(快速中断) */ 15 16 /* 复位中断 */ 17 Reset_Handler: 18 19 cpsid i /* 关闭全局中断 */ 20 21 /* 关闭 I,DCache 和 MMU 22 * 采取读-改-写的方式。 23 */ 24 mrc p15, 0, r0, c1, c0, 0 /* 读取 CP15 的 C1 寄存器到 R0 中 */ 25 bic r0, r0, #(0x1 << 12) /* 清除 C1 的 I 位,关闭 I Cache */ 26 bic r0, r0, #(0x1 << 2) /* 清除 C1 的 C 位,关闭 D Cache */ 27 bic r0, r0, #0x2 /* 清除 C1 的 A 位,关闭对齐检查 */ 28 bic r0, r0, #(0x1 << 11) /* 清除 C1 的 Z 位,关闭分支预测 */ 29 bic r0, r0, #0x1 /* 清除 C1 的 M 位,关闭 MMU */ 30 mcr p15, 0, r0, c1, c0, 0 /* 将 r0 的值写入到 CP15 的 C1 中 */ 31 32 33 #if 0 34 /* 汇编版本设置中断向量表偏移 */ 35 ldr r0, =0X87800000 36 37 dsb 38 isb 39 mcr p15, 0, r0, c12, c0, 0 40 dsb 41 isb 42 #endif 43 44 /* 设置各个模式下的栈指针, 45 * 注意:IMX6UL 的堆栈是向下增长的! 46 * 堆栈指针地址一定要是 4 字节地址对齐的!!! 47 * DDR 范围:0X80000000~0X9FFFFFFF 或者 0X8FFFFFFF 48 */ 49 /* 进入 IRQ 模式 */ 50 mrs r0, cpsr 51 bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */ 52 orr r0, r0, #0x12 /* r0 或上 0x12,表示使用 IRQ 模式 */ 53 msr cpsr, r0 /* 将 r0 的数据写入到 cpsr 中 */ 54 ldr sp, =0x80600000 /* IRQ 模式栈首地址为 0X80600000,大小为 2MB */ 55 56 /* 进入 SYS 模式 */ 57 mrs r0, cpsr 58 bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */ 59 orr r0, r0, #0x1f /* r0 或上 0x13,表示使用 SYS 模式 */ 60 msr cpsr, r0 /* 将 r0 的数据写入到 cpsr 中 */ 61 ldr sp, =0x80400000 /* SYS 模式栈首地址为 0X80400000,大小为 2MB */ 62 63 /* 进入 SVC 模式 */ 64 mrs r0, cpsr 65 bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */ 66 orr r0, r0, #0x13 /* r0 或上 0x13,表示使用 SVC 模式 */ 67 msr cpsr, r0 /* 将 r0 的数据写入到 cpsr 中 */ 68 ldr sp, =0X80200000 /* SVC 模式栈首地址为 0X80200000,大小为 2MB */ 69 70 cpsie i /* 打开全局中断 */ 71 72 #if 0 73 /* 使能 IRQ 中断 */ 74 mrs r0, cpsr /* 读取 cpsr 寄存器值到 r0 中 */ 75 bic r0, r0, #0x80 /* 将 r0 寄存器中 bit7 清零,也就是 CPSR 中 76 * 的 I 位清零,表示允许 IRQ 中断 77 */ 78 msr cpsr, r0 /* 将 r0 重新写入到 cpsr 中 */ 79 #endif 80 81 b main /* 跳转到 main 函数 */ 82 83 /* 未定义中断 */ 84 Undefined_Handler: 85 ldr r0, =Undefined_Handler 86 bx r0 87 88 /* SVC 中断 */ 89 SVC_Handler: 90 ldr r0, =SVC_Handler 91 bx r0 92 93 /* 预取终止中断 */ 94 PrefAbort_Handler: 95 ldr r0, =PrefAbort_Handler 96 bx r0 97 98 /* 数据终止中断 */ 99 DataAbort_Handler: 100 ldr r0, =DataAbort_Handler 101 bx r0 102 103 /* 未使用的中断 */ 104 NotUsed_Handler: 105 106 ldr r0, =NotUsed_Handler 107 bx r0 108 109 /* IRQ 中断!重点!!!!! */ 110 IRQ_Handler: 111 push {lr} /* 保存 lr 地址 */ 112 push {r0-r3, r12} /* 保存 r0-r3,r12 寄存器 */ 113 114 mrs r0, spsr /* 读取 spsr 寄存器 */ 115 push {r0} /* 保存 spsr 寄存器 */ 116 117 mrc p15, 4, r1, c15, c0, 0 /* 将 CP15 的 C0 内的值到 R1 寄存器中 118 * 参考文档 ARM Cortex-A(armV7)编程手册 V4.0.pdf P49 119 * Cortex-A7 Technical ReferenceManua.pdf P68 P138 120 */ 121 add r1, r1, #0X2000 /* GIC 基地址加 0X2000,得到 CPU 接口端基地址 */ 122 ldr r0, [r1, #0XC] /* CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器, 123 * GICC_IAR 保存着当前发生中断的中断号,我们要根据 124 * 这个中断号来绝对调用哪个中断服务函数 125 */ 126 push {r0, r1} /* 保存 r0,r1 */ 127 128 cps #0x13 /* 进入 SVC 模式,允许其他中断再次进去 */ 129 130 push {lr} /* 保存 SVC 模式的 lr 寄存器 */ 131 ldr r2, =system_irqhandler /* 加载 C 语言中断处理函数到 r2 寄存器中*/ 132 blx r2 /* 运行 C 语言中断处理函数,带有一个参数 */ 133 134 pop {lr} /* 执行完 C 语言中断服务函数,lr 出栈 */ 135 cps #0x12 /* 进入 IRQ 模式 */ 136 pop {r0, r1} 137 str r0, [r1, #0X10] /* 中断执行完成,写 EOIR */ 138 139 pop {r0} 140 msr spsr_cxsf, r0 /* 恢复 spsr */ 141 142 pop {r0-r3, r12} /* r0-r3,r12 出栈 */ 143 pop {lr} /* lr 出栈 */ 144 subs pc, lr, #4 /* 将 lr-4 赋给 pc */ 145 146 /* FIQ 中断 */ 147 FIQ_Handler: 148 149 ldr r0, =FIQ_Handler 150 bx r0
第 6 到 14 行是中断向量表。
第 17 到 81 行是复位中断服务函数 Reset_Handler, 第 19 行先调用指令“cpsid i”关闭 IRQ,第 24 到 30 行是关闭 I/D
Cache、MMU、对齐检测和分支预测。第 33 行到 42 行是汇编版本的中断向量表重映射。第 50 到 68 行是设置不同模式下的 sp 指针,分别设置 IRQ 模式、SYS 模式和 SVC 模式的栈指针,每种模式的栈大小都是 2MB。第 70 行调用指令“cpsie i”重新打开IRQ 中断,第 72 到 79 行是操作 CPSR 寄存器来打开 IRQ 中断。当初始化工作都完成以后就可以进入到 main 函数了,第 81 行就是跳转到 main 函数。
第 110 到 144 行是中断服务函数 IRQ_Handler,这个是本章的重点,因为所有的外部中断最终都会触发 IRQ 中断,所以 IRQ 中断服务函数主要的工作就是区分去当前发生的什么中断(中断 ID)?然后针对不同的外部中断做出不同的处理。第 111 到 115 行是保存现场,第 117 到122 行是获取当前中断号,中断号被保存到了 r0 寄存器中。第 131 和 132 行才是中断处理的重点,这两行相当于调用了函数 system_irqhandler,函数 system_irqhandler 是一个 C 语言函数,此函数有一个参数,这个参数中断号,所以我们需要传递一个参数。汇编中调用C 函数如何实现参数传递呢?根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。所以给 r0 寄存器写入中断号就可以了函数 system_irqhandler 的参数传递,在 136 行已经向 r0 寄存器写入了中断号了。中断的真正
处理过程其实是在函数 system_irqhandler 中完成,稍后需要编写函数 stem_irqhandler。
第 151 行向 GICC_EOIR 寄存器写入刚刚处理完成的中断号,当一个中断处理完成以后必须向GICC_EOIR 寄存器写入其中断号表示中断处理完成。
第 153 到 157 行就是恢复现场。
第 158 行中断处理完成以后就要重新返回到曾经被中断打断的地方运行,这里为什么要将 lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?ARM
的指令是三级流水线:取指、译指、执行,pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8。比如下面代码示例:
0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC
上面示例代码中,左侧一列是地址,中间是指令,最右边是流水线。当前正在执行 0X2000地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。当中断处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到 lr 里面保存的地址处(0X2008)开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这是一个很严重的错误!所以就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,R3”开始执行。
通用中断驱动文件编写
在 start.S 文件中我们在中断服务函数 IRQ_Handler 中调用了C 函数 system_irqhandler 来处 理具体的中断。此函数有一个参数,参数是中断号,但是函数
system_irqhandler 的具体内容还 没有实现,所以需要实现函数 system_irqhandler 的具体内容。不同的中断源对应不同的中断处 理函数,I.MX6U 有 160
个中断源,所以需要 160 个中断处理函数,我们可以将这些中断处理 函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后 函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。
在 bsp 目录下新建名为“int”的文件夹,在 bsp/int 文件夹里面创建bsp_int.c 和bsp_int.h 这两个文件。在 bsp_int.h 文件里面输入如下内容:
1 #ifndef _BSP_INT_H 2 #define _BSP_INT_H 3 #include "imx6ul.h" 4 /*************************************************************** 5 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 6 文件名 : bsp_int.h 7 作者 : 左忠凯 8 版本 : V1.0 9 描述 : 中断驱动头文件。 10 其他 : 无 11 论坛 : www.openedv.com 12 日志 : 初版 V1.0 2019/1/4 左忠凯创建 13 ***************************************************************/ 14 15 /* 中断处理函数形式 */ 16 typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param); 17 18 /* 中断处理函数结构体*/ 19 typedef struct _sys_irq_handle 20 { 21 system_irq_handler_t irqHandler; /* 中断处理函数 */ 22 void *userParam; /* 中断处理函数参数 */ 23 } sys_irq_handle_t; 24 25 /* 函数声明 */ 26 void int_init(void); 27 void system_irqtable_init(void); 28 void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam); 29 void system_irqhandler(unsigned int giccIar); 30 void default_irqhandler(unsigned int giccIar, void *userParam); 31 32 #endif
第 16~23 行是中断处理结构体,结构体 sys_irq_handle_t 包含一个中断处理函数和中断处理函数的用户参数。一个中断源就需要一个 sys_irq_handle_t 变量,I.MX6U 有 160 个中断源,因此需要 160 个 sys_irq_handle_t 组成中断处理数组。
在 bsp_int.c 中输入如下所示代码:
1 #include "bsp_int.h" 2 /*************************************************************** 3 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 4 文件名 : bsp_int.c 5 作者 : 左忠凯 6 版本 : V1.0 7 描述 : 中断驱动文件。 8 其他 : 无 9 论坛 : www.openedv.com 10 日志 : 初版 V1.0 2019/1/4 左忠凯创建 11 ***************************************************************/ 12 13 /* 中断嵌套计数器 */ 14 static unsigned int irqNesting; 15 16 /* 中断服务函数表 */ 17 static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; 18 19 /* 20 * @description : 中断初始化函数 21 * @param : 无 22 * @return : 无 23 */ 24 void int_init(void) 25 { 26 GIC_Init(); /* 初始化 GIC */ 27 system_irqtable_init(); /* 初始化中断表 */ 28 29 } set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移 */ 30 31 /* 32 * @description : 初始化中断服务函数表 33 * @param : 无 34 * @return : 无 35 */ 36 void system_irqtable_init(void) 37 { 38 unsigned int i = 0; 39 irqNesting = 0; 40 41 /* 先将所有的中断服务函数设置为默认值 */ 42 for(i = 0; i < NUMBER_OF_INT_VECTORS; i++) 43 { 44 system_register_irqhandler( (IRQn_Type)i, default_irqhandler, NULL); 45 } 46 } 47 48 /* 49 * @description : 给指定的中断号注册中断服务函数 50 * @param - irq : 要注册的中断号 51 * @param - handler : 要注册的中断处理函数 52 * @param - usrParam : 中断服务处理函数参数 53 * @return : 无 54 */ 55 void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) 56 { 57 irqTable[irq].irqHandler = handler; 58 irqTable[irq].userParam = userParam; 59 } 60 61 /* 62 * @description : C 语言中断服务函数,irq 汇编中断服务函数会 63 调用此函数,此函数通过在中断服务列表中查 64 找指定中断号所对应的中断处理函数并执行。 65 * @param - giccIar : 中断号 66 * @return : 无 67 */ 68 void system_irqhandler(unsigned int giccIar) 69 { 70 71 uint32_t intNum = giccIar & 0x3FFUL; 72 73 /* 检查中断号是否符合要求 */ 74 if ((intNum == 1020) || (intNum >= NUMBER_OF_INT_VECTORS)) 75 { 76 return; 77 } 78 79 irqNesting++; /* 中断嵌套计数器加一 */ 80 81 /* 根据传递进来的中断号,在 irqTable 中调用确定的中断服务函数*/ 82 irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); 83 84 irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */ 85 86 } 87 88 /* 89 * @description : 默认中断服务函数 90 * @param - giccIar : 中断号 91 * @param - usrParam : 中断服务处理函数参数 92 * @return : 无 93 */ 94 void default_irqhandler(unsigned int giccIar, void *userParam) 95 { 96 while(1) 97 { 98 } 99 }
第 14 行定义了一个变量 irqNesting,此变量作为中断嵌套计数器。
第 17 行定了中断服务函数数组 irqTable,这是一个 sys_irq_handle_t 类型的结构体数组,数组大小为 I.MX6U 的中断源个数,即 160 个。
第 24~28 行是中断初始化函数 int_init,在此函数中首先初始化了GIC,然后初始化了中断服务函数表,最终设置了中断向量表偏移。
第 36~46 行是中断服务函数表初始化函数 system_irqtable_init,初始化 irqTable,给其赋初值。
第 55~59 行是注册中断处理函数 system_register_irqhandler,此函数用来给指定的中断号注册中断处理函数。如果要使用某个外设中断,那就必须调用此函数来给这个中断注册一个中断处理函数。
第 68~86 行就是前面在 start.S 中调用的 system_irqhandler 函数,此函数根据中断号在中断处理函数表irqTable 中取出对应的中断处理函数并执行。
第 94~99 行是默认中断处理函数 default_irqhandler,这是一个空函数,主要用来给初始化中断函数处理表。
修改 GPIO 驱动文件
需要修改文件 GPIO 的驱动文件 bsp_gpio.c 和 bsp_gpio.h,加上中断相关函数。
bsp_gpio.h 文件,重新输入如下内容:
1 #ifndef _BSP_GPIO_H 2 #define _BSP_GPIO_H 3 #define _BSP_KEY_H 4 #include "imx6ul.h" 5 /*************************************************************** 6 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 7 文件名 : bsp_gpio.h 8 作者 : 左忠凯 9 版本 : V1.0 10 描述 : GPIO 操作文件头文件。 11 其他 : 无 12 论坛 : www.openedv.com 13 日志 : 初版 V1.0 2019/1/4 左忠凯创建 14 V2.0 2019/1/4 左忠凯修改 15 添加 GPIO 中断相关定义 16 17 ***************************************************************/ 18 19 /* 20 * 枚举类型和结构体定义 21 */ 22 typedef enum _gpio_pin_direction 23 { 24 kGPIO_DigitalInput = 0U, /* 输入 */ 25 kGPIO_DigitalOutput = 1U, /* 输出 */ 26 } gpio_pin_direction_t; 27 28 /* 29 * GPIO 中断触发类型枚举 30 */ 31 typedef enum _gpio_interrupt_mode 32 { 33 kGPIO_NoIntmode = 0U, /* 无中断功能 */ 34 kGPIO_IntLowLevel = 1U, /* 低电平触发 */ 35 kGPIO_IntHighLevel = 2U, /* 高电平触发 */ 36 kGPIO_IntRisingEdge = 3U, /* 上升沿触发 */ 37 kGPIO_IntFallingEdge = 4U, /* 下降沿触发 */ 38 kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */ 39 } gpio_interrupt_mode_t; 40 41 /* 42 * GPIO 配置结构体 43 */ 44 typedef struct _gpio_pin_config 45 { 46 gpio_pin_direction_t direction; /* GPIO 方向:输入还是输出 */ 47 uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */ 48 gpio_interrupt_mode_t interruptMode; /* 中断方式 */ 49 } gpio_pin_config_t; 50 51 52 /* 函数声明 */ 53 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); 54 int gpio_pinread(GPIO_Type *base, int pin); 55 void gpio_pinwrite(GPIO_Type *base, int pin, int value); 56 void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode); 57 void gpio_enableint(GPIO_Type* base, unsigned int pin); 58 void gpio_disableint(GPIO_Type* base, unsigned int pin); 59 void gpio_clearintflags(GPIO_Type* base, unsigned int pin); 60 61 #endif
相比前面试验的 bsp_gpio.h 文件,示例代码中添加了一个新枚举类型:
gpio_interrupt_mode_t,枚举出了 GPIO 所有的中断触发类型。还修改了结构体 gpio_pin_config_t,在,在里面加入了 interruptMode 成员变量。最后就是添加了一些跟中断有关的函数声明,bsp_gpio.h 文件的内容总体还是比较简单的。 打开 bsp_gpio.c 文件,重新输入如下代码:
1 #include "bsp_gpio.h" 2 /*************************************************************** 3 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 4 文件名 : bsp_gpio.c 5 作者 : 左忠凯 6 版本 : V1.0 7 描述 : GPIO 操作文件。 8 其他 : 无 9 论坛 : www.openedv.com 10 日志 : 初版 V1.0 2019/1/4 左忠凯创建 11 V2.0 2019/1/4 左忠凯修改: 12 修改 gpio_init()函数,支持中断配置. 13 添加 gpio_intconfig()函数,初始化中断 14 添加 gpio_enableint()函数,使能中断 15 添加 gpio_clearintflags()函数,清除中断标志位 16 17 ***************************************************************/ 18 19 /* 20 * @description : GPIO 初始化。 21 * @param - base : 要初始化的 GPIO 组。 22 * @param - pin : 要初始化 GPIO 在组内的编号。 23 * @param - config : GPIO 配置结构体。 24 * @return : 无 25 */ 26 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) 27 { 28 base->IMR &= ~(1U << pin); 29 30 if(config->direction == kGPIO_DigitalInput) /* GPIO 作为输入 */ 31 { 32 base->GDIR &= ~( 1 << pin); 33 } 34 else /* 输出 */ 35 { 36 base->GDIR |= 1 << pin; 37 gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认电平 */ 38 } 39 gpio_intconfig(base, pin, config->interruptMode);/* 中断功能配置 */ 40 } 41 42 /* 43 * @description : 读取指定 GPIO 的电平值 。 44 * @param – base : 要读取的 GPIO 组。 45 * @param - pin : 要读取的 GPIO 脚号。 46 * @return : 无 47 */ 48 int gpio_pinread(GPIO_Type *base, int pin) 49 { 50 return (((base->DR) >> pin) & 0x1); 51 } 52 53 /* 54 * @description : 指定 GPIO 输出高或者低电平 。 55 * @param - base : 要输出的的 GPIO 组。 56 * @param - pin : 要输出的 GPIO 脚号。 57 * @param – value : 要输出的电平,1 输出高电平, 0 输出低低电平 58 * @return : 无 59 */ 60 void gpio_pinwrite(GPIO_Type *base, int pin, int value) 61 { 62 if (value == 0U) 63 { 64 base->DR &= ~(1U << pin); /* 输出低电平 */ 65 } 66 else 67 { 68 base->DR |= (1U << pin); /* 输出高电平 */ 69 } 70 } 71 72 /* 73 * @description : 设置 GPIO 的中断配置功能 74 * @param - base : 要配置的 IO 所在的 GPIO 组。 75 * @param - pin : 要配置的 GPIO 脚号。 76 * @param – pinInterruptMode: 中断模式,参考 gpio_interrupt_mode_t 77 * @return : 无 78 */ 79 void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode) 80 { 81 volatile uint32_t *icr; 82 uint32_t icrShift; 83 84 icrShift = pin; 85 86 base->EDGE_SEL &= ~(1U << pin); 87 88 if(pin < 16) /* 低 16 位 */ 89 { 90 icr = &(base->ICR1); 91 } 92 else /* 高 16 位 */ 93 { 94 icr = &(base->ICR2); 95 icrShift -= 16; 96 } 97 switch(pin_int_mode) 98 { 99 case(kGPIO_IntLowLevel): 100 *icr &= ~(3U << (2 * icrShift)); 101 break; 102 case(kGPIO_IntHighLevel): 103 *icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift)); 104 break; 105 case(kGPIO_IntRisingEdge): 106 *icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift)); 107 break; 108 case(kGPIO_IntFallingEdge): 109 *icr |= (3U << (2 * icrShift)); 110 break; 111 case(kGPIO_IntRisingOrFallingEdge): 112 base->EDGE_SEL |= (1U << pin); 113 break; 114 default: 115 break; 116 } 117 } 118 119 /* 120 * @description : 使能 GPIO 的中断功能 121 * @param - base : 要使能的 IO 所在的 GPIO 组。 122 * @param - pin : 要使能的 GPIO 在组内的编号。 123 * @return : 无 124 */ 125 void gpio_enableint(GPIO_Type* base, unsigned int pin) 126 { 127 base->IMR |= (1 << pin); 128 } 129 130 /* 131 * @description : 禁止 GPIO 的中断功能 132 * @param - base : 要禁止的 IO 所在的 GPIO 组。 133 * @param - pin : 要禁止的 GPIO 在组内的编号。 134 * @return : 无 135 */ 136 void gpio_disableint(GPIO_Type* base, unsigned int pin) 137 { 138 base->IMR &= ~(1 << pin); 139 } 140 141 /* 142 * @description : 清除中断标志位(写 1 清除) 143 * @param - base : 要清除的 IO 所在的 GPIO 组。 144 * @param - pin : 要清除的 GPIO 掩码。 145 * @return : 无 146 */ 147 void gpio_clearintflags(GPIO_Type* base, unsigned int pin) 148 { 149 base->ISR |= (1 << pin); 150 }
在 bsp_gpio.c 文件中首先修改了 gpio_init 函数,在此函数里面添加了中断配置代码。另外也新增加了 4 个函数,如下:
gpio_intconfig:配置 GPIO 的中断功能。
gpio_enableint:GPIO 中断使能函数。
gpio_disableint:GPIO 中断禁止函数。
gpio_clearintflags:GPIO 中断标志位清除函数。
bsp_gpio.c 文件重点就是增加了一些跟 GPIO 中断有关的函数,都比较简单。
按键中断驱动文件编写
本例程的目的是以中断的方式编写 KEY 按键驱动,当按下 KEY 以后触发 GPIO 中断,然后在中断服务函数里面控制蜂鸣器的开关。所以接下来就是要编写按键 KEY 对应的UART1_CTS 这个 IO 的中断驱动,在 bsp 文件夹里面新建名为“exit”的文件夹,然后在 bsp/exit里面新建 bsp_exit.c 和 bsp_exit.h 两个文件。在bsp_exit.h 文件中输入如下代码:
1 #ifndef _BSP_EXIT_H 2 #define _BSP_EXIT_H 3 /*************************************************************** 4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 5 文件名 : bsp_exit.h 6 作者 : 左忠凯 7 版本 : V1.0 8 描述 : 外部中断驱动头文件。 9 其他 : 配置按键对应的 GPIP 为中断模式 10 论坛 : www.openedv.com 11 日志 : 初版 V1.0 2019/1/4 左忠凯创建 12 ***************************************************************/ 13 #include "imx6ul.h" 14 15 /* 函数声明 */ 16 void exit_init(void); /* 中断初始化 */ 17 void gpio1_io18_irqhandler(void); /* 中断处理函数 */ 18 19 #endif
bsp_exit.h 就是函数声明,很简单。接下来在 bsp_exit.c 里面输入如下内容:
/*************************************************************** Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.文件名 : bsp_exit.c 作者 : 左忠凯 版本 : V1.0 描述 : 外部中断驱动。 其他 : 配置按键对应的 GPIP 为中断模式论坛 : www.openedv.com 日志 : 初版 V1.0 2019/1/4 左忠凯创建 ***************************************************************/ 1 #include "bsp_exit.h" 2 #include "bsp_gpio.h" 3 #include "bsp_int.h" 4 #include "bsp_delay.h" 5 #include "bsp_beep.h" 6 7 /* 8 * @description : 初始化外部中断 9 * @param : 无 10 * @return : 无 11 */ 12 void exit_init(void) 13 { 14 gpio_pin_config_t key_config; 15 16 /* 1、设置 IO 复用 */ 17 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); 18 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080); 19 20 /* 2、初始化 GPIO 为中断模式 */ 21 key_config.direction = kGPIO_DigitalInput; 22 key_config.interruptMode = kGPIO_IntFallingEdge; 23 key_config.outputLogic = 1; 24 gpio_init(GPIO1, 18, &key_config); 25 /* 3、使能 GIC 中断、注册中断服务函数、使能 GPIO 中断 */ 26 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); 27 system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); 28 gpio_enableint(GPIO1, 18); 29 } 30 31 /* 32 * @description : GPIO1_IO18 最终的中断处理函数 33 * @param : 无 34 * @return : 无 35 */ 36 void gpio1_io18_irqhandler(void) 37 { 38 static unsigned char state = 0; 39 40 /* 41 *采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要 42 *快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解 43 *定时器中断消抖法!!! 44 */ 45 46 delay(10); 47 if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */ 48 { 49 state = !state; 50 beep_switch(state); 51 } 52 53 gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */ 54 }
bsp_exit.c 文件只有两个函数exit_init 和gpio1_io18_irqhandler,exit_init 是中断初始化函数。
第 14~24 行都是初始化 KEY 所使用的 UART1_CTS 这个 IO,设置其复用为 GPIO1_IO18,然后配置 GPIO1_IO18 为下降沿触发中断。重点是第 26~28 行,在
26 行调用函数 GIC_EnableIRQ来使能GPIO_IO18 所对应的中断总开关,I.MX6U 中GPIO1_IO16~IO31 这 16 个IO 共用 ID99。
27 行调用函数 system_register_irqhandler 注册 ID99 所对应的中断处理函数,GPIO1_IO16~IO31这 16 个 IO 共用一个中断处理函数,至于具体是哪个 IO 引起的中断,那就需要在中断处理函数中判断了。28 行通过函数 gpio_enableint 使能 GPIO1_IO18 这个 IO 对应的中断。
函数 gpio1_io18_irqhandler 就是 27 行注册的中断处理函数,也就是我们学习 STM32 的时候某个 GPIO 对应的中断服务函数。在此函数里面编写中断处理代码,第 50 行就是蜂鸣器开关控制代码,也就是我们本试验的目的。当中断处理完成以后肯定要清除中断标志位,第 53行调用函数gpio_clearintflags 来清除GPIO1_IO18 的中断标志位。
编写 main.c 文件
在 main.c 中输入如下代码:
/************************************************************** Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.文件名 : mian.c 作者 : 左忠凯 版本 : V1.0 描述 : I.MX6U 开发板裸机实验 9 系统中断实验其他 : 五 论坛 : www.openedv.com 日志 : 初版 V1.0 2019/1/4 左忠凯创建 **************************************************************/ 1 #include "bsp_clk.h" 2 #include "bsp_delay.h" 3 #include "bsp_led.h" 4 #include "bsp_beep.h" 5 #include "bsp_key.h" 6 #include "bsp_int.h" 7 #include "bsp_exit.h" 8 9 /* 10 * @description : main 函数 11 * @param : 无 12 * @return : 无 13 */ 14 int main(void) 15 { 16 unsigned char state = OFF; 17 18 int 19 imx6u_clkinit(); /* 初始化系统时钟 */ 20 clk_enable(); /* 使能所有的时钟 */ 21 led_init(); /* 初始化 led */ 22 beep_init(); /* 初始化 beep */ 23 key_init(); /* 初始化 key */ 24 exit_init(); /* 初始化按键中断 */ 25 26 while(1) 27 { 28 state = !state; 29 led_switch(LED0, state); 30 delay(500); 31 } 32 33 return 0; 34 }
main.c 很简单,重点是第 18 行调用函数 int_init 来初始化中断系统,第 24 行调用函数exit_init 来初始化按键 KEY 对应的 GPIO 中断。
在《IMX6UL裸机实现C语言按键输入实验》的 Makefile 基础上修改变量TARGET 为int,在变量 INCDIRS 和 SRCDIRS
中追加“bsp/exit”和 bsp/int,修改完成以后如下所示:
1 CROSS_COMPILE ?= arm-linux-gnueabihf- 2 TARGET ?= int 3 4 /* 省略掉其它代码...... */ 5 6 INCDIRS := imx6ul \ 7 bsp/clk \ 8 bsp/led \ 9 bsp/delay \ 10 bsp/beep \ 11 bsp/gpio \ 12 bsp/key \ 13 bsp/exit \ 14 bsp/int 15 16 SRCDIRS := project \ 17 bsp/clk \ 18 bsp/led \ 19 bsp/delay \ 20 bsp/beep \ 21 bsp/gpio \ 22 bsp/key \ 23 bsp/exit \ 24 bsp/int 25 26 /* 省略掉其它代码...... */ 27 28 clean: 29 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
第 2 行修改变量 TARGET 为“int”,也就是目标名称为“int”。
第 13、14 行在变量 INCDIRS 中添加 GPIO 中断和通用中断驱动头文件(.h)路径。第 23、24 行在变量SRCDIRS 中添加 GPIO
中断和通用中断驱动文件(.c)路径。链接脚本保持不变。
使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 int.bin 文件下载到 SD 卡中,命令如下:
chmod 777 imxdownload //给予 imxdownload 可执行权限,一次即可 ./imxdownload int.bin /dev/sdd //烧写到 SD 卡中
烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。本试验效果其实和试验“8_key”一样,按下 KEY 就会打开蜂鸣器,再次按下就会关闭蜂鸣器。LED0 会不断闪烁,周期大约
500ms。