Linux 中断实验-基于正点原子IMX6ULL开发板

详细内容参考《I.MX6U 嵌入式 Linux 驱动开发指南 V1.6 》第五十一章,本篇文章仅说明重点内容。

Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要一系列复杂的寄存器配置。

1 Linux 中断简介

1.1 Linux 中断 API 函数

1、中断号
每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中断线。在 Linux 内核中使用一个 int 变量表示中断号。

2、request_irq 函数
request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下:

int request_irq(unsigned int irq, 
                irq_handler_t handler, 
                unsigned long flags,
                const char *name, 
                void *dev)

3、free_irq 函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。

void free_irq(unsigned int irq, 
					void *dev)

4、中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:

irqreturn_t (*irq_handler_t) (int, void *)

中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义如下所示:

enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};

typedef enum irqreturn irqreturn_t;

5、中断使能与禁止函数

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)//disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。
local_irq_enable()//使能当前处理器中断系统
local_irq_disable()//禁止当前处理器中断系统
local_irq_save(flags)//local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中
local_irq_restore(flags)//local_irq_restore 用于恢复中断,将中断恢复到 flags 状态

1.2 上半部与下半部

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

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

哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,这个就很考验驱动编写人员的功底了。这里有一些可以借鉴的参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

1、软中断
Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

在 kernel/softirq.c 文件中一共定义了 10 个软中断,如下所示:

static struct softirq_action softirq_vec[NR_SOFTIRQS] 

NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

可以看出,一共有 10 个软中断,因此 NR_SOFTIRQS 为 10,因此数组 softirq_vec 有 10 个元素。softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU 所执行的软中断服务函数确是相同的,都是数组 softirq_vec 中定义的 action 函数。要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,open_softirq 函数原型如下:

void open_softirq(int nr, void (*action)(struct softirq_action *))

2、tasklet

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用 tasklet_struct 结构体来表示 tasklet:

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

func 函数就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数。如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:

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

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

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

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

void tasklet_schedule(struct tasklet_struct *t)

关于 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);
 ......
}

3、工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。

Linux 内核使用 work_struct 结构体表示一个工作,内容如下(省略掉条件编译):

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
};

这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下(省略掉条件编译):

struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */
	struct list_head	list;		/* PR: list of all workqueues */
	struct mutex		mutex;		/* protects this wq */
	int			work_color;	/* WQ: current work color */
	int			flush_color;	/* WQ: current flush color */
	atomic_t		nr_pwqs_to_flush; /* flush in progress */
	struct wq_flusher	*first_flusher;	/* WQ: first flusher */
	struct list_head	flusher_queue;	/* WQ: flush waiters */
	struct list_head	flusher_overflow; /* WQ: flush overflow list */
	struct list_head	maydays;	/* MD: pwqs requesting rescue */
	struct worker		*rescuer;	/* I: rescue worker */

	int			nr_drainers;	/* WQ: drain in progress */
	int			saved_max_active; /* WQ: saved pwq max_active */

	struct workqueue_attrs	*unbound_attrs;	/* WQ: only for unbound wqs */
	struct pool_workqueue	*dfl_pwq;	/* WQ: only for unbound wqs */

	char			name[WQ_NAME_LEN]; /* I: workqueue name */

	/*
	 * Destruction of workqueue_struct is sched-RCU protected to allow
	 * walking the workqueues list without grabbing wq_pool_mutex.
	 * This is used to dump all workqueues from sysrq.
	 */
	struct rcu_head		rcu;

	/* hot fields used during command issue, aligned to cacheline */
	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */
};

Linux 内核使用**工作者线程(worker thread)**来处理工作队列中的各个工作,Linux 内核使用worker 结构体表示工作者线程,worker 结构体内容如下:

struct worker {
	/* on idle list while idle, on busy hash table while busy */
	union {
		struct list_head	entry;	/* L: while idle */
		struct hlist_node	hentry;	/* L: while busy */
	};

	struct work_struct	*current_work;	/* L: work being processed */
	work_func_t		current_func;	/* L: current_work's fn */
	struct pool_workqueue	*current_pwq; /* L: current_work's pwq */
	bool			desc_valid;	/* ->desc is valid */
	struct list_head	scheduled;	/* L: scheduled works */

	/* 64 bytes boundary on 64bit, 32 on 32bit */

	struct task_struct	*task;		/* I: worker task */
	struct worker_pool	*pool;		/* I: the associated pool */
						/* L: for rescuers */
	struct list_head	node;		/* A: anchored at pool->workers */
						/* A: runs through worker->node */

	unsigned long		last_active;	/* L: last active timestamp */
	unsigned int		flags;		/* X: flags */
	int			id;		/* I: worker id */

	/*
	 * Opaque string set with work_set_desc().  Printed out with task
	 * dump for debugging - WARN, BUG, panic or sysrq.
	 */
	char			desc[WORKER_DESC_LEN];

	/* used only by rescuers to point to the target workqueue */
	struct workqueue_struct	*rescue_wq;	/* I: the workqueue to rescue */
};

每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(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);
 ......
}

1.3 设备树中断信息节点

如果使用设备树的话就需要在设备树中设置好中断属性信息,Linux 内核通过读取设备树中的中断属性信息来配置中断。对于中断控制器而言,设备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点,节点内容如下所示:

intc: interrupt-controller@00a01000 {
	compatible = "arm,cortex-a7-gic";
	#interrupt-cells = <3>;
	interrupt-controller;
	reg = <0x00a01000 0x1000>,
	      <0x00a02000 0x100>;
};

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下所示:

			gpio5: gpio@020ac000 {
				compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
				reg = <0x020ac000 0x4000>;
				interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
					     <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
				gpio-controller;
				#gpio-cells = <2>;
				interrupt-controller;
				#interrupt-cells = <2>;
			};

简单总结一下与中断有关的设备树属性信息:
①、#interrupt-cells,指定中断源的信息 cells 个数。
②、interrupt-controller,表示当前节点为中断控制器。
③、interrupts,指定中断号,触发方式等。
④、interrupt-parent,指定父中断,也就是中断控制器。

1.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 对应的中断号。

2 硬件原理图分析

在这里插入图片描述

3 实验程序编写

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

3.1 修改设备树文件

我们使用到了按键 KEY0,按键 KEY0 使用中断模式,因此需要在“key”节点下添加中断相关属性,添加完成以后的“key”节点内容如下所示:

key{
	compatible = "alientek,key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;  //此处gpio1_io18作为输入,电平可随便输入,可为高,也可为低
	status= "okay";
	interrupt-parent = <&gpio1>;//设置 interrupt-parent 属性值为“gpio1”因为 KEY0 所使用的         GPIO 为 GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>;//设置 interrupts 属性,也就是设置中断源,第一个 cells 的 18 表示 GPIO1 组的 18 号 IO。IRQ_TYPE_EDGE_BOTH 表示上升沿和下降沿同时有效。
};

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

3.2 按键中断驱动程序编写

新建工程,在 imx6uirq.c 里面输入如下内容:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>

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

 /* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                            /*io 编号*/
    int irqnum;                          /*中断号*/
    unsigned char value;                 /*键值*/
    char name[10];                       /*名字*/
    irqreturn_t (*handler)(int, void *); /*中断处理函数*/
};

/*imx6uirq 设备结构体*/
struct imx6uirq_dev
{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM]; /* 按键描述数组 */
    struct timer_list timer;    /* 定义一个定时器*/

    atomic_t keyvalue;          /* 有效的按键键值 */
    atomic_t releasekey;        /* 标记是否完成一次完成的按键*/
};

struct imx6uirq_dev imx6uirq; /*irq设备 */

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;
    unsigned char releasekey;
    struct imx6uirq_dev *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); /*按下标志清0*/
    }
    else
    {
        goto data_error;
    }

    return 0;
data_error:

    return -EINVAL;
}

/*操作集*/
static const struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*按键中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = dev_id;

    dev->timer.data = (volatile unsigned long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /*20ms定时*/
    return IRQ_HANDLED;
}

/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if (value == 0) /*按下*/
    {
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    }
    else if (value == 1) /*释放*/
    {
        atomic_set(&dev->keyvalue, 0x80 | (dev->irqkey[0].value));
        atomic_set(&dev->releasekey, 1); /*完整的按键过程*/
    }
}

/*按键初始化*/
static int keyio_init(struct imx6uirq_dev *dev)
{
    int ret = 0;
    int i = 0;
    /*1,按键初始化*/
    dev->nd = of_find_node_by_path("/key");
    if (dev->nd == NULL)
    {
        ret = -EINVAL;
        goto fail_nd;
    }

    /* 提取 GPIO */
    for (i = 0; i < KEY_NUM; i++)
    {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);
    }

    /* 初始化 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); /*获取中断号*/
#if 0   
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
    }


    /* 申请中断 */
    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value = KEY0VALUE;
    /*2,按键中断初始化*/
    for (i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[0].handler,
                          IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                          dev->irqkey[i].name, &imx6uirq);
        if (ret)
        {
            printk("irq %d failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /*初始化定时器*/
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;
    return 0;

fail_irq:
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/*驱动入口函数*/
static int __init imx6uirq_init(void)
{
    int ret = 0;

    /*1,注册字符设备驱动*/
    imx6uirq.major = 0;
    if (imx6uirq.major)
    { /*给定主设备号*/
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    if (ret < 0)
    {
        goto fail_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*/
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if (ret)
    {
        goto fail_cdevadd;
    }
    /*4,创建类*/
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class))
    {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }
    /*5,创建设备*/
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device))
    {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /*初始化IO*/
    ret = keyio_init(&imx6uirq);
    if (ret < 0)
    {
        goto fail_keyinit;
    }

    /*初始化原子变量*/
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);
    return 0;

fail_keyinit:

fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdevadd:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:

    return ret;
}

/*驱动出口函数*/
static void __exit imx6uirq_exit(void)
{
    int i = 0;
    /*1、释放中断*/
    for (i = 0; i < KEY_NUM; i++)
    {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    /*2、释放IO*/
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(imx6uirq.irqkey[i].gpio);
    }

    /*3、删除定时器*/
    del_timer(&imx6uirq.timer);

    /*注销字符设备驱动*/
    cdev_del(&imx6uirq.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("supersmart");

3.3 编写测试 APP

新建名为 imx6uirqApp.c 的文件,然后输入如下所示内容:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./imx6uirqAPP <filename> <0:1> 0 关灯,1 开灯
 * ./imx6uirqAPP /dev/imx6uirq
 */



int main(int argc, char *argv[])
{
    int fd, ret;
    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", filename);
        return -1;
    }

    /*循环读取*/
    while (1)
    {
        ret = read(fd, &data, sizeof(data));
        if (ret < 0)
        {
        }
        else
        {
            if(data)
            {
                printf("key value = %#x \r\n",data);
            }
        }
    }
    close(fd);
    return 0;
}

4 运行测试

4.1 编译驱动程序和测试 APP

1、编译驱动程序
2、编译测试 APP

4.2 运行测试

加载驱动
在这里插入图片描述
驱动加载成功以后可以通过查看/proc/interrupts 文件来检查一下对应的中断有没有被注册上
在这里插入图片描述
可以看出 imx6uirq.c 驱动文件里面的 KEY0 中断已经存在了,触发方式为跳边沿(Edge),中断号为 47。

接下来测试中断:
在这里插入图片描述
从上图可以看出,按键值获取成功,并且不会有按键抖动导致的误判发生,说明按键消抖工作正常。

卸载驱动
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值