嵌入式Linux設(shè)備驅(qū)動開發(fā)之:按鍵驅(qū)動程序?qū)嵗?/h1>
時間:2018-06-14 19:30:01
關(guān)鍵字:
基礎(chǔ)教程
嵌入式linux
操作系統(tǒng)
設(shè)備驅(qū)動
按鍵驅(qū)動程序
手機看文章掃描二維碼
隨時隨地手機看文章
掃描二維碼
隨時隨地手機看文章
LED和蜂鳴器是最簡單的GPIO的應(yīng)用,都不需要任何外部輸入或控制。按鍵同樣使用GPIO接口,但按鍵本身需要外部的輸入,即在驅(qū)動程序中要處理外部中斷。按鍵硬件驅(qū)動原理圖如圖11-7所示。在圖11-7的4×4矩陣按鍵(K1~K16)電路中,使用4個輸入/輸出端口(EINT0、EINT2、EINT11和EINT19)和4個輸出端口(KSCAN0~KSCAN3)。
圖11.7按鍵驅(qū)動電路原理圖
按鍵驅(qū)動電路使用的端口和對應(yīng)的寄存器如表11-18所示。
表11.18 按鍵電路的主要端口
管腳
端口
輸入/輸出
管腳
端口
輸入/輸出
KEYSCAN0
GPE11
輸出
EINT0
EINIT0/GPF0
輸入/輸出
KEYSCAN1
GPG6
輸出
EINT2
EINT2/GPF2
輸入/輸出
KEYSCAN2
GPE13
輸出
EINT11
EINT11/GPG3
輸入/輸出
KEYSCAN3
GPG2
輸出
EINT19
EINT19/GPG11
輸入/輸出
因為通常中斷端口是比較珍貴且有限的資源,所以在本電路設(shè)計中,16個按鍵復(fù)用了4個中斷線。那怎么樣才能及時而準確地對矩陣按鍵進行掃描呢?
某個中斷的產(chǎn)生表示,與它所對應(yīng)的矩陣行的4個按鍵中,至少有一個按鍵被按住了。因此可以通過查看產(chǎn)生了哪個中斷,來確定在矩陣的哪一行中發(fā)生了按鍵操作(按住或釋放)。例如,如果產(chǎn)生了外部2號線中斷(EINT2變?yōu)榈碗娖剑瑒t表示K7、K8、K9和K15中至少有一個按鍵被按住了。這時候4個EINT端口應(yīng)該通過GPIO配置寄存器被設(shè)置為外部中斷端口,而且4個KSCAN端口的輸出必須為低電平。
在確定按鍵操作所在行的位置之后,我們還得查看按鍵操作所在列的位置。此時要使用KSCAN端口組,同時將4個EINT端口配置為通用輸入端口(而不是中斷端口)。在4個KSCAN端口中,輪流將其中某一個端口的輸出置為低電平,其他3個端口的輸出置為高電平。這樣逐列進行掃描,直到按鍵所在列的KSCAN端口輸出為低電平,此時按鍵操作所在行的EINT管腳的輸入端口的值會變成低電平。例如,在確認產(chǎn)生了外部2號中斷之后,進行逐列掃描。若發(fā)現(xiàn)在KSCAN1為低電平時(其他端口輸出均為高電平),GPF2(EINT2管腳的輸入端口)變?yōu)榈碗娖?,則可以斷定按鍵K8被按住了。
以上的討論都是在按鍵的理想狀態(tài)下進行的,但實際的按鍵動作會在短時間(幾毫秒至幾十毫秒)內(nèi)產(chǎn)生信號抖動。例如,當(dāng)按鍵被按下時,其動作就像彈簧的若干次往復(fù)運動,將產(chǎn)生幾個脈沖信號。一次按鍵操作將會產(chǎn)生若干次按鍵中斷,從而會產(chǎn)生抖動現(xiàn)象。因此驅(qū)動程序中必須要解決去除抖動所產(chǎn)生的毛刺信號的問題。
11.6.2按鍵驅(qū)動程序首先按鍵設(shè)備相關(guān)的數(shù)據(jù)結(jié)構(gòu)的定義如下所示:
/*butt_drv.h*/
……
typedefstruct_st_key_info_matrix/*按鍵數(shù)據(jù)結(jié)構(gòu)*/
{
unsignedcharkey_id;/*按鍵ID*/
unsignedintirq_no;/*對應(yīng)的中斷號*/
unsignedintirq_gpio_port;/*對應(yīng)的中斷線的輸入端口地址*/
unsignedintkscan_gpio_port;/*對應(yīng)的KSCAN端口地址*/
}st_key_info_matrix;
typedefstruct_st_key_buffer/*按鍵緩沖數(shù)據(jù)結(jié)構(gòu)*/
{
unsignedlongjiffy[MAX_KEY_COUNT];/*按鍵時間,5s以前的銨鍵作廢*/
unsignedcharbuf[MAX_KEY_COUNT];/*按鍵緩沖區(qū)*/
unsignedinthead,tail;/*按鍵緩沖區(qū)頭和尾*/
}st_key_buffer;
……
下面是矩陣按鍵數(shù)組的定義,數(shù)組元素的信息(一個按鍵信息)按照0行0列,0行1列,…,3行2列,3行3列的順序逐行排列。
staticst_key_info_matrixkey_info_matrix[MAX_COLUMN][MAX_ROW]=
{
{{10,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPE11},/*0行0列*/
{11,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPG6},
{12,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPE13},
{16,IRQ_EINT0,S3C2410_GPF0,S3C2410_GPG2}},
{{7,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPE11},/*1行0列*/
{8,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPG6},
{9,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPE13},
{15,IRQ_EINT2,S3C2410_GPF2,S3C2410_GPG2}},
{{4,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPE11},/*2行0列*/
{5,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG6},
{6,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPE13},
{14,IRQ_EINT11,S3C2410_GPG3,S3C2410_GPG2}},
{{1,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPE11},/*3行0列*/
{2,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG6},
{3,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPE13},
{13,IRQ_EINT19,S3C2410_GPG11,S3C2410_GPG2}},
};
下面是與按鍵相關(guān)的端口的初始化函數(shù)。這些函數(shù)已經(jīng)在簡單的GPIO字符設(shè)備驅(qū)動程序里被使用過。此外,set_irq_type()函數(shù)用于設(shè)定中斷線的類型,在本實例中通過該函數(shù)將4個中斷線的類型配置為下降沿觸發(fā)式。
staticvoidinit_gpio(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPE11,S3C2410_GPE11_OUTP);/*GPE11*/
s3c2410_gpio_setpin(S3C2410_GPE11,0);
s3c2410_gpio_cfgpin(S3C2410_GPE13,S3C2410_GPE13_OUTP);/*GPE13*/
s3c2410_gpio_setpin(S3C2410_GPE13,0);
s3c2410_gpio_cfgpin(S3C2410_GPG2,S3C2410_GPG2_OUTP);/*GPG2*/
s3c2410_gpio_setpin(S3C2410_GPG2,0);
s3c2410_gpio_cfgpin(S3C2410_GPG6,S3C2410_GPG6_OUTP);/*GPG6*/
s3c2410_gpio_setpin(S3C2410_GPG6,0);
s3c2410_gpio_cfgpin(S3C2410_GPF0,S3C2410_GPF0_EINT0);/*GPF0*/
s3c2410_gpio_cfgpin(S3C2410_GPF2,S3C2410_GPF2_EINT2);/*GPF2*/
s3c2410_gpio_cfgpin(S3C2410_GPG3,S3C2410_GPG3_EINT11);/*GPG3*/
s3c2410_gpio_cfgpin(S3C2410_GPG11,S3C2410_GPG11_EINT19);/*GPG11*/
set_irq_type(IRQ_EINT0,IRQT_FALLING);
set_irq_type(IRQ_EINT2,IRQT_FALLING);
set_irq_type(IRQ_EINT11,IRQT_FALLING);
set_irq_type(IRQ_EINT19,IRQT_FALLING);
}
下面講解按鍵驅(qū)動的主要接口,以下為驅(qū)動模塊的入口和卸載函數(shù)。
/*初始化并添加structcdev結(jié)構(gòu)到系統(tǒng)之中*/
staticvoidbutton_setup_cdev(structcdev*dev,
intminor,structfile_operations*fops)
{
interr;
intdevno=MKDEV(button_major,minor);
cdev_init(dev,fops);/*初始化結(jié)構(gòu)體structcdev*/
dev->owner=THIS_MODULE;
dev->ops=fops;/*關(guān)聯(lián)到設(shè)備的file_operations結(jié)構(gòu)*/
err=cdev_add(dev,devno,1);/*將structcdev結(jié)構(gòu)添加到系統(tǒng)之中*/
if(err)
{
printk(KERN_INFO"Error%daddingbutton%dn",err,minor);
}
}
……
/*驅(qū)動初始化*/
staticintbutton_init(void)
{
intret;
/*將主設(shè)備號和次設(shè)備號定義到一個dev_t數(shù)據(jù)類型的結(jié)構(gòu)體之中*/
dev_tdev=MKDEV(button_major,0);
if(button_major)
{/*靜態(tài)注冊一個設(shè)備,設(shè)備號先前指定好,并設(shè)定設(shè)備名,用cat/proc/devices來查看*/
ret=register_chrdev_region(dev,1,BUTTONS_DEVICE_NAME);
}
else
{/*由系統(tǒng)動態(tài)分配主設(shè)備號*/
ret=alloc_chrdev_region(&dev,0,1,BUTTONS_DEVICE_NAME);
button_major=MAJOR(dev);/*獲得主設(shè)備號*/
}
if(ret<0)
{
printk(KERN_WARNING"Button:unabletogetmajor%dn",button_major);
returnret;
}
/*初始化和添加結(jié)構(gòu)體structcdev到系統(tǒng)之中*/
button_setup_cdev(&button_dev,0,&button_fops);
printk("Buttondriverinitialized.n");
return0;
}
/*驅(qū)動卸載*/
staticvoid__exitbutton_exit(void)
{
cdev_del(&button_dev);/*刪除結(jié)構(gòu)體structcdev*/
/*卸載設(shè)備驅(qū)動所占有的資源*/
unregister_chrdev_region(MKDEV(button_major,0),1);
printk("Buttondriveruninstalledn");
}
module_init(button_init);/*初始化設(shè)備驅(qū)動程序的入口*/
module_exit(button_exit);/*卸載設(shè)備驅(qū)動程序的入口*/
MODULE_AUTHOR("David");
MODULE_LICENSE("DualBSD/GPL");
按鍵字符設(shè)備的file_operations結(jié)構(gòu)定義為:
staticstructfile_operationsbutton_fops=
{
.owner=THIS_MODULE,
.ioctl=button_ioctl,
.open=button_open,
.read=button_read,
.release=button_release,
};
以下為open和release函數(shù)接口的實現(xiàn)。
/*打開文件,申請中斷*/
staticintbutton_open(structinode*inode,structfile*filp)
{
intret=nonseekable_open(inode,filp);
if(ret<0)
{
returnret;
}
init_gpio();/*相關(guān)GPIO端口的初始化*/
ret=request_irqs();/*申請4個中斷*/
if(ret<0)
{
returnret;
}
init_keybuffer();/*初始化按鍵緩沖數(shù)據(jù)結(jié)構(gòu)*/
returnret;
}
/*關(guān)閉文件,屏蔽中斷*/
staticintbutton_release(structinode*inode,structfile*filp)
{
free_irqs();/*屏蔽中斷*/
return0;
}
在open函數(shù)接口中,進行了GPIO端口的初始化、申請硬件中斷以及按鍵緩沖的初始化等工作。在以前的章節(jié)中提過,中斷端口是比較寶貴而且數(shù)量有限的資源。因此需要注意,最好要在第一次打開設(shè)備時申請(調(diào)用request_irq函數(shù))中斷端口,而不是在驅(qū)動模塊加載的時候申請。如果已加載的設(shè)備驅(qū)動占用而在一定時間段內(nèi)不使用某些中斷資源,則這些資源不會被其他驅(qū)動所使用,只能白白浪費掉。而在打開設(shè)備的時候(調(diào)用open函數(shù)接口)申請中斷,則不同的設(shè)備驅(qū)動可以共享這些寶貴的中斷資源。
以下為中斷申請和釋放的部分以及中斷處理函數(shù)。
/*中斷處理函數(shù),其中irq為中斷號*/
staticirqreturn_tbutton_irq(intirq,void*dev_id,structpt_regs*regs)
{
unsignedcharucKey=0;
disable_irqs();/*屏蔽中斷*/
/*延遲50ms,屏蔽按鍵毛刺*/
udelay(50000);
ucKey=button_scan(irq);/*掃描按鍵,獲得進行操作的按鍵的ID*/
if((ucKey>=1)&&(ucKey<=16))
{
/*如果緩沖區(qū)已滿,則不添加*/
if(((key_buffer.head+1)&(MAX_KEY_COUNT-1))!=key_buffer.tail)
{
spin_lock_irq(&buffer_lock);
key_buffer.buf[key_buffer.tail]=ucKey;
key_buffer.jiffy[key_buffer.tail]=get_tick_count();
key_buffer.tail++;
key_buffer.tail&=(MAX_KEY_COUNT-1);
spin_unlock_irq(&buffer_lock);
}
}
init_gpio();/*初始化GPIO端口,主要是為了恢復(fù)中斷端口配置*/
enable_irqs();/*開啟中斷*/
returnIRQ_HANDLED;/*2.6內(nèi)核返回值一般是這個宏*/
}
/*申請4個中斷*/
staticintrequest_irqs(void)
{
intret,i,j;
for(i=0;i<MAX_COLUMN;i++)
{
ret=request_irq(key_info_matrix[i][0].irq_no,
button_irq,SA_INTERRUPT,BUTTONS_DEVICE_NAME,NULL);
if(ret<0)
{
for(j=0;j<i;j++)
{
free_irq(key_info_matrix[j][0].irq_no,NULL);
}
return-EFAULT;
}
}
return0;
}
/*釋放中斷*/
static__inlinevoidfree_irqs(void)
{
inti;
for(i=0;i<MAX_COLUMN;i++)
{
free_irq(key_info_matrix[i][0].irq_no,NULL);
}
}
中斷處理函數(shù)在每次中斷產(chǎn)生的時候會被調(diào)用,因此它的執(zhí)行時間要盡可能得短。通常中斷處理函數(shù)只是簡單地喚醒等待資源的任務(wù),而復(fù)雜且耗時的工作則讓這個任務(wù)去完成。中斷處理函數(shù)不能向用戶空間發(fā)送數(shù)據(jù)或者接收數(shù)據(jù),不能做任何可能發(fā)生睡眠的操作,而且不能調(diào)用schedule()函數(shù)。
為了簡單起見,而且考慮到按鍵操作的時間比較長,在本實例中的中斷處理函數(shù)button_irq()里,通過調(diào)用睡眠函數(shù)來消除毛刺信號。讀者可以根據(jù)以上介紹的對中斷處理函數(shù)的要求改進該部分代碼。
按鍵掃描函數(shù)如下所示。首先根據(jù)中斷號確定操作按鍵所在行的位置,然后采用逐列掃描法最終確定操作按鍵所在的位置。
/*
**進入中斷后,掃描銨鍵碼
**返回:按鍵碼(1~16),0xff表示錯誤
*/
static__inlineunsignedcharbutton_scan(intirq)
{
unsignedcharkey_id=0xff;
unsignedcharcolumn=0xff,row=0xff;
s3c2410_gpio_cfgpin(S3C2410_GPF0,S3C2410_GPF0_INP);/*GPF0*/
s3c2410_gpio_cfgpin(S3C2410_GPF2,S3C2410_GPF2_INP);/*GPF2*/
s3c2410_gpio_cfgpin(S3C2410_GPG3,S3C2410_GPG3_INP);/*GPG3*/
s3c2410_gpio_cfgpin(S3C2410_GPG11,S3C2410_GPG11_INP);/*GPG11*/
switch(irq)
{/*根據(jù)irq值確定操作按鍵所在行的位置*/
caseIRQ_EINT0:
{
column=0;
}
break;
caseIRQ_EINT2:
{
column=1;
}
break;
caseIRQ_EINT11:
{
column=2;
}
break;
caseIRQ_EINT19:
{
column=3;
}
break;
}
if(column!=0xff)
{/*開始逐列掃描,掃描第0列*/
s3c2410_gpio_setpin(S3C2410_GPE11,0);/*將KSCAN0置為低電平*/
s3c2410_gpio_setpin(S3C2410_GPG6,1);
s3c2410_gpio_setpin(S3C2410_GPE13,1);
s3c2410_gpio_setpin(S3C2410_GPG2,1);
if(!s3c2410_gpio_getpin(key_info_matrix[column][0].irq_gpio_port))
{/*觀察對應(yīng)的中斷線的輸入端口值*/
key_id=key_info_matrix[column][0].key_id;
returnkey_id;
}
/*掃描第1列*/
s3c2410_gpio_setpin(S3C2410_GPE11,1);
s3c2410_gpio_setpin(S3C2410_GPG6,0);/*將KSCAN1置為低電平*/
s3c2410_gpio_setpin(S3C2410_GPE13,1);
s3c2410_gpio_setpin(S3C2410_GPG2,1);
if(!s3c2410_gpio_getpin(key_info_matrix[column][1].irq_gpio_port))
{
key_id=key_info_matrix[column][1].key_id;
returnkey_id;
}
/*掃描第2列*/
s3c2410_gpio_setpin(S3C2410_GPE11,1);
s3c2410_gpio_setpin(S3C2410_GPG6,1);
s3c2410_gpio_setpin(S3C2410_GPE13,0);/*將KSCAN2置為低電平*/
s3c2410_gpio_setpin(S3C2410_GPG2,1);
if(!s3c2410_gpio_getpin(key_info_matrix[column][2].irq_gpio_port))
{
key_id=key_info_matrix[column][2].key_id;
returnkey_id;
}
/*掃描第3列*/
s3c2410_gpio_setpin(S3C2410_GPE11,1);
s3c2410_gpio_setpin(S3C2410_GPG6,1);
s3c2410_gpio_setpin(S3C2410_GPE13,1);
s3c2410_gpio_setpin(S3C2410_GPG2,0);/*將KSCAN3置為低電平*/
if(!s3c2410_gpio_getpin(key_info_matrix[column][3].irq_gpio_port))
{
key_id=key_info_matrix[column][3].key_id;
returnkey_id;
}
}
returnkey_id;
}
以下是read函數(shù)接口的實現(xiàn)。首先在按鍵緩沖中刪除已經(jīng)過時的按鍵操作信息,接下來,從按鍵緩沖中讀取一條信息(按鍵ID)并傳遞給用戶層。
/*從緩沖刪除過時數(shù)據(jù)(5s前的按鍵值)*/
staticvoidremove_timeoutkey(void)
{
unsignedlongtick;
spin_lock_irq(&buffer_lock);/*獲得一個自旋鎖*/
while(key_buffer.head!=key_buffer.tail)
{
tick=get_tick_count()-key_buffer.jiffy[key_buffer.head];
if(tick<5000)/*5s*/
break;
key_buffer.buf[key_buffer.head]=0;
key_buffer.jiffy[key_buffer.head]=0;
key_buffer.head++;
key_buffer.head&=(MAX_KEY_COUNT-1);
}
spin_unlock_irq(&buffer_lock);/*釋放自旋鎖*/
}
/*讀鍵盤*/
staticssize_tbutton_read(structfile*filp,
char*buffer,size_tcount,loff_t*f_pos)
{
ssize_tret=0;
remove_timeoutkey();/*刪除過時的按鍵操作信息*/
spin_lock_irq(&buffer_lock);
while((key_buffer.head!=key_buffer.tail)&&(((size_t)ret)<count))
{
put_user((char)(key_buffer.buf[key_buffer.head]),&buffer[ret]);
key_buffer.buf[key_buffer.head]=0;
key_buffer.jiffy[key_buffer.head]=0;
key_buffer.head++;
key_buffer.head&=(MAX_KEY_COUNT-1);
ret++;
}
spin_unlock_irq(&buffer_lock);
returnret;
}
以上介紹了按鍵驅(qū)動程序中的主要內(nèi)容。
11.6.3按鍵驅(qū)動的測試程序按鍵驅(qū)動程序的測試程序所下所示。在測試程序中,首先打開按鍵設(shè)備文件和gpio設(shè)備(包括4個LED和蜂鳴器)文件,接下來,根據(jù)按鍵的輸入值(按鍵ID)的二進制形式,LEDD9~D12發(fā)亮(例如,按下11號按鍵,則D9、D10和D12會發(fā)亮),而蜂鳴器當(dāng)每次按鍵時發(fā)出聲響。
/*butt_test.c*/
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
#include<asm/delay.h>
#include"butt_drv.h"
#include"gpio_drv.h"
main()
{
intbutt_fd,gpios_fd,i;
unsignedcharkey=0x0;
butt_fd=open(BUTTONS_DEVICE_FILENAME,O_RDWR);/*打開按鈕設(shè)備*/
if(butt_fd==-1)
{
printf("Openbuttondevicebuttonerrr!n");
return0;
}
gpios_fd=open(GPIO_DEVICE_FILENAME,O_RDWR);/*打開GPIO設(shè)備*/
if(gpios_fd==-1)
{
printf("Openbuttondevicebuttonerrr!n");
return0;
}
ioctl(butt_fd,0);/*清空鍵盤緩沖區(qū),后面參數(shù)沒有意義*/
printf("PressNo.16keytoexitn");
do
{
if(read(butt_fd,&key,1)<=0)/*讀鍵盤設(shè)備,得到相應(yīng)的鍵值*/
{
continue;
}
printf("KeyValue=%dn",key);
for(i=0;i<LED_NUM;i++)
{
if((key&(1<<i))!=0)
{
ioctl(gpios_fd,LED_D09_SWT+i,LED_SWT_ON);/*LED發(fā)亮*/
}
}
ioctl(gpios_fd,BEEP_SWT,BEEP_SWT_ON);/*發(fā)聲*/
sleep(1);
for(i=0;i<LED_NUM;i++)
{
ioctl(gpios_fd,LED_D09_SWT+i,LED_SWT_OFF);/*LED熄滅*/
}
ioctl(gpios_fd,BEEP_SWT,BEEP_SWT_OFF);
}while(key!=16);/*按16號鍵則退出*/
close(gpios_fd);
close(butt_fd);
return0;
}
首先編譯和加載按鍵驅(qū)動程序,而且要創(chuàng)建設(shè)備文件節(jié)點。
$makeclean;make/*驅(qū)動程序的編譯*/
$insmodbutt_dev.ko/*加載buttons設(shè)備驅(qū)動*/
$cat/proc/devices/*通過這個命令可以查到buttons設(shè)備的主設(shè)備號*/
$mknod/dev/buttonsc2520/*假設(shè)主設(shè)備號為252,創(chuàng)建設(shè)備文件節(jié)點*/
接下來,編譯和加載GPIO驅(qū)動程序,而且要創(chuàng)建設(shè)備文件節(jié)點。
$makeclean;make/*驅(qū)動程序的編譯*/
$insmodgpio_drv.ko/*加載GPIO驅(qū)動*/
$cat/proc/devices/*通過這個命令可以查到GPIO設(shè)備的主設(shè)備號*/
$mknod/dev/gpioc2510/*假設(shè)主設(shè)備號為251,創(chuàng)建設(shè)備文件節(jié)點*/
然后編譯并運行驅(qū)動測試程序。
$arm-linux-gcc–obutt_testbutt_test.c
$./butt_test