我们在上一章的已经实现了定时器的基础功能使用,但是对于一个机械按键来说,因为有机械抖动会重复触发中断不能直接使用。所以我们今天要把中断功能加上定时器,来实现按键消抖的效果。
整个驱动的思路也是比较简单的,在设备结构体中定义一个定时器,当中断触发,我们不再中断里执行按键需要触发的程序而是启动定时器,如果在指定时间内发生抖动定时器重新启动,直到最后定时器超时,执行原本中断需要执行的内容。整个流程跟裸机驱动里写的按键抖动处理是一样的

是不是很熟悉!只不过中断触发的方式由下降沿变成双边沿触发。
设备结构体
设备结构体是在原有的中断基础上加上一个定时器
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/tech/aiops/276645.html