谈一个按键驱动
提示:::本人英语不好.忽略命名单词~~~谢谢合作...
联系方式: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了..睡觉了~~~
一会我把源码打包上传,之后发链接在这边,免资源分下载..哈哈