要实现开发板的按键驱动,就要了解按键,当你按下的时候有时候会按一下,结果打印出几次结果,感觉就像是按了好多下,这是按钮的结果造成的,因为当你按下按键的时候会有齿波产生,也叫抖动,如何才能让开发板的按键,我们按一下只打印出一次结果,去抖呢? 做法很多,在这里来讲解一下使用内核定时器的机制来去除抖动。
在s5pc100_button.c中
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /*包括timer.h头文件*/
//#define GPH0_1 0x
MODULE_LICENSE ("GPL");
int buttons_major = 250;
int buttons_minor = 0;
int number_of_devices = 0;
struct cdev cdev;
dev_t dev = 0;
//按键的
//等待队列定义
wait_queue_head_t readq;
static int flags = 0;
int n = 0;
//异步信号定义
struct fasync_struct *buttons_fasync_struct;
struct timer_list s_timer; /*设备要使用的定时器*/
static char pin_desc[6] = {1,2,3,4,6,7};
static unsigned char key_vals[6] = {1,2,3,4,5,6};
static int s5pc100_buttons_open(struct inode *inode, struct file *file)
{
return 0;
}
static int s5pc100_buttons_release(struct inode *inode, struct file *file)
{
return 0;
}
//注册异步信号函数
static int s5pc100_buttons_fasync (int fd, struct file *file, int on)
{
return fasync_helper(fd, file, on, &buttons_fasync_struct);
}
//等待wake_up_interruptible(&readq)来唤醒wait_event_interruptible,唤醒后,当使用copy_to_user()的时候,发给应用程序的数据放在打开这个设备的文件描述符fd中,
就是通过kill_sysnc的SIGNIO告诉应用程序开始读数据,其实时候应用程序在非阻塞的状态下运行....这样就形成了异步通信机制,可以很大程度上的节约了cpu的开销
ssize_t s5pc100_buttons_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
if (count != 1)
return -EINVAL;
//等待被唤醒
wait_event_interruptible(readq,flags != 0);
if(copy_to_user(buf,key_vals+n,1))
return -EFAULT;
// printk(KERN_WARNING"key_vals = %d\n",key_vals[n]);
flags = 0;
return 1;
}
//定时器时间到后会把赋值的中断号判断是哪个中断事件,然后再唤醒读,kill_fasync的SIGIO告诉应用程序可以开始读取数据了
static void time_handler(unsigned long arg)
{
switch(s_timer.data)
{
case IRQ_EINT(1):
n = 0;
break;
case IRQ_EINT(2):
n = 1;
break;
case IRQ_EINT(3):
n = 2;
break;
case IRQ_EINT(4):
n = 3;
break;
case IRQ_EINT(6):
n = 4;
break;
case IRQ_EINT(7):
n = 5;
}
//唤醒读
flags = 1;
wake_up_interruptible(&readq);
//判断是否有信号被定义, 有这读操作被唤醒,KILL——FASYNC 给应用层发送数据
if(buttons_fasync_struct)
kill_fasync(&buttons_fasync_struct, SIGIO, POLL_IN);
}
//当发生按键中断时,会调用中断处理函数,在中断处理函数中,给定时器赋值,在给开启定时器,时间到会唤起定时器注册函数time_handler()
static irqreturn_t my_handler(int irq, void *dev_id)
{
//给定时器初始化
s_timer.data = irq;
mod_timer(&s_timer,jiffies+5);
return IRQ_HANDLED;
}
//字符设备操作符
struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.open = s5pc100_buttons_open,
.release = s5pc100_buttons_release,
.read = s5pc100_buttons_read,
//异步信号机制
.fasync = s5pc100_buttons_fasync,
};
//设备初始化函数
static void char_reg_setup_cdev(void)
{
int error,devno;
devno = MKDEV(buttons_major,buttons_minor); //获得设备号
cdev_init(&cdev,&buttons_fops);//初始化字符设备,获得设备操作符
cdev.owner = THIS_MODULE;
cdev.ops = &buttons_fops;
error = cdev_add(&cdev,devno,1); //加载字符设备
if (error)
printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}
static int __init buttons_init (void)
{
int result;
dev = MKDEV (buttons_major, buttons_minor); //获得设备号
result = register_chrdev_region (dev, number_of_devices, "buttons"); //注册设备
if (result<0) {
printk (KERN_WARNING "hello: can't get major number %d\n", buttons_major);
return result;
}
init_timer(&s_timer); //初始化定时器
s_timer.function = time_handler; //注册定时器函数
//激活定时器
add_timer(&s_timer);
char_reg_setup_cdev(); //初始化字符设备
//等待队列初始化
init_waitqueue_head(&readq);
//注册中断
if( request_irq(IRQ_EINT(1),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY1",&pin_desc[0]))
printk(KERN_WARNING"error request_irq DMA irq\n");
if( request_irq(IRQ_EINT(2),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY2",&pin_desc[1]))
printk(KERN_WARNING"error request_irq DMA irq\n");
if( request_irq(IRQ_EINT(3),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY3",&pin_desc[2]))
printk(KERN_WARNING"error request_irq DMA irq\n");
if( request_irq(IRQ_EINT(4),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY4",&pin_desc[3]))
printk(KERN_WARNING"error request_irq DMA irq\n");
if( request_irq(IRQ_EINT(6),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY5",&pin_desc[4]))
printk(KERN_WARNING"error request_irq DMA irq\n");
if( request_irq(IRQ_EINT(7),my_handler,IRQF_DISABLED | IRQF_TRIGGER_FALLING,"KEY6",&pin_desc[5]))
printk(KERN_WARNING"error request_irq DMA irq\n");
return 0;
}
static void __exit buttons_exit (void)
{
dev_t devno = MKDEV (buttons_major, buttons_minor);
free_irq(IRQ_EINT(1),&pin_desc[0]);
free_irq(IRQ_EINT(2),&pin_desc[1]);
free_irq(IRQ_EINT(3),&pin_desc[2]);
free_irq(IRQ_EINT(4),&pin_desc[3]);
free_irq(IRQ_EINT(6),&pin_desc[4]);
free_irq(IRQ_EINT(7),&pin_desc[5]);
cdev_del (&cdev);
unregister_chrdev_region (devno, number_of_devices);
}
module_init (buttons_init);
module_exit (buttons_exit);
测试程序test.c
#include
#include
#include
#include
#include
#include
#include
#include
int key = 0;
int i = 0;
int fd;
#if 1
void sig_handler(int signo)
{
if(read(fd,&key,1) < 0)
perror("open");
// printf("asd\n");
// printf("signo = %d ", signo);
printf("key = %d i = %d \n", key,++i);
}
#endif
int main(int argc, const char *argv[])
{
int key = 0;
int oflags;
fd = open("/dev/buttons",O_RDWR);
if(fd < 0)
{
perror("open");
return 0;
}
#if 1
printf("asigio\n");
signal(SIGIO, sig_handler);
//异步信号通知
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags|FASYNC);
#endif
while(1)
{
sleep(1000000);
#if 0
if(read(fd,&key,1) < 0)
perror("open");
printf("key = %d\n",key);
#endif
}
close(fd);
return 0;
}
程序写到这里,为什么用定时器可以去抖动呢?
当按键发生中断是,之前的效果是可能发生调用2次或多次中断函数,但是现在让你按下后,第一次调用中断函数,开启定时器,第一个定时器为超时,第二个中断函数来了,这样就开启定时器,就这样,当你按下的时候回调用多次中断函数,但是没次调用中断函数都会开启定时器,所以只有当按下按键后的最后一个中断函数调用的定时器会超时,在会调用定时器函数,这样就可以读取时哪个按键发来的数据,唤醒读,异步机制告诉应用程序,数据已准备好,应用程序调用异步信号的机制非阻塞的方式捕捉SIGIO的到来,在调用信号处理函数,在信号处理函数中read(fd,buf,1); 对于不用的开发板的按键的结构不一样,要适当的设置定时器超时时间mod_timer(&s_timer,jiffies+5);
这样就可以很好的去除按键的抖动,这也是底层使用最多的一种去抖方法。
阅读(1136) | 评论(0) | 转发(0) |