摘 要: 針對(duì)嵌入式系統(tǒng)的鍵盤(pán)驅(qū)動(dòng)特點(diǎn),以Linux 2.6.21內(nèi)核為例,提出了一種基于嵌入式Linux的矩陣鍵盤(pán)的實(shí)現(xiàn)方案。介紹了矩陣鍵盤(pán)的結(jié)構(gòu)及原理,設(shè)計(jì)了基于Platform機(jī)制的矩陣鍵盤(pán)驅(qū)動(dòng)程序,并解決了按鍵去抖及重鍵問(wèn)題。通過(guò)測(cè)試實(shí)踐,證明該驅(qū)動(dòng)程序工作高效、穩(wěn)定可靠。
關(guān)鍵詞: 嵌入式Linux;Platform機(jī)制;矩陣鍵盤(pán);鍵盤(pán)驅(qū)動(dòng)程序
在嵌入式系統(tǒng)中,Linux操作系統(tǒng)由于具有開(kāi)放源碼、良好的可移植性、多任務(wù)等優(yōu)勢(shì),已成為開(kāi)發(fā)嵌入式產(chǎn)品的優(yōu)秀操作平臺(tái),其中鍵盤(pán)是人機(jī)交互設(shè)備中重要的輸入設(shè)備,用于向設(shè)備輸入數(shù)據(jù)和信息[1]。在嵌入式系統(tǒng)中,一般使用簡(jiǎn)易的鍵盤(pán)作為輸入設(shè)備[2],它由一系列開(kāi)關(guān)矩陣排列而成(包括數(shù)字鍵、字母鍵、符號(hào)鍵、功能鍵等)。實(shí)現(xiàn)鍵盤(pán)掃描的方法有采用特定芯片和軟件方法兩種。
采用特定芯片實(shí)現(xiàn)鍵盤(pán)掃描,會(huì)增加嵌入式系統(tǒng)開(kāi)發(fā)的成本。而利用ARM處理器強(qiáng)大的功能,采用軟件的方法實(shí)現(xiàn)鍵盤(pán)掃描不僅可以降低成本,還可以節(jié)省CPU的資源開(kāi)銷(xiāo)。因此,本文提出的鍵盤(pán)方案是以嵌入式Linux和AT91RM9200為軟硬件平臺(tái),設(shè)計(jì)了基于Platform機(jī)制的矩陣鍵盤(pán)驅(qū)動(dòng)程序,解決了按鍵去抖及重鍵問(wèn)題,在實(shí)際應(yīng)用中表明該方案具有很好的穩(wěn)定性和實(shí)時(shí)性。
1 矩陣式鍵盤(pán)的結(jié)構(gòu)及原理
硬件平臺(tái)是基于CE9200架構(gòu)的AT91RM9200處理器,工作于180 MHz時(shí)性能高達(dá)200 MIPS,功耗較低,適用于高性能的嵌入式系統(tǒng)。
在鍵盤(pán)中,排列開(kāi)關(guān)最常用、也最有效的方法是二維矩陣,所需的開(kāi)關(guān)數(shù)目根據(jù)需求而定,開(kāi)關(guān)放置在行與列的交點(diǎn)上。本系統(tǒng)設(shè)計(jì)的是一個(gè)4×4矩陣鍵盤(pán)(k1~k16),由4根行線和4根列線組成,分別使用CPU的8個(gè)通用輸入/輸出GPIO(General Purpose I/O port)口,利用排阻作為上拉電阻。鍵盤(pán)按鍵使用鍋片式,當(dāng)按下某鍵,對(duì)應(yīng)行和列的GPIO口相互導(dǎo)通[3]。驅(qū)動(dòng)程序初始化時(shí),所有行均為輸入端,并設(shè)置為高電平;所有列均為輸出端,置為低電平。其電路原理圖如圖1所示。
2 Platform總線模型下鍵盤(pán)驅(qū)動(dòng)
2.1 Platform總線模型
Platform總線是Linux 2.6 kernel中引入的一種虛擬總線,Platform機(jī)制中將設(shè)備本身的資源注冊(cè)進(jìn)內(nèi)核,由內(nèi)核統(tǒng)一管理。在驅(qū)動(dòng)程序中通過(guò)platform_device提供的標(biāo)準(zhǔn)接口進(jìn)行申請(qǐng)并使用這些資源。platform_driver通過(guò)platform bus獲取platform_device,platfrom_driver的根本目的是為了統(tǒng)一管理系統(tǒng)的外設(shè)資源,為驅(qū)動(dòng)程序提供統(tǒng)一的接口來(lái)訪問(wèn)系統(tǒng)資源,將驅(qū)動(dòng)和資源分離,從而來(lái)提高程序的可移植性[4]。
2.2 鍵盤(pán)驅(qū)動(dòng)
在Platform總線模型下,鍵盤(pán)驅(qū)動(dòng)通常是采用層次式結(jié)構(gòu),由上層鍵盤(pán)抽象層和下層鍵盤(pán)硬件處理層來(lái)實(shí)現(xiàn)[5]。上層是鍵盤(pán)驅(qū)動(dòng)程序中的核心部分,實(shí)現(xiàn)將掃描碼轉(zhuǎn)換成鍵碼,再將鍵碼轉(zhuǎn)換成目標(biāo)碼存放到鍵值緩沖區(qū)等功能。上層鍵盤(pán)抽象層中還定義了一些系統(tǒng)調(diào)用函數(shù),而這些系統(tǒng)調(diào)用功能是由下層硬件處理層來(lái)實(shí)現(xiàn)的。下層是直接對(duì)硬件進(jìn)行操作,其具體實(shí)現(xiàn)是由不同的硬件所決定的。
3 鍵盤(pán)驅(qū)動(dòng)程序的實(shí)現(xiàn)
鍵盤(pán)驅(qū)動(dòng)程序的實(shí)現(xiàn)可分為初始化函數(shù)的實(shí)現(xiàn)、系統(tǒng)調(diào)用函數(shù)的實(shí)現(xiàn)以及鍵盤(pán)掃描的實(shí)現(xiàn)三部分。
3.1 初始化函數(shù)的實(shí)現(xiàn)
初始化中主要完成設(shè)備注冊(cè)到系統(tǒng)內(nèi)核、資源申請(qǐng)、鍵盤(pán)設(shè)備檢測(cè)等工作。其具體實(shí)現(xiàn)過(guò)程可分為兩步:platform_device與platform_driver的定義及初始化、系統(tǒng)探測(cè)函數(shù)at91key_probe的實(shí)現(xiàn)。
3.1.1 platform_device與platform_driver的定義及初始化
首先,注冊(cè)、初始化platform_device結(jié)構(gòu)變量,并將platform_device添加到platform總線;然后再進(jìn)行設(shè)備號(hào)的申請(qǐng);最后對(duì)platform_driver進(jìn)行注冊(cè),注冊(cè)函數(shù)如下:
Ret=platform_driver_register(&at91key_driver);
platform_driver_register()注冊(cè)時(shí),會(huì)將當(dāng)前注冊(cè)的platform_driver中的name變量的值和已注冊(cè)的所有platform_device中的name變量的值進(jìn)行比較,只有找到具有相同名稱的platform_device才能注冊(cè)成功。當(dāng)注冊(cè)成功時(shí),會(huì)調(diào)用platform_driver結(jié)構(gòu)元素probe函數(shù)指針(即at91key_probe)。
3.1.2 系統(tǒng)探測(cè)函數(shù)at91key_probe的實(shí)現(xiàn)
在函數(shù)指針at91key_probe所指向的系統(tǒng)探測(cè)函數(shù)里,主要完成以下工作:
(1)鍵盤(pán)端口(即8個(gè)GPIO端口)進(jìn)行初始化,初始化函數(shù)如下:
Init_Keyboard();
在函數(shù)Init_Keyboard中,所有行的管腳均為輸入端,并設(shè)置為高電平;所有列的管腳為輸出端,并置為低電平。初始化函數(shù)如下:
void Init_Keyboard(void)
{
//行線
at91_set_gpio_input(AT91_PIN_PB0,1);
at91_set_gpio_input(AT91_PIN_PB1,1);
at91_set_gpio_input(AT91_PIN_PB2,1);
at91_set_gpio_input(AT91_PIN_PB3,1);
//列線
at91_set_gpio_output(AT91_PIN_PB4,0);
at91_set_gpio_output(AT91_PIN_PB5,0);
at91_set_gpio_output(AT91_PIN_PB11,0);
at91_set_gpio_output(AT91_PIN_PB12,0);
at91_sys_write(AT91_PMC_PCER,(0x1<<AT91RM9200_ID_PIOB));
}
?。?)將已分配到的設(shè)備號(hào)以及設(shè)備操作接口(即為struct file_operations結(jié)構(gòu))賦予struct cdev結(jié)構(gòu)變量,用cdev_init()函數(shù)初始化已分配到的結(jié)構(gòu)并與file_operations結(jié)構(gòu)關(guān)聯(lián)起來(lái),再調(diào)用cdev_add()函數(shù)將設(shè)備號(hào)與struct cdev結(jié)構(gòu)進(jìn)行關(guān)聯(lián)并向內(nèi)核正式報(bào)告新設(shè)備的注冊(cè)。其注冊(cè)函數(shù)如下:
cdev_init(&at91_key->chrdev,&key_fops);
ret=cdev_add(&at91_key->chrdev,dev_id,1);
?。?)利用函數(shù)class_create和class_device_create自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)。其函數(shù)如下:
key_class=class_create(THIS_MODULE,KEY_NAME);
cls_key_dev=class_device_create(key_class,NULL,dev_id,&pdev->dev,KEY_NAME);
?。?)啟動(dòng)系統(tǒng)內(nèi)核定時(shí)器key_run_timer(),按固定的時(shí)間間隔(即定時(shí)器處理函數(shù)觸發(fā)時(shí)間為100 ms)來(lái)執(zhí)行鍵盤(pán)掃描函數(shù)Scan_Keyboard()。
3.2 系統(tǒng)調(diào)用函數(shù)的實(shí)現(xiàn)
Linux為字符設(shè)備提供了統(tǒng)一的操作函數(shù)接口,內(nèi)核使用file_operations結(jié)構(gòu)建立主設(shè)備號(hào)和設(shè)備驅(qū)動(dòng)程序的連接[6]。file_operations數(shù)據(jù)結(jié)構(gòu)指明能夠?qū)ζ湓O(shè)備文件進(jìn)行的操作,其中大部分是指向用戶自己編寫(xiě)的設(shè)備操作函數(shù)的函數(shù)指針,其相當(dāng)于一個(gè)指針跳轉(zhuǎn)表。在Linux系統(tǒng)中,設(shè)備驅(qū)動(dòng)程序以文件系統(tǒng)結(jié)構(gòu)的方式為I/O設(shè)備提供一組入口點(diǎn)。因此,對(duì)此結(jié)構(gòu)的訪問(wèn)就相當(dāng)于操作設(shè)備文件。
在內(nèi)核中是使用file_operation結(jié)構(gòu)中函數(shù)指針來(lái)訪問(wèn)驅(qū)動(dòng)程序的函數(shù),文件可以認(rèn)為是一個(gè)“對(duì)象”,操作它的函數(shù)是“方法”,這些方法主要負(fù)責(zé)系統(tǒng)調(diào)用的實(shí)現(xiàn)。
下面介紹本鍵盤(pán)驅(qū)動(dòng)中打開(kāi)函數(shù)、關(guān)閉函數(shù)及讀函數(shù)等系統(tǒng)調(diào)用的具體實(shí)現(xiàn)。
3.2.1 打開(kāi)函數(shù)及關(guān)閉函數(shù)的實(shí)現(xiàn)
應(yīng)用程序打開(kāi)設(shè)備文件時(shí),會(huì)執(zhí)行驅(qū)動(dòng)中的打開(kāi)設(shè)備文件描述符的操作。通過(guò)file_opreation結(jié)構(gòu)中設(shè)備文件操作結(jié)構(gòu)的映射,調(diào)用驅(qū)動(dòng)中的key_open函數(shù)。此函數(shù)主要是使用try_module_get(THIS_MODULE),去增加管理此設(shè)備的THIS_MODULE模塊的使用計(jì)數(shù)。
同樣地,當(dāng)應(yīng)用程序中使用close函數(shù)來(lái)關(guān)閉設(shè)備文件時(shí),實(shí)質(zhì)是通過(guò)對(duì)應(yīng)文件的file_opreation結(jié)構(gòu)中的release函數(shù)指針來(lái)執(zhí)行系統(tǒng)調(diào)用函數(shù)key_release。在函數(shù)key_release中使用module_put(THIS_MODULE)減少對(duì)管理此設(shè)備的THIS_MODULE模塊的使用計(jì)數(shù)。
這樣,當(dāng)設(shè)備在使用時(shí),管理此設(shè)備的模塊就不能被卸載,只有設(shè)備不再使用時(shí)模塊才能被卸載。
3.2.2 讀函數(shù)的實(shí)現(xiàn)
鍵盤(pán)讀函數(shù)通過(guò)copy_to_user()函數(shù)將從緩沖區(qū)讀取的鍵值復(fù)制到用戶數(shù)據(jù)區(qū),上層應(yīng)用程序通過(guò)調(diào)用讀函數(shù)即可獲取該鍵值。鍵盤(pán)讀函數(shù)執(zhí)行流程如圖2所示。
在鍵盤(pán)驅(qū)動(dòng)中進(jìn)行讀操作時(shí),聲明等待隊(duì)列之后,判斷當(dāng)前循環(huán)隊(duì)列是否有數(shù)據(jù)可讀,若無(wú)數(shù)據(jù)可讀,則直接跳出等待隊(duì)列,得到緩沖區(qū)的數(shù)據(jù),調(diào)用函數(shù)cope_to_user,將得到的鍵值拷貝到用戶數(shù)據(jù)區(qū);若有數(shù)據(jù)可讀,設(shè)置當(dāng)前進(jìn)程的狀態(tài),利用中斷狀態(tài)來(lái)等待數(shù)據(jù)循環(huán)隊(duì)列。當(dāng)有數(shù)據(jù)到循環(huán)隊(duì)列,設(shè)置狀態(tài)為任務(wù)運(yùn)行狀態(tài)并跳出等待隊(duì)列,緩沖區(qū)的數(shù)據(jù)拷貝到用戶數(shù)據(jù)區(qū)。一旦上層用戶程序進(jìn)行讀操作,系統(tǒng)調(diào)用將通過(guò)key_read()函數(shù)來(lái)獲取用戶數(shù)據(jù)區(qū)的鍵值。
等待隊(duì)列是由等待某些事件發(fā)生的進(jìn)程組成的簡(jiǎn)單鏈表。內(nèi)核中每個(gè)等待隊(duì)列都要一個(gè)等待隊(duì)列頭(wake_queue_head),等待隊(duì)列頭是一個(gè)類(lèi)型為wake_queue_head_t的數(shù)據(jù)結(jié)構(gòu)。等待隊(duì)列可通過(guò)DECLARE_WAITQUEUE()靜態(tài)創(chuàng)建。
3.3 鍵盤(pán)掃描的實(shí)現(xiàn)
矩陣鍵盤(pán)通常是采用逐行(或列)掃描的方式識(shí)別按鍵,通常分兩步進(jìn)行:(1)識(shí)別鍵盤(pán)有無(wú)鍵按下;(2)在有鍵按下時(shí)識(shí)別出具體的按鍵。鍵盤(pán)的工作方式有3種:編程掃描、定時(shí)掃描和中斷掃描。本方案采用高效率的定時(shí)掃描,定時(shí)掃描按照內(nèi)核定時(shí)器指定的時(shí)間間隔來(lái)執(zhí)行掃描工作。
鍵盤(pán)掃描算法流程圖如圖3所示。
鍵盤(pán)掃描過(guò)程是微處理器通過(guò)定時(shí)查看鍵盤(pán)矩陣以確定是否有鍵按下,并查詢被按下的鍵。驅(qū)動(dòng)給每個(gè)按鍵分配一個(gè)鍵值,即按鍵的唯一標(biāo)識(shí)符。應(yīng)用程序通過(guò)按鍵鍵值識(shí)別被按下的鍵。初始化時(shí),所有行均為輸入端,并設(shè)置為高電平,所有的列為輸出端,置為低電平;當(dāng)無(wú)鍵按下時(shí),將從所有作為輸入端的行中讀到高電平。只要有按鍵閉合,其中一行將變?yōu)榈碗娖?。因此,微處理器只需檢測(cè)是否有某行電平變?yōu)榈碗娖郊纯纱_定是否有鍵按下。例如,在圖1中,如果PB1變?yōu)榈碗娖?,則表示k7、k8、k9和k15中至少有一個(gè)按鍵被按下。
在確定按鍵操作所在行的位置之后,下一步就是要查看按鍵操作所在列的位置。在4個(gè)列輸出端口中,輪流將其中某一個(gè)端口的輸出置為低電平,其他3個(gè)端口的輸出置為高電平。這樣逐列進(jìn)行掃描,直到按鍵所在的列端口輸出為低電平,此時(shí)按鍵操作所在行的管腳的輸入端口的值會(huì)變成低電平。例如,在確認(rèn)k7、k8、k9和k15這行中有按鍵按下之后,進(jìn)行逐列掃描。若發(fā)現(xiàn)在PB5為低電平時(shí)(其他端口輸出均為高電平),PB1管腳的輸入端口變?yōu)榈碗娖?,則可以斷定按鍵k8被按下了。因此可從行號(hào)和列號(hào)對(duì)應(yīng)的二維數(shù)組(也就是鍵值映射表)中找到該鍵的鍵值。
4 按鍵抖動(dòng)及重鍵問(wèn)題的解決
嵌入式系統(tǒng)中常用機(jī)械式按鍵,由于受到彈性作用的影響,鍵盤(pán)在被按下或釋放時(shí),通常會(huì)產(chǎn)生機(jī)械抖動(dòng),需經(jīng)過(guò)一段時(shí)間后才能穩(wěn)定下來(lái),因此處理器不能隨著按鍵的按下或釋放而產(chǎn)生明確的電平1或者0。雖然肉眼看來(lái)開(kāi)關(guān)能夠快速穩(wěn)定地閉合,但與處理器運(yùn)行的速度相比,開(kāi)關(guān)的動(dòng)作則相對(duì)較慢。
為了消除按鍵抖動(dòng)的問(wèn)題,根據(jù)開(kāi)關(guān)的回彈特性,處理器按照一定的時(shí)間間隔對(duì)鍵盤(pán)進(jìn)行掃描,該時(shí)間間隔被稱為去除回彈周期,一般為30 ms~100 ms。
鍵盤(pán)去抖的流程圖如圖4所示,流程描述如下:
?。?)初始化時(shí),將鍵盤(pán)的狀態(tài)標(biāo)志變量Bsflag置為1。按內(nèi)核定時(shí)器設(shè)置的100 ms時(shí)間間隔對(duì)鍵盤(pán)進(jìn)行逐行掃描,若發(fā)現(xiàn)有鍵按下的信號(hào)出現(xiàn)時(shí),此時(shí)就要確定是正常擊鍵行為還是抖動(dòng)。
?。?)在檢測(cè)是不是抖動(dòng)時(shí),先啟動(dòng)一個(gè)延時(shí)20 ms的定時(shí)器,20 ms之后再次對(duì)鍵盤(pán)進(jìn)行掃描,判斷硬件上是否有鍵按下,若沒(méi)有,則顯然是抖動(dòng);若有鍵按下,則是用戶正常擊鍵行為,因此將此鍵值iscancode存入鍵值緩沖區(qū)里,同時(shí)Bsflag=0。之后就啟動(dòng)一個(gè)100 ms的定時(shí)器,這個(gè)定時(shí)器的作用是判斷用戶何時(shí)松開(kāi)鍵盤(pán)(注意這里是100 ms的定時(shí)器,與剛才的20 ms不同)。
?。?)在100 ms定時(shí)器定時(shí)時(shí)間到了之后,要判斷此鍵是否已經(jīng)彈起。若還是按下,繼續(xù)啟動(dòng)延時(shí)100 ms的定時(shí)器,在下一個(gè)100 ms時(shí)再進(jìn)行判斷。若是彈起,則要進(jìn)一步判斷是抖動(dòng)現(xiàn)象還是已完全彈起,進(jìn)行一個(gè)20 ms的延遲去抖就可以完全判斷出來(lái)。當(dāng)判斷出鍵是完全彈起,則將此鍵值iscancode加上0x80存入鍵值緩沖區(qū)里,同時(shí)Bsflag=1。此時(shí)按鍵已經(jīng)完全地被松開(kāi)彈起了。
在程序中對(duì)鍵盤(pán)標(biāo)志變量Bsflag和鍵值緩沖區(qū)鍵值(是否小于0x80)進(jìn)行有效的判斷,完全可以解決按鍵的防抖及重鍵問(wèn)題。
5 鍵盤(pán)驅(qū)動(dòng)的測(cè)試
驅(qū)動(dòng)開(kāi)發(fā)完成后,用insmod將模塊加載入內(nèi)核,在PC和目標(biāo)板之間搭建好嵌入式交叉編譯環(huán)境。在硬件平臺(tái)CE9200目標(biāo)板中的Linux系統(tǒng)下進(jìn)行測(cè)試,通過(guò)PC上的超級(jí)終端將測(cè)試結(jié)果信息打印顯示出來(lái)。
在圖5中顯示了鍵盤(pán)驅(qū)動(dòng)設(shè)備的成功打開(kāi)與關(guān)閉,以及對(duì)按鍵動(dòng)作信號(hào)的高效準(zhǔn)確的響應(yīng),并成功解決了按鍵防抖及重鍵問(wèn)題,證明本設(shè)計(jì)的矩陣鍵盤(pán)工作高效、穩(wěn)定。
本文提出的一種基于CE9200平臺(tái)和嵌入式Linux鍵盤(pán)驅(qū)動(dòng)的實(shí)現(xiàn)方案,實(shí)現(xiàn)了操作的高效和穩(wěn)定,已成功應(yīng)用于工程實(shí)踐中的多款嵌入式設(shè)備,證明了在一定的要求下該方案完全能夠滿足性能要求。
參考文獻(xiàn)
[1] 怯肇乾.嵌入式人機(jī)界面中的鍵盤(pán)及其接口設(shè)計(jì)[J].單片機(jī)與嵌入式應(yīng)用系統(tǒng),2006,20(4):24-27.
[2] Tool interface standard executable and linking format specification(Version 1.2)[S]. 1995.
[3] 華清遠(yuǎn)見(jiàn)嵌入式培訓(xùn)中心.嵌入式Linux應(yīng)用程序開(kāi)發(fā)標(biāo)準(zhǔn)教程(第2版)[M].北京:人民郵電出版社,2009:335-356.
[4] 宋寶華.Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解(第2版)[M].北京:人民郵電出版社,2010:243-248.
[5] 林樹(shù)新,吳朝暉.Linux鍵盤(pán)驅(qū)動(dòng)的移植分析及實(shí)現(xiàn)[J].計(jì)算機(jī)工程,2005,31(2):211-213.
[6] Liu Kang, Qian Xu, Li Yaxu, et al. Research of matrix keyboard device driver based on embedded Linux [C]. 2010 Asia-Pacific Conference on Information Network and Digital Content Security (2010APCID), Scientific Research, 17-19 December 2010:239-243.