这个文档重在阐述TQ2440开发板上的按键驱动,该驱动是作为字符设备的.我们将从思路阐述.典型的数据/函数结构分析和一些注意事项三个方面阐述这个问题,最后附录上源代码.该代码是在开发板上调试通过的。
硬件环境:TQ2440
内核版本:linux-2.6.32
文件系统:busybox-1.13.1
交叉工具:arm-2009q1,codesource出品
效果图:
一、作为字符设备的按键驱动
在这里,先陈述几个观点,如果不赞同这几个观点的,这个文档没必要认真砍下去了,因为接下来的code就只基于这几个观点的,当然本人的观点未必正确,欢迎大家发表讨论。
a. 按键驱动,必须具备去除噪声的能力,通俗一点,就是必须具备去抖动的能力。
对于这个,我们可以通过定时器实现,本文将提供3种方案,实现该功能,由于处在Linux下,因此因此都是和定时器相关的,但其中有一种并未直接用到定时器,算是个人的一点点创新,有缺点,有优点,也一并附录上,欢迎大家评论。这些都是经过调试通过的---当然这些不会是单片机上这种被人鄙视的延时
- uchar i=0xff;
- while(i-->0){};
b. 按键驱动,必须能够如实反应按键值
所谓按键值,可以是一组数据结构,包含一些被我们认为有意义的数据成员.个人认为一个典型的按键值VALUE应该包含按键ID,按键事件EV,按键的数据VAR
所谓按键ID很好理解,就是我们给按键设置的代号;按键事件,就是放松或者按下等;按键数据,就是按下去对应的pin脚电平值.在我们的这个文档中,相关定义如下
- //00000000稳定的按下的pin value
- //00000001稳定的释放状态pin value
- #define VAR_KEY_DN (0x00<<0)
- #define VAR_KEY_UP (0x01<<0)
- //稳定的按下状态
- #define EV_KEY_DN (0x01<<2)
- //定义按键id
- #define ID_KEY_1 (0x01<<4)
- //value=ID_KEY_1 | EV_KEY_DN | VAR_KEY_DN
c. 按键驱动,必须能够记忆按键值的能力
按键驱动,不单要能记录某一次按键的值,还需要记录过去多少次的按键的值,这个,可以通过数组实现,在实现过程中,我们采用了环形数据结构,能够非常轻巧地识别按键值的先后顺序
d. 按键驱动,应该自从加载以后就开始工作,而不是等待调用open方法才开始工作
这个和时下很多按键驱动有写向左,欢迎大家一起参加探讨。个人认为,按键驱动工作,记录按键事件的时间,应该是在从init开始,而不是open
无论打开,还是关闭,这个按键事件在那里,不移不异,所以,按键驱动的工作时间,与open应该无关 o/\o
好了,以上就是基本的观点,现在进入code阶段
二 、 Code
- /*
- @按键驱动程序
- @针对S3C2440的驱动程序
- @2011.3.20,keytounix
- @hi.baidu.com/andio
- */
- //包含重要的和常用的头文件
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/sched.h>
- #include <linux/poll.h>
- #include <linux/timer.h>
- #include <linux/irq.h>
- #include <linux/cdev.h>
- #include <linux/wait.h>//waitqueue
- #include <linux/errno.h>
- #include <linux/interrupt.h>//irq
- #include <linux/io.h>
- #include <linux/types.h>
- #include <linux/delay.h>
- #include <linux/semaphore.h>//sem
- #include <linux/fs.h>
- #include <linux/platform_device.h>//
- #include <linux/miscdevice.h>
- #include <linux/device.h>
- #include <linux/workqueue.h>//workqueue
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- //soc相关
- #include <mach/gpio.h>
- #include <mach/regs-clock.h>
- #include <mach/regs-gpio.h>
- #include <mach/hardware.h>
- #include <mach/irqs.h>
- //////////////////////////////
- #define u8 unsigned char
- #define u16 unsigned short
- #define uint unsigned int
- #define SZ_BUF 16
- #define VAR_INIT 0xff
- #define CMD_RESET 0xff
- #define TM1000MS 100
- //这个 宏获得t[n]的n数目
- #define get_elem_num(t) (sizeof(t)/sizeof(t[0]))
- ////////////////////////////////////
- //debug info
- //这是一个debug宏,很有意思的
- #define _DEBUG_ 1
- #if _DEBUG_
- #define debug(format,msg...) printk(format,##msg)
- #else
- #define debug(format,msg...) (void *)(0)
- #endif
- //定义按键结构体
- typedef struct {
- //cdev 结构体
- struct cdev cdev;
- //缓冲区存放键值
- u8 buf_var[SZ_BUF];
- //当前指针,可以用来表征可用的键值的数目
- //初始化为 VAR_INIT_TAIL
- u8 tail;
- //按键状态,SZ_BUF表示满,满了的操作也不同
- u8 buf_status;
- } dev_key_t;
- dev_key_t dev_key;
- // 定义设备的相关信息
- #define KEY_DEV_MAJOR 253
- #define DEV_NAME "usr_dev_key"
- dev_t dev_id;
- struct class *usr_dev_class;
- struct key_info
- {
- uint irq_no;//中断号
- uint pin;//管脚值
- uint pin_set;//管脚设置
- u8 value; //值
- }key_info_tab[]=
- {
- [0]={
- .irq_no=IRQ_EINT1,
- .pin=S3C2410_GPF(1),
-
- .pin_set = S3C2410_GPF1_EINT1,
- .value=0xff
- },
- [1]={
- .irq_no=IRQ_EINT4,
- .pin=S3C2410_GPF(4),
- .pin_set = S3C2410_GPF4_EINT4,
- .value=0xff
- },
- [2]={
- .irq_no=IRQ_EINT2,
- .pin=S3C2410_GPF(2),
- .pin_set = S3C2410_GPF2_EINT2,
- .value=0xff
- },
- [3]={
- .irq_no=IRQ_EINT0,
- .pin_set = S3C2410_GPF0_EINT0,
- .pin=S3C2410_GPF(0),
- .value=0
- }
- };
- //设备操作结构体
- extern struct file_operations file_ops;
- //这个宏直接获得 表x中的irq值
- #define irq(x) (key_info_tab[x].irq_no)
- //这个宏直接获得 表x中的pin值
- #define pin(x) (key_info_tab[x].pin)
- //这个宏直接获得 表x中的pin_set值
- #define set(x) (key_info_tab[x].pin_set)
- //这个宏直接获得 表x中的value值
- #define var(x) (key_info_tab[x].value)
- //这个宏直接获得dev_key.buf_var[x]的值
- #define buf(x) (dev_key.buf_var[x])
- //获得tail值得
- #define get_tail() (dev_key.tail)
- //设置dev_key.tail=x
- #define set_tail_as(x) dev_key.tail=x
- //设置dev_key.buf_var[index]=x
- #define set_buf_as(index,x) dev_key.buf_var[index]=x
- //dev_key.tail)%(SZ_BUF),保证dev_key.tail处于0到15之间
- #define inc_tail() dev_key.tail=((dev_key.tail+1)%(SZ_BUF))
- //inc_buf_num确保dev_key.buf_status值在0-SZ_BUF之间
- #define inc_buf_num() dev_key.buf_status=(dev_key.buf_status==SZ_BUF?SZ_BUF:(dev_key.buf_status+1))
- //注意上面的inc_tail()这个宏,是实现环形状数据结构的关键
- //定义按键事件
- //
- #define EV_KEY_MASK (0x0f)
- //00000001稳定的释放状态
- #define EV_KEY_UP (0x01<<0)
- //00000010,稳定的按下状态
- #define EV_KEY_DN (0x02<<0)
- //0000,0011,按下,产生下降沿的那个状态
- #define EV_KEY_XIN (0x03<<0)
- //0000,0100,释放,产生上升沿的那个状态
- #define EV_KEY_XOUT (0x04<<0)
- //释放和按下,对应的GPIO的值
- #define VAR_KEY_DN 0x00
- #define VAR_KEY_UP 0x01
- //定义按键id,ID_KEY_MASK是为了获得键值
- #define ID_KEY_MASK (0xf0)
- //0001,0000
- #define ID_KEY_1 (0x01<<4)
- //0010,0000
- #define ID_KEY_2 (0x02<<4)
- //0011,0000
- #define ID_KEY_3 (0x03<<4)
- //0100,0000
- #define ID_KEY_4 (0x04<<4)
- //这是一个宏,通过键的值value获得EV
- #define get_key_ev(value) (value & EV_KEY_MASK)
- //这是一个宏,通过键的值value获得按键id
- #define get_key_id(value) (value & ID_KEY_MASK)>>4
- //下面这俩个宏,通过id,ev设置key_info_tab
- #define set_key_value(id,ev) (key_info_tab[id].value=((id+1) <<4|ev)&0xff)
- #define set_key_var(id,ev) (key_info_tab[id].value=(((id+1) <<4)|ev)&0xff)
-
-
-
-
- ////////////////////////////////////////////////////////////////////
- //@如下是实现中断下半部的机制,包含timer,workqueue,tasklet几种类型
- //定时器处理函数
- void my_timer_proc(long);
- //定义定时器
- DEFINE_TIMER(key_timer,my_timer_proc,0,0);
- //tasklet处理函数
- void my_task_proc(int);
- //定义tasklet
- DECLARE_TASKLET(my_task_let,my_task_proc,0);
- //工作队列结构体
- struct work_struct work_wq;
- //延时工作队列结构体,在linux/workqueue.h里面定义的
- struct delayed_work delayed_work_wq;
- //工作队列的处理函数
- void my_work_proc(struct work_struct * my_work);
- //按键等待队列头,这个是为了实现和等待队列相关的操作的
- static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
- ///如下表示采用何种方法实现中断下半部
- //!=0表示采用此方法,注意,一次只能有一个为1,否则会重复保存数据
- //
- //采用工作队列的方法
- #define METHOD_WORK 0
- //采用延时工作队列的方法
- #define METHOD_DELAYED_WORK 1
- //采用定时器的方法,mod_timer()
- #define METHOD_TIMER 0
- //采用新旧jiffies的方法,个人原创
- #define METHOD_JIFFIES 0
- //采用tasklet
- #define METHOD_TASKLET 0
- //采用msleep()方法,尚未调试成功
- #define METHOD_MSLEEP 0
- //jiffies2保存最旧的jiffies
- //jiffies1保存上一次的jiffies
- //jiffies0保存此次中断的jiffies
- unsigned long volatile jiffies2=0;
- unsigned long volatile jiffies1=0;
- unsigned long volatile jiffies0=0;
- //注意down1,和down0,这个是为了实现jiffies方法而实现的
- int volatile down1 =0;
- int volatile down0 =0;
- //信号量,实现互斥,潮流,必须的
- struct semaphore sem;
- //注意这个,这是一个public glob变量,注意不用优化的volatile修饰符
- unsigned int volatile active_key;
- //采用工作队列的方法
- //#define METHOD_WORK 1
- void my_work_proc(struct work_struct * my_work)
- {
-
- debug("in %s ,do somthing------------[ok]\n",__func__);
- return;
-
- }
- ////这是一个函数,这个函数实现保存
- //按键id到当前可用buf的功能
- //当前可用buf是指buf[tail]
- void save_to_buf(int id)
- {
- if(dev_key.buf_status==VAR_INIT)
- {
- set_tail_as(0);
- dev_key.buf_status=0;
- }
- //set_buf_as(index,x);
- set_buf_as(get_tail(),var(id));
-
- debug("in %s,buf[%d]=%x\n",__func__,get_tail(),buf(get_tail()));
-
- //每保存一次都需要自增tail,buf数据量增加一次
- inc_tail();
- inc_buf_num();
- return ;
- };
- /*
- //jiffies方法,个人原创的,
- //这个方法的主要特点是对应的
- // down0保存最新中断时的按键值
- //jiffies0如是
- //down1保存上一次中断的按键值,即上一个down0值
- //jiffies1如是
- //实现该功能的关键在于设置了如下的条件判断 if(jiffies1-jiffies0>TM1000MS/2)
- //这个判断是基于一个事实的:IRQ_TYPE_EDGE_BOTH类型的按键中断必然会产生>俩个的按键中断
- //一个是下降沿一个是上升边沿
- //如果俩个沿的时间差>500毫妙(因为我们认为每次按键的时间一般会大于500ms,低于500ms的我们认为是抖动)
- //并且俩个沿之间的电平为VAR_KEY_DN
- //我们则认为这是一个有效的按键具体可以
- // if(jiffies1-jiffies0>TM1000MS/2)
- */
- //#define METHOD_JIFFIES 1
- void my_jiffies_proc(int id)
- {
- jiffies1=jiffies0;
- jiffies0=jiffies;
- down1=down0;
- down0=s3c2410_gpio_getpin(pin(id));
- if(jiffies1-jiffies0>TM1000MS/2){
- if(down0==VAR_KEY_DN)
- {
- set_key_var(id,EV_KEY_DN);
- save_to_buf(id);
-
- debug("in %s save a value [%x]to buf[%d]",__func__,buf(get_tail()-1),get_tail()-1);
-
- }
- }
- }
- /*
- #define METHOD_DELAY_WORK 1
- 这实际上是一个定时器,但比定时器有内涵多了
- 他的名字叫做延时工作队列方法
- 对应调用方式schedule_delayed_work();
- */
- void my_delay_work_proc(struct work_struct * my_work)
- {
- int id= active_key;
- u8 key_real_var=s3c2410_gpio_getpin(pin(id));
- if(key_real_var==VAR_KEY_DN)
- {
-
- set_key_var(id,EV_KEY_DN);
- save_to_buf(id);
- debug("in %s save a value [%x]to buf[%d]",__func__,buf(get_tail()-1),get_tail()-1);
- wake_up_interruptible(&key_waitq);
-
-
- }
- return;
- }
- /*
- 定时器处理线程,实现延时去抖动
- 这个是最经典的方法
- */
- void my_timer_proc(long key )
- {
- debug("in %s ,do somthing------------[ok]\n",__func__);
- return;
- }
-
- /*
- tasklet方式,在这里说明一下,
- 这种方式虽然好,但是和 work_struct一样不能处理抖动问题
- */
- void my_task_proc(int key)
- {
- debug("in %s ,do somthing------------[ok]\n",__func__);
- return;
- }
- /*
- 利用msleep函数实现延时,调试没有通过
- */
- void my_msleep_proc(int key,int ms)
- {
- debug("in %s ,do somthing------------[ok]\n",__func__);
- msleep_interruptible(ms);
- return;
- }
- /*
- @key_open,打开函数
- @
- */
- static int key_open(struct inode * pnode,struct file * pfile)
- {
- int ret=0;
- pfile->private_data=(dev_key_t *)&dev_key;
- down(&sem);
- return ret;
- };
- /*
- @key_release,关闭函数
- @关闭后
- */
- static int key_release(struct inode * pnode,struct file * pfile){
- int ret=0;
- //buf(0)=VAR_INIT;
- var(0)=VAR_INIT;
- var(1)=VAR_INIT;
- var(2)=VAR_INIT;
- var(3)=VAR_INIT;
- // set_tail_as(0);
- up(&sem);
- return ret;
- };
- /*
- 这个函数用来干什么的呢?
- 这是为了实现一个理念:
- 排在列表头的或者列表尾的
- 一定要是最新的或者最旧的按键值
- 这个适合copy_to_user函数一起使用的
- */
- static int buf_sort(char a[])
- {
- int i;
- int j;
- if(dev_key.buf_status==SZ_BUF)
- {
- for(j=0,i=SZ_BUF+get_tail()-1;j<SZ_BUF;i--,j++)
- {
- a[j]=buf(i%SZ_BUF);
- }
- }
- else if(dev_key.buf_status<SZ_BUF)
- {
- for(i=0,j=dev_key.buf_status;j>=0;i++,j--)
- {
- a[i]=buf(j);
- }
- }
- return;
- }
- /*
- 这个函数实现阻塞读
- */
- static int key_read(struct file * pfile,char * buf,size_t count, loff_t *offp)
- {
- int ret=0;
- int i=0;
- int my_count=0;
- char *msg;
- u8 my_buf[SZ_BUF];
- dev_key_t *dev=pfile->private_data;
- DECLARE_WAITQUEUE(wq,current);
- if(dev_key.buf_status==VAR_INIT)
- {
- if(pfile->f_flags & O_NONBLOCK)
- {
- return -EAGAIN;
- }
- else
- {
- add_wait_queue(&key_waitq,&wq);
- wait_event_interruptible(key_waitq,buf(0)!=VAR_INIT);
- }
- }
-
- buf_sort(my_buf);
- if(dev_key.buf_status==SZ_BUF)
- {
- count = (count >SZ_BUF ? count :SZ_BUF);
- }
- else if(dev_key.buf_status <SZ_BUF)
- {
- count = count >dev_key.buf_status ? dev_key.buf_status : count;
- }
- ret=copy_to_user(buf,my_buf,count);
- msg=(ret==0?"OK":"NO");
- debug("key_read:-------------------[%s],using count=%d,offp=%d\n",msg,i,(int)*offp);
- remove_wait_queue(&key_waitq,&wq);
- return ret;
-
-
- };
- /*
- 中断处理函数,是枢纽
- */
- static irqreturn_t irq_proc(unsigned int irq,int _id)
- {
- active_key =_id;
- #if METHOD_WORK
- schedule_work(&work_wq);
- #endif
- #if METHOD_DELAYED_WORK
- schedule_delayed_work(&delayed_work_wq,TM1000MS/10);
- #endif
- #if METHOD_TIMER
- /*1HZ=1s=100个jiffies单位*/
- key_timer.data=_id;
- mod_timer(&key_timer,jiffies+TM1000MS/10);
- #endif
- #if METHOD_TASKLET
- my_task_let.data=_id;
- tasklet_schedule(&my_task_let);
- #endif
- #if METHOD_MSLEEP
- my_msleep_proc(_id, TM1000MS/50);
-
- #endif
-
-
-
-
- #if METHOD_JIFFIES
- my_jiffies_proc(_id);
- #endif
- wake_up_interruptible(&key_waitq);
- debug("extern from %s,active_key=%d\n",__func__,active_key);
- return IRQ_RETVAL(IRQ_HANDLED);
- };
- /*
- @dev_init,设备初始化函数,程式化的东西
- @
- */
- static int dev_init(void)
- {
- int ret=0;
- int i;
- char * msg;
-
-
-
- ///cdev
- cdev_init(&(dev_key.cdev),&file_ops);
- dev_key.cdev.owner=THIS_MODULE;
- dev_key.cdev.ops=&file_ops;
- cdev_add(&(dev_key.cdev),dev_id,1);
-
- //register chr dev
- #ifdef DEV_KEY_MAJOR
- dev_id=MKDEV(DEV_KEY_MAJOR,0);
- ret=register_chrdev_region(dev_id,1,DEV_NAME);
- if(ret!=0)//!=0意味着注册失败
-
- goto alloc_method;
- else
- goto print_irq_msg;
- #endif
- alloc_method:
- ret =alloc_chrdev_region(&dev_id,0,1,DEV_NAME);
-
- print_irq_msg:
- msg=(ret==0?"OK":"ERR");
- debug("register devivice: %s -------[%s],using major=%d\n",DEV_NAME,msg,MAJOR(dev_id));
- if(ret)
- goto unregister_irq_handle;
-
- //初始化中断
- for(i=0;i<get_elem_num(key_info_tab);i++)
- {
- ret=request_irq(key_info_tab[i].irq_no,irq_proc,IRQ_TYPE_EDGE_BOTH,DEV_NAME,i);
-
-
- msg=(ret==0?"OK":"ERR");
- debug("register %s.ext_irq[%d] -------[%s],using irq_no=%d\n",DEV_NAME,i,msg,key_info_tab[i].irq_no);
-
- if(ret!=0)
- goto unregister_irq_handle;
-
- }
-
- init_MUTEX(&sem);
- INIT_WORK(&work_wq,my_work_proc);
- INIT_DELAYED_WORK(&delayed_work_wq,my_delay_work_proc);
-
- init_waitqueue_head(&key_waitq);
- //setup_timer(&key_timer,&my_timer_proc,0);
- init_timer(&key_timer);
- key_timer.function=&my_timer_proc;
- add_timer(&key_timer);
- jiffies2=VAR_INIT;
- jiffies1=VAR_INIT;
- jiffies0=VAR_INIT;
-
- dev_key.buf_status=0;
- set_tail_as(0);
- usr_dev_class=class_create(THIS_MODULE,"usr_dev");
- device_create(usr_dev_class,NULL,dev_id,NULL,"usr_dev_key");
-
- out:
-
- return ret;
- unregister_irq_handle:
- for(i=i-1;i>=0;i--)
- {
- free_irq(key_info_tab[i].irq_no,dev_id);
- }
- return ret;
-
- };
- /*
- @key_poll
- */
- static int key_poll(struct file *filep,poll_table *wt_poll)
- {
- int mask=0;
- dev_key_t *dev = filep->private_data;
- poll_wait(filep,&key_waitq,wt_poll);
- if(buf(0)!=VAR_INIT)
- {
-
- mask=POLLIN | POLLRDNORM;
-
- }
- return mask;
- };
- /*
- @dev_exit,exit dev
- @
- */
- static int dev_exit(void)
- {
- int i;
- unregister_chrdev(MAJOR(dev_id),DEV_NAME);
- for(i=0;i<get_elem_num(key_info_tab);i++)
- {
- free_irq(irq(i),dev_id);
-
-
- }
- del_timer(&key_timer);
- device_destroy(usr_dev_class,dev_id);
- class_destroy(usr_dev_class);
- return 0;
-
-
- };
- /*
- @key_ioctl
- */
- static int key_ioctl(struct inode *inodep,struct file *filep,unsigned int cmd, unsigned long arg )
- {
- int ret;
- switch(cmd)
- {
- case CMD_RESET:
- dev_key.buf_status=0;
- set_tail_as(0);
- ret=1;
- break;
- default:
- ret =-EINVAL;
- break;
-
- };
- return ret;
- };
- /*
- file_operations结构体
- */
- struct file_operations file_ops={
- .owner = THIS_MODULE,
- .read = key_read,
- .ioctl = key_ioctl,
- .poll = key_poll,
- .open = key_open,
- .release = key_release
- };
- MODULE_AUTHOR("keytounix");
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_DESCRIPTION("driver for TQ2440 key");
- module_init(dev_init);
- module_exit(dev_exit);
阅读(2173) | 评论(0) | 转发(0) |