赞
踩
上节梳理完中断的相关知识,这一节就使用按键Key0作为中断源,在开发板上的Linux中添加一个外设中断。
开发步骤:
1.修改设备树文件,在KEY0设备节点中添加中断相关的属性
2.编写按键驱动程序和测试App程序
3.编译运行
在之前使用gpio子系统开发按键驱动时,给设备树中添加了一个按键的key节点:
key {
compatible = "amonter,key";
status = "okay";
key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
};
这一节就在此节点中添加中断相关的属性:
key {
compatible = "amonter,key";
status = "okay";
key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpiog>;
interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
//interrupts-extended = <&gpiog 3 IRQ_TYPE_EDGE_BOTH>;
};
设置 interrupt-parent 属性值为“gpiog”,因为 KEY0 所使用的 GPIO 为 PG3, 所以要设置 KEY0 的中断控制器为 gpiog
设置 interrupts 属性,也就是设置中断源,第一个 cells 的 3 表示 GPIOG 组的 3 号IO。IRQ_TYPE_EDGE_BOTH 为中断标志,表示上升沿和下降沿同时有效,所以按键按下和释放都有效。上一节讲述过,这个宏定义在文件include/linux/irq.h
中,是一个有十种标志的枚举类型的成员
这两句描述等效于 interrupts-extended 的一句描述
修改完设备树后使用make dtbs
命令重新编译出设备树文件,放在nfs的tftp目录用于启动开发板。
中断程序驱动的开发在gpio子系统的按键驱动基础上进行修改,如下:
keyirq.c:
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/semaphore.h> #include <linux/of_gpio.h> #include <linux/of_address.h> #include <linux/of.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/irq.h> #include <linux/of_irq.h> #define KEY_CNT 1 #define KEY_NAME "key" /*定义按键状态*/ enum key_status { KEY_PRESS = 0, /*按键按下*/ KEY_RELEASE, /*按键释放*/ KEY_KEEP, /*按键保持*/ }; /*key设备结构体*/ struct key_dev{ dev_t devid; /*设备号*/ struct cdev cdev; /*cdev*/ struct class *class; /*类*/ struct device *device; /*设备*/ int major; int minor; struct device_node *nd; /*设备节点*/ int key_gpio; /*key所使用的的gpio号*/ struct timer_list timer; /*定时器*/ int irq_num; /*中断号*/ spinlock_t spinlock; /*自旋锁*/ }; struct key_dev keydev; /*创建key设备*/ static int status = KEY_KEEP; /*按键状态*/ /*按键防抖处理,也就是开启定时器延时15ms,这里作为中断处理函数*/ static irqreturn_t key_interrupt(int irq, void *dev_id) { mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15)); return IRQ_HANDLED; } /*解析设备树中的按键属性*/ static int key_parse_dt(void){ int ret; const char *str; /*设置Key所使用的GPIO*/ /*获取设备节点*/ keydev.nd = of_find_node_by_path("/key"); if(keydev.nd == NULL){ printk("keydev node not find!\r\n"); return -EINVAL; } /*获取status值*/ ret = of_property_read_string(keydev.nd, "status", &str); if(ret < 0){ printk("read status failed!\r\n"); return -EINVAL; } if(strcmp(str, "okay")){ printk("key device not okay!\r\n"); return -EINVAL; } /*获取compatible属性并匹配*/ ret = of_property_read_string(keydev.nd, "compatible", &str); if(ret < 0){ printk("read compatible failed!\r\n"); return -EINVAL; } if(strcmp(str, "amonter,key")){ printk("keydev: compatible match failed!\r\n"); return -EINVAL; } /*获取设备树中的gpio属性,得到KEY0的设备编号*/ keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0); if(keydev.key_gpio < 0){ printk("can't get key-gpio"); return -EINVAL; } printk("key-gpio num = %d\r\n", keydev.key_gpio); /*获取GPIO对应的中断号*/ keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0); if(!keydev.irq_num){ return -EINVAL; } printk("irq-num = %d\r\n", keydev.irq_num); return 0; } static int key_gpio_init(void) { int ret; unsigned long irq_flags; /*向GPIO子系统申请使用GPIO*/ ret = gpio_request(keydev.key_gpio, "KEY0"); if(ret){ printk(KERN_ERR "keydev: failed to request key-gpio!\r\n"); return ret; } /*设置PG3为GPIO输入模式*/ ret = gpio_direction_input(keydev.key_gpio); if(ret < 0){ printk("can't set gpio mode!\r\n"); return ret; } /*获取设备树中指定的中断触发类型*/ irq_flags = irq_get_trigger_type(keydev.irq_num); if(IRQF_TRIGGER_NONE == irq_flags){ irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;; } /*申请中断*/ ret = request_irq(keydev.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL); if(ret){ gpio_free(keydev.key_gpio); return ret; } return 0; } static void key_timer_function(struct timer_list *arg) { static int last_val = 1; unsigned long flags; int current_val; /*自旋锁上锁*/ spin_lock_irqsave(&keydev.spinlock, flags); /*读取按键值并判断按键当前状态*/ current_val = gpio_get_value(keydev.key_gpio); if(0 == current_val && last_val){ /*按键按下*/ status = KEY_PRESS; } else if(1 == current_val && !last_val) { /*按键释放*/ status = KEY_RELEASE; } else{ /*保持*/ status = KEY_KEEP; } last_val = current_val; /*自旋锁解锁*/ spin_unlock_irqrestore(&keydev.spinlock, flags); } /*设备open操作函数*/ static int key_open(struct inode *inode, struct file *filp) { return 0; } /*设备read操作函数*/ static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){ unsigned long flags; int ret; /*自旋锁上锁*/ spin_lock_irqsave(&keydev.spinlock, flags); /*将按键信息发送给应用程序*/ ret = copy_to_user(buf, &status, sizeof(int)); /*状态重置*/ status = KEY_KEEP; /*自旋锁解锁*/ spin_unlock_irqrestore(&keydev.spinlock, flags); return ret; } /*设备write操作函数*/ static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { return 0; } /*设备release操作函数*/ static int key_release(struct inode *inode, struct file *filp) { return 0; } /*设备操作函数*/ static struct file_operations key_fops = { .owner = THIS_MODULE, .open = key_open, .read = key_read, .write = key_write, .release = key_release, }; /*驱动入口函数*/ static int __init mykey_init(void){ int ret; /*初始化自旋锁*/ spin_lock_init(&keydev.spinlock); /*设备树解析*/ ret = key_parse_dt(); if(ret){ /*出错*/ return ret; } /*gpio中断初始化*/ ret = key_gpio_init(); if(ret){ /*出错*/ return ret; } /*注册字符设备驱动*/ /*创建设备号,直接使用系统分配的设备号*/ ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME); //由系统分配设备号 if(ret < 0){ pr_err("%s couldn't alloc_chrdev_region, ret = %d\r\n", KEY_NAME, ret); goto free_gpio; } keydev.major = MAJOR(keydev.devid); keydev.minor = MINOR(keydev.devid); printk("kendev major = %d, minor = %d\r\n", keydev.major, keydev.minor); /*初始化cdev*/ keydev.cdev.owner = THIS_MODULE; cdev_init(&keydev.cdev, &key_fops); /*添加一个cdev*/ cdev_add(&keydev.cdev, keydev.devid, KEY_CNT); if(ret < 0){ goto del_unregister; } /*创建类*/ keydev.class = class_create(THIS_MODULE, KEY_NAME); if(IS_ERR(keydev.class)){ goto del_cdev; } /*创建设备*/ keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL,KEY_NAME); if(IS_ERR(keydev.device)){ goto destroy_class; } /*初始化timer,设置定时器处理函数*/ timer_setup(&keydev.timer, key_timer_function, 0); //未设置周期,所以不会激活定时期 return 0; destroy_class: device_destroy(keydev.class, keydev.devid); del_cdev: cdev_del(&keydev.cdev); del_unregister: unregister_chrdev_region(keydev.devid, KEY_CNT); free_gpio: free_irq(keydev.irq_num, NULL); gpio_free(keydev.key_gpio); return -EIO; } /*驱动出口函数*/ static void __exit mykey_exit(void){ cdev_del(&keydev.cdev); unregister_chrdev_region(keydev.devid, KEY_CNT); del_timer_sync(&keydev.timer); /*删除timer*/ device_destroy(keydev.class, keydev.devid); class_destroy(keydev.class); free_irq(keydev.irq_num, NULL); /*释放中断*/ gpio_free(keydev.key_gpio); /*释放IO*/ } module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("amonter"); MODULE_INFO(intree, "Y");
①首先包含中断相关的头文件<linux/irq.h>
、<linux/of_irq.h>
②将按键的键值封装成一个枚举类型key_status,包括三种状态:按下、释放、保持
③在设备结构体中增加三个成员变量:定时器、中断号、自旋锁
④维护一个中断服务函数 key_interrupt() ,在此中断中进行按键消抖处理(本质是延时15ms,使用定时器来计时)
⑤将从设备树中解析属性以及初始化GPIO设备分成两部分:key_parse_dt() 用来从设备树中解析属性,与gpio子系统下的驱动相比,多了一个申请中断号的步骤。key_gpio_init() 则用来向Linux内核申请使用GPIO,此部分还额外增加了向内核申请中断的步骤,使用 request_irq 申请中断,并设置 key_interrupt 函数作为按键中断处理函数,当按键中断发生之后便会跳转到该函数执行;request_irq 函数会默认使能中断,所以不需要 enable_irq 来使能中断;当然也可以在申请成功之后先使用 disable_irq 函数禁用中断,等所有工作完成之后再来使能中断,这样会比较安全,更推荐这样使用。
⑥key_timer_function() 为定时器处理函数,用于获取按键的键值,并以此来判断按键当前的状态,此部分用自旋锁进行保护
⑦key_read() 将按键当前的键值传递给用户程序,也用自旋锁保护
⑧在驱动入口函数中,增加了timer的初始化,其中包含着定时器处理函数的注册
⑨在驱动入口函数中获取gpio设备出错时,以及驱动注销函数中,在注销字符设备之后,就删除定时器;另外在释放gpio设备之前,先对中断进行释放
测试程序的功能为:不断读取/dev/key设备文件来获取按键状态,0表示按下,1表示松开,2表示按键保持某个状态。
key_irqApp.c
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int fd, ret; int key_val; /* 判断传参个数是否正确 */ if(2 != argc) { printf("Usage:\n" "\t./keyApp /dev/key\n"); return -1; } /* 打开设备 */ fd = open(argv[1], O_RDONLY); if(0 > fd) { printf("ERROR: %s file open failed!\n", argv[1]); return -1; } while(1){ read(fd, &key_val, sizeof(int)); if (0 == key_val){ printf("Key Press\n"); } else if(1 == key_val){ printf("Key Release\n"); } } /* 关闭设备 */ close(fd); return 0; }
在循环中不断的读取按键值,如果读取到的值是 0 则打印“Key Press”,读取到的值是 1 则打印“Key Release”。
对驱动文件和测试程序进行编译,然后放到nfs的对应目录下,运行开发板测试。
使用cat /proc/interrupts
命令就可以查看到注册的中断信息,再使用./key_irqApp /dev/key
命令来测试中断,当按下开发板的KEY0键,就会打印出按键按下和释放的信息,并且由于中断中做了按键消抖处理,所以不会因为按键抖动导致发生误判的情况:
最后使用rmmod keyirq.ko
卸载驱动设备。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。