九.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
bsp_gpio.h

 

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
bsp_exti.h

至此,中断就讲完了。这是非常重要的一个知识点,用了3篇去讲解!不了解的话对照注释看看代码就可以了!

posted @ 2022-01-11 17:53  银色的音色  阅读(699)  评论(0编辑  收藏  举报