i.MX6ULL终结者GPIO中断例程程序设计

本实验对应的例程在光盘资料的:i.MX6UL终结者光盘资料\04_裸机例程源码\8_int 目录下面,使用命令“mkdir 8_int”新建8_int文件夹。如图 1所示:
在这里插入图片描述

图 1

然后使用命令“cp -r …/7_clk/* ./”将上一章试验中的所有内容拷贝到刚刚新建的“8_int”里面,如图 2:
在这里插入图片描述

图 2

拷贝完成以后的工程如图 3所示:
在这里插入图片描述

图 3

将SDK包中的文件core_ca7.h通过ssh软件拷贝到8_int工程目录中的core文件夹中,可以参考i.MX6UL终结者光盘资料\04_裸机例程源码\8_int\core中 core_ca7.h 进行修改。主要留下和GIC相关的内容,我们重点是需要 core_ca7.h 中的10个API 函数,这 10 个函数如下所示:
GIC_Init GIC初始化
GIC_EnableIRQ 使能指定的外设中断
GIC_DisableIRQ 关闭指定的外设中断
GIC_AcknowledgeIRQ 返回中断号
GIC_DeactivateIRQ 无效化指定中断
GIC_GetRunningPriority 获取当前正在运行的中断优先级
GIC_SetPriorityGrouping 设置抢占优先级位数
GIC_GetPriorityGrouping 获取抢占优先级位数
GIC_SetPriority 设置指定中断的优先级
GIC_GetPriority 获取指定中断的优先级

修改好core_ca7.h以后,然后使用“vi core/imx6ul.h”打开imx6ul.h文件,在里面加上如下一行代码
#include "core_ca7.h"
添加完成后,如下图所示:
如图 4所示:
在这里插入图片描述

图 4

然后使用命令“vi start.S”打开start.S文件,如图5所示:
在这里插入图片描述

图 5

全部替换成如下内容:

1 
  2 .global _start                                  /* 全局标号 */
  3 
  4 /*
  5  * 描述:_start函数,首先是中断向量表的创建
  6  * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes a    
     nd Registers(ARM处理器模型和寄存器)
  7  * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exce    ption priorities(异常)
  8  */
  9 _start:
 10         ldr pc, =Reset_Handler          /* 复位中断 */                                    
 11         ldr pc, =Undefined_Handler      /* 未定义中断 */
 12         ldr pc, =SVC_Handler           /* SVC(Supervisor)中断*/
 13         ldr pc, =PrefAbort_Handler      /* 预取终止中断 */                                            
 14         ldr pc, =DataAbort_Handler      /* 数据终止中断 */                                  
 15         ldr     pc, =NotUsed_Handler    /* 未使用中断 */                                      
 16         ldr pc, =IRQ_Handler            /* IRQ中断                                          
 17         ldr pc, =FIQ_Handler            /* FIQ(快速中断)未定义中断*/                          
 18 
 19 /* 复位中断 */
 20 Reset_Handler:
 21 
 22         cpsid i        /* 关闭全局中断 */
 23 
 24         /* 关闭I,DCache和MMU 
 25          * 采取读-改-写的方式。
 26          */
 27         mrc     p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中*/                                       
 28     bic     r0,  r0, #(0x1 << 12)      /* 清除C1寄存器的bit12位(I位),关闭I C*/    
 29     bic     r0,  r0, #(0x1 <<  2)      /* 清除C1寄存器的bit2(C位)关闭D Cache*/                                   
 30     bic     r0,  r0, #0x2          /* 清除C1寄存器的bit1(A位),关闭对齐*/                                                 
 31     bic     r0,  r0, #(0x1 << 11)    /* 清除C1寄存器的bit11(Z位) 关闭分支> */   
 32     bic     r0,  r0, #0x1          /* 清除C1寄存器的bit0(M位),关闭MMU                                           
 33     mcr     p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存> */   
 34 
 35 
 36 #if 0
 37         /* 汇编版本设置中断向量表偏移 */
 38         ldr r0, =0X87800000
 39 
 40         dsb
 41         isb
 42         mcr p15, 0, r0, c12, c0, 0
 43         dsb
 44         isb
 45 #endif
 46 
 47         /* 设置各个模式下的栈指针,
 48          * 注意:IMX6UL的堆栈是向下增长的!
 49          * 堆栈指针地址一定要是4字节地址对齐的!!!
 50          * DDR范围:0X80000000~0X9FFFFFFF
 51          */
 52         /* 进入IRQ模式 */
 53         mrs r0, cpsr
 54         bic r0, r0, #0x1f  /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4*/    
 55         orr r0, r0, #0x12   /* r0或上0x13,表示使用IRQ模式 */                                      
 56         msr cpsr, r0            /* 将r0 的数据写入到cpsr_c中 */                                       
 57         ldr sp, =0x80600000     /*设置IRQ模式下的栈首地址0X80600000,大小2M*/    
 58 
 59         /* 进入SYS模式 */
 60         mrs r0, cpsr
 61         bic r0, r0, #0x1f       /* 将r0寄存器中低5位清零也就是cpsr的M0~M4*/    
 62         orr r0, r0, #0x1f       /* r0或上0x13,表示使用SYS模式 */                                      
 63         msr cpsr, r0           /* 将r0 的数据写入到cpsr_c中 */                                       
 64         ldr sp, =0x80400000     /* 设置SYS模式下栈首地址0X80400000,大小2M*/    
 65 
 66         /* 进入SVC模式 */
 67         mrs r0, cpsr
 68         bic r0, r0, #0x1f       /*将r0寄存器中低5位清零也就是cpsr的M0~M4*/    
 69         orr r0, r0, #0x13       /* r0或上0x13,表示使用SVC模式 */                                      
 70         msr cpsr, r0            /* 将r0 的数据写入到cpsr_c中 */                                       
 71         ldr sp, =0X80200000     /*设置SVC模式下栈首地址0X80200000,大小2M*/    
 72 
 73         cpsie i                         /* 打开全局中断 */
 74 #if 0
 75         /* 使能IRQ中断 */
 76         mrs r0, cpsr          /* 读取cpsr寄存器值到r0中 */                          
 77         bic r0, r0, #0x80       /* 将r0寄存器中bit7清零也就是CPSR的I位清
                                   零,表示允许IRQ中断 */
 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,也就是
                                    GIC的CPU接口端基地址 */
122         ldr r0, [r1, #0XC]  /* GIC的CPU接口端基地址加0X0C    
                              就是GICC_IAR寄存器GICC_IAR>,
123                         * 寄存器保存这当前发生中断的中断号,我们要根据
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语言中断处理函数带有一个参数保存在R0寄存器中 */
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 
147 
148 /* FIQ中断 */
149 FIQ_Handler:
150 
151         ldr r0, =FIQ_Handler
152         bx r0                                                                   
153 

第10行到第17行是是中断向量表,

第20到第80行是复位中断服务函数 Reset_Handler,第20行先调用指令“cpsid i”关闭IRQ,

第28到33行是关闭 I/D Cache、 MMU、对齐检测和分支预测。

第36行到45行是汇编版本的中断向量表重映射。

第53到71行是设置不同模式下的sp指针,分别设置IRQ模式、 SYS模式和SVC模式的栈指针,每种模式的栈大小都是2MB。

第73行调用指令“cpsie i”重新打开IRQ 中断。

第74到79行是操作CPSR寄存器来打开IRQ中断。当初始化工作都完成以后就可以进入到main函数了。

第110行到第144行中断服务函数IRQ_Handler,这个是本章的重点,因为所有的外部中断最终都会触发IRQ中断,所以IRQ中断服务函数主要的工作就是区分出当前发生的什么中断(中断 ID),然后针对不同的外部中断做出不同的处理。第111到115行是保存现场。第117到122行:获取当前中断号,中断号被保存到了r0寄存器中。
第131和132行才是中断处理的重点,这两行相当于调用了函数system_irqhandler,此函数是一个C语言函数,有一个参数,这个参数就是中断号,所以我们需要传递一个参数。汇编中调用C函数如何实现参数传递呢?根据ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在汇编调用C函数的时候建议形参不要超过4个,形参可以由r0~r3这四个寄存器来传递,如果形参大于4个,那么大于4个的部分要使用堆栈进行传递。所以给r0寄存器写入中断号就实现了函数system_irqhandler的参数传递了,在136行已经向r0寄存器写入了中断号了。中断的真正处理过程其实是在函数system_irqhandler中完成,稍后需要编写函数 system_irqhandler。

第137行是当一个中断处理完成以后必须向GICC_EOIR寄存器写入其中断号表示中断处理完成。

第139到143行是恢复现场。

第144行是中断处理完成以后就要重新返回到曾经被中断打断的地方继续运行。这里为什么要将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.MX6ULL有160个中断源,所以需要160个中断处理函数,我们可以将这些中断处理函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后,函数system_irqhandler根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。

接下来我们在该工程目录下使用命令“mkdir drivers/int/”建立“int”文件夹,如图 6所示
在这里插入图片描述

图 6

然后使用命令“vi drivers/int/int.h”新建int.h文件,如图 7:
在这里插入图片描述

图 7

在init.h文件中输入下面的内容:

  1 #ifndef _BSP_INT_H
  2 #define _BSP_INT_H
  3 #include "imx6ul.h"
  4 
  5 /* 中断服务函数形式 */
  6 typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
  7 
  8 
  9 /* 中断服务函数结构体*/
 10 typedef struct _sys_irq_handle
 11 {
    
    
 12     system_irq_handler_t irqHandler; /* 中断服务函数 */
 13     void *userParam;              /* 中断服务函数参数 */
 14 } sys_irq_handle_t;
 15 
 16 
 17 /* 函数声明 */
 18 void int_init(void);
 19 void system_irqtable_init(void);
 20 void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler,void 
                                                                *userParam);
 21 void system_irqhandler(unsigned int giccIar);
 22 void default_irqhandler(unsigned int giccIar, void *userParam);
 23 
 24 
 25 
 26 #endif

第10~14行是中断处理结构体sys_irq_handle_t,它包含一个中断处理函数和中断处理函数的用户参数。一个中断源就需要一个sys_irq_handle_t这样的结构体变量,i.MX6ULL有160个中断源,因此实现这160个中断处理,就需要160个这样的结构体。然后就是一些函数的申明了。
添加完成之后,保存并退出文件。然后使用命令“vi drivers/int/int.c”新建int.c文件。如图 8所示:
在这里插入图片描述

图 8

然后在init.c文件里面添加下面的内容:

  1 #include "int.h"
  2 
  3 /* 中断嵌套计数器 */
  4 static unsigned int irqNesting;
  5 
  6 /* 中断服务函数表 */
  7 static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
  8 
  9 /*
 10  * @description : 中断初始化函数
 11  * @param              : 无
 12  * @return              : 无
 13  */
 14 void int_init(void)
 15 {
    
    
 16         GIC_Init();                /* 初始化GIC*/                                                        
 17         system_irqtable_init();       /* 初始化中断表*/                                                             
 18         __set_VBAR((uint32_t)0x87800000);/* 中断向量表偏移,偏移到起始地址*/    
 19 }
 20 
 21 /*
 22  * @description : 初始化中断服务函数表 
 23  * @param              : 无
 24  * @return              : 无
 25  */
 26 void system_irqtable_init(void)
 27 {
    
    
 28         unsigned int i = 0;
 29         irqNesting = 0;
 30 
 31         /* 先将所有的中断服务函数设置为默认值 */
 32         for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
 33         {
    
    
 34                system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
 35         }
 36 }
 37 
 38 /*
 39  * @description                 : 给指定的中断号注册中断服务函数 
 40  * @param - irq                 : 要注册的中断号
 41  * @param - handler             : 要注册的中断处理函数
 42  * @param - usrParam    : 中断服务处理函数参数
 43  * @return                              : 无
 44  */
 45 void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler,
                                                         void *userParam)
 46 {
    
    
 47         irqTable[irq].irqHandler = handler;
 48         irqTable[irq].userParam = userParam;
 49 }
 50 
 51 /*
 52  * @description   : C语言中断服务函数,irq汇编中断服务函数会
 53                      调用此函数,此函数通过在中断服务列表中查
 54                     找指定中断号所对应的中断处理函数并执行。                                                  
 55  * @param - giccIar    : 中断号
 56  * @return           : 无
 57  */
 58 void system_irqhandler(unsigned int giccIar)
 59 {
    
    
 60 
 61    uint32_t intNum = giccIar & 0x3FFUL;
 62 
 63    /* 检查中断号是否符合要求 */
 64    if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
 65    {
    
    
 66                 return;
 67    }
 68 
 69    irqNesting++;        /* 中断嵌套计数器加一 */
 70 
 71    /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
 72    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
 73 
 74    irqNesting--;        /* 中断执行完成,中断嵌套寄存器减一 */
 75 
 76 }
 77 
 78 /*
 79  * @description           : 默认中断服务函数
 80  * @param - giccIar      : 中断号
 81  * @param - usrParam      : 中断服务处理函数参数
 82  * @return               : 无
 83  */
 84 void default_irqhandler(unsigned int giccIar, void *userParam)
 85 {
    
    
 86         while(1)
 87         {
    
    
 88         }
 89 }

第4行定义了一个变量irqNesting,此变量作为中断嵌套计数器。

第7行中断服务函数数组irqTable,这是一个sys_irq_handle_t类型的结构体数组,数组大小为i.MX6ULL 的中断源个数,即160个。

然后是几个函数的顶替,如下:
int_init函数实现中断初始化。首先初始化了GIC,然后初始化了中断服务函数表,最终设置了中断向量表偏移。
system_irqtable_init函数实现中断服务函数表初始化函数,初始化 irqTable,给其赋初值。
system_register_irqhandler函数实现注册中断处理函数,用来给指定的中断号注册中断处理函数。要使用某个外设中断,必须调用此函数来给这个中断注册一个中断处理函数。
system_irqhandler函数实现根据中断号在中断处理函数表irqTable中取出对应的中断处理函数并执行。
default_irqhandler函数是默认中断处理函数,这是一个空函数,主要用来给初始化中断函数处理表。

然后使用命令“vi drivers/gpio/gpio.h”打开gpio.h,如图 9所示:
在这里插入图片描述

图 9

修改内容如下:

 1 #ifndef _BSP_GPIO_H
  2 #define _BSP_GPIO_H
  3 #define _BSP_KEY_H
  4 #include "imx6ul.h"
  5 
  6 /* 
  7  * 枚举类型和结构体定义 
  8  */
  9 typedef enum _gpio_pin_direction
 10 {
    
    
 11     kGPIO_DigitalInput = 0U,            /* 输入 */
 12     kGPIO_DigitalOutput = 1U,           /* 输出 */
 13 } gpio_pin_direction_t;
 14 
 15 /*
 16  * GPIO中断触发类型枚举
 17  */
 18 typedef enum _gpio_interrupt_mode
 19 {
    
    
 20     kGPIO_NoIntmode = 0U,                    /* 无中断功能 */
 21     kGPIO_IntLowLevel = 1U,                   /* 低电平触发   */
 22     kGPIO_IntHighLevel = 2U,                   /* 高电平触发 */
 23     kGPIO_IntRisingEdge = 3U,                   /* 上升沿触发   */
 24     kGPIO_IntFallingEdge = 4U,                  /* 下降沿触发 */
 25     kGPIO_IntRisingOrFallingEdge = 5U,  /* 上升沿和下降沿都触发 */
 26 } gpio_interrupt_mode_t;
 27 
 28 /*
 29  * GPIO配置结构体
 30  */
 31 typedef struct _gpio_pin_config
 32 {
    
    
 33     gpio_pin_direction_t direction;            /* GPIO方向:输入还是输出 */
 34     uint8_t outputLogic;                      /* 如果是输出的话,默认输出电平 */
 35         gpio_interrupt_mode_t interruptMode; /* 中断方式 */
 36 } gpio_pin_config_t;
 37 
 38 
 39 /* 函数声明 */
 40 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
 41 int gpio_pinread(GPIO_Type *base, int pin);
 42 void gpio_pinwrite(GPIO_Type *base, int pin, int value);
 43 void gpio_intconfig(GPIO_Type* base, unsigned int pin, 
                            gpio_interrupt_mode_t pinInterruptMode);
 44 void gpio_enableint(GPIO_Type* base, unsigned int pin);
 45 void gpio_disableint(GPIO_Type* base, unsigned int pin);
 46 void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
 47 
 48 #endif

我们在gpio.h文件中增加了一个新枚举类型:gpio_interrupt_mode_t(枚举出了GPIO所有的中断触发类型)。还修改了结构体 gpio_pin_config_t(加入了interruptMode成员变量),然后添加一些跟中断有关的函数声明。
然后保存并退出gpio.h。接着使用命令“vi drivers/gpio/gpio.h”打开gpio.c。如图 10所示:
在这里插入图片描述

图 10

修改内容如下:

  1 #include "gpio.h"
  2 
  3 /*
  4  * @description         : GPIO初始化。
  5  * @param - base        : 要初始化的GPIO组。
  6  * @param - pin         : 要初始化GPIO在组内的编号。
  7  * @param - config      : GPIO配置结构体。
  8  * @return                      : 无
  9  */
 10 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
 11 {
    
    
 12         base->IMR &= ~(1U << pin);
 13 
 14         if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
 15         {
    
    
 16                 base->GDIR &= ~( 1 << pin);
 17         }
 18         else  /* 输出 */
 19         {
    
    
 20                 base->GDIR |= 1 << pin;
 21                 gpio_pinwrite(base,pin, config->outputLogic);    /* 设置默认输出*/
 
 22         }
 23         gpio_intconfig(base, pin, config->interruptMode);         /* 中断功能配置 */
 24 }
 25 
 26 /*
 27  * @description  : 读取指定GPIO的电平值 。
 28  * @param - base : 要读取的GPIO组。
 29  * @param - pin  : 要读取的GPIO脚号。
 30  * @return    : 无
 31  */
 32  int gpio_pinread(GPIO_Type *base, int pin)
 33  {
    
    
 34          return (((base->DR) >> pin) & 0x1);
 35  }
 36 
 37 /*
 38  * @description   : 指定GPIO输出高或者低电平 。
 39  * @param - base     : 要输出的的GPIO组。
 40  * @param - pin     : 要输出的GPIO脚号。
 41  * @param - value    : 要输出的电平,1 输出高电平, 0 输出低低电平
 42  * @return        : 无
 43  */
 44 void gpio_pinwrite(GPIO_Type *base, int pin, int value)
 45 {
    
    
 46          if (value == 0U)
 47          {
    
    
 48                  base->DR &= ~(1U << pin); /* 输出低电平 */
 49          }
 50          else
 51          {
    
    
 52                  base->DR |= (1U << pin); /* 输出高电平 */
 53          }
 54 }
 55 
 56 /*
 57  * @description                         : 设置GPIO的中断配置功能
 58  * @param - base                        : 要配置的IO所在的GPIO组。
 59  * @param - pin                         : 要配置的GPIO脚号。
 60  * @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
 61  * @return                              : 无
 62  */
 63 void gpio_intconfig(GPIO_Type* base, unsigned int pin, 
                        gpio_interrupt_mode_t pin_int_mode)
 64 {
    
    
 65         volatile uint32_t *icr;
 66         uint32_t icrShift;
 67 
 68         icrShift = pin;
 69 
 70         base->EDGE_SEL &= ~(1U << pin);
 71 
 72         if(pin < 16) /* 低16位 */
 73         {
    
    
 74                 icr = &(base->ICR1);
 75         }
 76         else          /* 高16位 */
 77         {
    
    
 78                 icr = &(base->ICR2);
 79                 icrShift -= 16;
 80         }
 81         switch(pin_int_mode)
 82         {
    
    
 83                 case(kGPIO_IntLowLevel):
 84                         *icr &= ~(3U << (2 * icrShift));
 85                         break;
 86                 case(kGPIO_IntHighLevel):
 87                          *icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
 88                         break;
 89                 case(kGPIO_IntRisingEdge):
 90                          *icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
 91                         break;
 92                 case(kGPIO_IntFallingEdge):
 93                         *icr |= (3U << (2 * icrShift));
 94                         break;
 95                 case(kGPIO_IntRisingOrFallingEdge):
 96                         base->EDGE_SEL |= (1U << pin);
 97                         break;
 98                 default:
 99                         break;
100         }
101 }
102 
103 
104 /*
105  * @description                         : 使能GPIO的中断功能
106  * @param - base                        : 要使能的IO所在的GPIO组。
107  * @param - pin                         : 要使能的GPIO在组内的编号。
108  * @return                             : 无
109  */
110 void gpio_enableint(GPIO_Type* base, unsigned int pin)
111 {
    
    
112     base->IMR |= (1 << pin);
113 }
114 
115 /*
116  * @description                         : 禁止GPIO的中断功能
117  * @param - base                        : 要禁止的IO所在的GPIO组。
118  * @param - pin                         : 要禁止的GPIO在组内的编号。
119  * @return                                      : 无
120  */
121 void gpio_disableint(GPIO_Type* base, unsigned int pin)
122 {
    
    
123     base->IMR &= ~(1 << pin);
124 }
125 
126 /*
127  * @description                         : 清除中断标志位(写1清除)
128  * @param - base                        : 要清除的IO所在的GPIO组。
129  * @param - pin                         : 要清除的GPIO掩码。
130  * @return                                      : 无
131  */
132 void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
133 {
    
    
134     base->ISR |= (1 << pin);
135 }

在gpio.c文件中主要修改了gpio_init函数,在此函数里面添加了中断配置代码。另外增加了4个函数,如下所示:
gpio_intconfig函数用于配置 GPIO 的中断。
gpio_enableint函数用于使能GPIO中断。
gpio_disableint函数用于禁用GPIO中断。
gpio_clearintflags函数用于清除GPIO中断标志位。

然后使用命令“mkdir drivers/exit”创建按键中断驱动文件夹。如图 11所示:
在这里插入图片描述

图 11

接着使用命令“vi drivers/exit/exit.h”新建exit.h文件,如图 12所示:
在这里插入图片描述

图 12

添加如下内容:

  1 #ifndef _BSP_EXIT_H
  2 #define _BSP_EXIT_H
  3 
  4 #include "imx6ul.h"
  5 
  6 /* 函数声明 */
  7 void exit_init(void);                 /* 中断初始>化 */
  8 void gpio1_io18_irqhandler(void);      /* 中断处理函数 */
  9 
 10 #endif

在exit.h里面声明了两个函数exit_init和gpio1_io18_irqhandler。添加完成,保存并退出。然后使用命令“vi drivers/exit/exit.c”新建exit.c文件,如图 13所示:
在这里插入图片描述

图 13

添加如下内容:

  1 #include "exit.h"
  2 #include "gpio.h"
  3 #include "int.h"
  4 #include "delay.h"
  5 #include "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 
 26         GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
 27         system_register_irqhandler(GPIO1_Combined_16_31_IRQn, 
                                    (system_irq_handler_t)gpio1_io18_irqhandler,
                                     NULL);       /* 注册中断服务函数 */
 28         gpio_enableint(GPIO1, 18);   /* 使能GPIO1_IO18的中断功能 */                                                              
 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 }

这个文件包含两个函数exit_init和gpio1_io18_irqhandler。exit_init函数实现初始化IO,复用为GPIO1_IO18功能,并配置为下降沿触发,然后调用函数GIC_EnableIRQ使能GPIO_IO18所对应的中断总开关(I.MX6ULL中GPIO1_IO16~IO31这16个IO共用ID99),然后调用函数system_register_irqhandler注册ID99所对应的中断处理函数。 GPIO1_IO16~IO31这16个IO共用一个中断处理函数,至于具体是哪个IO引起的中断,需要在中断处理函数中判断。最后通过函数gpio_enableint使能GPIO1_IO18这个 IO 对应的中断。

gpio1_io18_irqhandler函数是中断处理函数,里面添加了中断处理代码,即蜂鸣器开关控制代码。中断处理完成以后调用函数gpio_clearintflags来清除GPIO1_IO18的中断标志位。

保存并退出exit.c文件。接着使用命令“vi main.c”打开main.c,如图 14所示:
在这里插入图片描述

图 14

修改内容如下:

  1 #include "clk.h"
  2 #include "delay.h"
  3 #include "led.h"
  4 #include "beep.h"
  5 #include "key.h"
  6 #include "int.h"
  7 #include "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_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         return 0;
 34 }

在main.c文件中第6,7行添加头文件,18行初始化中断,24行初始化按键中断,主循环闪烁led灯即可。在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46635880/article/details/108843047