[摘要]本文在mini2440开发板的基础上,实现了一种基于轮训方式的按键驱动,并通过应用程序来验证了该驱动.mini2440是一款基于三星公司ARM9芯片s3c2440与linux操作系统2.6.14内核的开发板,故而该驱动稍加修改就可以应用于其它平台.大多数简易按键驱动都是通过中断方式来实现的,但是由于我还没有找到通过中断方式来实现加速键的方法,所以采用轮训方式.
关键字:arm9 ,linux2.6,按键,轮训
1概述
在linux下面编写键盘驱动有两种方法:一、通过linux系统提供的输入子系统接口;二、把键盘驱动当成一个简单的字符型驱动来处理。这两种方式各有利弊,其中方法一看起来更专业,与内核的接口更加紧密,但是这样处理相当复杂,必须得有深厚的驱动编写功底和经验,由于我还处于入门学习阶段,所以采用第二种方法,把键盘驱动当成一个字符型驱动,利用linux提供的字符驱动接口函数,来实现这个功能。
该键盘驱动在内核层注册一个周期定时器,定时周期到时,MCU就采集一次键盘的按键状态,由于按键是连接到MCU普通I/O口的,所以MCU可以通过读取I/O口的数据来获取键盘值,并把键盘值放入到内核中的一个环形缓冲里面,同时该键盘驱动为用户空间提供读取接口,应用程序可以通过该接口来读取内核空间键盘缓存中的值。此外,该驱动程序还为应用层提供了其它的必要的接口。
2驱动的具体实现
2.1 驱动的注册与注销
按照linux驱动的规则,在编写字符设备驱动的开始,需要声明一个字符型设备的结构体,在该驱动中,keypad.h文件定义了该设备机构struct KeyPad_dev;在keypad.c中声明了一个该结构的字符设备指针KeyPad_devices。
在keypad.c文件中,函数KEYPAD_init_module()用于注册该设备,其中该函数有几个重要的语句需要注意下:
1> Keypad.c第301行,该行调用MKDEV函数来获取一个设备号dev;
2> Keypad.c第302行,知道设备号dev和定义的设备名称”KEYPAD”后,即可调用该行register_chrdev_region函数来注册该设备;
3> Keypad.c第309行,通用kmalloc函数为该设备分配内核空间;
4> Keypad.c第317行,通过调用cdev_init函数把设备和设备的方法KeyPad_fops结合起来。
5> Keypad.c第322行,通过cdev_add函数添加设备到系统。
上述几个重要步骤是linux2.6内核下面字符型设备注册的通用方法,具体细节需参考驱动的相关方面资料。当我们把这个键盘字符驱动配置成模块加载的时候,KEYPAD_init_module()就会被调用,相反当我们需要卸载这个模块的时候,KEYPAD_cleanup_module()函数将别调用,该函数实现了和KEYPAD_init_module()函数相反的功能,具体见keypad.c文件。
6> Keypad.c第297行,该行调用我自己的键盘初始化函数initButton(),该函数把6个按键所对应的I/O口设置成了输入端口模式:见mini2440开发板按键相关的原理图:
图1 mini2440按键原理图
initButton()函数相当简单,需要注意的是,由于在内核空间不能直接向物理地址读写数据,所以需要调用linux提供的函数readl(),writel()系列函数,用来对MCU的寄存器进行操作。
2.2驱动的open方法
当应用层调用open函数打开键盘设备keypad时候,键盘驱动的open方法就会被调用,下面我们分析这个函数:观察KEYPAD_open函数,最重要的地方就是该函数调用了KeyScan()函数,而KeyScan()函数分别调用GetKey()函数和SetTimer()函数:
其中GetKey()函数通过检查与键盘相连的I/O口的数据来判断采样点的键盘值KeyValue,如果此时无键按下,观测图1即KeyValue等于0x3f,则函数直接返回;否则把键盘值放入到键值环形缓冲T_kvb.buf[T_kvb.tail]中,并且通过wake_up_interruptible()函数唤醒因获取键值不可得而休眠的进程,其中kill_fasync()函数用于异步I/O操作,具体见相关的知识。
SetTimer()函数用于设置下次键盘采样的延迟时间:delay_timer.expires = jiffies + 5,即当前时间滞后5个时间片。而delay_timer为该键盘驱动函数注册的内核定时器,具体实现参考相关内容。
2.3 驱动的read方法
Read方法为驱动程序提供给应用层的最主要的方法,应用程序通过调用该接口函数来读取键盘缓冲内的值,并进行相关的处理。
见KEYPAD_read()函数,第174行:当T_kvb.head 等于T_kvb.tail即键值缓冲无键值内容的时候,如果应用层以O_NONBLOCK方式打开键盘设备,则调用read方法的进程将不会阻塞,而直接返回-EAGAIN状态;否则调用read函数的方法将阻塞并加入到等待队列dev->wq上面,直到有新的键值被采集到,则应用程序进程继续运行,并调用GetKeyFromBuf()函数,观察该函数,知道它的功能为从键值缓冲里面读取键盘值,见keypad.c第135行。
如果应用程序调用read方法的时候,驱动程序的键值缓冲恰好有内容,则应用程序不会被阻塞,将自己读取键值缓冲内容,并通过copy_to_user()函数把键盘值拷贝到应用层。
2.4 驱动的ioctl()方法
Iotctl()方法也是一个重要的方法,应用程序主要通过该方法来控制和侦测设备的状态,在这里我利用该方法是方便应用程序用该方法来了解设备的状态而不被阻塞。
此外本键盘驱动函数系统还提供了其他重要方法:poll,fasync等,相关的详细内容见参考经典驱动教程,这里不一一累述。
3应用层的检验
应用层的检验函数采用了一般单片机的键值处理函数,见keytest.c源文件。首先打开键盘设备,然后通过ioctl函数来获取键盘的当前状态,如果当前键盘松开,则之前所有按键的计时清理,即key_cnt=0需要重新对按键累计计时;此外通过调用设备驱动程序的read方法来获取当前的键值key_tmp,并与上次的键值key_shw对比,如果key_tmp 等于key_shw
则说明保持了按键,则按键累计计时器key_cnt将自增,如果key_cnt增到一定的值,说明按键稳定,则应用程序通过pirntf函数把键盘值打印出来,并且此时人为的把key_cnt增加以实现加速按键的效果。如果按键不保持稳定即key_tmp 不等于key_shw,则需要对Key_cnt
与key_shw重新赋值。
4总结
本文提供了一个最基本的字符型按键驱动,由于自己的经验欠缺,还很不完善,从应用程序的测试结果来说,目前还存在以下问题:即判断键值的时间还不稳定,及加速键效果很别扭。还需要对驱动程序和应用程序进行不断的该进,也许通过这种轮训方式本身就不合适,请批评指正!
5参考文献
1.《linux设备驱动程序》第三版
6源文件
1>keypad.h
2>keypad.c
3>keytest.c