谈一个按键驱动

谈一个按键驱动

提示:::本人英语不好.忽略命名单词~~~谢谢合作...

联系方式:97164811@qq.com

可能这一篇好像没跟上我之前的其他文章,不过没事,回头我再慢慢补上那些帖子吧.今天主要谈谈按键驱动吧...一来是为自己总结,二则希望得到网友们的指点,写的不好的地方,还望指正..谢谢~~~~

首先来看看按键的硬件结构.

由图可知,按键按下keyint1会变成接地,也就是低电平..否则能,就会被上拉电阻拉高到高电平....这个地方有什么不明白的,咱们可以私聊~~

我相信大部分人这里都是小case...

下面进入正题..我是这么设想的,一个按键有3种基本的按键消息叫做 KEY_U, KEY_HOLD,KEY_DOWN.还有一些其他属性,比如按键去抖,按键检测时间,按键按住了多久..等等..其他比如双击之类的,可以根据按键间隔来判断.

根据这些,我先给出我的结构体代码:

/* 时间以10 ms为单位 */
struct key_info {
	unsigned int 	irq;		//中断号
	unsigned int	irq_flag;	//恢复中断使用的寄存器配置
	unsigned int	gpio;		//GPIO检测管脚
	unsigned int	code;		//该按键值
	unsigned int 	active_low;	//按键去抖时间
	unsigned int 	test_time;	//按键探测时间,检测引脚时间
	unsigned int	hold_time;	//多少时间视为按键按住
	unsigned int	hold_message;	//按住多少个test_time产生一次按键按住消息
	char		name[20];	//中断名字
};

这里我介绍的是单独的按键,并不是矩阵键盘,所以我把每一个按键都单独接在了一个中断口上...好了.有了这些基本的知识之后,我们开始分析怎么去写这样的一个驱动..

这里首先大家得了解下platfrom机制在内核中的概念.因为这次,我们要设计的就是让硬件配置和驱动分开管理的方式,并不是固定的只有一个按键,或者只能接在固定的口上.platfrom的机制引入让驱动和硬件相互寻找,你找我来,我来你,找到后驱动便向platfrom总线去要属于它的另一半.这样,当两者结合在一起的时候......(Ps.别想歪了~~)你懂的~~~只有当platfrom总线上同时挂接了硬件配置和驱动,那么才回导致驱动真正被创建.好了关于platfrom总线,大家可以百度下.这里咱们不是要重点介绍这个...跳过跳过啦~~

首先第一步,咱们先把硬件配置给挂到总线上去.就是去填充上面的结构体,让platfrom总线知道,我们有几个按键,对应什么中断口之类的..

struct key_info key_array[] = {
	[0] = {
		.irq 	=	IRQ_EINT(15),
		.irq_flag = S3C_GPIO_SPECIAL(0x02),
		.gpio	=	S3C64XX_GPN(15),
		.code	=	KEY_A,
		.active_low	=	1,		//10ms
		.test_time	= 	2,		//20ms
		.hold_time	=	100,		//1000ms
		.hold_message	=	20,		//200ms一次
		.name		=	"eint15",
	},
	[1] = {
		.irq 	=	IRQ_EINT(14),
		.irq_flag = S3C_GPIO_SPECIAL(0x02),
		.gpio	=	S3C64XX_GPN(14),
		.code	=	KEY_B,
		.active_low	=	1,		//10ms
		.test_time	= 	2,		//20ms
		.hold_time	=	100,		//1000ms
		.hold_message	=	20,		//200ms一次
		.name		=	"eint14",
	},
	[2] = {
		.irq 	=	IRQ_EINT(13),
		.irq_flag = S3C_GPIO_SPECIAL(0x02),
		.gpio	=	S3C64XX_GPN(13),
		.code	=	KEY_C,
		.active_low	=	1,		//10ms
		.test_time	= 	2,		//20ms
		.hold_time	=	100,		//1000ms
		.hold_message	=	20,		//200ms一次
		.name		=	"eint13",
	},
};


struct key_data key_array_data = {
	.button 	= key_array,
	.nbutton 	= ARRAY_SIZE(key_array),
};

好神奇是不是...一下子定义了3个按键信息了...接着就是注册进总线了.直接看代码吧

static int __init initialization_function(void)
{
	int ret;  
	/* 申请一个设备叫key的名字,-1是id号..用于区别同设备名,不同设备信息的吧...我一般设置为-1,因为我比较专一哈哈 */
	my_device = platform_device_alloc("key", -1);  
	/* 将上面的按键信息添加到设备key中 */
	platform_device_add_data(my_device,&key_array_data,sizeof(key_array_data));
	/* 将设备k添加到总线中 */
	ret = platform_device_add(my_device);
    	if(ret)  
    		printk("platform_device_add failed!/n");  
         
    return ret;  
}


static void __exit cleanup_function(void)
{
	/* 从总线中剥离该设备信息 */
	platform_device_unregister(my_device);
}

啥?你有些迷糊???别啊~~~慢慢看啊.一开始我也看不懂.现在慢慢也懂了啊....

前面的一些内容,我并不多讲,大家百度查查,度娘还是不错的....不过我相信大家都能看懂..下面我来讲讲按键检测的一些小原理吧...其实一开就讲的原理,这里我就是说说怎么去做这些事对吧...不过有些事,可别让我教啊~~~你懂的~~~

一个按键的检测大致如下:

1:申请中断

2:申请检测定时器

3:将按键信息填入按键队列

4:加入阻塞读取

5:加入poll->select轮训检测

Ps:

XX:囧多嘛碟....申请中断???申哪个?

LvApp:这个这个..我也不知道啊......爱申请哪个就哪个吧.....(我当然不会这么说,肯定是去总线上获取啦.对不对..傻瓜,一会教你)

XX:为嘛要用定时器啊,直接中断搞定不就行了.下降沿产生KEY_DOWN 上升沿产生KEY_UP 不就完事了...还搞什么定时器啊.

LvApp:这个...为了按键抖动(硬件也能处理) 为了获取按键时间...为了防止干扰..等

这里其实最主要的是前面2个.后面3个都是比较简单的...注意smp情况下的竞态产生就好了...

好.第一.我们来看看如何获得资源.我宿友让我少说话,多贴代码,用代码来说明一些....我日他~~~

首先资源获取在static int __devinit key_probe(struct platform_device *dev)这个函数中完成的,调用了下面的函数来保存按键信息

int get_resource(struct device_dev* dev,struct 	key_data *data)
{
	int i = 0;
	/* 获得按键个数 */
	dev->nkey = data->nbutton;
	/* 分配按键空间 */
	dev->key_status = kmalloc(sizeof(struct key_status) * dev->nkey,GFP_KERNEL);
	if(!dev->key_status){
		return -ENOMEM;
	}
	memset(dev->key_status,0,sizeof(struct key_status) * dev->nkey);
	/* 获取platform按键资源 */
	for(i = 0;i < dev->nkey;i++){
		dev->key_status[i].key = data->button[i];
	}
	return 0;
}

咦~~这里估计有人要骂我了...搞个函数上来,里面结构体啥的都不知道.dev data都是什么玩意,谁看得懂之类的.....

好好好..我这里只是大致的表达下意思..这些我都会在后面贴上完整的代码..或者发资源,自己下面啦...我要讲的主要是这个思路...当然高手嘴下留情...别喷我.....

消消气..我们要进入第一个流程,申请中断了..申请资源是在当设备文件被open的时候完成的..那么直接看代码吧...

static int request_irq_timer(struct device_dev* dev)
{
	int i= 0 ;
	int j = 0;
	if(S_OK != queue_init(20,&g_devp->queue_head)){
		return -ENOMEM;
	}
	for(i = 0;i < dev->nkey;i++){
		dev->key_status[i].key_status = KEY_NONE;       		/* 按键状态 */
		/* irq */
		if(request_irq(	dev->key_status[i].key.irq,     		/* 中断号 */
						int15_handler_t,		/* 中断执行函数,也称为中断顶半部 */
						IRQ_TYPE_EDGE_FALLING,		/* 下降沿触发 */
						dev->key_status[i].key.name,	/* 中断名 cat /proc/interrupts 可查看 */
						(void*)i) != 0){
			break;
		}
		init_timer(&dev->key_status[i].key_timer);
		dev->key_status[i].key_timer.function 	= timer_hander;
		dev->key_status[i].key_timer.data 		= i;
	}
	/* 如果资源没有全部申请成功,则撤销已申请的资源 */
	if(i != dev->nkey){
		for(j = 0; j < i;j++){
			free_irq(dev->key_status[j].key.irq,(void*)j);
		}
		return -EBUSY;
	}
	return 0;	
}

很简单是吧...对,非常简单.....那是因为我写的简单,复杂的不会写..嘿嘿~~~其实吧.这里即完成了中断申请也完成了定时器的初始化.一个函数搞定2个步骤....直接看中断处理函数吧...

irqreturn_t int15_handler_t(int _id, void *_pdata)
{
	int keyno = (int)_pdata;
#ifdef _DEBUG_	
	printk(KERN_INFO "_id = %d _pdate = %d\n",_id,(int)_pdata);
#endif
	/* 关闭中断,开启定时器采样 */
	disable_irq_nosync(_id);
	/* 设置按键状态为中断进入 */
	g_devp->key_status[keyno].key_status = KEY_EINT;
	/* 根据配置设置去抖动延时 */
	g_devp->key_status[keyno].key_timer.expires = jiffies + g_devp->key_status[keyno].key.active_low;
	/* 加入定时候,抖动时间之后,继续检测定时器 */
	add_timer(&g_devp->key_status[keyno].key_timer);
#ifdef _DEBUG_	
	printk(KERN_INFO "add_timer ok\n");
#endif
//	disable_irq(_id);		//该函数等待该中断执行完成并返回,会造成死锁
	return IRQ_HANDLED;
}

注释还算蛮多了,就不多讲了.这里就是一个按键去抖的时间修正问题了..其他没什么..就是关中断,开定时器吧...还算简单....

下面重点就是定时器分析了

void timer_hander(unsigned long keyno)
{
	int	state = 1;
	ITEM	key_code;
	/* 设置GPIO为输入模式 */
	s3c_gpio_cfgpin(g_devp->key_status[keyno].key.gpio,S3C_GPIO_INPUT);
	/* 获取按键值 */
	state = gpio_get_value(g_devp->key_status[keyno].key.gpio);


	if(state == 0){
		if(KEY_EINT == g_devp->key_status[keyno].key_status){
#ifdef _DEBUG_		
	printk(KERN_INFO "KEY_DOWN\n");
#endif
			/* 设置按键状态 */
			g_devp->key_status[keyno].key_keep_time = 0;
			g_devp->key_status[keyno].key_status = KEY_DOWNING;
			key_code.code 		= g_devp->key_status[keyno].key.code;
			key_code.key_status = g_devp->key_status[keyno].key_status;
			key_code.key_time	= g_devp->key_status[keyno].key_keep_time;
			/* 将按键状态送入队列 */
			queue_insert(key_code,&g_devp->queue_head);
			/* 唤醒等待队列 */
			wake_up_interruptible(&g_devp->r_wait);
			/* 更新定时器,用于下一次检测 */
			mod_timer(&g_devp->key_status[keyno].key_timer,jiffies + g_devp->key_status[keyno].key.test_time);
		}else{
			/* 跟新按键按住时间 */
			g_devp->key_status[keyno].key_keep_time += g_devp->key_status[keyno].key.test_time;
			/* 判断按键时间是否大于按键按住消息所需要的时间 */
			if(g_devp->key_status[keyno].key_keep_time > g_devp->key_status[keyno].key.hold_time){
				/* 判断按键时间是否在按键消息发出时间点 */
				if(0 == ((g_devp->key_status[keyno].key_keep_time / g_devp->key_status[keyno].key.test_time) % g_devp->key_status[keyno].key.hold_message)){
					/* 跟新按键状态 */
					g_devp->key_status[keyno].key_status = KEY_HOLDING;
					key_code.code 		= g_devp->key_status[keyno].key.code;
					key_code.key_status = g_devp->key_status[keyno].key_status;
					key_code.key_time	= g_devp->key_status[keyno].key_keep_time;
					/* 送入按键队列 */
					queue_insert(key_code,&g_devp->queue_head);
					/* 唤醒等待队列 */
					wake_up_interruptible(&g_devp->r_wait);
#ifdef _DEBUG_							
	printk(KERN_INFO "KEY_HOLD\n");
#endif
				}	
			}
			/* 更新定时器,用于下一次检测 */
			mod_timer(&g_devp->key_status[keyno].key_timer,jiffies + g_devp->key_status[keyno].key.test_time);
		}
	}else{
		if(KEY_EINT != g_devp->key_status[keyno].key_status){
#ifdef _DEBUG_				
	printk(KERN_INFO "KEY_UP\n");
#endif
			/* 跟新按键状态 */
			g_devp->key_status[keyno].key_keep_time += g_devp->key_status[keyno].key.test_time;
			g_devp->key_status[keyno].key_status = KEY_UPING;
			key_code.code 		= g_devp->key_status[keyno].key.code;
			key_code.key_status = g_devp->key_status[keyno].key_status;
			key_code.key_time	= g_devp->key_status[keyno].key_keep_time;
			/* 送入按键队列 */
			queue_insert(key_code,&g_devp->queue_head);
			/* 唤醒等待队列 */
			wake_up_interruptible(&g_devp->r_wait);
#ifdef _DEBUG_					
	printk(KERN_INFO "key code = %d,key_keep_time = %u\n",g_devp->key_status[keyno].key.code,g_devp->key_status[keyno].key_keep_time);
#endif
			s3c_gpio_cfgpin(g_devp->key_status[keyno].key.gpio,g_devp->key_status[keyno].key.irq_flag);
			enable_irq(g_devp->key_status[keyno].key.irq);			
		}
	}
}

这就是整个定时器的处理函数,里面注释也比较详细了.当产生按键信息则加入队列.(Ps.队列内实现了自旋锁,防止smp的问题).当产生消息时,则对等待队列进行唤醒,告诉他们...小的们 按键大哥有消息来了..醒醒了...别睡了....小的们就起床各找各妈的返回数据了.

到这里其实按键就没啥了...队列我也不贴了.这个大家应该都会写...咱们可是学驱动的人是吧..小小队列岂能难倒咱们.....下面看看流程的第三条吧..阻塞读代码吧...

ssize_t	key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
   	int 		ret = 0;
	ITEM		code;
   		
   	struct device_dev *devp = filp->private_data;
   	DECLARE_WAITQUEUE(wait,current);
  	
	down(&devp->sem);
	add_wait_queue(&devp->r_wait,&wait);					//进入写等待队列
	
	while (queue_isEmpty(&devp->queue_head)) {				//无数据可读,进入睡眠
		if (filp->f_flags & O_NONBLOCK) {				//若是非阻塞打开,则返回 -EAGAIN
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);			//修改当前进程状态为浅睡眠
		up(&devp->sem);
		schedule();							//重新调度进程
		if (signal_pending(current)) {
			ret = -ERESTARTSYS;
			goto out2;
		}
		down(&devp->sem);
	}
  			
	if (count < sizeof(ITEM))
		return -ENOMEM;
 			
	queue_getItem(&code,&devp->queue_head);
	/*内核空间->用户空间*/
	if (copy_to_user(buf,&code,sizeof(ITEM))) {
		ret = -EFAULT;
	}	
out:
	up(&devp->sem);
out2:	
	remove_wait_queue(&devp->r_wait,&wait);
	set_current_state(TASK_RUNNING);
	
	return ret;
}

这些都不是今天的重点嘿嘿~~回头另开新帖.写这些东西....最后看下poll吧...

unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	struct device_dev *devp = filp->private_data;

	down(&devp->sem);
	poll_wait(filp,&devp->r_wait,wait);

	if(!queue_isEmpty(&devp->queue_head)) {		/*可读*/
		mask |= POLLIN/* | POLLRDNORM*/;		
	}
	
	up(&devp->sem);
	return mask;
}

Ok..完事....整个按键驱动的代码都完成了...我也测试过了..木有问题..不过你们用的时候有啥问题,得告诉我...我得及时修正..谢谢.....

好了不多说了...11.18 Pm了..睡觉了~~~

一会我把源码打包上传,之后发链接在这边,免资源分下载..哈哈

http://download.youkuaiyun.com/detail/yyttiao/4477024

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值