从零开始之驱动发开、linux驱动(二十一、内核定时器的使用)
在异步通知章节中,使用驱动程序主动通用应用层调用绑定的信号函数。
有一个缺陷,那就是对按键程序来说,会出现抖动,会多次进入中断函数,这会影响应用层对按键事件的处理产生麻烦。
正常情况下都是按下和送来成对出现的,但上图因为有了抖动,则出现了问题。
一般我们消抖都是采用延时的方式处理的。
在操作系统中一般延时有两种选择
1.通过sleep、udelay等函数延时
2.通过定时延时
因为sleep的延时是秒级别的,udelay最大只能延时2000us,即2ms
通过内核定时器
关于内核定时器的使用可以查看我之前写的文章
https://blog.****.net/qq_16777851/article/details/81208671
我们CPU默认是200ZH的频率运行。
通常抖动的时间是在20ms以内,即按20ms的消抖,足以让电平稳定。
在异步通知软件的基础上修改。
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <linux/types.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/highmem.h> /* For wait_event_interruptible */
#include <linux/poll.h>
#include <asm/gpio.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/sysctl.h>
#include <linux/timer.h>
static unsigned int major;
static struct class *button_class;
static struct device *button_dev;
static unsigned char key_val;
static atomic_t cnt = ATOMIC_INIT(1);
static struct timer_list button_timer;
static struct fasync_struct *button_fasync;
struct pin_desc {
unsigned int pin;
unsigned int key_val;
};
static struct pin_desc * pin_dev = NULL;
/* 按下时 值分别是 0x01 , 0x02 */
/* 松开时 值分别是 0x00 , 0x00 */
static struct pin_desc pins_desc[] = {
{S5PV210_GPH0(2), 0x01},
{S5PV210_GPH0(3), 0x02},
};
static irqreturn_t irq_handler(int irq, void *dev_id)
{
pin_dev = dev_id;
/* 中断函数中,修改定时器值为 4个jiffies后处理定时函数 */
mod_timer(&button_timer, jiffies + 4);
return IRQ_HANDLED;
}
void button_timer_func(unsigned long date)
{
struct pin_desc *p = NULL;
int pin_val;
/* 判断当add_timer时jiffies为0时会不会进定时中断函数 */
if(pin_dev == NULL) {
printk(KERN_INFO"pin_dev is NULL\n");
return;
}
p = pin_dev;
pin_val = gpio_get_value(p->pin);
/* 得到键值,判断时按下还是松开 */
if(pin_val)
{
/* 松开 */
key_val &= ~p->key_val;
}
else
{
/* 按下 */
key_val |= p->key_val;
}
/* 产生一个异步读信号 */
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
/* open函数 */
static int button_drv_open(struct inode *inode, struct file *file)
{
int ret = 0;
/* 如果自减后为0,说明没人打开,否则已经被人打开 */
if(!atomic_dec_and_test(&cnt)) {
/* 已经被人打开,加回原来的操作,并返回忙 */
atomic_inc(&cnt);
return -EBUSY;
}
ret = request_irq(IRQ_EINT(2), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint2",&pins_desc[0]);
if(ret)
{
printk(KERN_ERR"request_irq IRQ_EINT(2) fail");
return -EBUSY;
}
ret = request_irq(IRQ_EINT(3), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint3",&pins_desc[1]);
if(ret)
{
free_irq(IRQ_EINT(2), &pins_desc[0]);
printk(KERN_ERR"request_irq IRQ_EINT(3) fail");
return -EBUSY;
}
/* 定时器初始化 */
init_timer(&button_timer);
/* 定时到后执行函数 */
button_timer.function = button_timer_func;
/* 把该定时器,加入到定时器链表中 */
add_timer(&button_timer);
return 0;
}
static ssize_t button_drv_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
int len;
if(size < 1)
{
return -EINVAL;
}
/* 赋值只是为了消除告警 */
len = copy_to_user(array , &key_val, 1);
return 1;
}
int button_drv_fasync (int fd, struct file *file, int on)
{
printk(KERN_INFO"button_drv_fasync\n");
/* 增加一个异步通知到到本设备的异步通知队列中 */
return fasync_helper(fd , file, on, &button_fasync);
}
static int button_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT(2), &pins_desc[0]);
free_irq(IRQ_EINT(3), &pins_desc[1]);
/* 移除一个async */
fasync_helper(-1, file, 0, &button_fasync);
del_timer(&button_timer);
atomic_inc(&cnt); /* 释放设备 */
return 0;
}
static const struct file_operations button_drv_file_operation = {
.owner = THIS_MODULE,
.open = button_drv_open,
.read = button_drv_read,
.fasync = button_drv_fasync,
.release = button_drv_close,
};
static int __init button_drv_init(void)
{
/* 获取一个自动的主设备号 */
major = register_chrdev(0,"button_drv",&button_drv_file_operation);
if(major < 0)
{
printk(KERN_ERR"register_chrdev button_drv fail \n");
goto err_register_chrdev;
}
/* 创建一个类 */
button_class = class_create(THIS_MODULE, "button_class");
if(!button_class)
{
printk(KERN_ERR"class_create button_class fail\n");
goto err_class_create;
}
/* 创建从属这个类的设备 */
button_dev = device_create(button_class,NULL,MKDEV(major, 0), NULL, "button");
if(!button_dev)
{
printk(KERN_ERR"device_create button_dev fail \n");
goto err_device_create;
}
return 0;
/* 倒影式错误处理机制 */
err_device_create:
class_destroy(button_class);
err_class_create:
unregister_chrdev(major,"button_drv");
err_register_chrdev:
return -EIO;
}
static void __exit button_drv_exit(void)
{
/* 注销类里面的设备 */
device_unregister(button_dev);
/* 注销类 */
class_destroy(button_class);
/* 注销字符设备 */
unregister_chrdev(major,"button_drv");
}
module_init(button_drv_init);
module_exit(button_drv_exit);
修改两点:
1.使用原子操作,保证该设备只能被一个应用程序打开。
2.增加内核定时器,做消抖处理
应用程序和异步通知的一样。
经过多次测试,没有出现抖动现象。
细心的朋友注意到了,默认增加定时器到内核的定时器链表是没有初始值的,即为0.因为没有按键,所以pin_dev 的值是NULL,我在定时器处理函数中做了检查,判断定时器默认值为0时会不会立即进去
因为刚启动引用程序,没按键,即没外部中断时就已经进去了。
即证明为传入的0时,会立刻执行一次定时器处理函数。