摘 要: 设计了一种基于TCA9535芯片的Android系统外扩键盘模块。该模块采用矩阵式键盘设计,通过I2C总线与主控芯片相连,利用按键产生的中断对键盘进行扫描,并完成键值的上报。详细介绍了Android系统的键盘驱动开发流程和键值处理的一些经验,实测证明达到了实用化的要求。这种总线方式的键盘模块设计最大限度地利用了主控芯片资源,具有良好的可移植性和可扩展性,有一定的应用参考价值。
关键词: TCA9535;Android系统;键盘模块;I2C总线
Android系统是一种基于Linux内核的开放源码的操作系统,目前主要应用于移动设备中(如智能手机、平板电脑等)。而在工业控制领域的终端设备中,则主要采用Windows CE和嵌入式Linux系统。Android系统的开源特性和良好的UI系统,相比Windows CE和Linux系统具有一定的优势,并有逐渐向工业控制的终端设备渗透的趋势。
键盘模块作为一种人机交互接口,在各种终端设备中得到了广泛应用。矩阵式键盘[1-7]占用较少的I/O,能提供较多的按键,是键盘设计中常见的一种低成本设计方案。随着工控领域终端设备的智能化,各主控芯片集成的功能越来越多,GPIO往往与其他功能引脚复用。为了最大限度地利用主控芯片资源,GPIO资源在硬件设计时须谨慎规划。虽然Android系统自带虚拟键盘,但屏幕的大小和触屏灵敏度直接影响虚拟键盘的使用效率和用户体验,一旦触摸屏失灵,虚拟键盘将不能使用。因此在可靠性和成本要求甚高的工业控制领域并不是最佳选择。
本文采用I2C接口的TCA9535[8]芯片实现了一种通用的矩阵式键盘模块,并完成了该模块在Android系统上的驱动开发。由于采用的是I2C总线方式,其他设备也可挂载到同一总线上,因此最大限度地利用了主控芯片的资源。
1 键盘模块硬件设计
1.1 键盘模块硬件接口
键盘模块通过TCA9535芯片扩展I/O实现。TCA9535芯片是TI公司生产的一款I2C接口扩展I/O的芯片,芯片供电范围为1.65 V~5.5 V,最大支持400 kHz的通信速率,最大待机电流为3 ?滋A;具有16个独立I/O和一个开漏极低电平输出的中断口,所有I/O口具备机型反转功能,能直接驱动LED;具有3根地址线,可根据应用系统要求设置芯片的地址。芯片内部有8个可编程的寄存器,分别是2个输入端口寄存器、2个输出端口寄存器、2个极性翻转寄存器和2个端口配置寄存器。
键盘模块采用5×5矩阵式按键设计,键盘背光通过一个I/O口控制一个MOSFET管驱动多个并联的LED实现,总共使用TCA9535芯片的11个I/O口。键盘模块与主控芯片AM3730之间通过I2C接口和一根中断线连接,如图1所示。
1.2 键盘模块工作原理
当按键阵列有按键按下时,TCA9535芯片产生一个低电平中断。主控芯片检测到中断信号后,通过I2C总线配置TCA9535芯片的相关寄存器,对键盘阵列I/O进行扫描。每一次扫描后,读取键盘阵列I/O值。多次扫描后,完成按键位置的确定,并根据位置确定键值。主控芯片确认有按键按下时,通过I2C总线控制TCA9535芯片控制背光的I/O口,点亮键盘的背光。在按键过后一段时间内,若没有新的按键产生,则主控芯片将关闭键盘背光。
2 Android系统驱动开发
Android系统大体可分为4层[9],从下往上依次是:Linux内核层、Libraries层、Framework层和Application层。Android系统与硬件相关的驱动基本在Linux内核层。因此,本文涉及的TCA9535设备驱动是指Linux内核层的设备驱动。本文Android系统为Android ICS 4.0.3,其中的Linux内核版本为2.6.37。
2.1 Linux内核I2C设备驱动
Linux内核I2C设备驱动包含3层[10],分别是:I2C总线驱动(I2C core)、I2C控制器驱动(I2C adapter)及I2C设备的驱动(I2C driver)。I2C总线驱动主要实现对I2C总线及控制器和设备驱动的管理。这部分代码为通用部分,Linux内核已经完善,不需要改动。I2C控制器驱动跟硬件相关,主要是构造一个与I2C总线层接口的数据结构,并通过接口函数向I2C总线注册一个控制器。同时,实现对I2C控制器中断的处理函数,完成I2C设备具体功能的实现。I2C设备驱动主要是构造一个与I2C总线层接口的数据结构,通过接口函数向I2C总线层注册一个I2C设备驱动。同时构造一个与用户层接口的数据结构,通过接口函数向内核注册一个字符型设备。本文涉及的驱动开发主要包含I2C控制器驱动和I2C设备驱动。
2.2 I2C控制器驱动开发
(1)驱动文件添加
在Linux内核的drivers/i2c/muxes目录下,新建一个tca9535kbd.c文件(该文件为TCA9535的设备驱动文件),同时在该层的Makefile和Kconfig文件中,添加对新建文件的支持。重新编译内核后,须选中添加的部分。如Makefile中添加:
obj-$(CONFIG_I2C_MUX_TCA9535)+=tca9535kbd.o
(2)构建与I2C总线层接口的数据结构和接口函数
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)
//接口函数
{
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);//注册中断处理函数tca9535kbd_keys_isr
Init_tca9535();//初始化TCA9535配置
}
static int tca9535kbd_detach_adapter(struct i2c_client *client)
//接口函数
{
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)键值处理
主控芯片接收到中断信号后,进入中断服务函数(tca9535kbd_keys_isr函数)进行按键事件处理。由于键盘按键的处理(键值扫描及去抖等)需要一定的时间,为了不长时间占用CPU资源,中断服务程序只负责将真正的按键事件处理函数(tca9535kbd_do_work函数)放在内核的后台任务队列。内核将根据规则进行自动调度并执行。向内核队列中加入按键事件处理函数通过INIT_WORK函数和schedule_work函数来实现。初始化工作队列函数(INIT_WORK函数)在接口函数tca9535kbd_attach_adapter中调用,任务加入内核后台队列(schedule_work函数)在中断服务程序中调用。
按键事件处理流程如图2所示。
图2中,键盘的防抖采取时间过滤、多次读取来确定。向系统上报按键事件包括按键按下事件和按键松开事件。由于TCA9535芯片只有在有按键按下时才产生中断,按键处理程序中的150 ms的较大延时(经验值)用于防止一次按键事件被处理成多次按键事件(不影响键连击和长按事件的处理)。另外,在上报按键事件时,须同时上报按键按下和按键松开事件,而不额外检测按键松开。具体实现函数如下:
input_event(data->input, EV_KEY, keycode, 1);
//向系统上报按键按下事件
input_sync(data->input);
//同步告知input core子系统事件结束
input_event(data->input, EV_KEY, keycode, 0);
//向系统上报按键松开事件
input_sync(data->input);//同上
2.3 I2C设备驱动开发
(1)构建I2C总线驱动数据结构
static struct i2c_driver tca9535kbd_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tca9535kbd",
},
.attach_adapter = tca9535kbd_attach_adapter,
.detach_adapter= tca9535kbd_detach_adapter,
};
(2)注册设备驱动
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)硬件平台设备初始化
在主控芯片对应的平台文件中须添加对设备的初始化参数,并向平台注册硬件设备(本文所对应的平台文件为内核中arm/arm/mach-omap2/board-omap3beagle.c)。
static struct i2c_board_info __initdata beagle_i2c_
tca9535keyboard[ ]= {
{
I2C_BOARD_INFO("tca9535kbd",0x26),
//驱动名称和地址
.flags=I2C_CLIENT_WAKE,
.irq=OMAP_GPIO_IRQ(65),//主控芯片中断管脚号
},
};
omap_register_i2c_bus(3,400,beagle_i2c_tca9535keyboard,
ARRAY_SIZE(beagle_i2c_tca9535keyboard));
//配置I2C总线时钟,向平台设备注册
2.4 Andorid键值映射
Andorid系统的键盘按键事件由WindowManagerService服务来管理,然后以消息的形式转发给当前活动的应用程序进行处理。Linux内核层上报按键事件时附带了一个键值(在Linux内核include/input.h文件中定义),该键值与Andorid系统使用中的键值对应时才能正确显示按键值。Andorid系统通过qwerty.kl和generic.kl文件进行键值的映射。一些常用的按键在Linux内核层和Android层基本一致。若需要自定义按键,或者内核与Andorid系统层个别键值不一致时,须修改一方的值以完成两者的统一。
本文介绍了一款Android系统矩阵式键盘的设计与实现方式,实际使用证明该键盘模块工作稳定,达到了预期设计目标。该方案基于I2C总线,有利于提高主控芯片的资源利用率,方便移植到其他嵌入式系统上,具备很好的可扩展性。文中介绍的驱动开发流程对Android系统的底层开发具有一定的参考价值和借鉴意义。
参考文献
[1] 王选民.智能仪器原理及设计[M].北京:清华大学出版社,2008.
[2] 黄菁,刘青春.ARM嵌入式系统GPIO扩展键盘设计[J].自动化应用,2011(7):1-2.
[3] 孟桂芳.基于嵌入式Linux的矩阵键盘设备驱动的设计[J]. 苏州大学学报(工科版),2011,31(4):71-74.
[4] 李其珂,付红桥.基于嵌入式Linux的矩阵键盘驱动研究与实现[J].重庆理工大学学报(自然科学),2012,26(12):88-92.
[5] 傅超,张昌华,孟劲松.基于嵌入式Linux的矩阵键盘模块的设计[J].微型机与应用,2012,31(21):7-10.
[6] 徐德龙,余瑾.基于嵌入式Linux系统的键盘驱动设计[J].单片机与嵌入式系统应用,2013(2):21-23.
[7] 张永强,邓少芝,王凯,等.专用键盘接口芯片的一种CPLD实现方案[J].电子技术应用,2002,28(11):17-18.
[8] TI.TCA9535 datasheet[EB/OL].(2009)[2009].http://www.ti.com/product/tca9535.
[9] 邓平凡.深入理解Android:卷I[M].北京:机械工业出版社,2011.
[10] 杜博,方向忠.嵌入式Linux系统下I2C设备驱动程序的开发[J].微计算机信息,2006,22(4-2):31-33.