⼀个嵌⼊式Linux系统的键盘驱动实现
1 引⾔
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 软件模块描述
初始化部分。这部分包括硬件层和软件层上的初始化。在本例中,需要先对矩阵电路和SN74hc164芯⽚所使⽤到的GPIO端⼝作配置,以使CPU可以对它们进⾏控制和访问。为了要将某个GPIO端⼝配置成输⼊输出或者是中断源,需要在对应的GPIO控制寄存器中设置正确的值,具体的值可以通过查阅S3C2410开发板⼿册来获得。⽐如,为了将GPB1设置成SN74hc164的输⼊端,需要将GPBCON这个控制字中2,3两位设置成⼆进制的01,为了将GPG6设置成电压低跳变中断源,需要将GPGCON中12,13两位设置成⼆进制的10。在完成了硬件初始化操作以后,就是软件层上的初始化了。⾸先将键盘中断处理函数注册到系统,然后设置好⼀个定时器结构,以便在中断发⽣时将其挂到内核的定时器队列中去,该定时器将触发对键盘的扫描操作。最后通过SN74hc164将矩阵电路的16列置零。
中断处理部分。如前所述,这部分软件应该完成的⼯作就是扫描特殊键盘,确定哪个键被按下,并且拿到稳定的扫描码,然后调⽤内核导出函数handle_scancode。在这个应⽤中,该特殊键盘的布局与PC标准键盘的布局⽐较相似,所以我们直接将PC键盘上对应键的系统扫描码作为我们特殊键盘上各个键的扫描码,同时我们将PC键盘驱动程序中扫描码到键码的转换函数pckbd_translate作为我们的
kbd_translate函数。
确定哪⼀个键被按下的算法如下。在中断到来时,我们已经可以根据中断号确定被按下的键在哪⼀⾏,我们还需要确定被按下的键在哪⼀列。为此,我们先给串联的两个SN74hc164芯⽚送⼀个CLR信号,清零,然后送16个1,使得特殊键盘的列均为⾼电位,此时我们在键盘的⾏端⼝读到的都是⾼电位。在16个时钟脉冲下,给SN74hc164芯⽚送⼊1个0和15个1,使得0在每⼀列上都唯⼀出现⼀次,于此同时在键盘⾏端⼝进⾏扫描。当被按下键所在列置0时,其所在⾏就会读到⼀个低电位。使⽤这种“⾛0法”,我们就可以确定出键盘上哪个键被按下了。但是这种简单的扫描算法还不够,因为在这种类型的矩阵扫描键盘中,键的每次按下和抬起都会有10~20ms(这段时间的长短由硬件特性决定)的⽑刺抖动存在,如图2所⽰,所以为了获取稳定的按键信息,必须要想办法去掉这种抖动,才能避免将⽤户的⼀次按键误当作⼏次按键来处理。去⽑刺的⼀种常见的⽅法是在有键盘中断到达时,并不⽴即去扫描键盘,⽽是先等待⼀段时间,等跳过⽑刺抖动以后再去扫描键盘,其伪代码如下所⽰:
等待⼀段时间,跳过抖动;
扫描键盘;
if 键盘上没有键被按下linux内核设计与实现 pdf
结束返回;
if 键盘上有键被按下
再次等待⼀段时间然后检查同样的键是否依然处于被按下状态;
if 同样的键任然是按下
将读到的扫描码返回;
else
直接返回;
这种解决⽅案固然可⾏,但是它使⽤了忙等的⽅法去⽑刺,在忙等期间,系统做不了任何有⽤的⼯作。这对于计算资源本⾝就很有限的嵌⼊式Linux系统来说,是⼀种奢侈的浪费。本应⽤中,我们设计了⼀种适合嵌⼊式系统的去⽑刺解决⽅案,使⽤效果良好。
由于Linux内核提供了定时器队列,所以我们可以使⽤这种机制来避免忙等,提⾼系统的性能。当键盘上有键被按下时,键盘中断处理程序⾸先关闭中断源,进⼊轮询模式,将⼀个timerlist对象挂⼊定时器队列以后就结束了。挂⼊内核的定时器按时地被触发,它所触发的函数完成以下⼀些⼯作:先对整个键盘上所有的键进⾏⼀次扫描,并且将扫描得到的结果保存到⼀个静态2维数组变量
snap_shot_matrix[16][4]中。该变量描述的是在本次键盘扫描的这个时刻,键盘上所有键的按下情况。如果某个键没有被按下,即处于松开状态,那么将snap_shot_matrix中对应的值置为0,如果某个键处于按下状态,那么将snap_shot_matrix中对应的值作⾃增1操作,若该值在⾃增1以后⼤于某个预先指定的数,我们就可以认为这是⼀个稳定值,并且将另⼀个⼤⼩为16*4的2维数组变量current_matrix对应坐标中的值置1,否则置0。这个变量描述的就是当前键盘上按键情况的稳定值了。也就是说我们⾸先把在本次扫描中得到的采样数据作处理以后保存到snap_shot_matrix中,然后依据该变量中的值,过滤得到current_matrix,通过这样⼀个过程来做去⽑刺处理。在得到了本次扫描的稳定值current_matrix以后,我们将其与上次得到的稳定值previous_matrix作⽐较,从⽽确定与上次扫描时相⽐,此刻键盘上的按键情况是否发⽣了变化,以及此刻键盘上是否有键按下。如果发现键盘上没有任何键被按下,则打开键盘中断,再次切回到中断模式。如果键盘上有键被按下,并且是不同于上次扫描到的被按下键,我们⽴刻调⽤按键处理函数process_key,它会调⽤键盘驱动中的上层函数handle_scancode。如果键盘上按下的键就是上次按下的那个键,我们将递增⼀个计数器,当这个计数器达到某个指定值以后,我们就启动所谓的Auto repeat功能,即⽤户⼀直按着某个键,驱动程序⾃动重复产⽣键盘输⼊。该计数器在被按下键发⽣变化时置0。但是只要键盘上仍然有键处于被按下状态,我们就将当前读到的键盘稳定值current_matrix拷贝到previous_matrix中去,并且再次将前⾯描述的定时器对象挂到内核定时器队列中,过⼀段时间以后再次扫描整个键盘,直⾄键盘上没有键被按下。
4 结束语
随着信息社会以及计算机软硬件技术的进步,嵌⼊式信息产品的设计和应⽤得到了迅速的发展,需要为⾃⼰的嵌⼊式Linux系统添加特殊键盘驱动的需求也越来越普遍。本⽂在介绍了Linux中键盘驱动程序的整体框架以后,以S3C2410开发板上的⼀个特殊键盘为例⼦,重点描述了在嵌⼊式Linux环境下,为特殊键盘编写驱动程序时需要完成的⼯作,为类似的开发提供了⼀种思路和参考。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。