嵌入式Linux(12):Liunx中断

嵌入式Linux中断

  • Linux 中断
    • 1、Linux 中断 API 函数
      • 1.1 中断 ID
      • 1.2 request_irq 函数
      • 1.3 free_irq 函数
      • 1.4 中断处理函数
      • 1.5 中断使能与禁止函数
    • 2、上半部与下半部
      • 2.1 软中断
      • 2.2 tasklet
      • 2.3 工作队列
    • 3、设备树中断信息节点
    • 4、获取中断号
  • 实验
    • 1、修改设备树
    • 2、按键中断驱动
    • 3、测试App
    • 4、运行测试

Linux 中断

  • 裸机实验里面中断的处理方法:
    ①、使能中断,初始化相应的寄存器
    ②、注册中断服务函数,也就是向 irqTable 数组的指定标号处写入中断服务函数
    ②、中断发生以后进入 IRQ 中断服务函数,在 IRQ 中断服务函数在数组 irqTable 里面查找具体的中断处理函数,找到以后执行相应的中断处理函数。

1、Linux 中断 API 函数

  • GIC中断控制器将众多的中断源分为三类:
    ①、 SPI(Shared Peripheral Interrupt),共享中断,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
    ②、 PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
    ③、 SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

1.1 中断 ID

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。

  • ID0~ID15:这 16 个 ID 分配给 SGI。
    ID16~ID31:这 16 个 ID 分配给 PPI。
    ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断

1.2 request_irq 函数

使用某个中断是需要申请的, request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断。

int request_irq(unsigned int irq,irq_handler_t handler,
				unsigned long flags,const char *name,
				void *dev)
函数参数和返回值含义如下:
		irq:要申请中断的中断号。
		handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
		flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志.
		name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
		dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
		返回值: 0 中断申请成功,其他负值中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

嵌入式Linux(12):Liunx中断_第1张图片

1.3 free_irq 函数

使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:

void free_irq(unsigned int irq,void *dev)
函数参数和返回值含义如下:
		irq: 要释放的中断。
		dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
		返回值:无

1.4 中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数

irqreturn_t (*irq_handler_t) (int, void *)
	第一个参数是要中断处理函数要相应的中断号。
	第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。

1.5 中断使能与禁止函数

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
  • enable_irq 和 disable_irq 用于使能和禁止指定的中断, irq 就是要禁止的中断号。
    disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
void disable_irq_nosync(unsigned int irq)
  • disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。
local_irq_enable()
local_irq_disable()
	local_irq_enable 用于使能当前处理器中断系统,
	local_irq_disable 用于禁止当前处理器中断系统。
local_irq_save(flags)
local_irq_restore(flags)
	这两个函数是一对, local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
	local_irq_restore 用于恢复中断,将中断到 flags 状态。

2、上半部与下半部

  • 我们在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。

  • 上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。

  • 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,

  • ①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
  • ②、如果要处理的任务对时间敏感,可以放到上半部。
  • ③、如果要处理的任务与硬件有关,可以放到上半部
  • ④、除了上述三点以外的其他任务,优先考虑放到下半部。

上半部中断处理函数,下半部机制:

2.1 软中断

Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中嵌入式Linux(12):Liunx中断_第2张图片

  • 一共有 10 个软中断,因此 NR_SOFTIRQS 为 10,因此数组 softirq_vec 有 10 个元素。 softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到。

要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数

void open_softirq(int nr, void (*action)(struct softirq_action *))
函数参数和返回值含义如下:
		nr:要开启的软中断,在示例代码 51.1.2.3 中选择一个。
		action:软中断对应的处理函数。
		返回值: 没有返回值。

注册好软中断以后需要通过 raise_softirq 函数触发

void raise_softirq(unsigned int nr)
函数参数和返回值含义如下:
		nr:要触发的软中断,在示例代码 51.1.2.3 中选择一个。
		返回值: 没有返回值。

软中断必须在编译的时候静态注册! Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面嵌入式Linux(12):Liunx中断_第3张图片

2.2 tasklet

tasklet 是利用软中断来实现的另外一种下半部机制,Linux 内核使用结构体

tasklet_struct 结构体
484 struct tasklet_struct
485 {
486 struct tasklet_struct *next; /* 下一个 tasklet */
487 unsigned long state; /* tasklet 状态 */
488 atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
489 void (*func)(unsigned long); /* tasklet 执行的函数 */
490 unsigned long data; /* 函数 func 的参数 */
491 };

如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

函数参数和返回值含义如下:
	t:要初始化的 tasklet
	func: tasklet 的处理函数。
	data: 要传递给 func 函数的参数
	返回值: 没有返回值

也可以使用宏DECLARE_TASKLET 来一次性完成tasklet的定义和初始化,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中

DECLARE_TASKLET(name, func, data)
name 为要定义的 tasklet 名字,是一个 tasklet_struct 类型的变量,
func就是 tasklet 的处理函数,
data 是传递给 func 函数的参数。

上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行, tasklet_schedule 函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)
函数参数和返回值含义如下:
	t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
	返回值: 没有返回值

tasklet 的参考使用示例

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
	/* tasklet 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	/* 调度 tasklet */
	tasklet_schedule(&testtasklet);
	......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 初始化 tasklet */
	tasklet_init(&testtasklet, testtasklet_func, data);
	/* 注册中断处理函数 */
	request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
	......
}

2.3 工作队列

工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。
嵌入式Linux(12):Liunx中断_第4张图片嵌入式Linux(12):Liunx中断_第5张图片
在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。

直接定义一个 work_struct 结构体变量,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:

#define INIT_WORK(_work, _func)
_work 表示要初始化的工作, _func 是工作对应的处理函数。

也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct), f 表示工作对应的处理函数。

和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

bool schedule_work(struct work_struct *work)
	函数参数和返回值含义如下:
	work: 要调度的工作。
	返回值: 0 成功,其他值 失败

工作队列的参考使用示例

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
	/* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	/* 调度 work */
	schedule_work(&testwork);
	......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 初始化 work */
	INIT_WORK(&testwork, testwork_func_t);
	/* 注册中断处理函数 */
	request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
	......
}

3、设备树中断信息节点

1、打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点
嵌入式Linux(12):Liunx中断_第6张图片

  • 第 2 行, compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7-gic”即可找到 GIC 中断控制器驱动文件。
  • 第 3 行, #interrupt-cells 和#address-cells、 #size-cells 一样。表示此中断控制器下设备的 cells大小,对于设备而言,会使用 interrupts 属性描述中断信息, #interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:
    • 第一个 cells:中断类型, 0 表示 SPI 中断, 1 表示 PPI 中断。
    • 第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号的范围为 0~15。
    • 第三个 cells:标志, bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。 bit[15:8]为 PPI 中断的 CPU 掩码。
  • 第 4 行, interrupt-controller 节点为空,表示当前节点是中断控制器。

2、 gpio 节点也可以作为中断控制器

imx6ull.dtsi 文件中的 gpio5 节点嵌入式Linux(12):Liunx中断_第7张图片
嵌入式Linux(12):Liunx中断_第8张图片
打开 imx6ull-alientek-emmc.dts 文件
嵌入式Linux(12):Liunx中断_第9张图片

  • ①、 #interrupt-cells,指定中断源的信息 cells 个数。
    ②、 interrupt-controller,表示当前节点为中断控制器。
    ③、 interrupts,指定中断号,触发方式等。
    ④、 interrupt-parent,指定父中断,也就是中断控制器。

4、获取中断号

irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
	函数参数和返回值含义如下:
	dev: 设备节点。
	index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
	返回值:中断号。

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:

int gpio_to_irq(unsigned int gpio)
	函数参数和返回值含义如下:
	gpio: 要获取的 GPIO 编号。
	返回值: GPIO 对应的中断号。

实验

驱动 I.MX6U-ALPHA 开发板上的 KEY0 按键,采用中断的方式,并且采用定时器来实现按键消抖,应用程序读取按键值并且通过终端打印出来。

1、修改设备树

按键 KEY0 使用中断模式,在“key”节点下添加中断相关属性
嵌入式Linux(12):Liunx中断_第10张图片

  • 第 211 行,设置 interrupt-parent 属性值为“gpio1”,因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。
  • 第 212行,设置 interrupts 属性,也就是设置中断源,第一个 cells 的 18 表示 GPIO1 组的 18号 IO
  • IRQ_TYPE_EDGE_BOTH 表示上升沿和下降沿同时有效,相当于 KEY0 按下和释放都会触发中断。

设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。

2、按键中断驱动

新建名为“13_irq”的文件夹,然后在 13_irq 文件夹里面创建 vscode 工程,工作区命名为“imx6uirq”。工程创建好以后新建 imx6uirq.c 文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define IMX6UIRQ_CNT           1          /*设备号个数*/
#define IMX6UIRQ_NAME       "imx6uirq"       /*设备名字*/
#define KEY0VALUE              0x01        /*KEY0按键值*/
#define INVAKEY                0xEF        /*无效按键值*/
#define KEY_NUM                 1            /*按键数量*/

/*中断IO描述结构体*/
struct irq_keydesc{
    int gpio; /* gpio */
    int irqnum; /* 中断号 */
    unsigned char value; /* 按键对应的键值 */
    char name[10]; /* 名字 */
    irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/*imx6uirq设备结构体*/
struct imx6uirq_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*lei*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node * nd; /*设备节点*/
    atomic_t keyvalue;       /*有效按键值*/
    atomic_t releasekey;     /*标记是否完成一次完整的按键*/
    struct timer_list timer; /*定义一个定时器*/
    struct irq_keydesc irqkey[KEY_NUM]; /*按键描述数组*/
    unsigned char curkeynum;            /*当前按键号*/
};

struct imx6uirq_dev imx6uirq;    /*定义irq设备*/

/*中断服务函数,开启定时器,延时10ms,定时器用于按键消抖*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data=(volatile long)dev_id;
    mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

/*定时器服务函数,用于按键消抖,定时器到了以后
再次读取按键值,如果按键还是处于按下状态就表示按键有效。*/
 void timer_function(unsigned long arg)
 {
     unsigned char value;
     unsigned char num;
     struct irq_keydesc *keydesc;
     struct imx6uirq_dev *dev=(struct imx6uirq_dev*)arg;

     num=dev->curkeynum;
    keydesc=&dev->irqkey[num];
    value=gpio_get_value(keydesc->gpio);/* 读取 IO 值 */
    if(value == 0){                   /* 按下按键 */
        atomic_set(&dev->keyvalue,keydesc->value);
    }else{
        atomic_set(&dev->keyvalue, 0x80|keydesc->value);/* 按键松开 */
        atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
    }
 }

/*初始化按键 IO*/
static int keyio_init(struct imx6uirq_dev *dev)
{
    unsigned char i = 0;
    int ret=0;

    /*1、获取设备节点:key*/
    dev->nd=of_find_node_by_path("/key");
    if(dev->nd == NULL){
        printk("key node can not found!\r\n");
        return  -EFAULT;
    }else{
        printk("key node has been found!\r\n");
    }

    /*2、获取设备树中的gpio属性,得到key所使用的编号*/
    for(i=0;i<KEY_NUM;i++){
        dev->irqkey[i].gpio =of_get_named_gpio(dev->nd,"key-gpio",i);
        if(dev->irqkey[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EFAULT;
        }
        printk("key%d:gpio= %d",i,dev->irqkey[i].gpio);
    }

    /*3. 初始化 key 所使用的 IO,并且设置成中断模式 */
    for(i=0;i<KEY_NUM;i++){
        memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name,"KEY%d",i);
        gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);
        dev->irqkey[i].irqnum=gpio_to_irq(dev->irqkey[i].gpio);

        printk(" irqnum=%d\r\n",dev->irqkey[i].irqnum);
    }

    /*4. 申请中断 */
    dev->irqkey[0].handler=key0_handler;
    dev->irqkey[0].value=KEY0VALUE;

    for(i=0;i<KEY_NUM;i++){
        ret=request_irq(dev->irqkey[i].irqnum,dev->irqkey[i].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,dev->irqkey[i].name,&imx6uirq);
        if(ret<0){
            printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
            return -EFAULT;
        }
        printk("irq %d request done!\r\n",dev->irqkey[i].irqnum);
    }

    /*5. 创建定时器*/
    init_timer(&dev->timer);
    dev->timer.function=timer_function;

    return 0;
}

/*打开设备*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;           /* 设置私有数据 */
    return 0;
}

/*从设备读取数据*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue=0;
    unsigned char releasekey=0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue=atomic_read(&dev->keyvalue);
    releasekey=atomic_read(&dev->releasekey);
    if(releasekey){             /*有按键按下*/
        if(keyvalue & 0x80){
            keyvalue &= ~0x80;
            ret=copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }else{
            goto data_error;
        }
        atomic_set(&dev->releasekey,0);   /* 按下标志清零 */
    }else{
        goto data_error;
    }
    return 0;

data_error:
    return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops={
    .owner=THIS_MODULE,
    .open=imx6uirq_open,
    .read=imx6uirq_read,
};

/*驱动模块入口函数*/
static int __init imx6uirq_init(void)
{
     /*注册字符设备驱动*/
    /*1、注册设备号*/
    if(imx6uirq.major){            /*定义设备号*/
    imx6uirq.devid=MKDEV(imx6uirq.major,0);
    register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }else{                         /*没有定义设备*/
        alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);  /*申请设备号*/
        imx6uirq.major=MAJOR(imx6uirq.devid);                            /* 获取分配号的主设备号 */
        imx6uirq.minor=MINOR(imx6uirq.devid);
    }
    printk("imx6uirq major=%d, minor=%d\r\n",imx6uirq.major,imx6uirq.minor);

    /*2、初始化cdev*/
    //imx6uirq.cdev.owner=THIS_MODULE;
    cdev_init(&imx6uirq.cdev,&imx6uirq_fops);

    /*3、添加一个cdev*/
    cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);

    /*4、创建类*/
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {
        return PTR_ERR(imx6uirq.class);
    }

    /*5、创建设备*/
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid,NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {
        return PTR_ERR(imx6uirq.device);
    }

    /*6、初始化按键*/
    atomic_set(&imx6uirq.keyvalue,INVAKEY);
    atomic_set(&imx6uirq.releasekey,0);
    keyio_init(&imx6uirq);
    return 0;
}

/*驱动出口*/
static void __exit imx6uirq_exit(void)
{
     unsigned int i = 0;

     del_timer_sync(&imx6uirq.timer);        /*删除timer定时器*/

    /*释放中断*/
    for(i=0;i<KEY_NUM;i++){
        free_irq(imx6uirq.irqkey[i].irqnum,&imx6uirq);
    }
    
    /* 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);                             /* 删除 cdev */
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); /*注销设备号*/

    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

/*注册模块入口和出口*/
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bruce");

3、测试App

通过不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上,新建名为 imx6uirqApp.c 的文件

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

/* 使用方法 :./irqApp /dev/imx6uirq 打开测试 App*/

int main(int argc, char* argv[])
{
    int fd;
    int ret = 0;
    char * filename;
    unsigned char data;

    if(argc!=2){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动*/
    fd=open(filename,O_RDWR);
    if(fd<0){
        printf("file %s open failed!\r\n",argv[1]);
        return -1;
    }

    /*/dev/irq读取数据*/
    while(1){
        ret=read(fd,&data,sizeof(data));
        if(ret<0){

        }else{
            if(data){
                printf("key value = %#X\r\n",data);
            }   
        }    
    }
    close(fd);/*关闭文件*/

    return ret;
}

4、运行测试

  • 编译驱动程序和测试 APP
    编写 Makefile 文件,将 obj-m 变量的值改为 imx6uirq.o
    输入命令make -j32编译出驱动模块文件:imx6uirq.ko
    输入如命令arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp编译出 imx6uirqApp 测试程序

  • imx6uirq.ko 和 imx6uirqApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入insmod imx6uirq.ko加载imx6uirq.ko 驱动模块在这里插入图片描述

  • 驱动加载成功以后可以通过查看/proc/interrupts 文件来检查一下对应的中断有没有被注册上cat /proc/interrupts嵌入式Linux(12):Liunx中断_第11张图片

  • ./imx6uirqApp /dev/imx6uirq按下开发板上的 KEY0 键,终端就会输出按键值,按键值获取成功,并且不会有按键抖动导致的误判发生,说明按键消抖工作正常。嵌入式Linux(12):Liunx中断_第12张图片

  • ./ mutexApp /dev/gpioled 0& //关闭

你可能感兴趣的:(嵌入式,linux,嵌入式,linux)