Linux-ARM裸机(八)-中断(下半)

通用中断驱动编写

做Linux开发时候要把Ubuntu的ip地址设置为静态,防止发生变化。

移植SDK包中断相关文件

        将 SDK 包中的文件 core_ca7.h 拷贝到工程中的“imx6ul”文件夹中。裁剪修改的内容参考试验“9_int”中 core_ca7.h 进行修改(直接使用正点提供的修改好的即可,copy过来直接用)。主要留下和 GIC 相关的内容,重点需要 core_ca7.h 中的 10 个 API 函数,这 10 个函数如下表:

Linux-ARM裸机(八)-中断(下半)_第1张图片

移植好 core_ca7.h 后,修改文件 imx6ul.h文件,将core_ca7.h头文件包含进去,imx6ul.h文件将:fsl_iomuxc.h,MCIMX6Y2.h,cc.h,fsl_common.h,core_ca7.h包含周在一起,引用时候直接:#include "core_ca7.h"

编写通用中断驱动

实现函数 system_irqhandler 的具体内容,不同中断源对应不同中断处理函数, I.MX6U 有 160 个中断源,所以需 160 个中断处理函数,可将这些中断处理函数放到一个数组里,中断处理函数在数组中的标号就是其对应的中断号。中断发生后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。

实现:在 bsp 目录下新建名为“int”的文件夹,在 bsp/int 文件夹里面创建 bsp_int.c 和 bsp_int.h 这
两个文件。

bsp_int.h 文件

typedef void (*system_irq_handler_t) (unsigned int giccIar,void *p);

声明了一种新的类型—systme_irq_handler_t,这个类型是一类函数的指针:返回值为void,两个参数分别为 unsigned int、void *p 类型的函数。当我们需要创建一系列某种类型的函数时,比如根据中断号的不同创建一系列的中断函数时,就可将中断函数的类型提取出来,将其声明为一种新类型。这样我们就可以像使用常规变量(比如int)一样来定义此类型的函数。比如定义irqHandler函数如下:

system_irq_handler_t irqHandler;
解释如下:void irqHandler (unsigned int giccIar,void *p);
#ifndef _BSP_INT_H
#define _BSP_INT_H
#include "imx6ul.h"

typedef void (*system_irq_handler_t) (unsigned int giccIar,void *param);

/* 中断处理函数结构体*/
typedef struct _sys_irq_handle
{
    system_irq_handler_t irqHandler; /* 中断处理函数 */
    void *userParam;                 /* 中断处理函数的参数 */
} sys_irq_handle_t;
/* 函数声明 */
    void int_init(void);
    void system_irqtable_init(void);

    void system_register_irqhandler(IRQn_Type irq,
    system_irq_handler_t handler,void *userParam);
    
    void system_irqhandler(unsigned int giccIar);
    void default_irqhandler(unsigned int giccIar, void *userParam);
#endif

bsp_int.c文件

static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

#define  NUMBER_OF_INT_VECTORS  160,在MCIMX6Y2.h中定义。

一个中断源需要一个 sys_irq_handle_t 变量,因为I.MX6U 有 160 个中断源,因此需 160 个 sys_irq_handle_t 组成中断处理数组,数组中每个元素都对应一个中断ID的中断处理函数结构体。

#include "bsp_int.h"

static unsigned int irqNesting;/* 中断嵌套计数器,记录到底嵌套了多少层中断 */

static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];/* 中断服务函数表 */
/*初始化中断服务函数表*/
void system_irqtable_init(void)
{
    unsigned int i = 0;
    irqNesting = 0;
    /* 先将所有的中断服务函数设置为默认值 */
    for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
    {
        system_register_irqhandler((IRQn_Type)i,default_irqhandler,NULL);
    }
}
/*默认中断服务函数*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
    while(1) 
    {  }
}
/*给指定的中断号注册中断服务函数*/
//参数:要注册的中断号,要注册的中断处理函数,中断服务处理函数参数
void system_register_irqhandler(IRQn_Type irq,
                                system_irq_handler_t handler,void *userParam)
{
    irqTable[irq].irqHandler = handler;
    irqTable[irq].userParam = userParam;
}
/*中断初始化函数*/
void int_init(void)
{
    GIC_Init(); /* 初始化 GIC */
    system_irqtable_init(); /* 初始化中断表 */
    __set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移 */
}
/*具体的中断处理函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行*/
void system_irqhandler(unsigned int giccIar)
{
    uint32_t intNum = giccIar & 0x3FFUL;//bit9~0是中断ID号,取低10位
    /* 检查中断号是否符合要求 */
    if (intNum >= NUMBER_OF_INT_VECTORS)
    {            //1023中断ID超了160,则return
        return;
    }
    irqNesting++; /* 中断嵌套计数器加一 */

    /* 根据中断ID号,在 irqTable 中调用执行对应的中断服务函数*/
    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);

    irqNesting--; /* 中断执行完成,中断嵌套计数器减一 */
}

向GPIO驱动添加中断处理函数

步骤原理

6ULL  GPIO  中断设置

1、首先,GPIO_ICR1或ICR2寄存器设置GPIO的中断触发方式:低电平,高电平,上升沿,下降沿。(ICR1设置低GPIO号的16个,ICR2设置GPIO号的高16个)开发板按键部分电路设置的是按下有效,因此设置为下降沿触发。

Linux-ARM裸机(八)-中断(下半)_第2张图片

此寄存器设置后,上升或下降沿都会触发中断,GPIO_ICR1寄存器设置的触发方式会变得无效。

Linux-ARM裸机(八)-中断(下半)_第3张图片

2、使能GPIO对应的中断,设置GPIO_IMR寄存器(对应位置1即为使能,置0失能)

Linux-ARM裸机(八)-中断(下半)_第4张图片

3、中断处理完之后,要清除对应的中断标志位,也就是清除GPIO_ISR寄存器的相应位,此寄存器对应位是写1清除标志位的。

Linux-ARM裸机(八)-中断(下半)_第5张图片

GIC配置

1、使能相应的中断ID,GPIO1_IO18对应的中断ID为67+32(32个PPI和SGI)=99。如下图:GPIO的16_31共用一个中断ID

Linux-ARM裸机(八)-中断(下半)_第6张图片

Linux-ARM裸机(八)-中断(下半)_第7张图片

2、设置中断优先级

3、注册GPIO1_IO18的中断处理函数

代码编写

之前学过GPIO 最基本的输入输出功能,现在需要使用GPIO 的中断功能,因此修改文件 GPIO 的bsp_gpio.c 和 bsp_gpio.h,加上中断相关函数。添加了一个新枚举类型:gpio_interrupt_mode_t,枚举出了 GPIO 所有的中断触发类型。还修改了结构体gpio_pin_config_t,在里面加入 interruptMode 成员变量。最后添加了一些跟中断有关的函数声明,bsp_gpio.h驱动文件新添加部分如下:

#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"

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; /* GPIO 方向:输入还是输出 */
    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_intconfig(GPIO_Type* base, unsigned int pin,
                    gpio_interrupt_mode_t pinInterruptMode);
void gpio_enableint(GPIO_Type* base, unsigned int pin);
void gpio_disableint(GPIO_Type* base, unsigned int pin);
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);

#endif

bsp_gpio.c驱动文件新添加部分如下:

//设置 GPIO 的中断配置功能
void gpio_intconfig(GPIO_Type* base, unsigned int pin,gpio_interrupt_mode_t pin_int_mode)
{
    volatile uint32_t *icr;
    uint32_t icrShift;

    icrShift = pin;

    base->EDGE_SEL &= ~(1U << pin);

    if(pin < 16)  /* 低 16 位 */
    {
        icr = &(base->ICR1);
    }else         /* 高 16 位 */
    {
    icr = &(base->ICR2);
        icrShift -= 16;
    }
    switch(pin_int_mode)
    {
        case(kGPIO_IntLowLevel):
            *icr &= ~(3U << (2 * icrShift));
        break;

        case(kGPIO_IntHighLevel):
           *icr = (*icr & (~(3U << (2 * icrShift)))) |(1U << (2 * icrShift));
        break;

        case(kGPIO_IntRisingEdge):
            *icr = (*icr & (~(3U << (2 * icrShift)))) |(2U << (2 * icrShift));
        break;

        case(kGPIO_IntFallingEdge):
            *icr |= (3U << (2 * icrShift));
        break;

        case(kGPIO_IntRisingOrFallingEdge):
            base->EDGE_SEL |= (1U << pin);
        break;
        default:

        break;
    }
}
//使能与失能GPIO的中断功能
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{
    base->IMR |= (1 << pin);
}
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{
    base->IMR &= ~(1 << pin);
}
//GPIO初始化函数
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
    base->IMR &= ~(1U << pin);
    if(config->direction == kGPIO_DigitalInput)     /* GPIO 作为输入 */
    {
        base->GDIR &= ~( 1 << pin);
    }
    else     /* 输出 */
    {
        base->GDIR |= 1 << pin;
        gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认电平 */
    }
    gpio_intconfig(base, pin, config->interruptMode);/* 中断功能配置 */
}
//使能GPIO中断,禁止GPIO中断,清除中断标志位(写1清除)三个函数
//@param - base : 要操作的 IO 所在的 GPIO 组。
//@param - pin : 要操作的 GPIO 在组内的编号。
void gpio_enableint(GPIO_Type* base, unsigned int pin){
    base->IMR |= (1 << pin);
}
void gpio_disableint(GPIO_Type* base, unsigned int pin){
    base->IMR &= ~(1 << pin);
}
void gpio_clearintflags(GPIO_Type* base, unsigned int pin){
    base->ISR |= (1 << pin);
}

编写按键中断驱动

之前的中断相关学习以及编写准备均是为本实验的实现做底层铺垫。本实验实现以中断的方式编写 KEY 按键驱动,当按下 KEY 触发 GPIO 中断,然后在中断服务函数里控制蜂鸣器开关。编写按键 KEY 对应的UART1_CTS这个 IO 的中断驱动。本实验实现的最终现象即为按键开关蜂鸣器。

在 bsp 文件夹里面新建 “exit” 文件夹,然后在 bsp/exit里新建 bsp_exit.c 和 bsp_exit.h 两文件。

bsp_exit.h (函数声明):

#ifndef _BSP_EXIT_H
#define _BSP_EXIT_H
#include "imx6ul.h"

/* 函数声明 */
void exit_init(void); /* 中断初始化 */
void gpio1_io18_irqhandler(void); /* 中断处理函数 */

#endif

bsp_exit.c(函数实现): 

中断初始化函数exit.init

第一步,初始化 KEY 所使用的 UART1_CTS 这个 IO,设置其复用为 GPIO1_IO18

第二步,配置GPIO1_IO18 为下降沿触发中断。

第三步,调用函数 GIC_EnableIRQ来使能 GPIO_IO18 所对应的中断总开关(I.MX6U 中 GPIO1_IO16 ~ IO31 这 16 个 IO 共用 ID99),之后调用函数 system_register_irqhandler 注册 ID99 所对应的中断处理函数,GPIO1_IO16~GPIO1_IO31这 16 个 IO 共用一个中断处理函数,至于具体是哪个 IO 所引起的中断,需要在中断处理函数中判断。最后通过函数 gpio_enableint 使能 GPIO1_IO18 这个 IO 对应的中断。

中断处理函数gpio1_io18_irqhandler

函数 gpio1_io18_irqhandler 就是system_register_irqhandler注册的中断处理函数。在此函数里编写中断处理代码,beep_switch()就是蜂鸣器开关控制代码,也就是本试验的目的。中断处理完成后要清除中断标志位,调函数 gpio_clearintflags 来清除 GPIO1_IO18 的中断标志位。

GPIO1_IO18对应的中断ID位为99,GPIO1的16_31共用一个中断ID。

Linux-ARM裸机(八)-中断(下半)_第8张图片

#include "bsp_exit.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "bsp_beep.h"

void exit_init(void)
{
    gpio_pin_config_t  key_config;

/* 1、设置 IO 复用 */
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);

/* 2、初始化 GPIO 为中断模式 */
    key_config.direction = kGPIO_DigitalInput;    //输入
    key_config.interruptMode = kGPIO_IntFallingEdge;    //下降沿触发
    key_config.outputLogic = 1;
    gpio_init(GPIO1, 18, &key_config);
/* 3、使能 GIC 中断、注册中断服务函数、使能 GPIO 中断 */
    GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
    system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
                              (system_irq_handler_t)gpio1_io18_irqhandler,NULL);
    gpio_enableint(GPIO1, 18); //使能GPIO1_IO18
}
void gpio1_io18_irqhandler(void)
{
    static unsigned char state = 0;
    
    delay(10);//延时消抖
    //中断服务需快进快出,实际开发中禁用延时函数,这里为了演示用了中断延时函数进行消抖
    //后面会用定时器中断消抖法
    if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */
    {
        state = !state;
        beep_switch(state);
    }

    gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
}

main.c:

重点是:调用函数 int_init 来初始化中断系统以及调用函数exit_init 来初始化按键 KEY 对应的GPIO 中断。

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"

int main(void)
{
    unsigned char state = OFF;
    
    int_init(); /* 初始化中断(一定要最先调用! ) */
    imx6u_clkinit(); /* 初始化系统时钟 */
    clk_enable(); /* 使能所有的时钟 */
    led_init(); /* 初始化 led */
    beep_init(); /* 初始化 beep */
    key_init(); /* 初始化 key */
    exit_init(); /* 初始化按键中断 */

    while(1)
    {
    state = !state;
    led_switch(LED0, state);
    delay(500);
}
    return 0;
}

注:存在一个错误,_bss_start在0x87800000的位置,中断向量表偏移后的首地址也处于此地址的位置,这样导致中断向量表混乱。通过反汇编.dis文件来找到的bug。

解决方法:将bss段放在中断向量偏移部分代码后面,保证中断向量偏移首地址要设置到0x87800000的起始地址处。

至此SOC的中断相关知识已经学习完毕,首先学习SOC的中断系统,之后从底层驱动搭起,实现中断里开关蜂鸣器的实验现象。

你可能感兴趣的:(linux,arm开发,运维)