ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-07-24 01:31:51  阅读:181  来源: 互联网

标签:定时器 struct 中断 消抖 dev int timer Linux new


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

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

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

设备结构体

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

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

 

标签:定时器,struct,中断,消抖,dev,int,timer,Linux,new
来源: https://www.cnblogs.com/yinsedeyinse/p/16504163.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有