1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
![0f458569c9a92a80568ef524e599b9e7.png](https://img-blog.csdnimg.cn/img_convert/0f458569c9a92a80568ef524e599b9e7.png)
17.3试验程序编写
本实验对应的例程路径为:开发板光盘-> 1、裸机例程->9_int。
本章试验的功能和第十五章一样,只是按键采用中断的方式处理。当按下按键KEY0以后就打开蜂鸣器,再次按下按键KEY0就关闭蜂鸣器。在第十六章的试验上完成本章试验。
17.3.1移植SDK包中断相关文件
将SDK包中的文件core_ca7.h拷贝到本章试验工程中的“imx6ul”文件夹中,参考试验“9_int”中core_ca7.h进行修改。主要留下和GIC相关的内容,我们重点是需要core_ca7.h中的10个API函数,这10个函数如表17.3.1.1所示:
![79776582576bf93e3774cb9154346245.png](https://img-blog.csdnimg.cn/img_convert/79776582576bf93e3774cb9154346245.png)
表17.3.1.1 GIC相关API操作函数
移植好core_ca7.h以后,修改文件imx6ul.h,在里面加上如下一行代码:
#include "core_ca7.h"
17.3.2 重新编写start.S文件
重新在start.S中输入如下内容:
示例代码17.3.2.1 start.S文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : start.s
作者 : 左忠凯
版本 : V2.0
描述 : I.MX6U-ALPHA/I.MX6ULL开发板启动文件,完成C环境初始化,
C环境初始化完成以后跳转到C代码。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/3 左忠凯修改
V2.0 2019/1/4 左忠凯修改
添加中断相关定义
**************************************************************/
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 #if0
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 #if0
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.1.2小节已经讲解过了。
第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”开始执行。
17.3.3通用中断驱动文件编写
在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文件里面输入如下内容:
示例代码17.3.3.1 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/* 中断处理函数形式 */
16typedefvoid(*system_irq_handler_t)(unsignedint giccIar,
void*param);
17
18/* 中断处理函数结构体*/
19typedefstruct _sys_irq_handle
20{
21 system_irq_handler_t irqHandler; /* 中断处理函数 */
22void*userParam; /* 中断处理函数参数 */
23} sys_irq_handle_t;
24
25/* 函数声明 */
26void int_init(void);
27void system_irqtable_init(void);
28void system_register_irqhandler(IRQn_Type irq,
system_irq_handler_t handler,
void*userParam);
29void system_irqhandler(unsignedint giccIar);
30void default_irqhandler(unsignedint 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中输入如下所示代码:
示例代码17.3.3.2 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/* 中断嵌套计数器 */
14staticunsignedint irqNesting;
15
16/* 中断服务函数表 */
17static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
18
19/*
20 * @description : 中断初始化函数
21 * @param : 无
22 * @return : 无
23 */
24void int_init(void)
25{
26 GIC_Init(); /* 初始化GIC */
27 system_irqtable_init(); /* 初始化中断表 */
28 __set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移 */
29}
30
31/*
32 * @description : 初始化中断服务函数表
33 * @param : 无
34 * @return : 无
35 */
36void system_irqtable_init(void)
37{
38 unsignedint 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 */
55void 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 */
68void system_irqhandler(unsignedint 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 */
94void default_irqhandler(unsignedint 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,这是一个空函数,主要用来给初始化中断函数处理表。
17.3.4修改GPIO驱动文件
在前几章节试验中我们只是使用到了GPIO最基本的输入输出功能,本章我们需要使用GPIO的中断功能。所以需要修改文件GPIO的驱动文件bsp_gpio.c和bsp_gpio.h,加上中断相关函数。关于GPIO中断内容已经在8.1.5小节进行了详细的讲解,这里就不赘述了。打开bsp_gpio.h文件,重新输入如下内容:
示例代码17.3.4.1 bsp_gpio.h文件代码
1 #ifndef _BSP_GPIO_H
2 #define _BSP_GPIO_H
3 #include "imx6ul.h"
4/***************************************************************
5 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
6文件名 : bsp_gpio.h
7作者 : 左忠凯
8版本 : V1.0
9描述 : GPIO操作文件头文件。
10其他 : 无
11论坛 : www.openedv.com
12日志 : 初版V1.0 2019/1/4 左忠凯创建
13 V2.0 2019/1/4 左忠凯修改
14 添加GPIO中断相关定义
15
16 ***************************************************************/
17
18/*
19 * 枚举类型和结构体定义
20 */
21typedefenum _gpio_pin_direction
22{
23 kGPIO_DigitalInput =0U,/* 输入 */
24 kGPIO_DigitalOutput =1U,/* 输出 */
25} gpio_pin_direction_t;
26
27/*
28 * GPIO中断触发类型枚举
29 */
30typedefenum _gpio_interrupt_mode
31{
32 kGPIO_NoIntmode =0U, /* 无中断功能 */
33 kGPIO_IntLowLevel =1U, /* 低电平触发 */
34 kGPIO_IntHighLevel =2U, /* 高电平触发 */
35 kGPIO_IntRisingEdge =3U, /* 上升沿触发 */
36 kGPIO_IntFallingEdge =4U, /* 下降沿触发 */
37 kGPIO_IntRisingOrFallingEdge =5U, /* 上升沿和下降沿都触发 */
38} gpio_interrupt_mode_t;
39
40/*
41 * GPIO配置结构体
42 */
43typedefstruct _gpio_pin_config
44{
45 gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
46uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
47 gpio_interrupt_mode_t interruptMode; /* 中断方式 */
48} gpio_pin_config_t;
49
50
51/* 函数声明 */
52void gpio_init(GPIO_Type *base,int pin, gpio_pin_config_t *config);
53int gpio_pinread(GPIO_Type *base,int pin);
54void gpio_pinwrite(GPIO_Type *base,int pin,int value);
55void gpio_intconfig(GPIO_Type* base,unsignedint pin,
gpio_interrupt_mode_t pinInterruptMode);
56void gpio_enableint(GPIO_Type* base,unsignedint pin);
57void gpio_disableint(GPIO_Type* base,unsignedint pin);
58void gpio_clearintflags(GPIO_Type* base,unsignedint pin);
59
60 #endif
相比前面试验的bsp_gpio.h文件,“示例代码17.3.3.2”中添加了一个新枚举类型:gpio_interrupt_mode_t,枚举出了GPIO所有的中断触发类型。还修改了结构体gpio_pin_config_t,在,在里面加入了interruptMode成员变量。最后就是添加了一些跟中断有关的函数声明,bsp_gpio.h文件的内容总体还是比较简单的。
打开bsp_gpio.c文件,重新输入如下代码:
示例代码17.3.4.2 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 */
26void gpio_init(GPIO_Type *base,int pin, gpio_pin_config_t *config)
27{
28 base->IMR &=~(1U<< pin);
29
30if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
31{
32 base->GDIR &=~(1<< pin);
33}
34else /* 输出 */
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 */
48int gpio_pinread(GPIO_Type *base,int pin)
49{
50return(((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 */
60void gpio_pinwrite(GPIO_Type *base,int pin,int value)
61{
62if(value ==0U)
63{
64 base->DR &=~(1U<< pin);/* 输出低电平 */
65}
66else
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 */
79void gpio_intconfig(GPIO_Type* base,unsignedint pin,
gpio_interrupt_mode_t pin_int_mode)
80{
81volatileuint32_t*icr;
82uint32_t icrShift;
83
84 icrShift = pin;
85
86 base->EDGE_SEL &=~(1U<< pin);
87
88if(pin <16)/* 低16位 */
89{
90 icr =&(base->ICR1);
91}
92else/* 高16位 */
93{
94 icr =&(base->ICR2);
95 icrShift -=16;
96}
97switch(pin_int_mode)
98{
99case(kGPIO_IntLowLevel):
100*icr &=~(3U<2* icrShift));
101break;
102case(kGPIO_IntHighLevel):
103*icr =(*icr &(~(3U<2* icrShift))))|
(1U<2* icrShift));
104break;
105case(kGPIO_IntRisingEdge):
106*icr =(*icr &(~(3U<2* icrShift))))|
(2U<2* icrShift));
107break;
108case(kGPIO_IntFallingEdge):
109*icr |=(3U<2* icrShift));
110break;
111case(kGPIO_IntRisingOrFallingEdge):
112 base->EDGE_SEL |=(1U<< pin);
113break;
114default:
115break;
116}
117}
118
119/*
120 * @description : 使能GPIO的中断功能
121 * @param - base : 要使能的IO所在的GPIO组。
122 * @param - pin : 要使能的GPIO在组内的编号。
123 * @return : 无
124 */
125void gpio_enableint(GPIO_Type* base,unsignedint 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 */
136void gpio_disableint(GPIO_Type* base,unsignedint 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 */
147void gpio_clearintflags(GPIO_Type* base,unsignedint 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中断有关的函数,都比较简单。
17.3.5按键中断驱动文件编写
本例程的目的是以中断的方式编写KEY按键驱动,当按下KEY以后触发GPIO中断,然后在中断服务函数里面控制蜂鸣器的开关。所以接下来就是要编写按键KEY对应的UART1_CTS这个IO的中断驱动,在bsp文件夹里面新建名为“exit”的文件夹,然后在bsp/exit里面新建bsp_exit.c和bsp_exit.h两个文件。在bsp_exit.h文件中输入如下代码:
示例代码17.3.5.1 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/* 函数声明 */
16void exit_init(void); /* 中断初始化 */
17void gpio1_io18_irqhandler(void);/* 中断处理函数 */
18
19 #endif
bsp_exit.h就是函数声明,很简单。接下来在bsp_exit.c里面输入如下内容:
示例代码17.3.5.2 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 */
12void 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 */
36void gpio1_io18_irqhandler(void)
37{
38 staticunsignedchar 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的中断标志位。
17.3.6编写main.c文件
在main.c中输入如下代码:
示例代码17.3.6.1 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 */
14int main(void)
15{
16 unsignedchar state = OFF;
17
18 int_init(); /* 初始化中断(一定要最先调用!) */
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 return0;
34}
main.c很简单,重点是第18行调用函数int_init来初始化中断系统,第24行调用函数exit_init来初始化按键KEY对应的GPIO中断。
17.4编译下载验证
17.4.1编写Makefile和链接脚本
在第十六章实验的Makefile基础上修改变量TARGET为int,在变量INCDIRS和SRCDIRS中追加“bsp/exit”和bsp/int,修改完成以后如下所示:
示例代码17.4.1.1 Makefile文件代码
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)路径。
链接脚本保持不变。
17.4.2编译下载
使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的int.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload //给予imxdownload可执行权限,一次即可
./imxdownload int.bin /dev/sdd //烧写到SD卡中
烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板。本试验效果其实和试验“8_key”一样,按下KEY就会打开蜂鸣器,再次按下就会关闭蜂鸣器。LED0会不断闪烁,周期大约500ms。