鼠标的原理控制以及编程联想研究院板卡中心臧超飞前言.鼠标作为现代计算机的一个标准配置已经深深的影响了计算机的使用者因此在BIOS中加入鼠标的功能将为计算机提供更好的人机接口为使用者使用计算机带来更大的方便不同类型的鼠标主要不同的表现在数据传输的协议上主要的协议有四种类型1P S/22S erial3U SB4A DB第一部分鼠标与系统架构图1鼠标与系统架构图一是一个典型的现代PC的鼠标配置架构在最底层的硬件层鼠标作为一个单片机通过PS/2协议和系统的KBC进行通讯传递数据和命令第二层BIOS通过KBC上提供的60H和64H口和鼠标进行传递信息由此可以看出KBC封装了PS/2协议使得PS/2协议对于底层编程而言是通明的同样我们本文的主要内容也是基于KBC之上而跨越PS/2协议的第三层BIOS提供了基本的INT15H中断的调用使用者可以通过这个中断的调用给鼠标发送一些基本配置的命令注意INT15H没有提供鼠标的运行命令和数据接受接口第四层次系统Driver可以通过BIOS的INT15H的中断调用或者直接通过KBC的60H64H口控制鼠标同时向上为操作系统和应用程序提供服务第二部分PS/2协议以及鼠标的基本命令1信号定义PS/2协议主要包括两根信号线如图2CLK和DATA还有+5V的电源线和鼠标线DATA线是半双工的正常状态下CLK和DATA被主机端的一个510K的电阻拉高到5V见图3但是鼠标和主机KBC在任何时候都可以将这两个信号拉低当端口处于空闲的状态是LCK和DATA线都处于高电平主机可以在任何时候通过拉低CLK信号切断设备图2PS/2 电缆以及接口信号定义图3鼠标系统原理图2数据传输数据的传输是一个字节一个字节Byte传输的对于每一次的数据传输包括一个开始位a logic 08个数据内容的比特bits一个奇偶校验位odd parity和一个停止位 a logic0我们可以可以很容易看出8个数据位和奇偶校验位一起那么其奇偶性必定是奇的在传输过程中设备首先将CLK信号拉低产生CLK信号传输这十一个位bits传输设备通过拉高或者悬浮DATA信号来传输logic 0或logic 1在传输过程中设备可能处于三种传输状态a.空闲态Idle这时CLK信号和DATA信号都处于高电平态总线上没有传输行为b.抑制态Inhibit这时主机将CLK信号拉低设备将被从数据传输中切断c.请求传输Request to send这时主机将DATA信号拉低报纸CLK信号那么主机就是准备发送命令或者命令参数(1)输出到主机3命令集如果鼠标以及处于流模式Stream Mode而且已经被Enable$F4命令激活那么在给鼠标发送任何命令之前必须先将鼠标Disable$F4这样可以保证命令响应以及数据传输的完整性这一点非常类似于我们在C语言读写文件中间一定要注意关闭文件用以保证将缓冲区中的内容写到磁盘上因此如果我们在这种模式下发送了命令那么鼠标将会放弃而不是中断所有的数据传输包的传输以及命令的响应$FF复位命令Reset这个命令将引起鼠标的软件复位和重新校准鼠标的回应信号是一个ACK FA接着一个AA在重新校准后的300500ms给出00信号$FE重传命令Resend$F6设置缺省值$F5Disable$F4Enalbe$F3设置采样率$F2读设备类型$F0设置远程模式$EB读数据$EA设置流模式Set Stream Mode$E9状态查询$E8设置分辨率$E7设置二维坐标$E6取消二维坐标第三部分KBC以及命令和数据的接收过程前一部分我们介绍了常用的标准的PS/2,其中设计到了很多信号发送响应以及确认等复杂的过程但是对于实际编程而言复杂的工作已经被8042的芯片所封装了因此我们所要做的工作就是了解8042的接口以及操作方法(图4)8042的接口1.8042的接口从图4可以看出8042是一个非扩展的键盘控制器支持一个键盘和一个鼠标辅助接口典型的8042具有一个输出缓冲接口Output Buffer和一个输入缓冲接口输出缓冲是一个8位的只读Read only寄存器我们通过60H访问这个寄存器如果8042从键盘或鼠标读到一个数据信息放到了缓冲区中同时将状态寄存器见图5bit1置1表明此时缓冲区中有数据而且对于鼠标输入而言同时会将状态寄存器的bit5置1表明这个数据是来自鼠标而不是来自键盘输入缓冲是一个8位只写寄存器我们是通过60H和64h口来访问这个缓冲区的我们往64H中写入一个字节8042将它解释成一个命令字节表现在8042自动置状态寄存器bit3为1如果写往60口将被8042解释位数据即是命令的参数表现在8042自动置状态寄存器bit3为0从前面可以看出我们对于KBC是的操作是通过两个I/O端口60H和64H口由此访问系统的输入缓冲和输出缓冲同时又要控制鼠标和接口所以我们的操作要有严格的规范同时我们还要参照8042提供的状态寄存器操作的规范是如果我们60H写入一个字节那么这个字节被送入输入缓冲区且被解释成数据因为输出缓冲是一个只读寄存器所以不会写入输出缓冲同样因为输入缓冲是一个只写寄存器所以我们如果从60H读一个字节那么这个字节将是输出缓冲区的内容而不是我们写往输入缓冲区中的内容同样我们如果写往64H将被解释为一个命令字节我们如果读64H将会读到状态寄存器的内容而不是我们写入的命令的内容图58042状态寄存器因为8042的状态寄存器是一个只读寄存器因此它的内容是8042根据外部条件自动设置的我们重点说明bit0和bit5我们的操作是必须满足同时满足这两位为1才能说明我们从60H读出的数据是来自鼠标否则只根据bit5来判断没有任何的意义28042的命令我们首先说明60H命令和D4H命令这两个命令我们知道当8042从键盘或者鼠标收到一个数据的时候它将状态寄存器的内容同时置位之后向系统发出一个中断如果是键盘则发出IRQ9如果是鼠标那么就发出IRQ12在这里60H命令给我们提供了一个控制这两个中断的接口例如如果我们取消键盘中断我们可以这样写MOV Al60HOUT STAT8042, ALMOV AL, 66HOUT DATA8042,AL其中我们定义STAT8042EQU64H和DATA8042EQU60H首先我们向64H写入60H接着在往60H写入一个字节这个字节将被解释成一个命令参数参数的意义详见图60D4H命令是也是一个带有参数的命令这个命令使得我们可以通过8042向鼠标发送命令例如我们如果想向鼠标发送一个RESET命令即是FFH我们可以这样操作MOV Al0D4HOUT STAT8042, ALMOV AL, 0FFHOUT DATA8042,AL这里我们首先向64H发送一个0D4H命令那么接下来我们送往60H的命令将被送往鼠标当然以上的两个小例子只是简单说明我们编程控制的原理而没有实际的可行性在实图68042的命令际的编程过程中我们要检测状态寄存器的内容决定命令的发送;***************************************************************************** Set_D4_KC_MS PROC NEARmov cl,0d4h; set command bytemov al,0f6h; forced valuecall Send_Command_to_KCjnz short sdkm_err ; error if input buffer fullclcjmp short sdkm_retsdkm_err:stc续图68042的命令sdkm_ret:retSET_D4_KC_MS ENDP;*****************************************************************************上面这段程序是一个设置鼠标初始置的一个例程其中调用了例程Send_Command_to_KC这个例程将把0D4H CL中送往64H而把0F6H送往60H进而送到鼠标;****************************************************************************** ; Procedure Name:Send_Command_to_KC; Call by OUT_AUX,; Inputs:cl = command byte; al = data byte; Output:zf = 0 : keyboard controller error;zf = 1 : successful; Destroy:ax, bh, cx;****************************************************************************** Send_Command_to_KC Proc Nearpush bx; save BXmov ah,20h ; try again countpush cxpush axTry_Again:cli; Disable interruptspop ax ; restore ax, cxpop cxpush cxpush axmov al,cl ;Save commandcall out_8042jnz short Can_Not_Complete_Commandin al,stat8042 ;Read statusjmp $+2 ;IO delayand al,OBF_MS ; AUX OBF+ KB OBFcmp al,OBF_MS ; MOUSE Data ?jz short Clear_Aux_OBFtest al,OBF_8042; Keyboard OBF ?jz short Ready_to_Send; Jump if zerosti; Enable interruptspop axdec ahpush axjnz short Try_Again ; Loop if zf=0, cx>0jmp short can_not_complete_commandClear_AUX_OBF:in al,DATA8042 ; error recoverReady_to_Send:pop axpush axout DATA8042,al ;Send dataxor ah,ah ;Set zero flagstiCan_Not_Complete_Command:pop axpop cxpop bx; restore BXretSend_Command_to_KC EndP本例程尝试20H次完成这个任务首先调用把0D4H CL放入AL中然后调用例程OUT_8042完成这个命令的发送如果完成不了则退出保持标志位ZF位1接着检查鼠标口有没有数据如果有那么取出这个数据然后完成发送如果没有那么检查键盘端口有没有数据如果没有那么可以发送如果有那么打开中断使得键盘的程序可以取走数据然后进行一轮新的循环其中调用的例程out_8042的代码如下;[]=============================================================[]; Procedure Name:out_8042;;Output a command to 8042 port 64h;; Inputs:al = command byte; Output:zf = 0 : keyboard controller error;zf = 1 : successful; Destroy:ax, cx;[]=============================================================[] PUBLIC OUT_8042OUT_8042PROC NEARmov ah,al; save alcall Buffer_8042_Full; wait till buffer empty, thenjnz out_8042_retmov al,ahout STAT8042,al; send commandcall Buffer_8042_Fullout_8042_ret:retOUT_8042ENDP;[]=============================================================[]在这个例程中首先检查输入缓冲是否为空如果不是空的说明还有其他的任务键盘或者鼠标的没有完成我们必须等待如果空了那么我们就可以进行数据的输入了输入以后我们再等待8042对于数据的反映如果完成则退出如果没有完成那么继续等待如果中间出现zf的变化则表明是出现了处理的错误其中调用的Buffer_8042_Full的例程如下;[]=============================================================[]; Procedure Name:buffer_8042_full;Check for 8042 input buffer full; Saves:; Inputs:none; Output:al = status register value;zf = 0 : input buffer full;zf = 1 : no input buffer full; Destroy:al, cx;[]=============================================================[] PUBLIC BUFFER_8042_FULLBUFFER_8042_FULL PROC NEARxor cx,cxinrck1:in al,STAT8042newiodelayand al,IBF_8042loopnz short inrck1retBUFFER_8042_FULL ENDP;*****************************************************************************很明显这里检查状态位的输入缓冲标志是否置1来判断8042是否对于缓冲作出了处理前面我们对于命令的发送的细节做了详细的讨论下面我们对于数据的接受过程做一下讨论原理上8042接到鼠标的数据会将数据放入键盘缓冲区同时去设置状态位的两个标志bit0置1表明输出缓冲区中有内容bit34置1表明这个数据是来自鼠标具体例程见下面;***************************************************************************** ; Procedure Name:get_data;receive data from mouse if Auxiliary device Output Buffer Full; Saves:; Inputs:none; Output:zf = 1 : no data received;zf = 0 : successful;al = data byte received; Destroy:ah;*****************************************************************************GET_DATA PROC NEARpush cxxor cx,cxgd_loop:push cxcall out_aux_full; check AOBFpop cxjnz short receive_dataloop short gd_loopjmp short gd_retreceive_data:in al,data8042gd_ret:pop cxretGET_DATA ENDP;***************************************************************在此例程中首先判断是否有鼠标的数据如果有则接受如果没有则循环如果在0FFFFH 此后没有收到数据那么退出保留了标志位zf为1说明接受失败其中判断是否有鼠标输入引用例程out_aux_full;[]=============================================================[]; Procedure Name:out_aux_full;Check for 8042 auxiliary device output buffer full; Saves:; Inputs:none; Output:al = status register value;zf = 0 : auxiliary device output buffer full;zf = 1 : no auxiliary device output buffer full; Destroy:ax, cx;[]=============================================================[] PUBLIC OUT_AUX_FULLOUT_AUX_FULL PROC NEARcall out_8042_fulljz short no_aux_fulltest al,OBF_AUXno_aux_full:retOUT_AUX_FULL ENDP此例程首先是检查输出缓冲区是否为空如果在规定的时间内没有数据输入那么保留标志位ZF位1退出如果有数据那么再判断这个数据是否是来自键盘其中检查输出缓冲区是调用out_8042_full例程;[]=============================================================[]; Procedure Name:out_8042_full;Check for 8042 output buffer full; Saves:; Inputs:none; Output:al = status register value;zf = 0 : output buffer full;zf = 1 : no output buffer full; Destroy:ax, cx;[]=============================================================[] PUBLIC OUT_8042_FULLOUT_8042_FULL PROC NEARmov ah,12public Out_8042_Full1Out_8042_Full1labelnearotrck:xor cx,cxinrck:in al,STAT8042test al,OBF_8042jnz short onretloop short inrckdec ahjnz short otrckonret:retOUT_8042_FULL ENDP此例程首先设立检查次数位12次在每一次循环中间都是读出状态寄存器的值判断bit0是否为1前面我们主要讨论了60H和0D4H这两个命令并且详细讨论了命令的发送和数据接受的机制我在简单的讨论一下一些常用的命令A7/A8两个命令是用来使失效/使能鼠标接口AD/AE两个命令是用来使失效/使能键盘接口因此我们接受鼠标数据也可以通过使失效键盘接口的方法D2/D3这两个命令是USB的Trap部分用来模拟Ps/2鼠标用的如果我们先发送一个D2命令给64H口在接着向60H写入一个数据那么8042就会将这个数据放入输出缓冲区并且同样的设置状态位和设置中断就像这个数据是键盘发来的一样同样如果我们使用了D2命令就可以模拟鼠标的动作第四部分AWARD BIOS INT 15H鼠标支持简介AWDBIOS本身不支持鼠标的动作但是其提供了一个个中断和一个IRQ12空的例程我们下面来分析一下这些功能和源代码这些代码在文件ATORG.ASM中对于INT 15H的调用方式是在AH中间放入功能号INT15H给鼠标的功能号是C1和C2其中C1是用来得到BIOS扩展数据段的段址而C2则是提供了丰富的对于鼠标的控制调用的方法是将子功能号写入AL中而把参数写入BH寄存器子功能号00是用来使能/使失效鼠标的Stream模式其中BH中放入00则是使失效如果放入01则是使能其实这里就是对应的向鼠标发送F4和F5命令子功能号01则是向鼠标发出Reset命令相当于直接发出FFH子功能号02则是用来设置鼠标的采样率采样率参数放在BH中间相当于直接发命令F3 00; 10 reports/second01; 20 reports/second02; 40 reports/second03; 60 reports/second04; 80 reports/second05; 100 reports/second06; 200 reports/second子功能号03则是用来设置鼠标的分辨率分辨率参数放在BH中间相当于直接发命令E8 BH中的值如下= 00 : 1 count/millimeter= 01 : 2 counts/millimeter= 02 : 4 counts/millimeter= 03 : 8 counts/millimeter子功能号04是用来读取鼠标的ID没有输入参数输出参数的意义如下Output:bp_flag : cf = 0 : no errorcf = 1 : errorbp_errcode (AH) = 00 : no error= 03 : interface error= 04 : resendbp_bh (BH) = device id (mouse:00)子功能号05是用来设置系统包的格式以及使鼠标初始化设置缺省值子功能号06是用来设置鼠标的坐标系统相当于发出E7/E6命令其参数和返回值如下Inputs:bp_bl (BL) = extended command= 00 - Return status= 01 - Set scalling factor to 1:1= 02 - Set scalling factor to 2:1Output:bp_flag : cf = 0 : no errorcf = 1 : errorbp_errcode (AH) = 00 : no error= 01 : invalid function子功能号07是用来设置对于驱动程序的远程调用前面讨论了Awdbios对于鼠标操作的支持下面我们讨论一下awdbios提供的一个缺省的irq12的例程Public Dummy_IRQ12_HandlerDummy_IRQ12_Handler:push axcliin al,DATA8042; get mouse datamov al,END_OF_INTout B8259,al ; EOI to slave 8259NEWIODELAYout A8259,al; EOI to master 8259pop axiretFCODE ENDS这段程序基本上的任务就是清除鼠标数据基本上是没有做任何的动作注意后面的向两个8259发出的EOI命令业界的规范与设置BIOS扩展数据段扩展数据段定义在639640K之间为系统应用鼠标提供了一个标准的协议其定义如下EXT_BIOS_DATA STRUCEXTSIZE DB?;00 = Number of bytes allocated in multiples; of K (Byte);R02 RESERVED1DB32 DUP(?);01-21 = ReservedRESERVED1DB33 DUP(?);01-21 = Reserved;R02FCOFFSET DW?;22 = Device Driver Far Call Offset (Word)FCSEGMENT DW?;24 = Device Driver Far Call Segment (Word)PDFLG_1DB?;26 = Pointing Device Flag (1st Byte); 7 = Command in Progress; 6 = Resend; 5 = Acknowledge; 4 = Error; 3 = Reserved = 0; 2-0 = Index CountPDFLG_2DB?;27 = Pointing Device Flag (2nd Byte); 7 = Device Driver Far Call flag; 6-3 = Reserved; 2-0 = Package SizeAUXDATA DB8 DUP(?);28-2F = Auxiliary Data BufferEXT_BIOS_DATA ENDS第五部分编程的具体实例具体的编程部分包括以下几个部分1鼠标设备的初始化检测KBC使能复位设置2拦截硬件中断3流模式使能4数据接受以及对于错误的数据包的处理5鼠标的显示Zangcf@Doraemong@。