摘要:为了适应嵌入式设备外设的多样性,本文以特殊矩阵键盘为例,设计了一套完整的驱动控制模块。硬件电路设计采用外扩3片SN74HC 164芯片的方式,节省了GPIO引脚的使用,大大提高了利用效率。同时,在此基础上引出了Linux内核中input子系统的特性和工作机制,呈现了较为完整的输入事件由内核空间传递到用户空间进程的过程。实验结果表明,设计的驱动模块具有良好的实时性和准确性。
关键词:SN74HC164;矩阵键盘;驱动控制模块;Linux内核;input子系统
引言
随着微处理器技术的不断发展和数字化产品的普及,嵌入式系统的研究开发逐渐成为热点,Linux也以其开源、稳定、可裁剪的优势成为嵌入式操作系统的主流。在众多的嵌入式系统中,键盘成为一种应用最为广泛的输入设备。然而,嵌入式设备的功能差异性又决定了为其提供一种通用性键盘是不可行的,往往需要根据系统的实际功能设计所需的特殊键盘,并实现相应的驱动程序。
S3C6410是三星公司高性能的32位RISC微处理器,内部集成了多种强大的硬件加速器,适合进行视频和图像处理,成为了目前嵌入式处理器领域的主流产品。本文以在S3C6410微处理器基础上实现一个24键矩阵键盘为例,呈现了在嵌入式系统中开发设备驱动程序的整体流程,并对Linux系统下输入事件的底层传递机制进行了研究和分析。
1 接口电路的设计
在嵌入式设备上扩展键盘的常用方式是通过对CPU的GPIO端口进行扫描实现的,显然这种方式在键盘按键数目较多的情况下,会占用过多的GPIO资源,增加了GPIO端口资源较为紧张的嵌入式处理器的负担。
本系统的硬件设计通过增加3片SN74HC164芯片来达到节约GPIO资源的目的。SN74HC164是一种8位的串行输入、并行输出移位寄存器,它的内部由8个D触发器串联而成。每当时钟信号由低电平变为高电平时,两个输入端将当前输入信号传送到并行输出端,并实现移位操作。系统硬件原理图如图1所示。
3个SN74HC164芯片串联后,将它们的CLK引脚接到S3C6410开发板的GPE4端口上。第一个SN74HC164芯片的A、B输入引脚共同接到开发板的GPE3端口上,并且将这两个GPIO端口配置成输出模式。GPE2端口与键盘按键的上拉端连接,系统运行时在中断模式和输入模式之间切换,以达到触发中断和对键盘扫描的目的。这样我们就借助于3个SN74HC164移位寄存器,只占用3个GPIO端口,给扫描键盘的24个按键提供输入信号,既节约了成本,又避免了GPIO资源的浪费。
2 扫描工作原理
扩展硬件电路的同时给键盘驱动程序的实现带来了一定的麻烦,驱动程序首先要将SN74HC164驱动起来,然后才能对电路进行控制。该电路的输出引脚被接到S3C6410的GPE2端口上,并且这个端口被配置成中断源,无键按下时直接读为高电位。键盘扫描时通过SN74HC164芯片先将键盘的24个键置低电平,任何一个键被按下,GPE2端口就会有从高电平到低电平的跳变,从而触发一次中断。
在中断处理过程中,将GPE2端口置为输入状态。然后根据SN74HC164芯片的输入/输出特性,给串联的3个SN74HC164芯片发送24个高电平信号,使得键盘的各键位均为高电平。在随后的24个时钟脉冲下,给SN74HC164芯片送入1个0和23个1,使得0在每个键位的输入端都只出现一次,同时在GPE2端口进行扫描。当被按下键处于0输入状态时,其所在行就会读到一个低电平,也就可以确定出键盘上哪个键被按下了。
3 驱动模块结构
在Linux2.6的版本中新加入了input子系统,给驱动编写者提供了一个完整的输入事件——从底层设备传递到用户进程的模型。本文基于input子系统架构,设计了一个较为完善的特殊键盘驱动模块。键盘驱动模块结构如图2所示。
在input子系统的设备内核模型中,最重要的数据结构体是struct input_dev,作为驱动的主体,每个structinput_dev代表一个输入设备。该结构体中既包含了设备所能响应的输入事件类型、响应按键种类、键盘码表,以及坐标范嗣等字段,同时还包含了设备打开、关闭以及回调函数等字段,能够完整地记录和标识整个设备的功能与行为。在向内核注册input_dev之前,需要进行input_dev结构的初始化,同时向内核申请键盘中断。
首先设置输入设备的功能,input_set_capability(&sim_key,EV_KEY,KEY_A)函数完成键盘A键的输入使能,类似可完成B~X共24个按键的输入使能。然后设置键盘的码表。该键盘包含20个按键,码表可表示为:static unsigned char sim_keycode[24]={KEY_A,KEY_B,KEY_C,KEY_D,KEY_E,KEY_F,KEY_G,KEY_H,KEY_I,KEY_J,KEY_K,KEY_L,KEY_M,KEY_N,KEY_O,KEY_P,KEY_Q,KEY_R,KEY_S,KEY_T,KEY_U,KEY_V,KEY_W,KEY_X)。当相应键按下时,码表中的键值将被作为键盘码上报到用户空间的进程。初始化工作完成之后,调用函数input_register_device(&sim_kb)向内核注册输入设备。
由于键盘设备的输入是异步的,可能会在任何时间得到按键事件,所以需向内核申请中断以保证对键盘输入的实时响应。中断函数完成键盘的扫描操作,并上报输入事件到用户进程,是整个驱动模块的功能主体。然而使用中断会遇到一个问题,在键盘的扫描过程中,按键的每次按下和抬起都会有10~20 ms的毛刺抖动存在,会将用户的一次按键操作误当作几次按键来处理。所以为了获取稳定的按键信息,必须要想办法去掉这种抖动。去毛刺的一种常见的方法是在注册输入设备时定义一个定时器timer,当触发中断时先关闭I/O中断,然后启动定时器,等跳过毛刺抖动以后再去调用扫描程序得到键值,并重新打开中断。按键事件被发送到input子系统核心后通知给用户进程,从而实现查键过程。
4 基于input子系统的事件传递机制
实现底层驱动程序与用户进程通信的最主要的函数是input_event(struct input_dev * dev,unsigned int type,unsigned int code,int value),也是input输入子系统的核心,其实现机制如下。
Linux系统在启动过程中会向系统核心注册input_handler,一般将其称为handler处理器,表示对输入事件的具体处理,input_handler为输入设备的功能实现了一个接口。在执行input_register_device注册输入设备的时候,会自动将input_dev结构与系统中已注册的input_
handler进行遍历匹配。与对应的input_handler成功匹配后,Linux内核自动创建evdev结构体来表示输入事件设备,该结构中包含了input _handle等字段,作为连接input_dev与input_handler的媒介。其中Linux内核中与键盘设备匹配的input_handler代码为:
static struct input_handler evdev_handler={
.event=evdev_event,
.connect=evdev_connect,
.disconnect=evdev_disconnect,
.fops=&evdev_fops,
.minor=EVDEV_MINOR_BASE,
.name=“evdev”,
.id_table=evdev_ids,
};
evdev_event函数为事件处理函数,输入设备所上报的事件通过evdev_handler中的evdev_event函数包装成input_event标准输入格式,并存放在evdev下的evdev_list缓冲区中,该结构代码如下:
struct input_event{
struct timeval time; //事件发生的时间
__u16 type; //事件类型
__u16 code; //子事件
__s32 value; //事件发生的相关值
};
用户进程读取键盘事件时即会按照此种特定格式进行。值得注意的是,当读取事件为鼠标输入时,需要先后读取X轴坐标和Y轴坐标两种数据,以完成完整的读取操作。
在Linux系统中,所有的外设都是通过虚拟文件系统向应用程序提供接口,所以每个具有独立功能的外设在Linux系统中都对应着相应的设备文件。同时,在内核中代表设备文件的结构体包含了实现该设备功能的特定操作函数。
完成驱动模块的安装之后,Linux系统会在/devr目录下自动创建输入事件设备文件,本文中该设备名为event0。用户进程打开对应的输入事件设备文件event0,即可执行相应的文件操作,如rcad、ioctl等。文件操作函数最终要进入内核,并调用存储在事件设备结构体中的
evdev_handler.evdev_fops操作函数集完成对应的文件操作。
例如用户进程在执行rcad操作时,会调用内核中evdev_fops->evdev_rcad函数,先判断当前输入事件设备缓冲区中是否有待读取的input _event事件。若缓冲区中无按键事件,进程则放入等待队列进行睡眠,直到有按键事件产生并保存到缓冲区后,将睡眠进程唤醒,调用copy_ to_user复制函数完成输入事件从内核空间到用户空间的拷贝,从而实现读取操作。
结语
通过以上分析可以得出,键盘设备所产生的输入事件以input子系统为传递介质,并通过虚拟文件系统接口得以通知用户进程。本文从键盘的驱动开发出发,呈现了较为完整的输入事件由内核空间传递到用户空间进程的过程,对于驱动开发者了解底层驱动的机制和更加有效地设计驱动模块有着较为重要的意义。经过测试,该键盘具有良好的响应特性,并实现了所预期的功能。