从零开始搭建环境编写操作系统 AT&T GCC (八)使用键盘和滚轮鼠标
2017-08-07 09:19
621 查看
终于要让键盘和鼠标使用起来了,前期工作都完成了,这一步其实是非常容易的。
一、键盘中断和处理
当键盘中的一个按钮被按下或抬起时,将通过8259A芯片向CPU发送一个键盘中断的消息,这时CPU将转入键盘中断处理程序。键盘上的每个按键都对应一个扫描码,当有键盘中断时,这个扫描码被送入0x60端口。CPU通过读取0x60端口中的扫描码就可以得知是键盘中的哪个键盘被按下或抬起了。
在system文件夹下新建一个keyboard文件夹,然后新建keyboard.c和keyboard.h
打开functions.s,要新建一个键盘的中断处理函数,不要忘记.global一下,因为我们还要在main.c中使用它的地址。
回到main.c,声明这个函数 extern void KeyboardIntCallBack(void);
修改如下内容:
简单解释一下,int 21是我们的键盘中断,可以在第五讲中分析出来,所以我们要特别设置int 21的回调函数为KeyboardIntCallBack,这个函数在汇编function.s中,调用这个函数后,KeyboardIntCallBack对中断进行处理,然后调用keyboard.c中的IntKeyboard处理函数进行处理。
我们先把keyboard.c加到Makefile中,Makefile添加下边一句话
然后修改链接ld为如下:
搞定,开始编写keyboard.c
按键的输入码并不是ASCII码,而是一种特殊的编码,如下给出:
所以编写如下数组,左侧是原始字符,右侧是大写字母锁定键或shift按下后的字符
另外,我们已经知道了键盘存放按键扫描码的端口为0x60,但还有一个键盘按键控制端口0x61,向它写入相应的控制字节,此字节每一位格式如下:
0:定时器扬声器
1:扬声器数据
2:奇偶校验
3:通道检查
4-6:保留
7:IRQ复位
对于0-6暂时不去管它们,只需要将IRQ复位即可。如果IRQ不复位,键盘中断只会被8259A响应一次,之后就不再触发键盘中断了。
我们写如下的keyboard.c
我们还要写keyboard.h
make一下,随便按一个键,成功!屏幕上显示出了按键。试一下shift也是可以用的
二、加快键盘处理速度,使用buffer
什么是buffer????缓冲区,顾名思义,当你连续操作键盘或鼠标的时候,会触发大量的中断,我们不能在中断函数中写过多的数据处理,否则会导致系统异常的卡,因为中断执行的优先级是高于主函数的,所以大量时间用在中断上会导致我们的主函数没有时间运行了。所以采用buffer机制,当触发键盘或鼠标时,只需要把操作值放入buffer,而不做任何处理,主线程运行到buffer检查处就会检查一下buffer里边有没有新的鼠标移动或者按键没有处理,这样就形成了缓冲机制,既然键盘都已经可以使用了,我们首先把缓冲机制搭建起来。
我采用的buffer为线性队列结构,可能有更好的实现方式,我的基础算法学的也是一塌糊涂,将就一下用吧。
大体讲一下我的线性队列结构,线性的是意思在内存中连续存储,不连续存储的为链表。队列的意思是先进后出,是堆,栈是先进先出。怎么做的呢,我新建了一个结构体:
buffer是一个255的数组,所以这个队列的大小也是255字节,然后我定义了一个头标记,定义了一个尾标记,一个满标记,怎么运转的呢。
最开始的时候,头标记和尾标记都指向buffer[0],当发生中断时操作头标记后移,在主线程中操作尾标记后移,在中断函数中,不断移动头标记,判断头标记是否与尾标记重合,若重合了,则buffer满了。在主线程中,不断移动尾标记,若头标记与尾标记重合有两种可能,一个是buffer空了,一个是buffer满了,再通过满标记判断buffer到底为空还是为满。代码如下
keyboard.h (部分)
main.c建立函数DealKeyboard(unsigned char scan_code)
void SysMain(),死循环取出“消息”,这就是windows的消息队列机制
keyboard.c 向buffer添加
void IntKeyboard()
make一下,成功了
三、使用鼠标
1、背景知识:
PS/2协议(前面我们也是用的PS/2协议)其实支持两种设备,一种是键盘,一种是鼠标,它是由IBM公司制定的,协议的本身定义了键盘与鼠标同主机进行通迅的规则,其中包括了大量的物理及电器方面的信息,比如鼠标连接线的插头的管脚(针)数,每个管脚(针)的用途,电平是多少等,不过幸运的是,我们并不需要对这样的硬件细节有详细的了解,就可以完成我们的操作系统,我们需要了解的就是怎样初始化鼠标,以及怎样从鼠标中获得信息。
这里,我们首先来看看怎样初始化鼠标。根据PS/2协议,鼠标是由键盘的控制器(i8042)进行控制的,当然现在用南桥芯片控制IO设备,但是其同样向下兼容i8042,键盘控制器(i8042)总共有两个通道,一个通道由键盘使用,另一个通道由鼠标使用,我们对鼠标进行操作也是通过“i8042芯片”来完成的,因此,现在的重点就是了解并熟悉怎样对i8042进行编程,来完成对鼠标的控制。
i8042支持两种工作模式——AT模式及PS/2模式,这都是由IBM所定义的一些规范,i8042在计算机启动时会自动检测用户是否使用的支持PS/2协议的键盘及鼠标,以决定是否工作在PS/2模式下,现在我们假设我们使用的都是PS/2键盘及鼠标,因此,现在i8042工作在PS/2模式下(请记住这一点,即i8042可以工作在AT模式或者PS/2模式下,并且现在假设其工作在PS/2模式下,这在后面将会用到)。
与i8042有关的I/O端口共有两个,一个是0x60端口,一个是0x64端口,如果我们想获得i8042的状态,我们需要读0x64端口,这将返回i8042中状态寄存器的内容。如果我们想向i8042发送命令,我们需要先把命令发送到0x64端口,然后紧接着把这个命令的参数发送到0x60端口,然后我们可以通过读0x60端口,获得i8042返回给我们的数据。
下面我们就来看看,应当发送什么样的命令去控制鼠标,这涉及到下面几个需要发送给i8042的命令:
0xA8命令:许可i8042的鼠标通道,即允许鼠标操作。
0xD4命令:把发往0x60端口的参数数据发给鼠标。
0x60命令:把发往0x60端口的参数数据写入i8042的控制寄存器。
从上面的分析我们可以基本窥见怎样操作鼠标。首先,我们应向i8042的0x64端口发送0xA8命令,以许可i8042的鼠标通道,以便完成对鼠标的操作。其次我们应向i8042的0x64端口发送0xD4命令,以通知i8042我们需要控制鼠标,并把控制鼠标的命令发到i8042的0x60端口,再从i8042的0x60端口取回鼠标发给我们的数据,这一过程无疑是比较简单的,我们先来看看,我们应向鼠标发送什么样的控制命令,然后再看看实际的代码。
控制鼠标的命令非常之多,比如0xFF命令可以让鼠标复位;0xFE命令可以让鼠标重新发送上次的数据包;0xF3命令可以设置鼠标的采样率,也即鼠标滑动时的灵敏度;0xF4命令可以允许鼠标向主机发送数据包等。这里最重要的就是0xF4命令,而其它的设置鼠标的命令我们暂时可以不用理会,因为使用默认值已经能很好的完成本实验了。要理解0xF4命令有什么作用,我们需要先了解一下鼠标的四种工作模式:Reset模式,Stream模式,Remote模式及Wrap模式。
首先是Reset模式,当鼠标初次加电或收到0xFF命令之后,鼠标就处于此模式,然后鼠标将进行一系列的初始化及检测工作,包括设定默认的采样率等,完成初始化极检测之后,鼠标将进入Stream模式。
在Stream模式下,如果鼠标被移动,或者有键被按下,那么鼠标将向主机发送数据包,并提请一个中断请求,主机需要响应此中断,并在中断处理程序中取得鼠标发送的数据包。如果在Stream模式下,我们向鼠标发送0xF0命令,将使鼠标进入Remote模式。
Remote模式同Stream模式差不多,主要工作就是检测鼠标是否被移动及是否有键被按下,不过它与Stream模式的区别在于,它并不会主动的向主机提请中断请求,也即它不会主动的向主机发送数据包,而是被动的等待主机使用0xEB(读数据命令)后,再向主机提请中断请求,发送数据包。换句话说,如果在Remote模式下,你每次欲读数据时,均需要向鼠标发送0xEB命令,而如果是在Stream模式下,鼠标会自动向你发送数据。
Wrap模式主要用于检测鼠标与主机之间的连线是否正常,主机向它发送命令,它一般不会执行此命令,而只是简单的将此命令原封不同的回送给主机,主机可比较它发出的命令及接收到的命令是否一致,并以此来认定鼠标与主机之间的连线是否正常。
从上面的描述中我们可以看出,我们需要关心的只有Reset模式及Stream模式,但对于操作系统编写人员而非BIOS编写人员来说,真正需要关心的只有Stream模式,这是因为当计算机启动的时候,BIOS会自动检测鼠标,与鼠标进行通信,这个时候它会向鼠标发送0xFF(复位)命令,然后鼠标会自检,并通知主机自检是否正常,然后鼠标就将处于Stream模式,此时,鼠标已经开始检测鼠标是否移动及是否有键按下了,但是它不会立即就向主机发送数据,因为有可能主机还没有进入真正的操作系统,主机还正在启动中,因此,鼠标会等待主机的通知,直到主机给它发送0xF4命令后,它才开始向主机发送数据。
数据包是什么样子的呢,这个可以查询ps/2标准协议,百度文库有中文版,我查阅的是英文版,网址如下:
http://www.computer-engineering.org/ps2mouse/
无滚轮数据包:
有滚轮数据包:
无论使用了什么鼠标,默认都是无滚轮的,若要使用滚轮,则需要用0xF3这个设置鼠标采样率的命令,按如下的顺序进行操作,协议给出了详细的操作步骤,下边是简化:
1. 设置鼠标采样率为200
2. 设置鼠标采样率为100
3. 设置鼠标采样率为80
这之后,如果你的鼠标是个滚轮鼠标,那么,它将转到滚轮鼠标下进行工作,这个时候,主机向它发送0xF2(获得鼠标类型ID)命令,你的工作在滚轮鼠标下的鼠标将向主机返回它的类型ID,但如果你的鼠标不支持滚轮鼠标,即如果你的鼠标只是一个二维鼠标,它返回给主机的类型ID将是0,这样,主机就能够知道现在你用的鼠标是什么类型的鼠标,并由此知道应当接受3个还是4个数据包了。
(由于我这里没有滚轮鼠标,我用的笔记本进行的编程,先不增加滚轮功能,之后我会补上,上边的信息足够自己做了,有兴趣的自己做一下)
2、中断具体实现
感觉鼠标的难点应该在数据解析上。
鼠标的中断是IRQ 12,这个可以从第六讲中查到,配置中断的步骤应该已经烂熟于心了吧。
按照键盘的中断配置过程。
首先在functions.s中建立
然后修改中断向量表,先看看IRQ12对应的是中断向量表的第几个,0x20+12=0x2c
打开cursor.c,我们鼠标的处理函数放在这里边。定义void IntMouse()函数。
随便写点什么:
中断相当于完成了,下一步配置鼠标
3、配置i8024具体实现
有了上面的描述,这段代码就相当简单了,首先它向i8042的0x64端口发送了一个0xA8命令,通知i8042,允许鼠标通道。然后,它向i8042的0x64端口发送了一个0xD4命令,这个命令表示,下面发给0x60的命令需要发给鼠标,所以,紧接着,它向i8042的0x60端口,也即向鼠标,发送了0xF4命令,这个命令将允许经过BIOS初始化后,现在已处于Stream模式下的鼠标给主机发送数据包。随后,它向i8042的0x64端口发送了0x60命令,表示,下面发向0x60端口的数据需要写入i8042的控制寄存器,最后它向i8042的0x60端口发送了值为0x47的数据,这个数据被写入了i8042的控制寄存器。下面,我们就来看看这个控制寄存器,以明白,这里为什么需要向它发送这样一个值为0x47的数据。
下面就是i8042的控制寄存器的格式,这个控制寄存器总共有8位,即1个字节。
位0:键盘中断标志位,如果置1,那么如果有键盘动作,则i8042将提请IRQ1中断。
位1:鼠标中断标志位,如果置1,那么如果有鼠标动作,则i8042将提请IRQ12中断(在AT模式下这位不使用,只在PS/2模式下有效。这里可以回忆一下前面我们提到的i8042可以工作在AT或者PS/2两种模式下的描述)。
位2:系统标志位,上电的时候是0,自检成功后是1。
位3:键盘锁标志位,如果是1,将忽略键盘锁,这主要是为了兼容一些老式的带锁的键盘,而且这位只在AT模式下使用,PS/2模式下将不使用此位。
位4:键盘接口标志位,如果置1,将禁止使用键盘接口。
位5:在AT模式下,这是AT键盘协议位。置0的时候,i8042将使用AT协议,如果置1,将使用XT协议。在PS/2模式下,这是鼠标接口(通道)标志位,如果置1将禁止鼠标接口(通道)。
位6:键盘扫描码转换标志位。
位7:保留,应置为0。
make一下,动一动鼠标,数据嗖嗖的传,这一步也完成了,我们继续做buffer。
4、buffer的实现
keyboard.h中的buffer可以直接使用,所以我们再在keyboard.h中
然后回到cursor.c
Int函数与keyboard一模一样,就不解读了
回到main.c
在while(1)中添加如下,同样与键盘一模一样
为了让main.c不显得臃肿,我把void DealKeyboard(unsigned char scan_code)函数放回keyboard.c中
然后把所有的绘图函数放在了font.c中
然后在cursor.c中定义了void DealMouse(unsigned char scan_code)函数,不要忘记声明。
我们要看看mouse的数据怎么解析。
初始化后的鼠标首先会收到第一个中断,传来的消息是0xfa,这是一条应答(response),可以忽略掉
然后开始传递标准数据包,给出过图了,再拿出来分析一下,我暂时没有滚轮鼠标可以测试,现在暂时用三数据包。
byte1:
位0:左键按下标志位,为1表示左键被按下。
位1:右键按下标志位,为1表示右键被按下。
位2:中键按下标志位,为1表示中键被按下。
位3:保留位,总是为1。
位4:X符号标志位,为1表示X位移量为负。
位5:Y符号标志位,为1表示Y位移量为负。
位6:X溢出标志位,为1表示X位移量溢出了。
位7:Y溢出标下位,为1表示Y位移量溢出了。
这里我们要使用一个技巧来分离数据包,Byte1的第三位总是为0,所以我们过滤掉第一个0xfa的应答数据包之后,依次获得三个数据包,然后通过检验第一个数据包的第三位是否为1来保证数据的准确。
当然我们又要用到结构体了
cursor.h定义如下结构体
声明全局结构体:
解析过程:
这里说明一点,鼠标数据穿过来是以补码的形式,可能做底层的小伙伴们经常遇到这种情况,其实对我们程序员来说,这种操作大大方便了我们的计算,因为只需要补齐符号位,直接就可以进行加减运算。协议中用这句话描述了传来的信息:
//The movement values are 9-bit 2’s complement integers, where the most significant bit appears as a “sign” bit in byte 1 of the movement data packet
9位补码,补齐符号位,直接与int类型进行加减运算就ok!
运算过程就是这段代码
最后我把鼠标的坐标和三个数据包显示了出来
还剩最后一步,画鼠,终于到最后了,这篇博客一天没有写完
四、移动鼠标
我们之前已经写了void DrawCursor(int x,int y, int type)这个函数,我们直接拿来使用就可以了。
把鼠标处理函数的case 3:改为如下。
但是由于我们没有制作图层,所以会出现这种情况
慢慢来吧!
一、键盘中断和处理
当键盘中的一个按钮被按下或抬起时,将通过8259A芯片向CPU发送一个键盘中断的消息,这时CPU将转入键盘中断处理程序。键盘上的每个按键都对应一个扫描码,当有键盘中断时,这个扫描码被送入0x60端口。CPU通过读取0x60端口中的扫描码就可以得知是键盘中的哪个键盘被按下或抬起了。
在system文件夹下新建一个keyboard文件夹,然后新建keyboard.c和keyboard.h
打开functions.s,要新建一个键盘的中断处理函数,不要忘记.global一下,因为我们还要在main.c中使用它的地址。
KeyboardIntCallBack: cli pushal pushfl //调用键盘中断处理函数 call IntKeyboard popfl popal sti iret
回到main.c,声明这个函数 extern void KeyboardIntCallBack(void);
修改如下内容:
for (i=1;i<0x30;i++) { idt[i].offset1 = (short)((int)(void*)(DefaultIntCallBack)); idt[i].selector = 0x0008; idt[i].no_use = 0x8e00; idt[i].offset2 = (short)(((int)(void*)(DefaultIntCallBack))>>16); }//增加下边的四行 idt[0x21].offset1 = (short)((int)(void*)(KeyboardIntCallBack)); idt[0x21].selector = 0x0008; idt[0x21].no_use = 0x8e00; idt[0x21].offset2 = (short)(((int)(void*)(KeyboardIntCallBack))>>16);
简单解释一下,int 21是我们的键盘中断,可以在第五讲中分析出来,所以我们要特别设置int 21的回调函数为KeyboardIntCallBack,这个函数在汇编function.s中,调用这个函数后,KeyboardIntCallBack对中断进行处理,然后调用keyboard.c中的IntKeyboard处理函数进行处理。
我们先把keyboard.c加到Makefile中,Makefile添加下边一句话
keyboard.o : keyboard/keyboard.c Makefile gcc -c keyboard/keyboard.c -o $(OBJ_DIR)/keyboard.o -m32
然后修改链接ld为如下:
system.bin : system.o Makefile system.lds main.o functions.o font.o font_code.o cursor.o keyboard.o ld -m elf_i386 -o $(BIN_DIR)/system.elf \ $(OBJ_DIR)/system.o $(OBJ_DIR)/functions.o $(OBJ_DIR)/main.o \ $(OBJ_DIR)/font.o $(OBJ_DIR)/font_code.o \ $(OBJ_DIR)/cursor.o $(OBJ_DIR)/keyboard.o \ -T system.lds
搞定,开始编写keyboard.c
按键的输入码并不是ASCII码,而是一种特殊的编码,如下给出:
#define KEY_ESC 0X01 // ESC #define KEY_1 0X02 // 1 #define KEY_2 0X03 // 2 #define KEY_3 0X04 // 3 #define KEY_4 0X05 // 4 #define KEY_5 0X06 // 5 #define KEY_6 0X07 // 6 #define KEY_7 0X08 // 7 #define KEY_8 0X09 // 8 #define KEY_9 0X0A // 9 #define KEY_0 0X0B // 0 #define KEY_DASH 0X0C // - #define KEY_EQUAL 0X0D // = #define KEY_BACKSPACE 0X0E // BACKSPACE #define KEY_TAB 0X0F // TAB #define KEY_Q 0X10 // Q #define KEY_W 0X11 // W #define KEY_E 0X12 // E #define KEY_R 0X13 // R #define KEY_T 0X14 // T #define KEY_Y 0X15 // Y #define KEY_U 0X16 // U #define KEY_I 0X17 // I #define KEY_O 0X18 // O #define KEY_P 0X19 // P #define KEY_LBRACKET 0X1A // [ #define KEY_RBRACKET 0X1B // ] #define KEY_ENTER 0X1C // ENTER #define KEY_CTRL 0X1D // CTRL #define KEY_A 0X1E // A #define KEY_S 0X1F // S #define KEY_D 0X20 // D #define 1b666 KEY_F 0X21 // F #define KEY_G 0X22 // G #define KEY_H 0X23 // H #define KEY_J 0X24 // J #define KEY_K 0X25 // K #define KEY_L 0X26 // L #define KEY_SEMICOLON 0X27 // ; #define KEY_RQUOTE 0X28 // ' #define KEY_LQUOTE 0X29 // ` #define KEY_LEFT_SHIFT 0X2A // LEFT SHIFT #define KEY_BACKSLASH 0X2B // '\' #define KEY_Z 0X2C // Z #define KEY_X 0X2D // X #define KEY_C 0X2E // C #define KEY_V 0X2F // V #define KEY_B 0X30 // B #define KEY_N 0X31 // N #define KEY_M 0X32 // M #define KEY_COMMA 0X33 // , #define KEY_PERIOD 0X34 // . #define KEY_SLASH 0X35 // / #define KEY_RIGHT_SHIFT 0X36 // RIGHT SHIFT #define KEY_PRTSC 0X37 // PRINT SCREEN #define KEY_ALT 0X38 // ALT #define KEY_SPACE 0X39 // SPACE #define KEY_CAPS_LOCK 0X3A // CAPS LOCK #define KEY_F1 0X3B // F1 #define KEY_F2 0X3C // F2 #define KEY_F3 0X3D // F3 #define KEY_F4 0X3E // F4 #define KEY_F5 0X3F // F5 #define KEY_F6 0X40 // F6 #define KEY_F7 0X41 // F7 #define KEY_F8 0X42 // F8 #define KEY_F9 0X43 // F9 #define KEY_F10 0X44 // F10 #define KEY_NUM_LOCK 0X45 // NUM LOCK #define KEY_SCROLL_LOCK 0X46 // SCROLL LOCK #define KEY_HOME 0X47 // HOME #define KEY_UP 0X48 // UP #define KEY_PAGE_UP 0X49 // PAGE UP #define KEY_SUB 0X4A // SUB #define KEY_LEFT 0X4B // LEFT #define KEY_CENTER 0X4C // CENTER #define KEY_RIGHT 0X4D // RIGHT #define KEY_ADD 0X4E // ADD #define KEY_END 0X4F // END #define KEY_DOWN 0X50 // DOWN #define KEY_PAGE_DOWN 0X51 // PAGE DOWN #define KEY_INSERT 0X52 // INSERT #define KEY_DEL 0X53 // DEL
所以编写如下数组,左侧是原始字符,右侧是大写字母锁定键或shift按下后的字符
char keys[0x53][2] = { { 0x0, 0x0 }, // ESC { '1', '!' }, // 1 { '2', '@' }, // 2 { '3', '#' }, // 3 { '4', '$' }, // 4 { '5', '%' }, // 5 { '6', '^' }, // 6 { '7', '&' }, // 7 { '8', '*' }, // 8 { '9', '(' }, // 9 { '0', ')' }, // 0 { '-', '_' }, // - { '=', '+' }, // = { 0x0, 0x0 }, // BACKSPACE { 0x0, 0x0 }, // TAB { 'q', 'Q' }, // Q { 'w', 'W' }, // W { 'e', 'E' }, // E { 'r', 'R' }, // R { 't', 'T' }, // T { 'y', 'Y' }, // Y { 'u', 'U' }, // U { 'i', 'I' }, // I { 'o', 'O' }, // O { 'p', 'P' }, // P { '[', '{' }, // [ { ']', '}' }, // ] { '\n', 0x0 }, // ENTER { 0x0, 0x0 }, // CTRL { 'a', 'A' }, // A { 's', 'S' }, // S { 'd', 'D' }, // D { 'f', 'F' }, // F { 'g', 'G' }, // G { 'h', 'H' }, // H { 'j', 'J' }, // J { 'k', 'K' }, // K { 'l', 'L' }, // L { ';', ':' }, // ; { '\'', '"' }, // ' { '`', '~' }, // ` { 0x0, 0x0 }, // LEFTSHIFT { '\\', '|' }, // '\' { 'a', 'Z' }, // Z { 'x', 'X' }, // X { 'c', 'C' }, // C { 'v', 'V' }, // V { 'b', 'B' }, // B { 'n', 'N' }, // N { 'm', 'M' }, // M { ',', '<' }, // , { '.', '>' }, // . { '/', '?' }, // / { 0x0, 0x0 }, // RIGHTSHIFT { 0x0, 0x0 }, // PRINTSCREEN { 0x0, 0x0 }, // ALT { 0x0, 0x0 }, // SPACE { 0x0, 0x0 }, // CAPSLOCK { 0x0, 0x0 }, // F1 { 0x0, 0x0 }, // F2 { 0x0, 0x0 }, // F3 { 0x0, 0x0 }, // F4 { 0x0, 0x0 }, // F5 { 0x0, 0x0 }, // F6 { 0x0, 0x0 }, // F7 { 0x0, 0x0 }, // F8 { 0x0, 0x0 }, // F9 { 0x0, 0x0 }, // F10 { 0x0, 0x0 }, // NUMLOCK { 0x0, 0x0 }, // SCROLLLOCK { 0x0, 0x0 }, // HOME { 0x0, 0x0 }, // UP { 0x0, 0x0 }, // PAGEUP { 0x0, 0x0 }, // SUB { 0x0, 0x0 }, // LEFT { 0x0, 0x0 }, // CENTER { 0x0, 0x0 }, // RIGHT { 0x0, 0x0 }, // ADD { 0x0, 0x0 }, // END { 0x0, 0x0 }, // DOWN { 0x0, 0x0 }, // PAGEDOWN { 0x0, 0x0 }, // INSERT { 0x0, 0x0 } // DEL };
另外,我们已经知道了键盘存放按键扫描码的端口为0x60,但还有一个键盘按键控制端口0x61,向它写入相应的控制字节,此字节每一位格式如下:
0:定时器扬声器
1:扬声器数据
2:奇偶校验
3:通道检查
4-6:保留
7:IRQ复位
对于0-6暂时不去管它们,只需要将IRQ复位即可。如果IRQ不复位,键盘中断只会被8259A响应一次,之后就不再触发键盘中断了。
我们写如下的keyboard.c
#include "keyboard.h" #include "../font/font.h" char keys[0x53][2] = { { 0x0, 0x0 }, // ESC …………………………………………省略了 { 0x0, 0x0 } // DEL }; void IntKeyboard() { //取得扫描码 unsigned char scan_code = FunctionIn8(0x60); //扫描码的索引 unsigned char key_ind = scan_code & 0x7f; //shift按下 static unsigned char kb_key_shift = 0x0; static unsigned char word_x=0; word_x++; printf(0,word_x*16,0xffffff,"sao miao ma:%x",scan_code); if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && ((scan_code & 0x80) == 0x00))//按键的最高位,0为按下,1为抬起 { kb_key_shift = 0x1; PutString(100,100,0xffffff,"shift down:"); } //shift抬起 else if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && ((scan_code & 0x80) == 0x80)) { kb_key_shift = 0x0; PutString(100,116,0xffffff,"shift up:"); } if (((scan_code & 0x80) == 0x80)) { //显示字符 printf(200,(word_x*16)/2,0xffffff,"char is :%c",keys[key_ind-1][kb_key_shift]); } //清除键盘状态可以接受新按键,不清除shift位置 FunctionOut8(scan_code & 0x7f, 0x61); //通知PIC可以接受新中断 FunctionOut8(0x20, 0x20); }
我们还要写keyboard.h
extern void FunctionOut8(int port, int data); extern int FunctionIn8(int port); #define KEY_ESC 0X01 // ESC #define KEY_1 0X02 // 1 …………………………………………省略了 #define KEY_INSERT 0X52 // INSERT #define KEY_DEL 0X53 // DEL extern char keys[0x53][2];
make一下,随便按一个键,成功!屏幕上显示出了按键。试一下shift也是可以用的
二、加快键盘处理速度,使用buffer
什么是buffer????缓冲区,顾名思义,当你连续操作键盘或鼠标的时候,会触发大量的中断,我们不能在中断函数中写过多的数据处理,否则会导致系统异常的卡,因为中断执行的优先级是高于主函数的,所以大量时间用在中断上会导致我们的主函数没有时间运行了。所以采用buffer机制,当触发键盘或鼠标时,只需要把操作值放入buffer,而不做任何处理,主线程运行到buffer检查处就会检查一下buffer里边有没有新的鼠标移动或者按键没有处理,这样就形成了缓冲机制,既然键盘都已经可以使用了,我们首先把缓冲机制搭建起来。
我采用的buffer为线性队列结构,可能有更好的实现方式,我的基础算法学的也是一塌糊涂,将就一下用吧。
大体讲一下我的线性队列结构,线性的是意思在内存中连续存储,不连续存储的为链表。队列的意思是先进后出,是堆,栈是先进先出。怎么做的呢,我新建了一个结构体:
struct char_buffer_struct{ unsigned char buffer[255];//255个字节的缓冲区 int buffer_point_head;//建立队列结构,这是队列头的位置 int buffer_point_tail;//队列尾的位置,新的中断放在队列头,系统处理队列尾的消息,当头尾相同则队列为 空或满 unsigned char is_full;//1为满,0为空 };
buffer是一个255的数组,所以这个队列的大小也是255字节,然后我定义了一个头标记,定义了一个尾标记,一个满标记,怎么运转的呢。
最开始的时候,头标记和尾标记都指向buffer[0],当发生中断时操作头标记后移,在主线程中操作尾标记后移,在中断函数中,不断移动头标记,判断头标记是否与尾标记重合,若重合了,则buffer满了。在主线程中,不断移动尾标记,若头标记与尾标记重合有两种可能,一个是buffer空了,一个是buffer满了,再通过满标记判断buffer到底为空还是为满。代码如下
keyboard.h (部分)
extern char keys[0x53][2];
struct char_buffer_struct{ unsigned char buffer[255];//255个字节的缓冲区 int buffer_point_head;//建立队列结构,这是队列头的位置 int buffer_point_tail;//队列尾的位置,新的中断放在队列头,系统处理队列尾的消息,当头尾相同则队列为 空或满 unsigned char is_full;//1为满,0为空 };
extern struct char_buffer_struct keyboard_buffer;
main.c建立函数DealKeyboard(unsigned char scan_code)
void DealKeyboard(unsigned char scan_code) { //shift是否按下 static unsigned char kb_key_shift = 0x00; //ctrl是否按下 static unsigned char kb_key_ctrl = 0x00; static unsigned char word_x=0;//显示出来 //扫描码的索引 unsigned char key_ind = scan_code & 0x7f; word_x++; printf(0,word_x*16,0xffffff,"scan_code:%x",scan_code); if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && ((scan_code & 0x80) == 0x00)) { kb_key_shift = 0x1; printf(400,0,0xffffff,"shift key down!"); } //shift抬起 else if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && ((scan_code & 0x80) == 0x80)) { kb_key_shift = 0x0; printf(400,16,0xffffff,"shift key up!"); } if (((scan_code & 0x80) == 0x80)) { //显示字符 printf(200,(word_x*16)/2,0xffffff,"char is :%c",keys[key_ind-1][kb_key_shift]); } }
void SysMain(),死循环取出“消息”,这就是windows的消息队列机制
void SysMain() { int i=0; InitPIC(); InitIDT(); DrawRectangle(0,0,800,600,0x000033);//draw background DrawBar(); DrawButton(150,530,100,50); DrawCursor(50,50, 0); while(1) { if (keyboard_buffer.buffer_point_head == keyboard_buffer.buffer_point_tail) //先判断是否收尾相接,如果是,且没有满,则说明buffer为空,否则buffer为满 { if (keyboard_buffer.is_full == 1)//满了 { DealKeyboard(keyboard_buffer.buffer[keyboard_buffer.buffer_point_tail]); if (keyboard_buffer.buffer_point_tail < 255) keyboard_buffer.buffer_point_tail++; else keyboard_buffer.buffer_point_tail=0; keyboard_buffer.is_full == 0; } } else { DealKeyboard(keyboard_buffer.buffer[keyboard_buffer.buffer_point_tail]); if (keyboard_buffer.buffer_point_tail < 255) keyboard_buffer.buffer_point_tail++; else keyboard_buffer.buffer_point_tail=0; } } }
keyboard.c 向buffer添加
void IntKeyboard()
void IntKeyboard() { //取得扫描码 unsigned char scan_code = FunctionIn8(0x60); if (keyboard_buffer.is_full == 0)//如果buffer没满 { keyboard_buffer.buffer[keyboard_buffer.buffer_point_head] = scan_code; if (keyboard_buffer.buffer_point_head < 255)//如果head没到头 keyboard_buffer.buffer_point_head++; else keyboard_buffer.buffer_point_head=0; if (keyboard_buffer.buffer_point_head == keyboard_buffer.buffer_point_tail) //如果head与tail相遇,说明满了 { keyboard_buffer.is_full = 1; } } //清除键盘状态可以接受新按键,不清除shift位置 FunctionOut8(scan_code & 0x7f, 0x61); //通知PIC可以接受新中断 FunctionOut8(0x20, 0x20); }
make一下,成功了
三、使用鼠标
1、背景知识:
PS/2协议(前面我们也是用的PS/2协议)其实支持两种设备,一种是键盘,一种是鼠标,它是由IBM公司制定的,协议的本身定义了键盘与鼠标同主机进行通迅的规则,其中包括了大量的物理及电器方面的信息,比如鼠标连接线的插头的管脚(针)数,每个管脚(针)的用途,电平是多少等,不过幸运的是,我们并不需要对这样的硬件细节有详细的了解,就可以完成我们的操作系统,我们需要了解的就是怎样初始化鼠标,以及怎样从鼠标中获得信息。
这里,我们首先来看看怎样初始化鼠标。根据PS/2协议,鼠标是由键盘的控制器(i8042)进行控制的,当然现在用南桥芯片控制IO设备,但是其同样向下兼容i8042,键盘控制器(i8042)总共有两个通道,一个通道由键盘使用,另一个通道由鼠标使用,我们对鼠标进行操作也是通过“i8042芯片”来完成的,因此,现在的重点就是了解并熟悉怎样对i8042进行编程,来完成对鼠标的控制。
i8042支持两种工作模式——AT模式及PS/2模式,这都是由IBM所定义的一些规范,i8042在计算机启动时会自动检测用户是否使用的支持PS/2协议的键盘及鼠标,以决定是否工作在PS/2模式下,现在我们假设我们使用的都是PS/2键盘及鼠标,因此,现在i8042工作在PS/2模式下(请记住这一点,即i8042可以工作在AT模式或者PS/2模式下,并且现在假设其工作在PS/2模式下,这在后面将会用到)。
与i8042有关的I/O端口共有两个,一个是0x60端口,一个是0x64端口,如果我们想获得i8042的状态,我们需要读0x64端口,这将返回i8042中状态寄存器的内容。如果我们想向i8042发送命令,我们需要先把命令发送到0x64端口,然后紧接着把这个命令的参数发送到0x60端口,然后我们可以通过读0x60端口,获得i8042返回给我们的数据。
下面我们就来看看,应当发送什么样的命令去控制鼠标,这涉及到下面几个需要发送给i8042的命令:
0xA8命令:许可i8042的鼠标通道,即允许鼠标操作。
0xD4命令:把发往0x60端口的参数数据发给鼠标。
0x60命令:把发往0x60端口的参数数据写入i8042的控制寄存器。
从上面的分析我们可以基本窥见怎样操作鼠标。首先,我们应向i8042的0x64端口发送0xA8命令,以许可i8042的鼠标通道,以便完成对鼠标的操作。其次我们应向i8042的0x64端口发送0xD4命令,以通知i8042我们需要控制鼠标,并把控制鼠标的命令发到i8042的0x60端口,再从i8042的0x60端口取回鼠标发给我们的数据,这一过程无疑是比较简单的,我们先来看看,我们应向鼠标发送什么样的控制命令,然后再看看实际的代码。
控制鼠标的命令非常之多,比如0xFF命令可以让鼠标复位;0xFE命令可以让鼠标重新发送上次的数据包;0xF3命令可以设置鼠标的采样率,也即鼠标滑动时的灵敏度;0xF4命令可以允许鼠标向主机发送数据包等。这里最重要的就是0xF4命令,而其它的设置鼠标的命令我们暂时可以不用理会,因为使用默认值已经能很好的完成本实验了。要理解0xF4命令有什么作用,我们需要先了解一下鼠标的四种工作模式:Reset模式,Stream模式,Remote模式及Wrap模式。
首先是Reset模式,当鼠标初次加电或收到0xFF命令之后,鼠标就处于此模式,然后鼠标将进行一系列的初始化及检测工作,包括设定默认的采样率等,完成初始化极检测之后,鼠标将进入Stream模式。
在Stream模式下,如果鼠标被移动,或者有键被按下,那么鼠标将向主机发送数据包,并提请一个中断请求,主机需要响应此中断,并在中断处理程序中取得鼠标发送的数据包。如果在Stream模式下,我们向鼠标发送0xF0命令,将使鼠标进入Remote模式。
Remote模式同Stream模式差不多,主要工作就是检测鼠标是否被移动及是否有键被按下,不过它与Stream模式的区别在于,它并不会主动的向主机提请中断请求,也即它不会主动的向主机发送数据包,而是被动的等待主机使用0xEB(读数据命令)后,再向主机提请中断请求,发送数据包。换句话说,如果在Remote模式下,你每次欲读数据时,均需要向鼠标发送0xEB命令,而如果是在Stream模式下,鼠标会自动向你发送数据。
Wrap模式主要用于检测鼠标与主机之间的连线是否正常,主机向它发送命令,它一般不会执行此命令,而只是简单的将此命令原封不同的回送给主机,主机可比较它发出的命令及接收到的命令是否一致,并以此来认定鼠标与主机之间的连线是否正常。
从上面的描述中我们可以看出,我们需要关心的只有Reset模式及Stream模式,但对于操作系统编写人员而非BIOS编写人员来说,真正需要关心的只有Stream模式,这是因为当计算机启动的时候,BIOS会自动检测鼠标,与鼠标进行通信,这个时候它会向鼠标发送0xFF(复位)命令,然后鼠标会自检,并通知主机自检是否正常,然后鼠标就将处于Stream模式,此时,鼠标已经开始检测鼠标是否移动及是否有键按下了,但是它不会立即就向主机发送数据,因为有可能主机还没有进入真正的操作系统,主机还正在启动中,因此,鼠标会等待主机的通知,直到主机给它发送0xF4命令后,它才开始向主机发送数据。
数据包是什么样子的呢,这个可以查询ps/2标准协议,百度文库有中文版,我查阅的是英文版,网址如下:
http://www.computer-engineering.org/ps2mouse/
无滚轮数据包:
有滚轮数据包:
无论使用了什么鼠标,默认都是无滚轮的,若要使用滚轮,则需要用0xF3这个设置鼠标采样率的命令,按如下的顺序进行操作,协议给出了详细的操作步骤,下边是简化:
1. 设置鼠标采样率为200
2. 设置鼠标采样率为100
3. 设置鼠标采样率为80
这之后,如果你的鼠标是个滚轮鼠标,那么,它将转到滚轮鼠标下进行工作,这个时候,主机向它发送0xF2(获得鼠标类型ID)命令,你的工作在滚轮鼠标下的鼠标将向主机返回它的类型ID,但如果你的鼠标不支持滚轮鼠标,即如果你的鼠标只是一个二维鼠标,它返回给主机的类型ID将是0,这样,主机就能够知道现在你用的鼠标是什么类型的鼠标,并由此知道应当接受3个还是4个数据包了。
(由于我这里没有滚轮鼠标,我用的笔记本进行的编程,先不增加滚轮功能,之后我会补上,上边的信息足够自己做了,有兴趣的自己做一下)
2、中断具体实现
感觉鼠标的难点应该在数据解析上。
鼠标的中断是IRQ 12,这个可以从第六讲中查到,配置中断的步骤应该已经烂熟于心了吧。
按照键盘的中断配置过程。
首先在functions.s中建立
MouseIntCallBack: cli pushal pushfl //调用键盘中断处理函数 call IntMouse popfl popal sti iret
然后修改中断向量表,先看看IRQ12对应的是中断向量表的第几个,0x20+12=0x2c
//mouse idt[0x2c].offset1 = (short)((int)(void*)(MouseIntCallBack)); idt[0x2c].selector = 0x0008; idt[0x2c].no_use = 0x8e00; idt[0x2c].offset2 = (short)(((int)(void*)(MouseIntCallBack))>>16);
打开cursor.c,我们鼠标的处理函数放在这里边。定义void IntMouse()函数。
随便写点什么:
void IntMouse() { static int a=0; unsigned char scan_code = FunctionIn8(0x60); a++; printf(0,0,0xff0000,"mouse!!!!!:%d",a); //通知PIC1可以接受新中断 FunctionOut8(0xa0, 0x64); //通知PIC0可以接受新中断 FunctionOut8(0x20, 0x62); }
中断相当于完成了,下一步配置鼠标
3、配置i8024具体实现
void InitMouse() { // 对 8042 键盘控制芯片进行编程 // 允许 鼠标 接口 FunctionOut8( 0x64 , 0xa8 ) ; // 通知 8042 下个字节的发向 0x60 的数据将发给 鼠标 FunctionOut8( 0x64 , 0xd4 ) ; // 允许 鼠标 发数据 FunctionOut8( 0x60 , 0xf4 ) ; // 通知 8042,下个字节的发向 0x60 的数据应放向 8042 的命令寄存器 FunctionOut8( 0x64 , 0x60 ) ; // 许可键盘及 鼠标 接口及中断 FunctionOut8( 0x60 , 0x47 ) ; }
有了上面的描述,这段代码就相当简单了,首先它向i8042的0x64端口发送了一个0xA8命令,通知i8042,允许鼠标通道。然后,它向i8042的0x64端口发送了一个0xD4命令,这个命令表示,下面发给0x60的命令需要发给鼠标,所以,紧接着,它向i8042的0x60端口,也即向鼠标,发送了0xF4命令,这个命令将允许经过BIOS初始化后,现在已处于Stream模式下的鼠标给主机发送数据包。随后,它向i8042的0x64端口发送了0x60命令,表示,下面发向0x60端口的数据需要写入i8042的控制寄存器,最后它向i8042的0x60端口发送了值为0x47的数据,这个数据被写入了i8042的控制寄存器。下面,我们就来看看这个控制寄存器,以明白,这里为什么需要向它发送这样一个值为0x47的数据。
下面就是i8042的控制寄存器的格式,这个控制寄存器总共有8位,即1个字节。
位0:键盘中断标志位,如果置1,那么如果有键盘动作,则i8042将提请IRQ1中断。
位1:鼠标中断标志位,如果置1,那么如果有鼠标动作,则i8042将提请IRQ12中断(在AT模式下这位不使用,只在PS/2模式下有效。这里可以回忆一下前面我们提到的i8042可以工作在AT或者PS/2两种模式下的描述)。
位2:系统标志位,上电的时候是0,自检成功后是1。
位3:键盘锁标志位,如果是1,将忽略键盘锁,这主要是为了兼容一些老式的带锁的键盘,而且这位只在AT模式下使用,PS/2模式下将不使用此位。
位4:键盘接口标志位,如果置1,将禁止使用键盘接口。
位5:在AT模式下,这是AT键盘协议位。置0的时候,i8042将使用AT协议,如果置1,将使用XT协议。在PS/2模式下,这是鼠标接口(通道)标志位,如果置1将禁止鼠标接口(通道)。
位6:键盘扫描码转换标志位。
位7:保留,应置为0。
make一下,动一动鼠标,数据嗖嗖的传,这一步也完成了,我们继续做buffer。
4、buffer的实现
keyboard.h中的buffer可以直接使用,所以我们再在keyboard.h中
extern char keys[0x53][2];
struct char_buffer_struct{ unsigned char buffer[255];//255个字节的缓冲区 int buffer_point_head;//建立队列结构,这是队列头的位置 int buffer_point_tail;//队列尾的位置,新的中断放在队列头,系统处理队列尾的消息,当头尾相同则队列为 空或满 unsigned char is_full;//1为满,0为空 };
extern struct char_buffer_struct keyboard_buffer;
extern struct char_buffer_struct mouse_buffer;
然后回到cursor.c
#include "../keyboard/keyboard.h" struct char_buffer_struct mouse_buffer;
Int函数与keyboard一模一样,就不解读了
void IntKeyboard() { //取得扫描码 unsigned char scan_code = FunctionIn8(0x60); if (keyboard_buffer.is_full == 0)//如果buffer没满 { keyboard_buffer.buffer[keyboard_buffer.255] = scan_code; if (keyboard_buffer.255 < 255)//如果head没到头 keyboard_buffer.255++; else keyboard_buffer.255=0; if (keyboard_buffer.255 == keyboard_buffer.buffer_point_tail) //如果head与tail相遇,说明满了 { keyboard_buffer.is_full = 1; } } //通知PIC0可以接受新中断 FunctionOut8(0x20, 0x61); }
回到main.c
在while(1)中添加如下,同样与键盘一模一样
//鼠标消息处理 if (mouse_buffer.255 == mouse_buffer.buffer_point_tail) //先判断是否收尾相接,如果是,且没有满,则说明buffer为空,否则buffer为满 { if (mouse_buffer.is_full == 1)//满了 { DealMouse(mouse_buffer.buffer[mouse_buffer.buffer_point_tail]); if (mouse_buffer.buffer_point_tail < 255) mouse_buffer.buffer_point_tail++; else mouse_buffer.buffer_point_tail=0; mouse_buffer.is_full == 0; } } else { DealMouse(mouse_buffer.buffer[mouse_buffer.buffer_point_tail]); if (mouse_buffer.buffer_point_tail < 255) mouse_buffer.buffer_point_tail++; else mouse_buffer.buffer_point_tail=0; } } }
为了让main.c不显得臃肿,我把void DealKeyboard(unsigned char scan_code)函数放回keyboard.c中
然后把所有的绘图函数放在了font.c中
然后在cursor.c中定义了void DealMouse(unsigned char scan_code)函数,不要忘记声明。
我们要看看mouse的数据怎么解析。
初始化后的鼠标首先会收到第一个中断,传来的消息是0xfa,这是一条应答(response),可以忽略掉
然后开始传递标准数据包,给出过图了,再拿出来分析一下,我暂时没有滚轮鼠标可以测试,现在暂时用三数据包。
byte1:
位0:左键按下标志位,为1表示左键被按下。
位1:右键按下标志位,为1表示右键被按下。
位2:中键按下标志位,为1表示中键被按下。
位3:保留位,总是为1。
位4:X符号标志位,为1表示X位移量为负。
位5:Y符号标志位,为1表示Y位移量为负。
位6:X溢出标志位,为1表示X位移量溢出了。
位7:Y溢出标下位,为1表示Y位移量溢出了。
这里我们要使用一个技巧来分离数据包,Byte1的第三位总是为0,所以我们过滤掉第一个0xfa的应答数据包之后,依次获得三个数据包,然后通过检验第一个数据包的第三位是否为1来保证数据的准确。
当然我们又要用到结构体了
cursor.h定义如下结构体
struct cursor_data_struct{ unsigned char buf[3];//3个数据包 int move_x;// 移动 x 坐标 int move_y;//移动 y 坐标 int x;//x 绝对坐标 int y;//y 绝对坐标 unsigned char lrm;//左右中键,1为按下 unsigned char num_buf;//这是第几个数据包,0为应答包 } ;
声明全局结构体:
struct cursor_data_struct cursor_data;
解析过程:
void DealMouse(unsigned char scan_code) { switch (cursor_data.num_buf) { case 0: if(scan_code == 0xfa) { cursor_data.num_buf = 1; } break; case 1: if((scan_code & 0xc8) == 0x08)//如果溢出和数据包错位,丢弃 { cursor_data.buf[cursor_data.num_buf-1] = scan_code; cursor_data.num_buf = 2; break; } case 2: cursor_data.buf[cursor_data.num_buf-1] = scan_code; cursor_data.num_buf = 3; break; case 3: cursor_data.buf[cursor_data.num_buf-1] = scan_code; cursor_data.num_buf = 1; cursor_data.lrm = cursor_data.buf[0] & 0x07; //The movement values are 9-bit 2's complement integers, //where the most significant bit appears as a "sign" bit in byte 1 of the movement data packet cursor_data.move_x = cursor_data.buf[0] & 0x10 ? 0xffffff00 : 0 ;//获得符号位 cursor_data.move_y = cursor_data.buf[0] & 0x20 ? 0xffffff00 : 0 ; cursor_data.move_x |= cursor_data.buf[1];//获得位移 cursor_data.move_y |= cursor_data.buf[2]; cursor_data.x += cursor_data.move_x; cursor_data.y -= cursor_data.move_y;//y反向 DrawRectangle(400,200,800,216,0x000033); printf(400,200,0xffffff,"buf0=%x buf1=%x buf2=%x", \ cursor_data.buf[0], cursor_data.buf[1], cursor_data.buf[2]); DrawRectangle(400,300,800,316,0x000033); printf(400,300,0xffffff,"move_x=%d move_y=%d x=%d y=%d", \ cursor_data.move_x, cursor_data.move_y, cursor_data.x,cursor_data.y); break; } }
这里说明一点,鼠标数据穿过来是以补码的形式,可能做底层的小伙伴们经常遇到这种情况,其实对我们程序员来说,这种操作大大方便了我们的计算,因为只需要补齐符号位,直接就可以进行加减运算。协议中用这句话描述了传来的信息:
//The movement values are 9-bit 2’s complement integers, where the most significant bit appears as a “sign” bit in byte 1 of the movement data packet
9位补码,补齐符号位,直接与int类型进行加减运算就ok!
运算过程就是这段代码
cursor_data.move_x = cursor_data.buf[0] & 0x10 ? 0xffffff00 : 0 ;//获得符号位 cursor_data.move_y = cursor_data.buf[0] & 0x20 ? 0xffffff00 : 0 ; cursor_data.move_x |= cursor_data.buf[1];//获得位移 cursor_data.move_y |= cursor_data.buf[2];
最后我把鼠标的坐标和三个数据包显示了出来
还剩最后一步,画鼠,终于到最后了,这篇博客一天没有写完
四、移动鼠标
我们之前已经写了void DrawCursor(int x,int y, int type)这个函数,我们直接拿来使用就可以了。
把鼠标处理函数的case 3:改为如下。
case 3:
DrawRectangle(cursor_data.x,cursor_data.y,cursor_data.x+16,cursor_data.y+16,0x000033);
cursor_data.buf[cursor_data.num_buf-1] = scan_code;
cursor_data.num_buf = 1;
cursor_data.lrm = cursor_data.buf[0] & 0x07;
//The movement values are 9-bit 2's complement integers,
//where the most significant bit appears as a "sign" bit in byte 1 of the movement data packet
cursor_data.move_x = cursor_data.buf[0] & 0x10 ? 0xffffff00 : 0 ;//获得符号位 cursor_data.move_y = cursor_data.buf[0] & 0x20 ? 0xffffff00 : 0 ; cursor_data.move_x |= cursor_data.buf[1];//获得位移 cursor_data.move_y |= cursor_data.buf[2];
cursor_data.x += cursor_data.move_x;
cursor_data.y -= cursor_data.move_y;
DrawRectangle(400,200,800,216,0x000033);
printf(400,200,0xffffff,"buf0=%x buf1=%x buf2=%x", \
cursor_data.buf[0], cursor_data.buf[1], cursor_data.buf[2]);
DrawRectangle(400,300,800,316,0x000033);
printf(400,300,0xffffff,"move_x=%d move_y=%d x=%d y=%d", \
cursor_data.move_x, cursor_data.move_y, cursor_data.x,cursor_data.y);
if (cursor_data.x < 0)
cursor_data.x = 0;
if (cursor_data.x > 800 -16)//因为超过800-16就会从左侧显示,等以后解决
cursor_data.x = 800-16;
if (cursor_data.y < 0)
cursor_data.y = 0;
if (cursor_data.y > 600)
cursor_data.y = 600;
DrawRectangle(cursor_data.x,cursor_data.y,cursor_data.x+16,cursor_data.y+16,0x000033);
DrawCursor(cursor_data.x,cursor_data.y,0);
break;
但是由于我们没有制作图层,所以会出现这种情况
慢慢来吧!
相关文章推荐
- 从零开始搭建环境编写操作系统 AT&T GCC (五)显示鼠标和字符
- 从零开始搭建环境编写操作系统 AT&T GCC (三)引入C语言
- 从零开始搭建环境编写操作系统 AT&T GCC (七)GDB调试和-monitor
- 从零开始搭建环境编写操作系统 AT&T GCC (九)内存管理
- 从零开始搭建环境编写操作系统 AT&T GCC (四)绘制界面
- 从零开始搭建环境编写操作系统 AT&T GCC (一)搭建环境和测试环境
- 从零开始搭建环境编写操作系统 AT&T GCC (二)从实模式到保护模式
- [入门教程]使用Cocos2d-html5游戏引擎编写一个简单的游戏 第一回合: 搭建开发环境
- Linux 环境使用vim搭建php IDE -- 提高代码编写数度数倍!手把手教你打造程序员的上古神器VIM!
- ARM-Tiny6410-开发环境搭建-Hardware && Linux && arm-linux-gcc
- [工作环境搭建]笔记本与台式机共享鼠标与键盘
- 从零开始学android:搭建Android开发环境 —— ADT、Eclipse、Android SDK的配置及使用
- windows GCC环境 UT搭建选型,及 mockcpp的使用小东东
- Win7环境使用VC2008编写DLL实现键盘钩子
- 在操作系统Centos 6.3 上使用Nexus 2.8搭建MAVEN私服 及 cloudstack 开发环境
- orange's一个操作系统的实现--环境搭建
- bochs使用(linux下搭建操作系统开发环境)(zt)
- 可启动程序编写环境的搭建和使用
- Orange's一个操作系统的实现的开发环境的搭建
- bochs使用(linux下搭建操作系统开发环境,原创)