Linux驱动开发十.中断——2.中断结合定时器对按键消抖

2022/7/25 5:24:06

本文主要是介绍Linux驱动开发十.中断——2.中断结合定时器对按键消抖,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

我们在上一章的已经实现了定时器的基础功能使用,但是对于一个机械按键来说,因为有机械抖动会重复触发中断不能直接使用。所以我们今天要把中断功能加上定时器,来实现按键消抖的效果。

整个驱动的思路也是比较简单的,在设备结构体中定义一个定时器,当中断触发,我们不再中断里执行按键需要触发的程序而是启动定时器,如果在指定时间内发生抖动定时器重新启动,直到最后定时器超时,执行原本中断需要执行的内容。整个流程跟裸机驱动里写的按键抖动处理是一样的

是不是很熟悉!只不过中断触发的方式由下降沿变成双边沿触发。

设备结构体

设备结构体是在原有的中断基础上加上一个定时器

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

 



这篇关于Linux驱动开发十.中断——2.中断结合定时器对按键消抖的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程