linux字符驱动之定时器去抖动按键驱动

上一节里,实现同一时刻只能有一个进程使用同一个设备,例如:只能有一个进程,在同一时刻里使用/dev/buttons这个设备。

上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/17563953

相信大家在写单片机的按键程序时,也必将会涉及一点,就去按键去抖动。按键去抖动的方法无非有二种,一种是硬件电路去抖动,这种在要求不是特别高的情况下是不会被采用的;另一种就是延时去抖动了。而延时又一般分为二种,一种是for循环死等待,一种是定时延时。对,这一节里我们来使用内核的定时器去抖动。

问:linux内核定时器有哪些要素?

答:有两个要素:

一、超时时间

二、处理函数

问:linux定时器结构是怎样的?

答:

struct timer_list {
	struct list_head entry;
	unsigned long expires;
	void (*function)(unsigned long);
	unsigned long data;
	struct tvec_base *base;
	.....
};

问:void (*function)(unsigned long data)里面的参数是谁传给它的?

答:是timer_list.data传给它的,如果需要向function传递参数时,则应该设置timer_list.data,否则可以不设置。

问:与定时器相关的操作函数有哪些?

答:

一、使用init_timer函数初始化定时器

二、设置timer_list.function,并实现这个函数指针

三、使用add_timer函数向内核注册一个定时器

四、使用mod_timer修改定时器时间,并启动定时器

问:int mod_timer(struct timer_list *timer, unsigned long expires)的第二个参数为超时时间,怎么设置超时时间,如果定时为10ms?

答:一般的形式为:   jiffies + (HZ /100),HZ 表示100个jiffies,jiffies的单位为10ms,即HZ = 100*10ms = 1s


详细请参考驱动源码:

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/module.h>
#include <linux/device.h> 		//class_create
#include <mach/regs-gpio.h>		//S3C2410_GPF1
//#include <asm/arch/regs-gpio.h>  
#include <mach/hardware.h>
//#include <asm/hardware.h>
#include <linux/interrupt.h>  //wait_event_interruptible
#include <linux/poll.h>   //poll
#include <linux/fcntl.h>


/* 定义并初始化等待队列头 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);


static struct class *sixthdrv_class;
static struct device *sixthdrv_device;

static struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

static struct pin_desc pins_desc[4] = {
		{S3C2410_GPF1,0x01},
		{S3C2410_GPF4,0x02},
		{S3C2410_GPF2,0x03},
		{S3C2410_GPF0,0x04},
}; 
struct pin_desc *irq_pindes;

static int ev_press = 0;

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
int major;

static struct fasync_struct *button_fasync;
static struct timer_list buttons_timer;  /* 定义一个定时器结构体 */

#if 0
static atomic_t canopen = ATOMIC_INIT(1);     //定义原子变量canopen并初始化为1
#endif

static DECLARE_MUTEX(button_lock);     //定义互斥锁

/* 用户中断处理函数 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	int ret;
	irq_pindes = (struct pin_desc *)dev_id;
	
	/* 修改定时器定时时间,定时10ms,即10秒后启动定时器
	 * HZ 表示100个jiffies,jiffies的单位为10ms,即HZ = 100*10ms = 1s
	 * 这里HZ/100即定时10ms
	 */
	ret = mod_timer(&buttons_timer, jiffies + (HZ /100));
	if(ret == 1)
	{
		printk("mod timer success\n");
	}
	return IRQ_HANDLED;
}
static int sixth_drv_open(struct inode * inode, struct file * filp)
{
#if 0
	/* 自减操作后测试其是否为0,为0则返回true,否则返回false */
	if(!atomic_dec_and_test(&canopen))
	{
		atomic_inc(&canopen); //原子变量增加1
		return -EBUSY;
	}
#endif

	/* 当打开的文件有O_NONBLOCK标记时,表示不阻塞 */
	if(filp->f_flags & O_NONBLOCK)
	{
		/* 尝试获取button_lock信号量,当获取不到时立即返回 */
		if (down_trylock(&button_lock))
				return -EBUSY;
	}
	else
	{
		/* 获取button_lock信号量,当获取不到时,将会休眠
	 	* 但是这种休眠是不可以被中断打断的
	 	*/
		down(&button_lock);
	}
		
	/*  K1 ---- EINT1,K2 ---- EINT4,K3 ---- EINT2,K4 ---- EINT0
  	 *  配置GPF1、GPF4、GPF2、GPF0为相应的外部中断引脚
  	 *  IRQT_BOTHEDGE应该改为IRQ_TYPE_EDGE_BOTH
	 */
	request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]);
	request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]);
	request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]);
	request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]);
	return 0;
}

static ssize_t sixth_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos)
{
	if (size != 1)
			return -EINVAL;

	/* 当打开的文件有O_NONBLOCK标记时,表示不阻塞 */
	if(file->f_flags & O_NONBLOCK)
	{
		/* 当ev_press = 0时,表示没有按键被按下,即表示没有数据 */
		if(!ev_press)
			return -EAGAIN;
	}
	else
	{
		/* 当没有按键按下时,休眠。
		 * 即ev_press = 0;
		 * 当有按键按下时,发生中断,在中断处理函数会唤醒
		 * 即ev_press = 1; 
		 * 唤醒后,接着继续将数据通过copy_to_user函数传递给应用程序
		 */
		wait_event_interruptible(button_waitq, ev_press);
	}
	

	copy_to_user(user, &key_val, 1);
	
	/* 将ev_press清零 */
	ev_press = 0;
	return 1;	
}

static int sixth_drv_close(struct inode *inode, struct file *file)
{
#if 0
	atomic_inc(&canopen); //原子变量增加1
#endif
	free_irq(IRQ_EINT1,&pins_desc[0]);
	free_irq(IRQ_EINT4,&pins_desc[1]);
	free_irq(IRQ_EINT2,&pins_desc[2]);
	free_irq(IRQ_EINT0,&pins_desc[3]);

	/* 释放信号量 */
	up(&button_lock);
	return 0;
}

static unsigned int sixth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;

	/* 该函数,只是将进程挂在button_waitq队列上,而不是立即休眠 */
	poll_wait(file, &button_waitq, wait);

	/* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0 
	 * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1
	 */
	if(ev_press)
	{
		mask |= POLLIN | POLLRDNORM;  /* 表示有数据可读 */
	}

	/* 如果有按键按下时,mask |= POLLIN | POLLRDNORM,否则mask = 0 */
	return mask;  
}

/* 当应用程序调用了fcntl(fd, F_SETFL, Oflags | FASYNC); 
 * 则最终会调用驱动的fasync函数,在这里则是sixth_drv_fasync
 * sixth_drv_fasync最终又会调用到驱动的fasync_helper函数
 * fasync_helper函数的作用是初始化/释放fasync_struct
 */
static int sixth_drv_fasync(int fd, struct file *filp, int on)
{
	return fasync_helper(fd, filp, on, &button_fasync);
}

/* File operations struct for character device */
static const struct file_operations sixth_drv_fops = {
	.owner		= THIS_MODULE,
	.open		= sixth_drv_open,
	.read		= sixth_drv_read,
	.release    = sixth_drv_close,
	.poll       = sixth_drv_poll,
	.fasync		= sixth_drv_fasync,
};

/* 定时器处理函数 */
static void buttons_timer_function(unsigned long data)
{
	struct pin_desc *pindesc = irq_pindes;
	unsigned int pinval;
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if(pinval)
	{
		/* 松开 */
		key_val = 0x80 | (pindesc->key_val);
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

	ev_press = 1;							 /* 表示中断已经发生 */
	wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */

	/* 用kill_fasync函数告诉应用程序,有数据可读了 
	 * button_fasync结构体里包含了发给谁(PID指定)
	 * SIGIO表示要发送的信号类型
	 * POLL_IN表示发送的原因(有数据可读了)
	 */
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

/* 驱动入口函数 */
static int sixth_drv_init(void)
{
	/* 初始化定时器 */
	init_timer(&buttons_timer);
	/* 当定时时间到达时uttons_timer_function就会被调用 */
	buttons_timer.function 	= buttons_timer_function;
	/* 向内核注册一个定时器 */
	add_timer(&buttons_timer);
	
	/* 主设备号设置为0表示由系统自动分配主设备号 */
	major = register_chrdev(0, "sixth_drv", &sixth_drv_fops);

	/* 创建sixthdrv类 */
	sixthdrv_class = class_create(THIS_MODULE, "sixthdrv");

	/* 在sixthdrv类下创建buttons设备,供应用程序打开设备*/
	sixthdrv_device = device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons");

	return 0;
}

/* 驱动出口函数 */
static void sixth_drv_exit(void)
{
	unregister_chrdev(major, "sixth_drv");
	device_unregister(sixthdrv_device);  //卸载类下的设备
	class_destroy(sixthdrv_class);		//卸载类
}

module_init(sixth_drv_init);  //用于修饰入口函数
module_exit(sixth_drv_exit);  //用于修饰出口函数	

MODULE_AUTHOR("LWJ");
MODULE_DESCRIPTION("Just for Demon");
MODULE_LICENSE("GPL");  //遵循GPL协议

应用测试程序源码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>	//sleep
#include <poll.h>
#include <signal.h>
#include <fcntl.h>


/* buttons_all_test
 */ 
int main(int argc ,char *argv[])
{
	int fd;
	unsigned char key_val;
	fd = open("/dev/buttons",O_RDWR);	/* 以阻塞方式读 */
	if (fd < 0)
	{
		printf("open error\n");
		return -1;
	}

	while(1)
	{
		int ret =read(fd,&key_val,1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
		//sleep(3);
	}
	return 0;
}

测试步骤:

[WJ2440]# ls
Qt                  fourth_drv.ko       sixth_drv.ko
TQLedtest           fourth_test         sixth_test
app_test            home                sixthdrvtest
bin                 lib                 sys
buttons_all_drv.ko  linuxrc             third_drv.ko
buttons_all_test    mnt                 third_test
dev                 opt                 tmp
driver_test         proc                udisk
etc                 root                usr
fifth_drv.ko        sbin                var
fifth_test          sddisk              web
first_drv.ko        second_drv.ko
first_test          second_test
[WJ2440]# insmod  buttons_all_drv.ko 
[WJ2440]# lsmod 
buttons_all_drv 3936 0 - Live 0xbf000000
[WJ2440]# ls /dev/buttons -l 
crw-rw----    1 root     root      252,   0 Jan  2 05:43 /dev/buttons
[WJ2440]# ./buttons_all_test 
key_val: 0x1, ret = 1
key_val: 0x81, ret = 1
key_val: 0x1, ret = 1
key_val: 0x81, ret = 1
key_val: 0x4, ret = 1
key_val: 0x84, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x3, ret = 1
key_val: 0x83, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
key_val: 0x2, ret = 1
key_val: 0x82, ret = 1
[WJ2440]# ./buttons_all_test  &
[WJ2440]# top
Mem: 9996K used, 50168K free, 0K shrd, 0K buff, 7180K cached
CPU:  0.3% usr  0.5% sys  0.0% nic 99.0% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.02 0.05 0.01 1/23 604
  PID  PPID USER     STAT   VSZ %MEM CPU %CPU COMMAND
  604   589 root     R     2092  3.4   0  0.9 top
  589     1 root     S     2092  3.4   0  0.0 -/bin/sh
    1     0 root     S     2088  3.4   0  0.0 init
  590     1 root     S     2088  3.4   0  0.0 /usr/sbin/telnetd -l /bin/login
  587     1 root     S     1508  2.5   0  0.0 EmbedSky_wdg
  603   589 root     S     1428  2.3   0  0.0 ./buttons_all_test
  573     2 root     SW<      0  0.0   0  0.0 [rpciod/0]
    5     2 root     SW<      0  0.0   0  0.0 [khelper]
  329     2 root     SW<      0  0.0   0  0.0 [nfsiod]
    2     0 root     SW<      0  0.0   0  0.0 [kthreadd]
    4     2 root     SW<      0  0.0   0  0.0 [events/0]
    3     2 root     SW<      0  0.0   0  0.0 [ksoftirqd/0]
   11     2 root     SW<      0  0.0   0  0.0 [async/mgr]
  237     2 root     SW<      0  0.0   0  0.0 [kblockd/0]
  247     2 root     SW<      0  0.0   0  0.0 [khubd]
  254     2 root     SW<      0  0.0   0  0.0 [kmmcd]
  278     2 root     SW       0  0.0   0  0.0 [pdflush]
  279     2 root     SW       0  0.0   0  0.0 [pdflush]
  280     2 root     SW<      0  0.0   0  0.0 [kswapd0]
  325     2 root     SW<      0  0.0   0  0.0 [aio/0]

由测试结果可知,无论按多少次,按键都是成对出现的,即按下、松开;按下、松开;按下、松开,而不会出现按下、按下、按下、松开这种抖动情况,这就完成了定时器消抖动的目的。

这里贴一张韦老师的定时器消抖动的按键分析图:

linux字符驱动之定时器去抖动按键驱动_第1张图片


上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/17563953


你可能感兴趣的:(linux字符驱动之定时器去抖动按键驱动)