Linux内核按键去抖动问题
按键抖动
按键抖动表现出来的现象其实就是明明只是按下去一次按键,但是却总是触发多次中断,本质其实是因为按键属于机械结构,所以不可避免会在按下和松开时产生意外的中断触发。
按键驱动的方法
按键去抖动的方法有两种:
-
硬件去抖动:硬件工程师只需要添加一个滤波电路等方式即可去除不规整的波形导致的抖动产生,但是这样会提高产品的成本,但是去除抖动效果最好。
-
软件去抖动:传统的单片机利用循环延时去除抖动,成本低,能够明显去除抖动,但是效果不如硬件去抖动好。
Linux内核去除抖动的原理
从图中可以看到,每次中断触发后,先触发一个定时器(抖动时间差不多5~10ms),所以设定一个10ms的定时器,在这期间的多次中断触发也仅仅是刷新定时器重新开始计时,而当10ms的定时器时间到期后会触发真正的按键处理事件。也就是说当信号稳定10ms时才能算是真正准确的信号,而不是抖动产生的中断信号。以这种方式屏蔽抖动产生的错误中断信号是经常使用的方式(类似于单片机上采用的循环延时方式)
代码示例
将之前进程读取按键状态进入休眠等待队列,按键按下后触发中断唤醒进程任务读取硬件状态的程序进行去除抖动处理,修改后如下:
- btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h> //TASK_INTERRUPTIBLE等
#include <linux/input.h>
#include <linux/timer.h>
//声明描述按键信息的数据结构
struct btn_resource {
int gpio; //按键对应的GPIO编号
char *name;//按键名称
int code;//按键值
};
//声明上报按键信息的数据结构
struct btn_event {
int state; //上报按键的状态:1:按下;0:松开
int code; //上报按键值
};
//定义初始化四个按键的硬件信息对象
static struct btn_resource btn_info[] = {
{
.gpio = PAD_GPIO_A + 28,
.name = "KEY_UP",
.code = KEY_UP
}
};
//分配内核缓冲区,记录当前操作的按键信息
static struct btn_event kbtn;
static int ispressed; //指示按键是否有操作
//如果有操作:ispressed=1
//如果无操作:ispressed=0
//定义定时器对象
static struct timer_list btn_timer;
//定义一个等待队列头对象(造鸡妈妈)
static wait_queue_head_t rwq;
static ssize_t btn_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//1.如果按键无操作,进程直接进入休眠状态
//如果按键有操作,进程立即返回
//进程休眠时ispressed=0
//进程一旦被唤醒,此时ispressed=1
wait_event_interruptible(rwq, ispressed);
ispressed = 0;//为了下一次read能够进行休眠操作
//2.此时的kbtn已经被中断处理函数进行赋值操作
copy_to_user(buf, &kbtn, sizeof(kbtn));
return count;
}
//记录当前操作的按键的硬件信息,全局化
static struct btn_resource *pdata;
//定时器超时处理函数
static void btn_timer_function(unsigned long data)
{
//1.获取按键的状态和按键值保存在全局变量中
kbtn.state = gpio_get_value(pdata->gpio);
kbtn.code = pdata->code;
//2.一旦有按键操作,硬件上势必产生中断
//也就说明按键有操作,应该唤醒read进程读取按键的信息
ispressed = 1;//为了唤醒进程以后,能够让进程从
//wait_event_interruptible中返回
//否则进程永远在此函数中出不来
wake_up(&rwq);
}
//中断处理函数
static irqreturn_t button_isr(int irq, void *dev)
{
//1.获取当前操作的按键的硬件信息
pdata = dev;
//2.删除之前的定时器,指定一个新的超时时间
//并且向内核重新添加定时器
mod_timer(&btn_timer,
jiffies + msecs_to_jiffies(10));
return IRQ_HANDLED; //中断返回有可能才轮到read进程执行
}
//定义初始化硬件操作接口对象
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.read = btn_read,
};
//定义初始化混杂设备对象
static struct miscdevice btn_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mybtn",
.fops = &btn_fops
};
static int btn_init(void)
{
int i;
//注册混杂设备对象
misc_register(&btn_misc);
//初始化等待队列头(武装鸡妈妈)
init_waitqueue_head(&rwq);
//申请GPIO资源
//申请中断资源,注册中断处理函数
for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
int irq = gpio_to_irq(btn_info[i].gpio);
gpio_request(btn_info[i].gpio,
btn_info[i].name);
request_irq(irq, button_isr,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
btn_info[i].name, &btn_info[i]);
}
//初始化定时器对象
init_timer(&btn_timer);
//指定定时器的超时处理函数
btn_timer.function = btn_timer_function;
return 0;
}
static void btn_exit(void)
{
int i;
//各种释放
for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
int irq = gpio_to_irq(btn_info[i].gpio);
gpio_free(btn_info[i].gpio);
free_irq(irq, &btn_info[i]);
}
//卸载混杂设备对象
misc_deregister(&btn_misc);
//删除定时器
del_timer(&btn_timer);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
- btn_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//声明按键信息数据结构
struct btn_event {
int state; //按键状态:1:松开;0:按下
int code; //按键值
};
int main(int argc, char *argv[])
{
int fd;
struct btn_event btn; //分配用户缓冲区,记录按键的信息
//打开设备
fd = open("/dev/mybtn", O_RDWR);
if (fd < 0) {
printf("打开设备失败!\n");
return -1;
}
while(1) {
read(fd, &btn, sizeof(btn));
printf("按键[%d]的状态为[%s]\n",
btn.code, btn.state ?"松开":"按下");
}
//关闭设备
close(fd);
return 0;
}
- Makefile
obj-m += btn_drv.o
all:
make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
make -C /opt/kernel SUBDIRS=$(PWD) clean
- 执行结果:
总结
在Linux内核中去除按键抖动其实原理同单片机下的延时去除方式类似,只不过Linux内核调用自己的延时机制来触发按键中断响应。效果还是很好的。