基于ARM9+linux2.6内核的键盘驱动初探

[摘要]本文在mini2440开发板的基础上,实现了一种基于轮训方式的按键驱动,并通过应用程序来验证了该驱动.mini2440是一款基于三星公司ARM9芯片s3c2440linux操作系统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.c301行,该行调用MKDEV函数来获取一个设备号dev

2>     Keypad.c302行,知道设备号dev和定义的设备名称”KEYPAD”后,即可调用该行register_chrdev_region函数来注册该设备;

3>     Keypad.c309行,通用kmalloc函数为该设备分配内核空间;

4>     Keypad.c317行,通过调用cdev_init函数把设备和设备的方法KeyPad_fops结合起来。

5>     Keypad.c322行,通过cdev_add函数添加设备到系统。

上述几个重要步骤是linux2.6内核下面字符型设备注册的通用方法,具体细节需参考驱动的相关方面资料。当我们把这个键盘字符驱动配置成模块加载的时候,KEYPAD_init_module()就会被调用,相反当我们需要卸载这个模块的时候,KEYPAD_cleanup_module()函数将别调用,该函数实现了和KEYPAD_init_module()函数相反的功能,具体见keypad.c文件。

6>     Keypad.c297行,该行调用我自己的键盘初始化函数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,如果此时无键按下,观测图1KeyValue等于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.c135行。

如果应用程序调用read方法的时候,驱动程序的键值缓冲恰好有内容,则应用程序不会被阻塞,将自己读取键值缓冲内容,并通过copy_to_user()函数把键盘值拷贝到应用层。

2.4 驱动的ioctl()方法

Iotctl()方法也是一个重要的方法,应用程序主要通过该方法来控制和侦测设备的状态,在这里我利用该方法是方便应用程序用该方法来了解设备的状态而不被阻塞。

 

此外本键盘驱动函数系统还提供了其他重要方法:pollfasync等,相关的详细内容见参考经典驱动教程,这里不一一累述。

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设备驱动程序》第三版

2http://www.linuxdriver.cn/

 

 

 

 

6源文件

1>keypad.h

2>keypad.c

3>keytest.c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 引言 当前,由于Linux资源完全公开,使得Linux的发展日益广泛快速。基于Linux的各种应用已逐渐深入日常生活的方方面面,尤其是在嵌入式领域,由于内核可裁减定制,因此可随意地根据用户需求进行整个系统的定制与重构。其中,我们可以通过对各种标准外部设备的驱动进行改造,从而实现用户对标准设备的特定需求,例如可以通过对键盘的模拟来实现操作的自动化,从而可以避免重复的键盘操作。 2 Linux内核支持的外部调用接口 由于Linux内核作为系统最深层次的核心,因此外部的开发人员并不能直接对内核进行操作。然而在一些应用程序的开发过程中,又不得不使用内核的某些功能,因此就提供了一些外部接口供开发人员直接与底层内核打交道。 2.1 中断 在Linux 下,硬件中断叫做IRQ(Interrupt Requests)。有两种IRQ,短类型和长类型。短IRQ需要很短的时间,在此期间机器的其他部分被锁定,而且没有其他中断被处理。一个长IRQ需要较长的时间,在此期间可能发生其他中断(但不是发自同一个设备)。如果可能的话,最好把一个中段声明为长类型。如果CPU接到一个中断,它就会停止一切工作(除非它正在处理一个更重要的中断,在这种情况下要等到更重要的中断处理结束后才会处理这个中断),把相关的参数存储到栈里,然后调用中断处理程序。这意味着在中断处理程序本身中有些事情是不允许的,因为这时系统处在一个未知状态。解决这个问题的方法是让中断处理程序做需要马上做的事,通常是从硬件读取信息或给硬件发送信息,然后把对新信息的处理调度到以后去做。 实现的方法是在接到相关的IRQ(在Intel平台上有16个IRQ)时调用中断处理程序。这个函数接到IRQ号码、函数名、标志、一个/proc/interrupts的名字和传给中断处理程序的一个参数。标志中可以包括 SA_SHIRQ来表明你希望和其他处理程序共享此IRQ(通常很多设备公用一个IRQ),或者一个SA_INTERRUPT表明这是一个紧急中断。这个函数仅在此IRQ没有其他处理程序或需要共享所有处理程序时才会成功运行。 2.2 系统调用 系统调用发生在用户进程,通过一些特殊的函数来请求内核提供服务。这时,用户进程被挂起,内核验证用户请求,尝试执行并把结果反馈给用户进程,接着用户进程重新启动。一般当前系统的系统调用作为一张表sys_call_table进行定义的,是由指向实现各种系统调用的内核函数的函数指针组成的表。具体参数参见Linux内核源代码arch/i386/kernel/entry.S文件中: ENTRY(sys_call_table) l long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/ l long SYMBOL_NAME(sys_exit) …

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值