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