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