引入
一次按下,因为抖动会发生多次中断。这样就有可能会引起驱动程序的一次按键按下,多次上报事件,这是不合理的。因此,需要某种手段来确保一次按键按下,仅上报事件一次。
原理
在中断中,先不上报事件,只修改定时器的超时时间,在定时器的回调函数里上报事件。如此一来,只要确保定时器的超时时间大于按键的抖动间隔,即可实现一次按键动作只有一次上报事件。
对于定时器,可以有一个合理的猜测:包含两方面,首先是超时时间,其次就是到时间后做什么,即处理函数。
操作函数
struct timer_list timer; // 定义一个定时器 init_timer(&timer); //在入口函数中初始化定时器 timer.data = xxx; // 这就是回调函数的参数 timer.expires = jiffies + yyy; // 设置超时时间 timer.function = (void (*)(unsigned long))zzz; // 回调函数 add_timer(&timer); // 添加定时器 mod_timer(&timer, jiffies + yyy); // 修改定时器超时时间 del_timer(&timer); // 删除定时器
源码
驱动程序:
/* * 引脚:PI0,1,2 */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/leds.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/module.h> #include <linux/pinctrl/consumer.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <asm/io.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/timer.h> struct pin_desc{ int pin; int val; }; /* * 按下时,返回:0x81, 0x82, 0x83 * */ static struct pin_desc pins_desc[3] = { {NUC970_PI0, 0x1}, {NUC970_PI1, 0x2}, {NUC970_PI2, 0x3}, }; static unsigned char val = 0; static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq); static int evpress = 0; static struct timer_list button_timer; static irqreturn_t buttons_handler(int irq, void *dev_id) { struct pin_desc *pin = (struct pin_desc *)dev_id; //int res; //res = gpio_get_value(pin->pin); val = 0x80 | pin->val; mod_timer(&button_timer, jiffies + 13); return IRQ_HANDLED; } static void buttons_timer_func(unsigned long data) { if (val == 0) return; evpress = 1; wake_up_interruptible(&buttons_waitq); } static int buttons_open(struct inode *inode, struct file *filp) { // request_irq会自动设置引脚,此处不再配置 request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]); request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]); request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]); init_timer(&button_timer); button_timer.function = buttons_timer_func; add_timer(&button_timer); return 0; } static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos) { if (count != 1) { return -EINVAL; } wait_event_interruptible(buttons_waitq, evpress); evpress = 0; copy_to_user(buf, &val, 1); return 1; } int buttons_release (struct inode *inode, struct file *filp) { free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]); free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]); free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]); del_timer(&button_timer); return 0; } static unsigned int buttons_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; poll_wait(file, &buttons_waitq, wait); // 不会立即休眠,只是把进程挂到buttons_waitq队列 if (evpress) { mask = POLLIN | POLLRDNORM; } return mask; } static struct file_operations buttons_fops = { .owner = THIS_MODULE, .open = buttons_open, .read = buttons_read, .release = buttons_release, .poll = buttons_poll, }; static int major; static struct class *buttons_class; static struct device *button_device; static int buttons_init(void) { major = register_chrdev(0, "buttons", &buttons_fops); buttons_class = class_create(THIS_MODULE, "buttons"); button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons"); return 0; } static void buttons_exit(void) { device_destroy(buttons_class, MKDEV(major, 0)); class_destroy(buttons_class); unregister_chrdev(major, "buttons"); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(void) { int fd; unsigned char key_val; fd = open("/dev/buttons", O_RDONLY); if (fd < 0) { printf("Can't open /dev/buttons\n"); return -1; } while (1) { read(fd, &key_val, 1); printf("key_val = 0x%x\n", key_val); } close(fd); return 0; }