Linux中断实验:定时器实现按键消抖处理实验一

一.  简介

前面文章学习了Linux驱动按键中断实验,文章地址如下:

Linux驱动按键中断实验:按键中断功能的实现-CSDN博客

本文在Linux驱动按键中断实现的基础上,使用定时器实现按键消抖处理。

二. Linux中断实验:定时器实现按键消抖处理

1.  定时器处理按键消抖的原理

按键在实际操作过程中,当按下按键时会存在电平不稳定的情况,实际情况如下:

可以看出,t1 时刻按键被按下,但是由于抖动的原因,直到 t2 时刻才稳定下来, t1 t2 这段时间就是抖动。一般这段时间就是十几 ms 左右,
从上图中 可以看出,在抖动期间会 有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取 IO 值发现电平多次跳变以为按下了多次。
所以,我们需要跳过这段抖动时间再去读取按键的 IO 值,也就是至少要在 t2 时刻以后再去读 IO 值。例如,可以在按键按下后,延时 大约 20ms 后再去读取按键 IO 值,如果此时按键的值依旧是 0 ,那么就表示这是一次有 效的按键触发。

2. 实现定时器消抖代码

进入ubuntu系统下,通过vscode 打开 13_key_irq工程。

key_irq.c文件添加定时器消抖代码后,内容如下:

#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/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>


#define   IRQ_NAME          "key_irq"
#define   IRQ_CNT           1
#define   KEY_NUM           1 
#define   KEY0_VALUE        0x01  //key0按键值
#define   INVAL_KEY_VALUE   0xFF  //无效的按键值


/*key按键结构体 */
struct key_dev{
    int gpio_number;        //IO编号
    int interrupt_number;   //中断号
    unsigned char value;    //键值
    unsigned char name[50]; //按键名字
    irqreturn_t (*handler)(int, void*); //中断处理函数
};

/*imx6ull_irq设备结构体 */
struct irq_dev{
    dev_t devid;  //主设备号+次设备号
    int major; //主设备号
    int minor; //次设备号
    struct cdev cdev;
    struct class* class;
    struct device* device;
    struct device_node * dev_node;//设备节点
    struct key_dev key[KEY_NUM];
    struct timer_list timer; //定时器

};

struct irq_dev irq;

/* 打开设备函数*/
static int irq_dev_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &irq;

    return 0;
}

ssize_t irq_dev_read(struct file * filp, char __user * buf, size_t count, loff_t * ppos)
{
    return 0;
}

/* 关闭设备函数 */
int irq_dev_close(struct inode * inode, struct file * filp)
{
    return 0;
}

static const struct file_operations irq_fops = {
	.open = irq_dev_open,
	.owner = THIS_MODULE,
	.read = irq_dev_read,
    .release = irq_dev_close, 
};

/*定时器处理函数 */
void timer_handler(unsigned long data)
{
    int value = 0;
    struct irq_dev* dev = (struct irq_dev*)data;
    //读取按键值
    value = gpio_get_value(dev->key[0].gpio_number);
    if(value == 0) //按键按下
    {
        printk("KEY0 Press!\n");
    }
    else if(value == 1) //按键释放
    {
        printk("KEY0 Release!\n");
    } 
}

/* 中断处理函数 */
static irqreturn_t key0_irq_handler(int irq, void *param)
{
    struct irq_dev* dev = param;
    
    dev->timer.data = (volatile unsigned long)param;
    //设置定时器超时时间,开启定时器
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //按键按下后延时20ms
    
    return IRQ_HANDLED;
}

/*按键初始化 */
static int key_io_init(struct irq_dev* dev)
{
    int ret = 0;
    int i = 0;
    int n = 0;

    /*1.获取设备节点 */
    dev->dev_node = of_find_node_by_path("/key");
    if(NULL == dev->dev_node)
    {
        printk("find dev_node failed!\n");
        goto find_dev_node;
    }
    
    /*2.获取IO编号 */
    for(i=0; i< KEY_NUM; i++)
    {   
       dev->key[i].gpio_number = of_get_named_gpio(dev->dev_node, "key-gpio", i);
        if(dev->key[i].gpio_number < 0)
        {
            printk("get gpio number failed!\n");
            goto get_gpio_number;
        }
    }

    /*3.申请IO */
    for(i = 0; i< KEY_NUM; i++)
    {
        memset(dev->key[i].name, 0, sizeof(dev->key[i].name));
        sprintf(dev->key[i].name, "KEY%d", i);
        ret = gpio_request(dev->key[i].gpio_number, dev->key[i].name);
        if(ret != 0)
        {
            printk("gpio request fail!\n");
            goto gpio_request;
        }
        /* 将GPIO设置为输入*/
        gpio_direction_input(dev->key[i].gpio_number);

        /* 获取中断号*/
        //dev->key[i].interrupt_number = irq_of_parse_and_map(dev->dev_node, i);
        dev->key[i].interrupt_number = gpio_to_irq(dev->key[i].gpio_number);
        if(!dev->key[i].interrupt_number)
        {
            printk("irq_of_parse_and_map fail!\n");
            goto get_irq_number;
        }
    }

    dev->key[0].handler = key0_irq_handler;
    dev->key[0].value = KEY0_VALUE;
    /*申请中断 */
    for(i=0; i<KEY_NUM; i++)
    {
        ret = request_irq(dev->key[i].interrupt_number, dev->key[i].handler, 
                         (IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING), dev->key[i].name, dev);  
        printk("request_irq ret: %d\n", ret);
        if(ret != 0)
        {
            printk("%d request interrupt fail!\n", i);
            goto irq_request;
        }                     
    }

    /*定时器初始化 */
    init_timer(&dev->timer);
    dev->timer.function = timer_handler;


    return 0;

irq_request:
get_irq_number:
    for(i=0; i<KEY_NUM; i++)
    {
         gpio_free(dev->key[i].gpio_number);
    }
gpio_request:
    for(n = 0; n<i; n++)
    {
        gpio_free(dev->key[n].gpio_number);
    }
get_gpio_number:
find_dev_node:
    return ret;
}

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

    /*1. 注册/申请设备号 */
    irq.major = 0;  
    if(irq.major) //如果给出主设备号,则注册设备号
    {
        irq.devid = MKDEV(irq.major, 0);
        ret = register_chrdev_region(irq.devid, IRQ_CNT, IRQ_NAME);	
    }
    else //否则申请设备号
    {
      ret = alloc_chrdev_region(&irq.devid, 0, IRQ_CNT, IRQ_NAME);
      irq.major = MAJOR(irq.devid);
      irq.minor = MINOR(irq.devid);
    }
    if(ret < 0)
    {
        printk("devid apply failed!\n");
        goto devid_failed;
    }
    printk("dev: major: %d minor: %d\n", irq.major,irq.minor);

    /*2. 设备初始化,添加设备*/
    irq.cdev.owner = THIS_MODULE;
    cdev_init(&irq.cdev, &irq_fops);
    ret = cdev_add(&irq.cdev, irq.devid, IRQ_CNT);
    if(ret < 0)
    {
        printk("cdev_add failed!\n");
        goto cdev_init_failed;
    }
    /*3. 自动创建设备节点 */
    irq.class = class_create(THIS_MODULE, IRQ_NAME);
    if(IS_ERR(irq.class)){
        printk(KERN_ERR "class_create failed!\n");
        ret = PTR_ERR(irq.class);
		goto class_create_failed;
    }
    irq.device = device_create(irq.class, NULL, irq.devid, NULL, IRQ_NAME);
    if (IS_ERR(irq.device)) {
		printk(KERN_ERR "device_create failed!\n");
		ret = PTR_ERR(irq.device);
		goto device_create_failed;
	}

    /*4. 按键初始化*/
    ret = key_io_init(&irq);
    if(ret != 0)
    {
        printk("key_io_init failed!\n");
        goto key_io_init_failed;
    }

    return 0;

key_io_init_failed:
    device_destroy(irq.class, irq.devid);
device_create_failed:
    class_destroy(irq.class);
class_create_failed:
    cdev_del(&irq.cdev);
cdev_init_failed: 
    unregister_chrdev_region(irq.devid, IRQ_CNT);
devid_failed:
    return ret;
}

/*驱动出口函数 */
static void __exit imx6ull_irq_exit(void)
{
    int i = 0;

    /*删除定时器 */
    del_timer(&irq.timer);

    /*释放中断 */
    for(i=0; i < KEY_NUM; i++)
    {
        free_irq(irq.key[i].interrupt_number, &irq);
    }
    /*释放GPIO编号 */
    for(i=0; i < KEY_NUM; i++)
    {
        gpio_free(irq.key[i].gpio_number);
    }

    /*2. 删除设备 */
    cdev_del(&irq.cdev);
    /*3. 注销设备号*/
    unregister_chrdev_region(irq.devid, IRQ_CNT);

    /*4. 销毁设备 & 类*/
    device_destroy(irq.class, irq.devid);
    class_destroy(irq.class);
}

/*驱动入口与出口函数 */
module_init(imx6ull_irq_init);
module_exit(imx6ull_irq_exit);
MODULE_LICENSE("GPL"); //驱动 License
MODULE_AUTHOR("WeiYing"); //作者

三.  编译驱动

终端进入 13_key_irq工程目录下,对上述驱动程序进行编译:

wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/13_key_irq$ make
make -C /home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/wangtian/zhengdian_Linux/Linux_Drivers/13_key_irq modules
make[1]: 进入目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
  CC [M]  /home/wangtian/zhengdian_Linux/Linux_Drivers/13_key_irq/key_irq.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/wangtian/zhengdian_Linux/Linux_Drivers/13_key_irq/key_irq.mod.o
  LD [M]  /home/wangtian/zhengdian_Linux/Linux_Drivers/13_key_irq/key_irq.ko
make[1]: 离开目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/13_key_irq$ 

可以看出,驱动模块正常编译通过。

接下来就是对该驱动模块进行测试,确定定时器是否实现对按键的消抖处理的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/429762.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java:String和StringBuilder 的相互转换实现字符串拼接

public class StringDemo {/* 练习题&#xff1a;字符串拼接升级版1.定义一个int类型的数组&#xff0c;用静态初始化完成数组元素的初始化2.定义一个方法&#xff0c;用于把int数组中的数据按照指定格式拼接成一个字符串返回3.在方法中用StringBuilder按照要求进行拼接&#x…

新生儿放屁的温馨小贴士:呵护宝宝舒适健康成长

引言 新生儿的生活充满了各种令人惊喜和可爱的瞬间&#xff0c;其中包括他们放臭屁的时刻。尽管这看似简单的行为可能引发父母的担忧&#xff0c;但实际上&#xff0c;它通常是宝宝健康发展的自然表现。在这篇文章中&#xff0c;我们将分享一些关于新生儿放臭屁的注意事项&…

面试经典150题 -- 回溯 (总结)

总的链接 : 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 17 . 电话号码的字母组合 1 . 先创建一个下标 与 对应字符串映射的数组&#xff0c;这里使用hash表进行映射也是可以的 &#xff1b; 2 . 对于回溯 &#xff0c;…

MySQL性能优化-Mysql索引篇(1)

什么是索引&#xff1f; 数据库中的索引&#xff0c;就好比一本书的目录&#xff0c;它可以帮我们快速进行特定值的定位与查找&#xff0c;从而加快数据查询的效率。索引就是帮助数据库管理系统高效获取数据的数据结构。如果我们不使用索引&#xff0c;就必须从第 1 条记录开始…

什么台灯护眼效果好?一文搞懂如何正确挑选护眼台灯

现在的孩子学习状态可以用四个字来形容&#xff0c;“学业繁重”&#xff0c;不少孩子从上小学开始&#xff0c;晚上完成功课到八九点都是在正常不过的事情了&#xff0c;因此室内的光线环境是非常重要的&#xff0c;直接影响了视力健康尤其是书桌上的那一盏台灯&#xff0c;有…

012 Linux_线程控制

前言 本文将会向你介绍线程控制&#xff08;创建&#xff08;请见上文&#xff09;&#xff0c;终止&#xff0c;等待&#xff0c;分离&#xff09; 线程控制 线程终止 pthread_t pthread_self(void); 获取线程自身的ID 如果需要只终止某个线程而不终止整个进程,可以有三种…

SparkShop开源可商用,匹配小程序H5和PC端带分销功能!

SparkShop(星火商城)B2C商城是基于thinkphp6 elementui的开源免费可商用的高性能商城系统&#xff1b;包含小程序商城、H5商城、公众号商城、PC商城、App&#xff0c;支持页面diy、秒杀、优惠券、积分、分销、会员等级。营销功能采用插件化的方式方便扩展、二次开发 源码下载…

表单验证、属性绑定(一个属性根据另一个属性有无进行操作)

表单验证 一个属性根据另一个属性有无进行操作&#xff08;属性绑定&#xff09; 1、问题描述 ​ 需求&#xff1a;表单里面后两个属性需要根据前面一个属性进行有无判断。如果前面属性没有输入值&#xff0c;则不需要进行操作&#xff1b;如果前面属性有输入值&#xff0c;则…

Docker Swarm全解析:实现微服务高可用与故障转移的秘密武器

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Docker入门到精通》 《k8s入门到实战》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、基本概念和介绍 1、Docker Swarm 是什么&#xff0c;它与 …

Rabbitmq消息丢失-消费者消息丢失(二)

说明&#xff1a;消费端在处理消息的过程中出现异常&#xff0c;例如&#xff1a;业务逻辑异常&#xff0c;或者消费者被停机&#xff0c;或者网络断开连接等&#xff0c;以上等情况使消息没有得到正确恰当的处理&#xff0c;也会使消息丢失。 分析&#xff1a;分析就是说明中…

【MATLAB第97期】基于MATLAB的贝叶斯Bayes算法优化BiGRU双向门控循环单元的多输入单输出回归预测模型,含GRU与BiGRU多层结构优化选择

【MATLAB第97期】基于MATLAB的贝叶斯Bayes算法优化BiGRU双向门控循环单元的多输入单输出回归预测模型&#xff0c;含GRU与BiGRU结构层数优化 前言 前面在【MATLAB第10期】讲解了基于贝叶斯Bayes算法优化LSTM长短期记忆网络的多输入单输出回归预测模型。 本次模型难点包括&am…

Ps:图案图章工具

图案图章工具 Pattern Stamp Tool可将各种预设图案或自定义的图案&#xff0c;通过画笔涂抹的方式填充到图像中。 快捷键&#xff1a;S 图案图章工具提供了一种快速、灵活的方式来为图像局部添加纹理和装饰。 这个工具类似于仿制图章工具&#xff0c;但区别在于&#xff0c;它使…

初阶数据结构:二叉树(补充扩展)

目录 1. 堆排序1.1补充&#xff1a;建堆的时间复杂度1.2 堆排序&#xff1a;升序与降序 2. TopK问题3. 二叉树的链式结构及其遍历方式3.1 二叉树的链式结构3.2 二叉树的前序遍历2.2 二叉树的中序遍历2.3 后序遍历2.4 层序遍历 4. 二叉树OJ练习4.1 单值二叉树4.2 判断两棵二叉树…

three.js如何实现简易3D机房?(一)基础准备-上

目录 一、tips 二、功能说明 1.模型初始化 2.功能交互 三、初始化准备 1.目录结构 2.创建三要素 3.创建轨道控制器 4.初始化灯光 5.适配 6.循环渲染 一、tips 1.three.js入门的相关基础性知识就不在此过多赘述了&#xff0c;可以自行提前了解 three.js docs&…

PyTorch深度学习实战(38)——StyleGAN详解与实现

PyTorch深度学习实战&#xff08;38&#xff09;——StyleGAN详解与实现 0. 前言1. StyleGAN1.1 模型介绍1.2 模型策略分析 2. 实现 StyleGAN2.1 生成图像2.2 风格迁移 小结系列链接 0. 前言 StyleGAN (Style-Generative Adversarial Networks) 是生成对抗网络 (Generative Ad…

基于Docker部署本地ChatGPT环境

基于Docker部署本地ChatGPT环境 一、拉取镜像 docker pull pengzhile/pandora二、运行镜像 docker run -e PANDORA_CLOUDcloud -e PANDORA_SERVER0.0.0.0:8899 -p 8899:8899 -d pengzhile/pandora三、查看容器是否启动成功 docker ps四、登录 http://IP:8899 这里有两种方…

原始手写helloworld并打jar包允许

1.创建文件夹test统一在其中操作 2.创建hello.java文件 【hello.txt改属性为hello.java】并在里面添加代码 public class hello {public static void main(String[] args) {System.out.println("hello world");} } 注意&#xff1a;类名与文件名一致 然后运行…

使用AI创建令人惊叹的3D模型

老子云平台《《《《《 使内容创作者能够在一分钟内毫不费力地将文本和图像转换为引人入胜的 3D 资产。 文本转 3D 我们的文本转 3D 工具使创作者&#xff08;包括那些没有 3D 经验的创作者&#xff09;能够使用文本输入在短短一分钟内生成 3D 模型。 一句话生成3D模型 老子…

FPGA-VGA成像原理与时序

什么是VGA: VGA, Video Graphics Array。即视频图形阵列,具有分辨率高、显示速率快、颜色丰富等优点。VGA接口不但是CRT显示设备的标准接口,同样也是LCD液晶显示设备的标准接口,具有广泛的应用范围。在FGPA中,常广泛用于图像处理等领域。 VGA 显示器成像原理 在 VGA 标准刚兴…

Material UI 5 学习02-其它按钮组件

Material UI 5 学习02-其它按钮组件 一、IconButton按钮二、 ButtonGroup按钮组1、最基本的实例2、垂直按钮组 一、IconButton按钮 图标按钮通常适用于切换按钮&#xff0c;允许选择或选择单个选项 取消选择&#xff0c;例如在项目中添加或删除星号。 <IconButton aria-lab…