1、第九章 Keypad接口设计与驱动开发,本章要点使用基本的逻辑器件,设计Keypad的外围电路 根据外围电路编写相应的驱动程序 在驱动程序中使用等待队列、定时器,9.1 最小硬件系统设计,嵌入式系统和用户进行人机交互时,最基本的输入设备是按键或者键盘。简单的应用可以选用按键,比较复杂的应用可以选用键盘。不论是最简单的按键,还是比较复杂的键盘,总的说来,都是通过0、1来代表按键状态的变化。按键以0、1代表“断开”和“闭合”,键盘以0、1的序列代表按键的键值。,通常,CPU获知按键状态变化的方式有两种:查询式和中断式。当采用查询式时,CPU定时(例如每隔0.2s)查询IO接口的状态,当IO接口的状
2、态有变化时,系统认为有按键被按下,并读取键值。当采用中断式时,一般将按键电路连接到一个逻辑器件,再将此逻辑器件输出端连接到CPU的中断输入引脚。当有按键被按下时,与键盘电路相连的逻辑电路触发中断,CPU执行中断服务程序将键值读入内存。,查寻方式电路简单,但是占用较多CPU资源;中断方式节省CPU资源,但电路稍复杂,并且占用CPU中断资源。通常在嵌入式系统中,按照情况区别对待。对于功能要求简单的应用场合,一般采用查询式的按键电路,而对于功能要求复杂的应用场合,一般采用中断式的键盘电路。本章节主要介绍查询式的按键电路设计和驱动程序开发。,嵌入式系统课件,5,本章节所设计的简单键盘通过双向收发器和3
3、8译码器实现,如图9-1所示。双向收发器将Keypad电路连接到PXA255的数据总线,3-8译码器连接着地址总线和异步静态存储器的片选信号nCS2,它的输出充当双向收发器的使能信号,由KEY-CS#表示。,9.1.1 Keypad接口设计,3-8译码器 使能,nCS2#,SA-D0:7,SA-A20:22,PXA255,KEY-CS#,键 盘,双向 收发器,使能,图9-1 键盘与处理器接口框图,其它设备,嵌入式系统课件,7,Keypad电路中采用了标准的3-8译码器74LCXl38。 3-8译码器的工作原理。键盘的使能电路如下图:,9.1.2 Keypad电路原理,嵌入式系统课件,8,KEY
4、-CS#片选信号连接着双向收发器的使能端,具体电路见图9-3:,图9-3 键盘的电路原理图,由图9-3可以看到,当KEY-CS#片选信号为低电平时,按键信息透过双向收发器74LCX245送到PXA255数据线SA-DOSA-D7。当没有键按下时,SA-DOSAD7读入的键值为“0xFF”。 当有键按下时,SA-D0SA-D7读入的键值对应的位为“0”。KEY-CS对应着3-8译码器的Y2脚,当该脚使能时,它对应的输入SA-A22:20为010, 也就是地址0x00200000。由于SA-CS2#的物理地址是0x08000000,故最终KEY-CS信号的物理地址是:0x08000000+0x00
5、200000=0x08200000。,在驱动程序中,假定KEY-CS的物理地址0x08200000对应的虚地址为0xf8200000。由上面的介绍可知,当KEY-CS#片选信号为低电平使能时,数据通过74LCX245传送到数据总线上,因而可以用如下宏定义读键值(也就是从KEY-CS对应的地址读取数据):#define KEY_CS(*(volatile unsigned short *)(0xf8200000)其中,volatile关键字是一种类型修饰符,用它声明的变量不会被缓存在寄存器中,也不会对它做常量合并、常量传播以及读/写优化等。在嵌入式Linux中,与I/O存储器访问相关的变量都应加
6、volatile类型修饰符。,加volatile修饰符是为了保证读取数值的正确性。因为KEY-CS地址的数据是随时可能发生变化的,所以每次使用它时,必须从地址中直接读取。如果不使用volatile声明,则编译器生成的汇编代码会做一些优化。编译器如果发现有连续两次从同一地址读取数据的代码,而它们之间没有对该地址进行写操作的代码,那么编译器会自动地把上次读取的数据放在寄存器中作为第二次读取的数据,而不是重新从该地址里面读取。这样一来,读取的键值就不是当前最新的键值,因此不能对这类的I/O操作进行优化。,9.2 Keypad软件驱动原理,在驱动程序中,注册函数、撤销函数、虚拟文件接口函数及其结构体构
7、成了驱动程序的基本框架。 本节将首先介绍注册函数、撤销函数、内核的接口函数及其结构体,然后介绍接口函数的具体功能。这些函数中最核心的部分是读取键值的函数。,嵌入式系统课件,13,在加载模块时,首先运行的是内核模块的注册函数。它的功能包括向内核注册设备以及变量的初始化。内核模块的注册函数如下:,9.2.1 内核模块的注册和撤销,嵌入式系统课件,14,与注册相对应的就是撤销。内核模块的注销函数的主要功能是释放资源和注销设备。内核模块的注销函数如下:,嵌入式系统课件,15,Keypad是字符型设备,通常直接使用file_operations接口。Keypad设备接口函数的定义:,9.2.2 虚拟文件
8、系统与硬件驱动的接口,在应用程序中,可以通过调用open()、read()、close()等通用函数,进而调用这里定义的接口函数,完成某种功能。,嵌入式系统课件,16,设备打开操作接口函数可以完成两大操作,一是完成必要的设备初试化,而是设备引用计数加1。设备的初试化是由read_xy()函数完成的, read_xy()函数有2个子函数:new_data()和Keypad_starttimer() 。 它们分别负责获取键值和开启内核定时器。具体如下:,9.2.3 设备打开操作接口函数,嵌入式系统课件,17,读取设备的函数比较复杂,涉及等待队列、内核定时器等机制。函数的主要作用就是从缓冲区中取出键
9、值。该功能主要通过调用get_data()实现,然后通过copy_to_user()函数复制键值到用户数据区。具体见P226:,9.2.4 设备读取操作接口函数,嵌入式系统课件,18,设备关闭接口函数可实现3个功能:关闭设备异步通知;设备计数器减1;删除定时器信号中断;最后函数返回0。,9.2.5 设备关闭操作接口函数,嵌入式系统课件,19,获取键值子函数new_data()从KEY_CS对应的地址读入键值,存人环形缓冲区buf。环形缓冲区的写指针是head,读指针是tail。cur_data.click=1;1代表有键按下,0则代表没有。cur_data.status=x;x代表被按下的键。
10、具体程序见P227。,9.2.6 获取键值子函数,嵌入式系统课件,20,每读出一个缓冲区,读出指针tail就加1。代码如下:,9.2.7 读缓冲区子函数,嵌入式系统课件,21,在Keypad的驱动程序中,对键盘键值的获取是以0.2 秒为周期执行的,需要用到内核定时器。源代码如下:,9.2.8 内核定时器的使用,嵌入式系统课件,22,配置timer_list结构体可以完成“在未来某一个特定时刻执行某个特定任务”的功能。该结构在includelinuxtimer.h头文件中定义。timer_list的结构如下:,嵌入式系统课件,23,在本驱动程序中,首先通过init_timer(timer)初始化
11、定时器结构,配置timer结构体的function和expires;最后通过使用add_timer(&timer)语句,将定时器插入活动定时器的全局队列中。当定时器达到超时时限时,会调用定时器服务程序function。,嵌入式系统课件,24,利用等待队列实现阻塞型Io,9.2.9 利用等待队列实现阻塞型I/O,在用户程序执行读操作时有可能尚无数据可读,为此,需要让read操作等待,直到数据可读,这就是阻塞型I/O。阻塞型I/0可使用进程休眠的方法实现。在无数据可读时,采用等待队列让进程休眠,直到数据到达才唤醒进程完成数据的读操作。Keypad驱动的读操作就是采用等待队列配合内核定时器实现这种键
12、值读取的阻塞唤醒机制。在read()函数中,若缓存中没有按键数据。则进程采用等待队列机制进入休眠态。定时器函数每隔0.2s读取一次按键状态,将按键状态放入缓存并适时唤醒进程读取数据。,嵌入式系统课件,25,利用等待队列实现阻塞型Io,等待队列的使用流程如下:声明一个等待队列;把当前进程加入等待队列;将进程态设置为TASK_INTERRUPTIBLE或 TASK_UNINTERRUPTIBLE;调用schedule()让出CPU;检测所需资源是否可用,若是,则把当前进程从等待队列里删除,否则返回。设备读操作接口函数Keypad_read()中与等待队列相关的部分代码见P230 。,嵌入式系统课件
13、,26,当用户程序用open()函数打开设备时,调用了驱动的Keypad_open()函数,启动内核定时器。这使内核定时器函数Keypad_timer()每隔0.2s得以运行一次。Keypad_timer()函数调用read_xy(),并由它调用new_data()。在new_data()获取键值并在函数的最后一句时,唤醒了进程。wake_up_interruptible(&queue);,嵌入式系统课件,27,利用等待队列实现阻塞型Io,9.2.10 poll系统调用操作接口函数,当程序需要进行对多个文件读(写)时,如果某个文件没有准备好,则系统就会处于读(写)阻塞的状态,这影响了其他文件的
14、读/写。为了避免读(写)阻塞,一般可以在应用程序中使用poll()(或select()函数。当poll()函数返回时,会给出一个“文件是否可读/写”的标志,应用程序根据不同的标志读(写)相应的文件,来实现非阻塞的读/写。poll()函数通过po11()系统调用,调用对应设备驱动的poll()接口函数。poll()接口函数通过返回不同的标志,告诉主进程文件究竟是否可读/写。这些返回标志及其含义在include/asm/poll.h定义如P232:,嵌入式系统课件,28,利用等待队列实现阻塞型Io,9.2.11 在设备驱动中实现异步通知,虽然大多数时候阻塞型和非阻塞型操作的组合及poll() /s
15、elect()方法可以有效查询设备是否可读/写,但是如果驱动程序能避免主动的查询,改主动的查询为被动的信号通知触发,则可以提高程序的效率,这也就是异步通知的目的。异步通知向进程发送SIGIO信号,通知访问设备的进程,这表示设备已准备好I/O读/写了。要启动异步通知,必须执行两个步骤。首先,须指定某个进程作为文件的“属主”。文件属主的进程ID保存在 flip-f_owner中,这可以通过fcntl()系统调用执行F_SETOWN命令设置。此外,用户程序还必须设置设备的FASYNC标志,以真正启用异步通知机制。这里的FASYNC标志也是使用fcntl()设置。,嵌入式系统课件,29,利用等待队列实
16、现阻塞型Io,在完成这两个步骤之后,当新数据到达时就会产生一个SIGIO信号,此信号发送给存放filp-f_owner中的进程。从驱动程序的角度看,则主要是通过调用以下两个内核提供的函数来实现(它们定义在include/linux/fs/fcntl.h里):,嵌入式系统课件,30,利用等待队列实现阻塞型Io,编写驱动时要注意,应在设备关闭操作接口函数release()中(此函数在用户程序关闭Keypad设备时被调用)调用fasync方法从活动的异步读/写进程列表中删除该文件。它们的代码如下:,9.3 键盘信息读取应用实例,键盘信息读取程序的主要功能是完成Keypad设备的打开、键值的读取和设备
17、的关闭。程序的主体是一个条件循环。主循环体函数每执行一次,就读取一次键值。当有新按键被按下时,测试函数将打印出该键值。,嵌入式系统课件,32,利用等待队列实现阻塞型Io,1. 打开Keypad设备,因为使用标准的接口,所以打开Keypad设备和打开普通的设备一样使用open()函数即可。如果open()函数返回的值大于0,则说明打开设备成功。代码见P235 。,2. 读取Keypad键值,Keypad设备是一个字符设备, 键值的读取使用标准的read()函数即可。代码见P235 。,嵌入式系统课件,33,利用等待队列实现阻塞型Io,3. 关闭Keypad设备,与打开设备和读取字符设备一样关闭设备也使用标准的close()函数。代码如下:/close the deviceclose(fb);printf(“Good Bye”);,嵌入式系统课件,34,OVER !,