linux设备驱动(二)---字符设备之按键驱动

按键驱动比较复杂,主要是软件去抖动要用到定时器,手头没有画流程图,回头补一下,先说一下大概

按键直接接在了外部驱动的管脚上,因此首先使用外部中断,然后进入外部中断后,将按健状态设置为不确定态DOWNX,然后关闭外部中断,启动定时器,定时20ms,然后进入定时器中断函数,这时候首先判断按键是否还在按下,

若不是,说明是抖动,直接恢复外部中中断,设置按键状态为UP擡起状态,退出

若是,说明按键真的按下了,那么将按键状态设置位DOWN,同时调用相关函数记录按键值,在启动一个100ms的定时器,目的是检测按键什么时候擡起。每次进入这个定时器中断,都去判断按键是否还在按下

若是,则在其动一次100ms定时

若不是,说明按键擡起了,则设置对应的状态,使能中断


几个问题说明一下

1. 关于request_irq的第三个参数,首先要说,这里4个按键用同一个驱动程序,那么中断中就要判断是哪个按键按下

根据中断函数传递的参数

static irqreturn_t s3c2440_eint_key(int irq,void *dev_id)

有两种方法,一个是根据中断号 int irq ,由于我的板子4个按键分别接再了4个外部中断上,中断号都不相同,那么用着个方法就很简单的区别

另一种就是要用void *dev_id这个参数,这个参数是怎么来的呢?先看中断申请的时候

request_irq(key_info_tab[i].irq_no,s3c2440_eint_key,IRQF_SHARED,DEVICE_NAME,(void *)(&key_id[i]))

这里的(void *)(&key_id[i])就是这个参数

之前我定义了static int key_id[4]={0,1,2,3};,这个作为给每个按键分配的id,为什么取0,1,2,3,很简单,我后面直接用这个id做按键数据结构数组 static struct key_info 的下标,这样定义可以直接用。

再说这个参数为什么要定义一个static int key_id[4],书上给出例子是

static int request_irqs(void)
{
int i;
for(i=0;i<(sizeof(key_info_tab))/sizeof(key_info_tab[0]);i++){
set_irq_type(key_info_tab[i].irq_no,IRQF_TRIGGER_RISING);//这里原书用的函数是老的形式,现在内核参数宏定义变了
if(request_irq(key_info_tab[i].irq_no,s3c2440_eint_key,IRQF_SHARED,DEVICE_NAME,i)){
return -1;
}
}
return 0;
}

我的定义是这样

static int request_irqs(void)
{
int i;
for(i=0;i<(sizeof(key_info_tab))/sizeof(key_info_tab[0]);i++){
//printk(KERN_EMERG "irq_no=%d\n",key_info_tab[i].irq_no);
set_irq_type(key_info_tab[i].irq_no,IRQF_TRIGGER_RISING);
if(request_irq(key_info_tab[i].irq_no,s3c2440_eint_key,IRQF_SHARED,DEVICE_NAME,(void *)(&key_id[i]))){
return -1;
}
}
return 0;
}

区别就是他直接用了循环的索引变量i,可能是内核更新的原因,现在要求这个函数的第三个参数为通用指针void * 类型,不知到以前是不是传值类型

如果简单的修改i位 &i,虽然类型对了,但这样做是不行的,因为&i取了i的地址,这个变量是局部变量,函数退出就销毁,以后再想访问当前值是访问不到的

我第一次没注意,改成&i,编译通过了,但使用中oops,说是调用了空指针,因此才有前面定义个静态数组的方法。


2. 使用定时器timer的话,一定要调用init_timer,初始化

3. disable_irq和disable_irq_nosync,两者都是关中断函数,不同在于,前者要等待中断处理完成才返回,后者不等,直接返回

因此再中断处理函数关中断操作的话一定要调用disable_irq_nosync,否则会死机。

这个处理在后文

static irqreturn_t s3c2440_eint_key(int irq,void *dev_id)

函数中

disable_irq_nosync(key_info_tab[key].irq_no);

4. 关于按键资源数据结构

static struct key_info
{
int irq_no;
unsigned int gpio_port;
int key_no;
}key_info_tab[KEY_NUM]=
{
{IRQ_EINT0,GPF0,0},
{IRQ_EINT2,GPF2,1},
{IRQ_EINT3,GPF3,2},
{IRQ_EINT4,GPF4,3},
};

这里的GPF0等定义为

#define GPF0 (S3C2410_GPF(0)) //定义pin mach/gpio-nrs.h 头文件中的宏
#define GPF2 (S3C2410_GPF(2))
#define GPF3 (S3C2410_GPF(3))
#define GPF4 (S3C2410_GPF(4))

关于这个宏定义再上一篇博客中有详细的解释

这里的gpio_port对应的pin号一定要设置对,也就是这个GPF0,如果算不清楚就用上面的宏

我第一次就是自己算的地址写的,结果算错了,各种奇怪的问题



#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#include<mach/regs-gpio.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/slab.h>
#include<linux/sched.h>
#include<linux/wait.h>
#include<mach/gpio-fns.h>


#define MAX_KEY_BUF 16
#define KEY_NUM 4
#define KEY_MAJOR 250
#define KEYSTATUS_UP 1
#define KEYSTATUS_DOWNX 2
#define KEYSTATUS_DOWN 0
//保证x在mod-1中循环
//比如,mod-1=7 -> 0x0000 0111
//x累加超过7的部分,经过与操作之后,高位为0
#define INC_BUF_POINTOR(x,mod)    ((++(x))&((mod)-1))
//判断按键是否按下,参数为对应的pin值
#define ISKEY_DOWN(x) (s3c2410_gpio_getpin(key_info_tab[x].gpio_port)==KEYSTATUS_DOWN)
#define KEY_TIMER_DELAY1 HZ/100
#define KEY_TIMER_DELAY2 HZ/10
#define DEVICE_NAME "mykeys"
//#define GPFCON (0x02A2)
#define GPF0 (S3C2410_GPF(0)) //定义pin mach/gpio-nrs.h 头文件中的宏
#define GPF2 (S3C2410_GPF(2))
#define GPF3 (S3C2410_GPF(3))
#define GPF4 (S3C2410_GPF(4))


//主设备号
static int key_major=KEY_MAJOR;
typedef unsigned char key_ret;
//typedef void (*f)(unsigned int);
//f keyEvent;
//设备结构体
struct key_dev
{
unsigned int keyStatus[KEY_NUM];//记录按键状态
key_ret buf[MAX_KEY_BUF];//记录键值
unsigned int head,tail;
wait_queue_head_t wq;
struct cdev cdev;
};
struct key_dev *key_devp;
//定义定时器,定时器使用之前要init_timer
static struct timer_list key_timer[KEY_NUM];
//按键信息,中断号,pin号,键值
static struct key_info
{
int irq_no;
unsigned int gpio_port;
int key_no;
}key_info_tab[KEY_NUM]=
{
{IRQ_EINT0,GPF0,0},
{IRQ_EINT2,GPF2,1},
{IRQ_EINT3,GPF3,2},
{IRQ_EINT4,GPF4,3},
};
//记录按键值
static void keyEvent(unsigned int key_index)
{
//printk(KERN_EMERG "keyEvent\n");
key_devp->buf[key_devp->head]=key_index;
//printk(KERN_EMERG "key_pressed=%d,key_head=%d\n",key_devp->buf[key_devp->head],key_devp->head);


key_devp->head=INC_BUF_POINTOR(key_devp->head,MAX_KEY_BUF);
//printk(KERN_EMERG "key_head=%d\n",key_devp->head);
//唤醒等待按键的列队
wake_up_interruptible(&(key_devp->wq));


}
static int s3c2440_key_open(struct inode *inode, struct file *filp)
{
//printk(KERN_EMERG "s3c2440_key_open\n");
key_devp->head=0;
key_devp->tail=0;
return 0;
}
static int s3c2440_key_release(struct inode *inode, struct file *filp)
{
//keyEvent=keyEvent_dummy;
return 0;
}
static ssize_t s3c2440_key_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
unsigned long flag;
unsigned int ret,tmp;
//printk(KERN_EMERG "s3c2440_key_read\n");


retry: if(key_devp->head!=key_devp->tail)
{
//进入临界区
local_irq_save(flag);
ret=key_devp->buf[key_devp->tail];
key_devp->tail=INC_BUF_POINTOR(key_devp->tail,MAX_KEY_BUF);
//推出临界区
local_irq_restore(flag);


//printk(KERN_EMERG "buffer not empty,\n");
tmp=copy_to_user(buf,&ret,sizeof(unsigned int));
//printk(KERN_EMERG "copy to user,tmp=%d\n",tmp);
return (sizeof(unsigned int));
}
else{
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
//请求按键值,没有读到则挂起等待
interruptible_sleep_on(&(key_devp->wq));
goto retry;
}
return 0;
}
static struct file_operations s3c2440_key_ops=
{
owner:THIS_MODULE,
open:s3c2440_key_open,
release:s3c2440_key_release,
read:s3c2440_key_read,
};
//字符设备注册
static void key_setup_cdev(struct key_dev* dev,int index)
{
int err,devno=MKDEV(key_major,index);
cdev_init(&dev->cdev,&s3c2440_key_ops);
dev->cdev.owner=THIS_MODULE;
dev->cdev.ops=&s3c2440_key_ops;
err=cdev_add(&dev->cdev,devno,1);
if(err)
{
printk(KERN_NOTICE "Error %d adding Key%d",err,index);
} 


}
//按键中断
static irqreturn_t s3c2440_eint_key(int irq,void *dev_id)
{
int key,cnt;
key=0;
//根据中断号判断当前按键是哪个
for(cnt=0;cnt<KEY_NUM;cnt++){
if(key_info_tab[cnt].irq_no==irq)
{
key=key_info_tab[cnt].key_no;
break;
}
}
//printk(KERN_EMERG "key=%d,irq_no=%d,irq=%d\n",key,key_info_tab[key].irq_no,irq);
disable_irq_nosync(key_info_tab[key].irq_no);
key_devp->keyStatus[key]=KEYSTATUS_DOWNX;
key_timer[key].expires=jiffies+KEY_TIMER_DELAY1;
//启动去抖动定时器
add_timer(&key_timer[key]);
return IRQ_HANDLED;
}
static void key_timer_handler(unsigned long data)
{
int key=data;
//printk(KERN_EMERG "Timer interrupt handler,key=%d\n",key);
//printk(KERN_EMERG "tab1=%x,tab2=%x,tab3=%x,tab4=%x\n",&key_info_tab[0],&key_info_tab[1],&key_info_tab[2],&key_info_tab[3]);
//printk(KERN_EMERG "gpio=%x\n",key_info_tab[key].gpio_port);

//printk(KERN_EMERG "is_key_down=%x,gpio_port=%x\n",ISKEY_DOWN(key),s3c2410_gpio_getpin(key_info_tab[key].gpio_port));
if(ISKEY_DOWN(key))
{
//printk(KERN_EMERG "Key Down\n");
if(key_devp->keyStatus[key]==KEYSTATUS_DOWNX){
key_devp->keyStatus[key]=KEYSTATUS_DOWN;
key_timer[key].expires=jiffies+KEY_TIMER_DELAY2;
//printk(KERN_EMERG "key downX,key=%d\n",key);
keyEvent(key);
add_timer(&key_timer[key]);
}
else{
//printk(KERN_EMERG "Key Down down\n");
key_timer[key].expires=jiffies+KEY_TIMER_DELAY2;
add_timer(&key_timer[key]);
}
}
else{
key_devp->keyStatus[key]=KEYSTATUS_UP;
//printk(KERN_EMERG "key status=%d\n",key_devp->keyStatus[key]);
enable_irq(key_info_tab[key].irq_no);
//printk(KERN_EMERG "enabled key_irq_no=%d\n",key_info_tab[key].irq_no);
}


}
//按键id分配,用于传入中断申请的第三个参数,目前用中断号判断,这个参数暂时用不上
//如果相同中断号的设备用统一个中断程序,则可用这个参数
static int key_id[4]={0,1,2,3};
//申请中断
static int request_irqs(void)
{
int i;
for(i=0;i<(sizeof(key_info_tab))/sizeof(key_info_tab[0]);i++){
//printk(KERN_EMERG "irq_no=%d\n",key_info_tab[i].irq_no);
set_irq_type(key_info_tab[i].irq_no,IRQF_TRIGGER_RISING);
if(request_irq(key_info_tab[i].irq_no,s3c2440_eint_key,IRQF_SHARED,DEVICE_NAME,(void *)(&key_id[i]))){
return -1;
}
}
return 0;

}
static void free_irqs(void)
{
struct key_info *k;
int i;
for(i=0;i<sizeof(key_info_tab)/sizeof(key_info_tab[0]);i++)
{
k=key_info_tab+i;
free_irq(k->irq_no,s3c2440_eint_key);
}
}


static int __init s3c2440_key_init(void)
{
int result,i,tmp;
//设备号,次设备号为0
dev_t devno=MKDEV(key_major,0);
result=register_chrdev_region(devno,1,"key");
key_devp=kmalloc(sizeof(struct key_dev),GFP_KERNEL);
if(!key_devp)
{
result=-ENOMEM;
//goto fail_malloc;
}
memset(key_devp,0,sizeof(struct key_dev));
key_setup_cdev(key_devp,0);
tmp=request_irqs();
if(tmp==-1)
{
printk(KERN_NOTICE "request_irqs error\n");
}
key_devp->head=0;
key_devp->tail=0;
//配置GPIO管脚,中断模式
s3c2410_gpio_cfgpin(GPF0,S3C2410_GPIO_IRQ);
s3c2410_gpio_cfgpin(GPF2,S3C2410_GPIO_IRQ);
s3c2410_gpio_cfgpin(GPF3,S3C2410_GPIO_IRQ);
s3c2410_gpio_cfgpin(GPF4,S3C2410_GPIO_IRQ);




for(i=0;i<KEY_NUM;i++)
{
key_devp->keyStatus[i]=KEYSTATUS_UP;
}
init_waitqueue_head(&(key_devp->wq));
for(i=0;i<KEY_NUM;i++)
{
init_timer(&key_timer[i]);
setup_timer(&key_timer[i],key_timer_handler,i);
}
printk(KERN_NOTICE "init module, result=%d\n",result);
//fail_malloc: unregister_chrdev_region(devno,1);
return result;

}
static void __exit s3c2440_key_exit(void)
{
free_irqs();
cdev_del(&key_devp->cdev);
kfree(key_devp);
unregister_chrdev_region(MKDEV(key_major,0),1);
printk(KERN_NOTICE "exit module\n");
}
MODULE_AUTHOR("weicz");
MODULE_LICENSE("Dual BSD/GPL");
module_init(s3c2440_key_init);
module_exit(s3c2440_key_exit);


测试程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#define LED0 "/dev/led0"    
#define LED1 "/dev/led1"    
#define LED2 "/dev/led2"    
#define LED3 "/dev/led3"    
#define LED4 "/dev/led4"    
static char* led_group[]={LED1,LED2,LED3,LED4};
static int cmd=0;
int main()
{
    int fd,ret,key_num;
    int led_fd;
    fd=open("/dev/key",0);
    if(fd<0)
    {
        printf("open error\n");
        return -1;
    }
    printf("open /dev/key\n");
    while(1){
        //printf("reading...\n");
        ret=read(fd,&key_num,sizeof(int));
        printf("key_value=%d\n",key_num);
        led_fd=open(led_group[key_num],0);
        ioctl(led_fd,cmd,0);
        cmd=(cmd==0)?1:0;
        close(led_fd);


    }
    close(fd);
    return 0;
}



这个程序是跟上一篇博客中led驱动联调用的

Makefile跟led那个差不多,模块名改一下就好

暂时先写着么多把

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章