按键驱动之定时器去抖动

学习目的:

  • 熟悉Linux内核中定时器的使用
  • 使用定时器实现按键按下时的去抖动操作

 1、定时器按键去抖动原理

 在测试前面按键驱动程序的过程中,有时会发现这种现象:当按键按下时或松开时,会连续打印两条按键按下或松开信息,这和我们所期望的结果不符,如下图所示:

出现这种情况原因是因为我们所使用的按键均为机械弹性开关,当按键按下或断开,里面的机械触点断开、闭合,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,变化状态如下图所示:

我们想获取稳定的按键状态值,就要想办法去屏蔽按键按下、松开两部分带来的前沿和后沿抖动,最简单的方法就是在检测到按键按下时,延迟一段时间等过了前沿抖动阶段再去读取按键值,此时读取的结果是键稳定值。

今天所学习的定时器按键去抖动原理也是基于上面的思想进行的,按键按下在前沿抖动阶段可能会连续触发中断,我们在中断服务函数中设置一个定时器,当定时时间到达时调用回调函数唤醒休眠的应用程序进程读取按键值。如果前沿抖动连续触发中断服务程序,那么定时器时间在中断服务函数中被重新设置,能够正常保证在定时回调函数中读取的是按键稳定值。

2、内核中定时器使用

2.1 定时器结构体成员timer_list

1) data 传递给function回调函数的参数

2) expires 定时器到期的时间,当expires小于等于jiffies时,这个定时器便到期并调用定时器超时处理函数,然后就不会再调用了,比如要使用10ms后到期,赋值(jiffies+HZ/100)即可

3) void (*function)(unsigned long) 指向定时器超时处理函数

2.2 定时器使用相关的内核全局变量

jiffies: 系统时钟,全局变量,默认每隔10ms加1

HZ: 是每s的频率,通过系统时钟换算出来,比如每隔10ms加1,那么HZ就等于100

2.3 内核中软件定时器的操作函数

init_timer(struct timer_list*) ------------------------>①   
add_timer(struct timer_list*)  ------------------------>②  
mod_timer(struct timer_list *, unsigned long jiffier_timerout) ------------------------>③
timer_pending(struct timer_list *)   ------------------------>④
del_timer(struct timer_list*)    ------------------------>⑤    

① 初始化定时器相关结构体函数

② 往系统添加定时器,告诉内核有个创建了一个软件定时器

③ 修改定时器的超时时间为jiffies_timerout,当expires小于等于jiffies是,便调用定时器超时处理函数

④ 定时器状态查询,如果在系统的定时器列表中则返回1,否则返回0

⑤ 删除定时器

3、按键驱动程序的修改

1)定义定时器结构体,在button_timertimer_drv_init函数将其初始化,调用add_timer将定时器告诉内核

static struct timer_list button_timer;

static int button_drv_init(void)
{
    ...
    init_timer(&button_timer);
    button_timer.expires = 0;
    button_timer.function = button_timer_handler;
    add_timer(&button_timer);
    ...
}

2)修改按键中断服务程序内容,在按键中断服务程序中重新设定定时器的超时时间,以实现能够屏蔽前后沿抖动,读取稳定时按键数据

static irqreturn_t button_irq_handle(int irq, void *dev_id)
{    
    pdesc = (struct button_desc *)dev_id;
    
    mod_timer(&button_timer, jiffies+HZ/100);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

 3)设置按键超时回调函数,在按键超时回调函数中读取按键值

void button_timer_handler(unsigned long data)
{
    unsigned char pin_val;
    
    if(pdesc != NULL)
    {
        pin_val = gpio_get_value(pdesc->pin);    
        if(pin_val == 1)
        {
            key_status = pdesc->key_val | 0x80;
        }
        else
        {
            key_status = pdesc->key_val;
        }
    
        event_trig = 1;
        wake_up_interruptible(&button_waitq); 
    
        kill_fasync (&button_async, SIGIO, POLL_IN);
    }
}

注意:必须在pdesc指针为非空时读取按键值,因为在button_timertimer_drv_init函数中设置超时时间为0,也会触发定时器超时回调函数,此时去读取按键状态值时无效的,因此使用pdesc指针区分。pdesc指针在按键中断服务程序中设定,指向描述当前按下的按键数据信息。

4)在卸载驱动时,删除创建定时器

static void button_drv_exit(void)
{
    ...
    del_timer(&button_timer);
    ...
}

完整驱动程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <plat/gpio-fns.h>
#include <mach/gpio-nrs.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/poll.h>

#define BUTTON_NUMS    4
#define IRQT_BOTHEDGE IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING

struct button_desc
{
    int pin;
    int irq_type;
    unsigned long flags;
    char *name;
    int key_val;
};

static int major;
static int event_trig = 0;

static unsigned char key_status;

static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpgcon = NULL;
static volatile unsigned long *gpfdat = NULL;
static volatile unsigned long *gpgdat = NULL;

static struct class *button_drv_class;
static struct class_device    *button_drv_class_dev;

static struct fasync_struct *button_async;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static struct timer_list button_timer;

#if 0
static atomic no_opera = ATOMIC_INIT(1);
#else
struct semaphore sem;
#endif

struct button_desc *pdesc = NULL;

static struct button_desc btn_desc[BUTTON_NUMS] = {
    {S3C2410_GPF(0),  IRQ_EINT0,  IRQT_BOTHEDGE, "S2", 1},
    {S3C2410_GPF(2),  IRQ_EINT2,  IRQT_BOTHEDGE, "S3", 2},
    {S3C2410_GPG(3),  IRQ_EINT11, IRQT_BOTHEDGE, "S4", 3},
    {S3C2410_GPG(11), IRQ_EINT19, IRQT_BOTHEDGE, "S5", 4},
};

static int button_drv_open(struct inode *inode, struct file *file);
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static int button_drv_close(struct inode *inode, struct file *file);
static unsigned button_drv_poll(struct file *file, poll_table *wait);
static int button_drv_fasync (int fd, struct file *filp, int on);


struct file_operations button_drv_fileop = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   button_drv_open,
    .read   =   button_drv_read,
    .write  =   button_drv_write,
    .release =  button_drv_close,
    .poll = button_drv_poll,
    .fasync = button_drv_fasync,
};

void button_timer_handler(unsigned long data)
{
    unsigned char pin_val;
    
    if(pdesc != NULL)
    {
        pin_val = gpio_get_value(pdesc->pin);    
        if(pin_val == 1)
        {
            key_status = pdesc->key_val | 0x80;
        }
        else
        {
            key_status = pdesc->key_val;
        }
    
        event_trig = 1;
        wake_up_interruptible(&button_waitq); 
    
        kill_fasync (&button_async, SIGIO, POLL_IN);
    }
}

static irqreturn_t button_irq_handle(int irq, void *dev_id)
{    
    pdesc = (struct button_desc *)dev_id;
    
    mod_timer(&button_timer, jiffies+HZ/100);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int button_drv_open(struct inode *inode, struct file *file)
{
    int i;
#if 0
    if(!atomic_dec_and_test(&no_opera))
    {
        atomic_inc(&no_opera);
        return -EBUSY;
    }
#else 
    if(file->f_flags & O_NONBLOCK)
    {
        if(down_trylock(&sem))
            return -EBUSY;
    }
    else
    {
        down(&sem);
    }
    
#endif
    
    *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
    *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
    
    /* 注册中断处理函数 */
    for(i = 0; i < BUTTON_NUMS; i++)
        request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
    
    return 0;
}

static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    if(count != 1)
        return EINVAL;
    
    if(file->f_flags & O_NONBLOCK)
    {
        if(event_trig != 1)
            return -EBUSY;
    }
    else
    {
        wait_event_interruptible(button_waitq, event_trig);
    }
    
    if(copy_to_user(buf, &key_status, count))
        return EFAULT;
    
    event_trig = 0;
    return 1;
}

static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("button_drv_write
");
    
    return 0;
}

static int button_drv_close(struct inode *inode, struct file *file)
{
    int i;
#if 0
    atomic_inc(&no_opera);
#else 
    up(&sem);
#endif
    for(i = 0; i < BUTTON_NUMS; i++)
        free_irq(btn_desc[i].irq_type, &btn_desc[i]);
    
    return 0;
}

static unsigned button_drv_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait); // 不会立即休眠

    if (event_trig)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}

static int button_drv_fasync (int fd, struct file *filp, int on)
{
    printk("driver: sixth_drv_fasync
");
    return fasync_helper (fd, filp, on, &button_async);
}
        
static int button_drv_init(void)
{
    sema_init(&sem, 1);
    init_timer(&button_timer);
    button_timer.expires = 0;
    button_timer.function = button_timer_handler;
    add_timer(&button_timer);
    
    major = register_chrdev(0, "button_light", &button_drv_fileop);
    
    button_drv_class = class_create(THIS_MODULE, "button_drv");
    //button_drv_class_dev = class_device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    button_drv_class_dev = device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpfdat = gpfcon + 1;
    gpgdat = gpgcon + 1;
    
    return 0;
}

static void button_drv_exit(void)
{
    unregister_chrdev(major, "button_drv");
    del_timer(&button_timer);
    
    //class_device_unregister(button_drv_class_dev);
    device_unregister(button_drv_class_dev);
    class_destroy(button_drv_class);
    
    iounmap(gpfcon);
    iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");
irq_button_drv.c

完整测试程序

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>

int fd;

void handler_fcn(int signum)
{
    int ret;
    unsigned char key_buf;
    
    ret = read(fd, &key_buf, 1);
    if(ret < 0)
    {
        printf("read err...
");
        exit(EXIT_FAILURE);
    }
    /* 判断有按键按下,打印按键信息 */
    printf("key_val=0x%x
", key_buf);
}

int main(int argc, char **argv)
{
    int flags;
    
    fd = open("/dev/button", O_RDWR);
    if(fd < 0)
    {
        printf("can't open...
");
        exit(EXIT_FAILURE);
    }
    
    signal(SIGIO, handler_fcn); //设置信号的处理函数
    
    fcntl(fd, F_SETOWN, getpid());
    flags = fcntl(fd, F_GETFL); 
    fcntl(fd, F_SETFL, flags | FASYNC);
    
    while(1)
    {
        sleep(100);
    }

    exit(EXIT_SUCCESS);
}
irq_button_test.c
原文地址:https://www.cnblogs.com/053179hu/p/13737224.html