按键消抖的两种方法--中断延迟工作与定时器

35 篇文章 2 订阅
8 篇文章 0 订阅

按键消抖的两种方法–中断延迟工作与定时器



按键由于物理特性,按下瞬间会多次置为 1/0,俗称抖动。
所以我们需要给一个合理的延时,来判断是否按下按钮,为了实现这个延时我们可以使用中断延迟工作和定时器来实现,下面是具体实现的方法。

中断延迟工作

1.相关结构体

内核使用struct delayed_work结构描述一个延迟工作,定义在include/linux/workqueue.h中:
/

/描述一个延迟工作
struct delayed_work {
    struct work_struct work;
    struct timer_list timer;

    /* target workqueue and CPU ->timer uses to queue ->work */
    struct workqueue_struct *wq;
    int cpu;
};

2.接口函数

初始化延迟工作

定义并初始化延迟工作函数:

#define INIT_DELAYED_WORK(_work, _func)                    \
    __INIT_DELAYED_WORK(_work, _func, 0)

动态定义并初始化延迟工作

#define DECLARE_DELAYED_WORK(n, f)                    \
    struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

静态定义并初始化延迟工作

调度延迟工作

static inline bool schedule_delayed_work(struct delayed_work *dwork,
                     unsigned long delay)

作用:在共享工作队列上调度延迟工作。
参数:*dwork:延迟的工作。delay:要延迟的时间。单位是节拍。

static inline bool queue_delayed_work(struct workqueue_struct *wq,
                      struct delayed_work *dwork,
                      unsigned long delay)

作用:在自定义工作队列上调度延迟工作
参数:
*wq:自定义的工作队列。
*dwork:延迟的工作。
delay:要延迟的时间。单位是节拍。

取消调度

bool cancel_delayed_work_sync(struct delayed_work *dwork)

作用:取消己经调度的延迟工作。

3.模板

.....
struct delayed_work test_workqueue_work;
    struct workqueue_struct *test_workqueue;
 .....
 /*创建和初始化*/
 test_workqueue = create_workqueue("test_workqueue");
    INIT_DELAYED_WORK(&irq_keydesc.test_workqueue_work,test_work);
 ......
  /*上半部*/
 static irqreturn_t key0_handler(int irq, void *dev_id)
{
.....
    
/*1ms延时*/
    queue_delayed_work(irq_keydesc.test_workqueue, &test_workqueue_work, (1*HZ)/1000);
    .....
    return IRQ_RETVAL(IRQ_HANDLED);
}
.....
 /*下半部*/
 void test_work(struct work_struct* work){

    ......
    printk("test\n");

}

 /*取消调度*/
 cancel_delayed_work_sync(&test_workqueue_work);

4.代码实现

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.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/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/workqueue.h>


#define IMX6UIRQ_CNT        1            /* 设备号个数     */
#define IMX6UIRQ_NAME        "imx6uirq"    /* 名字         */
#define KEY0VALUE            0X01        /* KEY0按键值     */
#define INVAKEY                0XFF        /* 无效的按键值 */
#define KEY_NUM                1            /* 按键数量     */

/* 中断IO描述结构体 */
struct irq_keydesc {
    int gpio;                                /* gpio */
    int irqnum;                                /* 中断号     */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                            /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
    struct delayed_work test_workqueue_work;
    struct workqueue_struct *test_workqueue;
}irq_keydesc;
//static struct delayed_work test_workqueue_work;
//static struct workqueue_struct *test_workqueue;

void test_work(struct work_struct* work){

    
    printk("test\n");

}
/* imx6uirq设备结构体 */
struct imx6uirq_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    atomic_t keyvalue;        /* 有效的按键键值 */
    atomic_t releasekey;    /* 标记是否完成一次完成的按键,包括按下和释放 */
    struct timer_list timer;/* 定义一个定时器*/
    struct irq_keydesc irqkeydesc[KEY_NUM];    /* 按键描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */
};

struct imx6uirq_dev imx6uirq;    /* irq设备 */

/* @description        : 中断服务函数,开启定时器,延时10ms,
 *                        定时器用于按键消抖。
 * @param - irq     : 中断号 
 * @param - dev_id    : 设备结构。
 * @return             : 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    
    queue_delayed_work(irq_keydesc.test_workqueue, &irq_keydesc.test_workqueue_work, (1*HZ)/100000000);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description    : 定时器服务函数,用于按键消抖,定时器到了以后
 *                  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg    : 设备结构变量
 * @return         : 无
 */

/*
 * @description    : 按键IO初始化
 * @param         : 无
 * @return         : 无
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;
    
    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    } 

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
        if (imx6uirq.irqkeydesc[i].gpio < 0) {
            printk("can't get key%d\r\n", i);
        }
    }
    
    /* 初始化key所使用的IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));    /* 缓冲区清零 */
        sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);        /* 组合名字 */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);    
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, 
                                         imx6uirq.irqkeydesc[i].irqnum);
    }
    /* 申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
        if(ret < 0){
            printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }
    
    irq_keydesc.test_workqueue = create_workqueue("test_workqueue");
    INIT_DELAYED_WORK(&irq_keydesc.test_workqueue_work,test_work);
    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;    /* 设置私有数据 */
    return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */    
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
    } else {
        goto data_error;
    }
    return 0;
    
data_error:
    return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init imx6uirq_init(void)
{
    /* 1、构建设备号 */
    if (imx6uirq.major) {
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2、注册字符设备 */
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3、创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {
        return PTR_ERR(imx6uirq.class);
    }

    /* 4、创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {
        return PTR_ERR(imx6uirq.device);
    }
    
    /* 5、初始化按键 */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    
        
    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
    
    
    cancel_delayed_work_sync(&irq_keydesc.test_workqueue_work);
    flush_workqueue(irq_keydesc.test_workqueue);
    
    
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");

Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

# ROOTFS_DIR 根文件系统中存放 *.ko文件所在目录
# PROJECT_NAME 在存放.ko文件目录中创建对应项目的目录
# DRIVER_NAME 项目中需要编译出.ko来的驱动
# APP_NAME 项目中的应用测试文件

#make 编译项目
#make file 在存放.ko文件目录中创建对应项目的目录
#make install 将*.ko及其应用测试文件移动到根文件中

KERN_DIR = /home/alientek/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
ROOTFS_DIR = /home/alientek/linux/nfs/rootfs/experiment
#项目名字
PROJECT_NAME = wq_button
#各驱动名字,ko
DRIVER_NAME1 = imx6uirq
DRIVER_NAME2 = 
#app名字
APP_NAME = imx6uirqApp
all:
    make -C $(KERN_DIR) M=`pwd` modules 


clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f $(APP_NAME)
file:
    mkdir $(ROOTFS_DIR)/$(PROJECT_NAME)
install:
    cp *.ko $(ROOTFS_DIR)/$(PROJECT_NAME)

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m += $(DRIVER_NAME1).o

在这里插入图片描述

定时器

1.相关结构体

Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;//内核使用
    unsigned long expires;//超时的jiffies值
    struct tvec_base *base;//内核使用,管理用

    void (*function)(unsigned long);//超时处理函数
    unsigned long data;//超时处理函数参数

    int slack;

#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

2.接口函数

init_timer 函数

init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下。 init_timer 函数原型如下:

void init_timer(struct timer_list *timer)

函数参数和返回值含义如下:
timer:要初始化定时器。
返回值: 没有返回值。

add_timer 函数

add_timer 函数用于向Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,
定时器就会开始运行,函数原型如下:

void add_timer(struct timer_list *timer)

函数参数和返回值含义如下:
timer:要注册的定时器。
返回值:没有返回值。

del_timer 函数

del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时
器之前要先等待其他处理器的定时处理器函数退出。del_timer 函数原型如下:

int del_timer(struct timer_list * timer)

函数参数和返回值含义如下:
timer:要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活。

del_timer_sync 函数

del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
del_timer_sync 不能使用在中断上下文中。del_timer_sync 函数原型如下所示:

int del_timer_sync(struct timer_list *timer)

函数参数和返回值含义如下:
timer:要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活。

mod_timer 函数

mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时
器!函数原型如下:

int mod_timer(struct timer_list *timer, unsigned long expires)

函数参数和返回值含义如下:
timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。
返回值:0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已
被激活。

3.模板

struct timer_list timer; /* 定义定时器  */

/* 定时器回调函数 */
void function(unsigned long arg){
  /*
 * 定时器处理代码
*/

/* 如果需要定时器周期性运行的话就使用 mod_timer
 * 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}

/* 初始化函数 */
void init(void) {
init_timer(&timer);                / *初始化定时器          */


timer.function = function;         /* 设置定时处理函数     * /
timer.expires=jffies + msecs_to_jiffies(2000);/ *超时时间 2 秒 */
timer.data = (unsigned long)&dev;  / *将设备结构体作为参数  */

add_timer(&timer);                    /* 启动定时器            */
}

/* 退出函数* /
 void exit(void){
del_timer(&timer);  / *删除定时器 */   
/ *或者使用 */
 del_timer_sync(&timer);
}

代码实现

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.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/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT        1            /* 设备号个数     */
#define IMX6UIRQ_NAME        "imx6uirq"    /* 名字         */
#define KEY0VALUE            0X01        /* KEY0按键值     */
#define INVAKEY                0XFF        /* 无效的按键值 */
#define KEY_NUM                1            /* 按键数量     */

/* 中断IO描述结构体 */
struct irq_keydesc {
    int gpio;                                /* gpio */
    int irqnum;                                /* 中断号     */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                            /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    atomic_t keyvalue;        /* 有效的按键键值 */
    atomic_t releasekey;    /* 标记是否完成一次完成的按键,包括按下和释放 */
    struct timer_list timer;/* 定义一个定时器*/
    struct irq_keydesc irqkeydesc[KEY_NUM];    /* 按键描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */
};

struct imx6uirq_dev imx6uirq;    /* irq设备 */

/* @description        : 中断服务函数,开启定时器,延时10ms,
 *                        定时器用于按键消抖。
 * @param - irq     : 中断号 
 * @param - dev_id    : 设备结构。
 * @return             : 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));    /* 10ms定时 */
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description    : 定时器服务函数,用于按键消抖,定时器到了以后
 *                  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg    : 设备结构变量
 * @return         : 无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);     /* 读取IO值 */
    if(value == 0){                         /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else{                                     /* 按键松开 */
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1);    /* 标记松开按键,即完成一次完整的按键过程 */            
    }    
}

/*
 * @description    : 按键IO初始化
 * @param         : 无
 * @return         : 无
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;
    
    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    } 

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
        if (imx6uirq.irqkeydesc[i].gpio < 0) {
            printk("can't get key%d\r\n", i);
        }
    }
    
    /* 初始化key所使用的IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));    /* 缓冲区清零 */
        sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);        /* 组合名字 */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);    
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, 
                                         imx6uirq.irqkeydesc[i].irqnum);
    }
    /* 申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
        if(ret < 0){
            printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;
    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;    /* 设置私有数据 */
    return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */    
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
    } else {
        goto data_error;
    }
    return 0;
    
data_error:
    return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init imx6uirq_init(void)
{
    /* 1、构建设备号 */
    if (imx6uirq.major) {
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2、注册字符设备 */
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3、创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {
        return PTR_ERR(imx6uirq.class);
    }

    /* 4、创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {
        return PTR_ERR(imx6uirq.device);
    }
    
    /* 5、初始化按键 */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    /* 删除定时器 */
    del_timer_sync(&imx6uirq.timer);    /* 删除定时器 */
        
    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在STM32中,可以使用定时器来实现按键的消抖。以下是一个实现按键定时器消抖的关键步骤: 1. 设置外部中断触发方式,例如上升沿触发或下降沿触发。 2. 计算预分频系数psc和计数值大小arr来确定定时器的时间。 3. 在外部中断的回调函数中,打开定时器并开始计数。 4. 在定时器溢出的回调函数中,关闭定时器,并再次检测按键IO口的电平值。 5. 如果按键的电平值仍然保持稳定,即没有抖动,可以认为按键被按下。 通过以上步骤,可以实现按键的定时器消抖。这样可以避免按键在按下或释放时产生的抖动信号对系统造成误触发。\[2\] 另外,还可以通过软件消抖的方法来解决按键抖动问题。其中一种方法是利用延时来避免抖动。在按键触发后,可以通过延时一段时间再次检测按键的电平值,如果仍然保持稳定,即没有抖动,可以认为按键被按下。这种方法相对简单,但需要根据具体的系统需求来确定延时的时间。\[3\] #### 引用[.reference_title] - *1* *2* [STM32 CubeMx HAL库外部中断检测按键,定时器延时消抖](https://blog.csdn.net/DIVIDADA/article/details/128364061)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [STM32按键消抖(HAL)](https://blog.csdn.net/m0_62683461/article/details/130767102)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦梨甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值