摘 要: 設(shè)計(jì)了一種基于TCA9535芯片的Android系統(tǒng)外擴(kuò)鍵盤模塊。該模塊采用矩陣式鍵盤設(shè)計(jì),通過I2C總線與主控芯片相連,利用按鍵產(chǎn)生的中斷對鍵盤進(jìn)行掃描,并完成鍵值的上報(bào)。詳細(xì)介紹了Android系統(tǒng)的鍵盤驅(qū)動(dòng)開發(fā)流程和鍵值處理的一些經(jīng)驗(yàn),實(shí)測證明達(dá)到了實(shí)用化的要求。這種總線方式的鍵盤模塊設(shè)計(jì)最大限度地利用了主控芯片資源,具有良好的可移植性和可擴(kuò)展性,有一定的應(yīng)用參考價(jià)值。
關(guān)鍵詞: TCA9535;Android系統(tǒng);鍵盤模塊;I2C總線
Android系統(tǒng)是一種基于Linux內(nèi)核的開放源碼的操作系統(tǒng),目前主要應(yīng)用于移動(dòng)設(shè)備中(如智能手機(jī)、平板電腦等)。而在工業(yè)控制領(lǐng)域的終端設(shè)備中,則主要采用Windows CE和嵌入式Linux系統(tǒng)。Android系統(tǒng)的開源特性和良好的UI系統(tǒng),相比Windows CE和Linux系統(tǒng)具有一定的優(yōu)勢,并有逐漸向工業(yè)控制的終端設(shè)備滲透的趨勢。
鍵盤模塊作為一種人機(jī)交互接口,在各種終端設(shè)備中得到了廣泛應(yīng)用。矩陣式鍵盤[1-7]占用較少的I/O,能提供較多的按鍵,是鍵盤設(shè)計(jì)中常見的一種低成本設(shè)計(jì)方案。隨著工控領(lǐng)域終端設(shè)備的智能化,各主控芯片集成的功能越來越多,GPIO往往與其他功能引腳復(fù)用。為了最大限度地利用主控芯片資源,GPIO資源在硬件設(shè)計(jì)時(shí)須謹(jǐn)慎規(guī)劃。雖然Android系統(tǒng)自帶虛擬鍵盤,但屏幕的大小和觸屏靈敏度直接影響虛擬鍵盤的使用效率和用戶體驗(yàn),一旦觸摸屏失靈,虛擬鍵盤將不能使用。因此在可靠性和成本要求甚高的工業(yè)控制領(lǐng)域并不是最佳選擇。
本文采用I2C接口的TCA9535[8]芯片實(shí)現(xiàn)了一種通用的矩陣式鍵盤模塊,并完成了該模塊在Android系統(tǒng)上的驅(qū)動(dòng)開發(fā)。由于采用的是I2C總線方式,其他設(shè)備也可掛載到同一總線上,因此最大限度地利用了主控芯片的資源。
1 鍵盤模塊硬件設(shè)計(jì)
1.1 鍵盤模塊硬件接口
鍵盤模塊通過TCA9535芯片擴(kuò)展I/O實(shí)現(xiàn)。TCA9535芯片是TI公司生產(chǎn)的一款I(lǐng)2C接口擴(kuò)展I/O的芯片,芯片供電范圍為1.65 V~5.5 V,最大支持400 kHz的通信速率,最大待機(jī)電流為3 ?滋A;具有16個(gè)獨(dú)立I/O和一個(gè)開漏極低電平輸出的中斷口,所有I/O口具備機(jī)型反轉(zhuǎn)功能,能直接驅(qū)動(dòng)LED;具有3根地址線,可根據(jù)應(yīng)用系統(tǒng)要求設(shè)置芯片的地址。芯片內(nèi)部有8個(gè)可編程的寄存器,分別是2個(gè)輸入端口寄存器、2個(gè)輸出端口寄存器、2個(gè)極性翻轉(zhuǎn)寄存器和2個(gè)端口配置寄存器。
鍵盤模塊采用5×5矩陣式按鍵設(shè)計(jì),鍵盤背光通過一個(gè)I/O口控制一個(gè)MOSFET管驅(qū)動(dòng)多個(gè)并聯(lián)的LED實(shí)現(xiàn),總共使用TCA9535芯片的11個(gè)I/O口。鍵盤模塊與主控芯片AM3730之間通過I2C接口和一根中斷線連接,如圖1所示。

1.2 鍵盤模塊工作原理
當(dāng)按鍵陣列有按鍵按下時(shí),TCA9535芯片產(chǎn)生一個(gè)低電平中斷。主控芯片檢測到中斷信號(hào)后,通過I2C總線配置TCA9535芯片的相關(guān)寄存器,對鍵盤陣列I/O進(jìn)行掃描。每一次掃描后,讀取鍵盤陣列I/O值。多次掃描后,完成按鍵位置的確定,并根據(jù)位置確定鍵值。主控芯片確認(rèn)有按鍵按下時(shí),通過I2C總線控制TCA9535芯片控制背光的I/O口,點(diǎn)亮鍵盤的背光。在按鍵過后一段時(shí)間內(nèi),若沒有新的按鍵產(chǎn)生,則主控芯片將關(guān)閉鍵盤背光。
2 Android系統(tǒng)驅(qū)動(dòng)開發(fā)
Android系統(tǒng)大體可分為4層[9],從下往上依次是:Linux內(nèi)核層、Libraries層、Framework層和Application層。Android系統(tǒng)與硬件相關(guān)的驅(qū)動(dòng)基本在Linux內(nèi)核層。因此,本文涉及的TCA9535設(shè)備驅(qū)動(dòng)是指Linux內(nèi)核層的設(shè)備驅(qū)動(dòng)。本文Android系統(tǒng)為Android ICS 4.0.3,其中的Linux內(nèi)核版本為2.6.37。
2.1 Linux內(nèi)核I2C設(shè)備驅(qū)動(dòng)
Linux內(nèi)核I2C設(shè)備驅(qū)動(dòng)包含3層[10],分別是:I2C總線驅(qū)動(dòng)(I2C core)、I2C控制器驅(qū)動(dòng)(I2C adapter)及I2C設(shè)備的驅(qū)動(dòng)(I2C driver)。I2C總線驅(qū)動(dòng)主要實(shí)現(xiàn)對I2C總線及控制器和設(shè)備驅(qū)動(dòng)的管理。這部分代碼為通用部分,Linux內(nèi)核已經(jīng)完善,不需要改動(dòng)。I2C控制器驅(qū)動(dòng)跟硬件相關(guān),主要是構(gòu)造一個(gè)與I2C總線層接口的數(shù)據(jù)結(jié)構(gòu),并通過接口函數(shù)向I2C總線注冊一個(gè)控制器。同時(shí),實(shí)現(xiàn)對I2C控制器中斷的處理函數(shù),完成I2C設(shè)備具體功能的實(shí)現(xiàn)。I2C設(shè)備驅(qū)動(dòng)主要是構(gòu)造一個(gè)與I2C總線層接口的數(shù)據(jù)結(jié)構(gòu),通過接口函數(shù)向I2C總線層注冊一個(gè)I2C設(shè)備驅(qū)動(dòng)。同時(shí)構(gòu)造一個(gè)與用戶層接口的數(shù)據(jù)結(jié)構(gòu),通過接口函數(shù)向內(nèi)核注冊一個(gè)字符型設(shè)備。本文涉及的驅(qū)動(dòng)開發(fā)主要包含I2C控制器驅(qū)動(dòng)和I2C設(shè)備驅(qū)動(dòng)。
2.2 I2C控制器驅(qū)動(dòng)開發(fā)
(1)驅(qū)動(dòng)文件添加
在Linux內(nèi)核的drivers/i2c/muxes目錄下,新建一個(gè)tca9535kbd.c文件(該文件為TCA9535的設(shè)備驅(qū)動(dòng)文件),同時(shí)在該層的Makefile和Kconfig文件中,添加對新建文件的支持。重新編譯內(nèi)核后,須選中添加的部分。如Makefile中添加:
obj-$(CONFIG_I2C_MUX_TCA9535)+=tca9535kbd.o
(2)構(gòu)建與I2C總線層接口的數(shù)據(jù)結(jié)構(gòu)和接口函數(shù)
Struct tca9535kbd_data{
Struct i2c_client client;
Struct input_dev *input;//輸入事件
Struct work_struct tca9535kdb_work;//按鍵處理
}
static int tca9535kbd_attach_adapter(struct i2c_adapter *adapter)
//接口函數(shù)
{
struct i2c_client *new_client;
struct input_dev *input;
struct tca9535kbd_data *data;
data = kzalloc(sizeof(struct tca9535kbd_data), GFP_KERNEL);
//申請分配空間
input = input_allocate_device();
input_register_device(input);//向總線注冊
……//此處省略一些I2C配置
request_irq(new_client->irq, tca9535kbd_keys_isr,
IRQF_TRIGGER_FALLING | IRQF_SHARED, "tca9535kbd",
data);//注冊中斷處理函數(shù)tca9535kbd_keys_isr
Init_tca9535();//初始化TCA9535配置
}
static int tca9535kbd_detach_adapter(struct i2c_client *client)
//接口函數(shù)
{
struct tca9535kbd_data *data = container_of(client, struct
tca9535kbd_data, client);
input_unregister_device(data->input);//向I2C總線注銷
free_irq(client->irq, data);
kfree(i2c_get_clientdata(client));
return 0;
}
(3)鍵值處理
主控芯片接收到中斷信號(hào)后,進(jìn)入中斷服務(wù)函數(shù)(tca9535kbd_keys_isr函數(shù))進(jìn)行按鍵事件處理。由于鍵盤按鍵的處理(鍵值掃描及去抖等)需要一定的時(shí)間,為了不長時(shí)間占用CPU資源,中斷服務(wù)程序只負(fù)責(zé)將真正的按鍵事件處理函數(shù)(tca9535kbd_do_work函數(shù))放在內(nèi)核的后臺(tái)任務(wù)隊(duì)列。內(nèi)核將根據(jù)規(guī)則進(jìn)行自動(dòng)調(diào)度并執(zhí)行。向內(nèi)核隊(duì)列中加入按鍵事件處理函數(shù)通過INIT_WORK函數(shù)和schedule_work函數(shù)來實(shí)現(xiàn)。初始化工作隊(duì)列函數(shù)(INIT_WORK函數(shù))在接口函數(shù)tca9535kbd_attach_adapter中調(diào)用,任務(wù)加入內(nèi)核后臺(tái)隊(duì)列(schedule_work函數(shù))在中斷服務(wù)程序中調(diào)用。
按鍵事件處理流程如圖2所示。

圖2中,鍵盤的防抖采取時(shí)間過濾、多次讀取來確定。向系統(tǒng)上報(bào)按鍵事件包括按鍵按下事件和按鍵松開事件。由于TCA9535芯片只有在有按鍵按下時(shí)才產(chǎn)生中斷,按鍵處理程序中的150 ms的較大延時(shí)(經(jīng)驗(yàn)值)用于防止一次按鍵事件被處理成多次按鍵事件(不影響鍵連擊和長按事件的處理)。另外,在上報(bào)按鍵事件時(shí),須同時(shí)上報(bào)按鍵按下和按鍵松開事件,而不額外檢測按鍵松開。具體實(shí)現(xiàn)函數(shù)如下:
input_event(data->input, EV_KEY, keycode, 1);
//向系統(tǒng)上報(bào)按鍵按下事件
input_sync(data->input);
//同步告知input core子系統(tǒng)事件結(jié)束
input_event(data->input, EV_KEY, keycode, 0);
//向系統(tǒng)上報(bào)按鍵松開事件
input_sync(data->input);//同上
2.3 I2C設(shè)備驅(qū)動(dòng)開發(fā)
(1)構(gòu)建I2C總線驅(qū)動(dòng)數(shù)據(jù)結(jié)構(gòu)
static struct i2c_driver tca9535kbd_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tca9535kbd",
},
.attach_adapter = tca9535kbd_attach_adapter,
.detach_adapter= tca9535kbd_detach_adapter,
};
(2)注冊設(shè)備驅(qū)動(dòng)
static int __init tca9535kbd_init(void)
{
return i2c_add_driver(&tca9535kbd_driver);
}
static void __exit tca9535kbd_exit(void)
{
i2c_del_driver(&tca9535kbd_driver);
}
module_init(tca9535kbd_init);
module_exit(tca9535kbd_exit);
(3)硬件平臺(tái)設(shè)備初始化
在主控芯片對應(yīng)的平臺(tái)文件中須添加對設(shè)備的初始化參數(shù),并向平臺(tái)注冊硬件設(shè)備(本文所對應(yīng)的平臺(tái)文件為內(nèi)核中arm/arm/mach-omap2/board-omap3beagle.c)。
static struct i2c_board_info __initdata beagle_i2c_
tca9535keyboard[ ]= {
{
I2C_BOARD_INFO("tca9535kbd",0x26),
//驅(qū)動(dòng)名稱和地址
.flags=I2C_CLIENT_WAKE,
.irq=OMAP_GPIO_IRQ(65),//主控芯片中斷管腳號(hào)
},
};
omap_register_i2c_bus(3,400,beagle_i2c_tca9535keyboard,
ARRAY_SIZE(beagle_i2c_tca9535keyboard));
//配置I2C總線時(shí)鐘,向平臺(tái)設(shè)備注冊
2.4 Andorid鍵值映射
Andorid系統(tǒng)的鍵盤按鍵事件由WindowManagerService服務(wù)來管理,然后以消息的形式轉(zhuǎn)發(fā)給當(dāng)前活動(dòng)的應(yīng)用程序進(jìn)行處理。Linux內(nèi)核層上報(bào)按鍵事件時(shí)附帶了一個(gè)鍵值(在Linux內(nèi)核include/input.h文件中定義),該鍵值與Andorid系統(tǒng)使用中的鍵值對應(yīng)時(shí)才能正確顯示按鍵值。Andorid系統(tǒng)通過qwerty.kl和generic.kl文件進(jìn)行鍵值的映射。一些常用的按鍵在Linux內(nèi)核層和Android層基本一致。若需要自定義按鍵,或者內(nèi)核與Andorid系統(tǒng)層個(gè)別鍵值不一致時(shí),須修改一方的值以完成兩者的統(tǒng)一。
本文介紹了一款A(yù)ndroid系統(tǒng)矩陣式鍵盤的設(shè)計(jì)與實(shí)現(xiàn)方式,實(shí)際使用證明該鍵盤模塊工作穩(wěn)定,達(dá)到了預(yù)期設(shè)計(jì)目標(biāo)。該方案基于I2C總線,有利于提高主控芯片的資源利用率,方便移植到其他嵌入式系統(tǒng)上,具備很好的可擴(kuò)展性。文中介紹的驅(qū)動(dòng)開發(fā)流程對Android系統(tǒng)的底層開發(fā)具有一定的參考價(jià)值和借鑒意義。
參考文獻(xiàn)
[1] 王選民.智能儀器原理及設(shè)計(jì)[M].北京:清華大學(xué)出版社,2008.
[2] 黃菁,劉青春.ARM嵌入式系統(tǒng)GPIO擴(kuò)展鍵盤設(shè)計(jì)[J].自動(dòng)化應(yīng)用,2011(7):1-2.
[3] 孟桂芳.基于嵌入式Linux的矩陣鍵盤設(shè)備驅(qū)動(dòng)的設(shè)計(jì)[J]. 蘇州大學(xué)學(xué)報(bào)(工科版),2011,31(4):71-74.
[4] 李其珂,付紅橋.基于嵌入式Linux的矩陣鍵盤驅(qū)動(dòng)研究與實(shí)現(xiàn)[J].重慶理工大學(xué)學(xué)報(bào)(自然科學(xué)),2012,26(12):88-92.
[5] 傅超,張昌華,孟勁松.基于嵌入式Linux的矩陣鍵盤模塊的設(shè)計(jì)[J].微型機(jī)與應(yīng)用,2012,31(21):7-10.
[6] 徐德龍,余瑾.基于嵌入式Linux系統(tǒng)的鍵盤驅(qū)動(dòng)設(shè)計(jì)[J].單片機(jī)與嵌入式系統(tǒng)應(yīng)用,2013(2):21-23.
[7] 張永強(qiáng),鄧少芝,王凱,等.專用鍵盤接口芯片的一種CPLD實(shí)現(xiàn)方案[J].電子技術(shù)應(yīng)用,2002,28(11):17-18.
[8] TI.TCA9535 datasheet[EB/OL].(2009)[2009].http://www.ti.com/product/tca9535.
[9] 鄧平凡.深入理解Android:卷I[M].北京:機(jī)械工業(yè)出版社,2011.
[10] 杜博,方向忠.嵌入式Linux系統(tǒng)下I2C設(shè)備驅(qū)動(dòng)程序的開發(fā)[J].微計(jì)算機(jī)信息,2006,22(4-2):31-33.
