引入
一次按下,因为抖动会发生多次中断。这样就有可能会引起驱动程序的一次按键按下,多次上报事件,这是不合理的。因此,需要某种手段来确保一次按键按下,仅上报事件一次。
原理
在中断中,先不上报事件,只修改定时器的超时时间,在定时器的回调函数里上报事件。如此一来,只要确保定时器的超时时间大于按键的抖动间隔,即可实现一次按键动作只有一次上报事件。
对于定时器,可以有一个合理的猜测:包含两方面,首先是超时时间,其次就是到时间后做什么,即处理函数。
操作函数
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;
}