我们在上一章的已经实现了定时器的基础功能使用,但是对于一个机械按键来说,因为有机械抖动会重复触发中断不能直接使用。所以我们今天要把中断功能加上定时器,来实现按键消抖的效果。
整个驱动的思路也是比较简单的,在设备结构体中定义一个定时器,当中断触发,我们不再中断里执行按键需要触发的程序而是启动定时器,如果在指定时间内发生抖动定时器重新启动,直到最后定时器超时,执行原本中断需要执行的内容。整个流程跟裸机驱动里写的按键抖动处理是一样的
是不是很熟悉!只不过中断触发的方式由下降沿变成双边沿触发。
设备结构体
设备结构体是在原有的中断基础上加上一个定时器
struct new_dev { dev_t dev_id; int major; int minor; struct class *class; struct device *device; struct cdev cdev; struct device_node *dev_nd; int dev_gpio; struct irq_keydesc irqkey[KEY_NUM]; //按键描述数组 struct timer_list timer; //定时器 int timer_per; };
没什么可讲的,就是一个简单的定时器。要注意点是,我们在初始化定时器的时候不能使用add_timer,因为在前面讲定时器的时候我讲过,一旦调用了add_timer定时器会直接启动。所以在初始化的时候我们只初始化定时器和绑定定时回调函数
//此处不设置定时值,防止定时器add后直接运行 init_timer(&dev->timer); dev->timer.function = timer_func;
只用这样就行了,连定时器时间都不用设。
中断回调函数
中断函数里只需要对定时器进行激活即可
1 static irqreturn_t key0_handle_irq(int irq, void *dev_id) 2 { 3 int value = 0; 4 struct new_dev *dev = dev_id; 5 dev->timer.data = dev_id; 6 mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10)); 7 return IRQ_HANDLED; 8 }
第6行的激活定时器的时候指定了定时器时间为10ms,这个时间可以根据实际情况进行修改,如果还有抖动的现象可以将时间再放的长一些。这样,每次中断只是激活定时器,定时器溢出后执行我们原先中断执行的函数。
定时器回调函数
定时器回调函数里是原先中断回调函数的内容
1 timer_func(unsigned long arg){ 2 3 int value = 0; 4 struct new_dev *dev =(struct new_dev*)arg; 5 6 value = gpio_get_value(dev->irqkey[0].gpio); 7 printk("gpio read value = %d/r/n",value); 8 if(value == 0){ 9 //按下 10 printk("key0 push/r/n"); 11 } 12 else{ 13 //释放 14 printk("key0 released/r/n"); 15 } 16 }
这样就达到了消抖的效果,看看实际效果
没有连续出现的0或者1,说明抖动消除效果良好!如果有连续的0或者1出现就要适当修改定时器对定时周期了。最后放出来完整的代码
/** * @file irq.c * @author your name (you@domain.com) * @brief 按键中断消抖版本 * @version 0.1 * @date 2022-07-24 * * @copyright Copyright (c) 2022 * */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/types.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/irq.h> #include <linux/interrupt.h> #define DEVICE_CNT 1 #define DEVICE_NAME "imx6uirq" #define KEY_NUM 1 #define KEY0VALUE 0x01 #define INVALKEYS 0xFF /** * @brief 按键中断结构体 * */ struct irq_keydesc { int gpio; //io编号 int irqnum; //中断号 unsigned char value; //键值 char name[10]; //按键名字 irqreturn_t (*handler)(int,void*); //中断处理函数 }; /** * @brief 设备结构体 * */ struct new_dev { dev_t dev_id; int major; int minor; struct class *class; struct device *device; struct cdev cdev; struct device_node *dev_nd; int dev_gpio; struct irq_keydesc irqkey[KEY_NUM]; //按键描述数组 struct timer_list timer; //定时器 int timer_per; }; /** * @brief 文件操作集合 * */ static const struct file_operations key_fops = { .owner = THIS_MODULE, // .write = new_dev_write, // .open = new_dev_open, // .release = new_dev_release, }; struct new_dev new_dev; static irqreturn_t key0_handle_irq(int irq, void *dev_id) { int value = 0; struct new_dev *dev = dev_id; dev->timer.data = dev_id; mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10)); return IRQ_HANDLED; } timer_func(unsigned long arg){ int value = 0; struct new_dev *dev =(struct new_dev*)arg; value = gpio_get_value(dev->irqkey[0].gpio); printk("gpio read value = %d/r/n",value); if(value == 0){ //按下 printk("key0 push/r/n"); } else{ //释放 printk("key0 released/r/n"); } } static int dev_gpio_init(struct new_dev *dev) { int ret = 0; int i = 0; //搜索设备树节点 dev->dev_nd = of_find_node_by_path("/key"); if(dev->dev_nd == NULL){ printk("can't find device key/r/n"); ret = -EINVAL; goto fail_nd; } for(i=0;i<KEY_NUM;i++) { dev->irqkey[i].gpio = of_get_named_gpio(dev->dev_nd,"key-gpios",i); //多个按键获取 if(dev->irqkey[i].gpio<0){ ret = -EINVAL; goto fail_gpio_num; } ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name); if(ret){ ret = -EBUSY; goto fail_gpio_request; } gpio_direction_input(dev->irqkey[i].gpio); dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); //获取中断号 // dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->dev_nd,i) //方法2获取中断号 } dev->irqkey[0].handler = key0_handle_irq; dev->irqkey[0].value = KEY0VALUE; 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); //将格式化数据写入字符串中 ret = request_irq(dev->irqkey[i].irqnum, //中断号 key0_handle_irq, //中断处理函数 IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING, //中断处理函数 dev->irqkey[i].name, //中断名称 dev //设备结构体 ); if(ret){ printk("irq %d request err/r/n",dev->irqkey[i].irqnum); goto fail_irq; } } //此处不设置定时值,防止定时器add后直接运行 init_timer(&dev->timer); dev->timer.function = timer_func; return 0; fail_gpio_request: fail_irq: for(i=0; i<KEY_NUM;i++){ gpio_free(dev->irqkey[i].gpio); } fail_gpio_num: fail_nd: return ret; } static int __init key_init(void){ int ret = 0; // unsigned int val = 0; //申请设备号 new_dev.major = 0; if(new_dev.major){ //手动指定设备号,使用指定的设备号 new_dev.dev_id = MKDEV(new_dev.major,0); ret = register_chrdev_region(new_dev.dev_id,DEVICE_CNT,DEVICE_NAME); } else{ //设备号未指定,申请设备号 ret = alloc_chrdev_region(&new_dev.dev_id,0,DEVICE_CNT,DEVICE_NAME); new_dev.major = MAJOR(new_dev.dev_id); new_dev.minor = MINOR(new_dev.dev_id); } printk("dev id geted!/r/n"); if(ret<0){ //设备号申请异常,跳转至异常处理 goto faile_devid; } //字符设备cdev初始化 new_dev.cdev.owner = THIS_MODULE; cdev_init(&new_dev.cdev,&key_fops); //文件操作集合映射 ret = cdev_add(&new_dev.cdev,new_dev.dev_id,DEVICE_CNT); if(ret<0){ //cdev初始化异常,跳转至异常处理 goto fail_cdev; } printk("chr dev inited!/r/n"); //自动创建设备节点 new_dev.class = class_create(THIS_MODULE,DEVICE_NAME); if(IS_ERR(new_dev.class)){ //class创建异常处理 printk("class err!/r/n"); ret = PTR_ERR(new_dev.class); goto fail_class; } printk("dev class created/r/n"); new_dev.device = device_create(new_dev.class,NULL,new_dev.dev_id,NULL,DEVICE_NAME); if(IS_ERR(new_dev.device)){ //设备创建异常处理 printk("device err!/r/n"); ret = PTR_ERR(new_dev.device); goto fail_device; } printk("device created!/r/n"); ret = dev_gpio_init(&new_dev); if(ret<0){ goto fail_gpio_init; } return ret; fail_gpio_init: fail_device: //device创建失败,意味着class创建成功,应该将class销毁 printk("device create err,class destroyed/r/n"); class_destroy(new_dev.class); fail_class: //类创建失败,意味着设备应该已经创建成功,此刻应将其释放掉 printk("class create err,cdev del/r/n"); cdev_del(&new_dev.cdev); fail_cdev: //cdev初始化异常,意味着设备号已经申请完成,应将其释放 printk("cdev init err,chrdev register/r/n"); unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT); faile_devid: //设备号申请异常,由于是第一步操作,不需要进行其他处理 printk("dev id err/r/n"); return ret; } static void __exit key_exit(void){ int i = 0; //释放中断 for(i=0;i<KEY_NUM;i++){ free_irq(new_dev.irqkey[i].irqnum,&new_dev); } for(i=0;i<KEY_NUM;i++){ gpio_free(new_dev.irqkey[i].gpio); } del_timer_sync(&new_dev.timer); cdev_del(&new_dev.cdev); unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT); device_destroy(new_dev.class,new_dev.dev_id); class_destroy(new_dev.class); } module_init(key_init); module_exit(key_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZeqiZ");
View Code
原创文章,作者:254126420,如若转载,请注明出处:https://blog.ytso.com/276645.html