linux由于其具有内核强大且稳定,易于扩展和裁减,丰富的硬件支持等诸多优点,在嵌入式系统中得到了广泛的应用。很多嵌入式linux系统,特别是一些具有与用户强交互的嵌入式系统,往往需要配备一个特殊键盘,此时开发者需要根据实际情况,为自己的特殊键盘编写驱动程序。
2 linux键盘驱动简介
linux中的大多数驱动程序都采用了层次型的体系结构,键盘驱动程序也不例外。在linux中,键盘驱动被划分成两层来实现。其中,上层是一个通用的键盘抽象层,完成键盘驱动中不依赖于底层具体硬件的一些功能,并且负责为底层提供服务;下层则是硬件处理层,与具体硬件密切相关,主要负责对硬件进行直接操作。键盘驱动程序的上层公共部分都在driver/keyboard.c中。该文件中最重要的就是内核用export_symbol这个宏导出的handle_scancode函数。handle_scancode完成的功能是:首先将扫描码转换成键码,接着根据shift, alt等扩展键的按下情况将键码转换成目标码,一般情况下是ascii码,最后将该ascii码放到终端设备的缓冲区中,并且调度一个tasklet负责将其在显示器上回显出来。可以看出,这个函数完成的是键盘驱动程序中最核心的一些工作,而这些核心的逻辑功能是不依赖于底层硬件的,所以可以将其独立出来,并且导出给底层的硬件处理函数调用。在这个文件中还定义了其它几个回调函数,它们由键盘驱动程序中的上层公共部分调用,并由底层硬件处理函数实现。比如kbd_init_hw, kbd_translate, kbd_unexpected_up等等。其中kbd_translate由handle_scancode调用,负责将扫描码转换成键码;键盘驱动程序的底层硬件处理部分则根据不同的硬件有不同的实现。例如pc平台上标准键盘的底层硬件处理函数都集中在driver/pc_keyb.c中。这个文件包括了键盘中断处理函数keyboard_interrupt,扫描码到键码转换函数pckbd_translate等其他一些与底层硬件密切相关的函数。
在这种体系结构下,要添加一块特殊键盘到系统中就显得格外清晰。开发者只需为其编写驱动程序中的底层硬件处理函数,就可以将该键盘驱动起来。一般说来,底层硬件处理函数中最重要的工作就是在键盘中断处理中获取被按下键的扫描码,并且以它为参数调用handle_scancode,该扫描码可以自己定义,但它必须唯一地标识出被按下键在键盘上的位置。此外,开发者还需要提供对应的从自定义扫描码到键码的转换函数kbd_translate。具体的键码转换,将目标码放到终端的输入缓冲区,以及回显等工作都由handle_scancode负责完成。在此我们也可以看出,内核导出函数handle_scancode在整个键盘驱动程序中,起着将上层通用抽象层和底层硬件处理层粘和起来的关键作用。
3 应用实例
下面我们将以一个具体的应用实例来说明在嵌入式linux系统中给一个特殊键盘编写驱动程序的具体过程。
3.1 硬件模块描述
本系统的构建选用了三星公司的s3c2410开发板作为硬件平台。特殊键盘的硬件模块主要由两个sn74hc164芯片和一个4行16列的矩阵扫描电路构成。sn74hc164是一个8位的串形输入并形输出移位寄存器,它的内部由8个d触发器串联而成。其工作原理简单说来是这样的,sn74hc164芯片在时钟clk脉冲的上升沿将a,b引脚上的串形输入在8个时钟脉冲以后并行输出到输出引脚qa到qh。其真值表见图1所示。
两个sn74hc164芯片先串联后,将它们的clk引脚和clr引脚分别接到s3c2410开发板的gpb2和gpb4端口上,并且将第一个sn74hc164芯片的a,b引脚接到开发板的gpb1端口上,这三个gpio端口配置成输出端口。这样我们就借助于两个sn74hc164寄存器,实现了只占用3个gpio端口,给矩阵扫描电路的16列提供输入,从而既节约了成本,又避免了gpio资源的浪费。但这同时也给键盘驱动程序的实现带来了一定的麻烦,驱动程序首先要将sn74hc164驱动起来,然后才能对矩阵电路的16列进行控制。该矩阵电路的4个行引脚分别被接到s3c2410的gpg6,gpg7,gpg8,gpg9端口上,并且这四个端口被配置成中断源。无键按下时直接读为高电位,使用时通过sn74hc164芯片先将键盘的16列置低电位,任何一个键被按下,相应的行gpg端口就会有从高到低的电压跳变,从而触发一次中断。
3.2 软件模块描述
以上就是一个嵌入式linux系统的键盘驱动实现 (1)的内容。
