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那個差不多,模塊名改一下就好

暫時先寫着麼多把

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