九.GPIO中断试验3——GPIO中断驱动
在完成了通用的中断程序编写后,我们就要针对不同的外设进行中断函数的编写了。这一章我们使用的硬件是还是那个按钮,当按钮按下时,触发中断、调用终端函数。
GPIO中断初始化
在构造函数前,我们先要对GPIO进行中断初始化,这里要参考I.MX6ULL的参考手册28章GPIO,我们前面做通用GPIO驱动时已经用了DR和GDIR两个寄存器,这里要用到另外几个
中断信号类型
GPIO的中断设置是由GPIO_ICR1和GPIO_ICR2决定的。我们看一下这个寄存器的参数
每个ICR寄存器用2个bit来描述一个IO口的中断信号类型,每组GPIO最有32个IO口,所以就用了2个寄存器(ICR1和ICR2)来描述所有端口的信号类型(高电平、低电平、上升沿和下降沿)。ICR1对应32个IO口的低16位,ICR2对应高16位。
这里还有另外一个寄存器:EDGE_SEL,设置后可以在对应位IO口上下降沿同时都可以触发。
使能GPIO对应中断
GPIO的中断使能是GPIOx_IMR决定的,每个Bit代表一个IO口(1时为使能,0时禁止)
中断标志位
GPIO_ISR,中断完成后,需要清除中断标志位,中断对应的bit应该写1.
代码构成
这个代码要对以前的通用GPIO驱动进行修改,添加了中断相关的功能
#include "bsp_gpio.h" /* * @description : GPIO初始化 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @param-config : GPIO配置结构体 * @return : None */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) { if(config->direction == kGPIO_DigitalInput) //gpio为输入 { base->GDIR &= ~(1 << pin); } else //gpio为输出 { base->GDIR |= 1<<pin; gpio_pinwrite(base,pin,config->outputLogic); //设置默认电平 } gpio_intinit(base,pin,config->interruptMode); //中断功能配置 } /* * @description : 读GPIO指定管脚 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @retrun : int 0 or 1 */ 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为低电平 * @retrun : None */ void gpio_pinwrite(GPIO_Type *base,int pin,int value) { if (value == 0U) //输出值为0 { base->DR &= ~(1U << pin); } else //输出值为1 { base->DR |= (1U<<pin); } } /* * @description : GPIO中断使能 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @return : None */ void gpio_enable(GPIO_Type *base,unsigned int pin) { base->IMR |= 1<<pin; } /* * @description : GPIO中断禁止 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @return : None */ void gpio_disable(GPIO_Type *base,unsigned int pin) { base->IMR &= ~(1<<pin); } /* * @description : GPIO中断禁止标志清除位 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @return : None */ void gpio_clearIntFlags(GPIO_Type *base,unsigned int pin) { base->ISR |= 1<<pin; } /* * @description : GPIO初始化 * @param-base : 待初始化的GPIO组 * @param-pin : 待初始化的GPIO在组内的编号 * @param-pin_int_mode : 中断信号类型 * @return : None */ void gpio_intinit(GPIO_Type *base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode) { volatile uint32_t *icr; //icr寄存器,icr通过pin的值来判定 uint32_t icrShift; //最终使用的pin的值, icrShift = pin; //icrShift值初始化 base->EDGE_SEL &= ~(1<<pin); //清除边沿触发标志位,该位如果为1,上升、下降沿触发失效 if(pin<16) //pin对应低16位时 {icr = &(base->ICR1);} //&为取址符,icr为IRC1地址 else //pin对应高16为时 { icr = &(base->ICR2); //icr为ICR2地址 icrShift -= 16; //超过16,减16就是对应bit位,前面初始化的icrShift被修改 } /* 根据触发信号类型修改ICR1/ICR2的值 */ switch (pin_int_mode) { case kGPIO_IntLowLevel: *icr &= (3<< (2*icrShift)); break; case kGPIO_IntHighLevel: *icr &= (3<< (2*icrShift)); *icr |= (1<<(2*icrShift)); break; case kGPIO_IntRisingEdge: *icr &= (3<< (2*icrShift)); *icr |= (2<<(2*icrShift)); break; case kGPIO_IntFallingEdge: *icr &= (3<< (2*icrShift)); *icr |= (3<<(2*icrShift)); break; case kGPIO_IntRisingOrFallingEdge: base->EDGE_SEL |=(1<<pin); break; default: break; } }
在上面的代码中,我们修改了原有的通用GPIO驱动,添加了中断的初始化、GPIO对应IO口的中断使能/禁止,标志位清除。要注意点是标志位清除是对应bit置一。整个流程备注的很详细了,最后记得修改头文件,在头文件里声明新建的函数,还要定义新的枚举型数据gpio_interrupt_mode_t
#ifndef __BSP_GPIO_H #define __BSP_GPIO_H #include "imx6ul.h" /*枚举类型描述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; 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_enable(GPIO_Type *base,unsigned int pin); void gpio_disable(GPIO_Type *base,unsigned int pin); void gpio_clearIntFlags(GPIO_Type *base,unsigned int pin); void gpio_intinit(GPIO_Type *base,unsigned int pin,gpio_interrupt_mode_t pin_int_mode); #endif
GIC配置
配置完GPIO相关参数后,要对GIC进行配置
使能相应中断ID
GPIO的中断ID要在参考手册第3章查询,要注意的是3.2章节开始说明了,前32个中断ID是不包含在那个给定的表中的
开发板按键映射的端口是UART1_CTS,可以复用为GPIO1_IO18,所以我们查到的GPIO中断ID应该加上32
也就是99。这个中断ID在我们移植的头文件里有预定义,可以直接使用
中断优先级设置
由于就用了一个中断源,我们这里就不再设置了,具体的设置方法在上一章有介绍。
外部中断服务函数
上面关于GIC的在过程放在一个新的文件夹里:
作为外部中断的服务函数,c文件里只有两个函数,一个用来按照上面说的过程对GPIO和GIC进行初始化,另一个函数用来定义实际中断要实现的效果
#include "bsp_exti.h" #include "bsp_int.h" #include "bsp_beep.h" #include "bsp_gpio.h" #include "bsp_delay.h" // 初始化外部中断:GPIO1_IO18 /* * @description : 外部中断初始化 * @param : None * @return : None */ void exti_init(void) { gpio_pin_config_t key_config; //定义GPIO配置 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //GPIO复用初始化 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xf080); //GPIO电气初始化 key_config.direction = kGPIO_DigitalInput; //GPIO定义为输入 key_config.interruptMode = kGPIO_IntFallingEdge; //定义中断触发信号类型 gpio_init(GPIO1,18,&key_config); //GPIO初始化 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); //GIC使能中段, //GPIO1_Combined_16_31_IRQn=99,即GPIO01_IO18中断ID system_register_irqHandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); //注册中断函数 gpio_enable(GPIO1,18); //使能GPIO1_IO18中断 } /* * @description : 外部中断初始化 * @param : 这里不需要没有实际参数,但是定义函数的时候是按照前面声明函数是定义的,就把直接把样式复制过来了 * @return : None */ void gpio1_io18_irqhandler(unsigned int gicciar, void *param) { static unsigned char state = ON; /*这里要注意:定时器用来进行按键消抖,实际工况下严禁在中断服务中调用延时!!!*/ delay(10); if(gpio_pinread(GPIO1,18)==0) { state = !state; beep_switch(state); } gpio_clearIntFlags(GPIO1,18); //清除中断标志位。 }
在初始化的过程中,一定要注意最后两行代码:注册中断函数和使能中断的顺序,一定要先定义函数,因为如果是先使能GPIO中断等话,一上电,有可能被GPIO申请进入中断,而此刻没有对应的函数程序可能异常。所以一定要先注册函数。
程序里也说明了,在实际的中断函数中是严禁使用延时类函数的,因为这里还没有讲到定时器,所以先用了这个delay函数凑合一下实现按键消除抖动的效果,后面会讲到实际的用法。这里是使用前面的蜂鸣器来调试实际中断效果,没什么可讲的。
记得在头文件里声明用到的函数
#ifndef __BSP_EXTI_H #define __BSP_EXTI_H #include "imx6ul.h" void exti_init(void); void gpio1_io18_irqhandler(unsigned int gicciar, void *param); #endif
至此,中断就讲完了。这是非常重要的一个知识点,用了3篇去讲解!不了解的话对照注释看看代码就可以了!