失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 从零开始搭建环境编写操作系统 ATT GCC (八)使用键盘和滚轮鼠标

从零开始搭建环境编写操作系统 ATT GCC (八)使用键盘和滚轮鼠标

时间:2019-04-21 01:00:46

相关推荐

从零开始搭建环境编写操作系统 ATT GCC (八)使用键盘和滚轮鼠标

终于要让键盘和鼠标使用起来了,前期工作都完成了,这一步其实是非常容易的。

一、键盘中断和处理

当键盘中的一个按钮被按下或抬起时,将通过8259A芯片向CPU发送一个键盘中断的消息,这时CPU将转入键盘中断处理程序。键盘上的每个按键都对应一个扫描码,当有键盘中断时,这个扫描码被送入0x60端口。CPU通过读取0x60端口中的扫描码就可以得知是键盘中的哪个键盘被按下或抬起了。

在system文件夹下新建一个keyboard文件夹,然后新建keyboard.c和keyboard.h

打开functions.s,要新建一个键盘的中断处理函数,不要忘记.global一下,因为我们还要在main.c中使用它的地址。

KeyboardIntCallBack: clipushalpushfl//调用键盘中断处理函数call IntKeyboardpopflpopalstiiret

回到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 Makefilegcc -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.okeyboard.old -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_10X02 // 1#define KEY_20X03 // 2#define KEY_30X04 // 3#define KEY_40X05 // 4#define KEY_50X06 // 5#define KEY_60X07 // 6#define KEY_70X08 // 7#define KEY_80X09 // 8#define KEY_90X0A // 9#define KEY_00X0B // 0#define KEY_DASH 0X0C // -#define KEY_EQUAL 0X0D // =#define KEY_BACKSPACE 0X0E // BACKSPACE#define KEY_TAB 0X0F // TAB#define KEY_Q0X10 // Q#define KEY_W0X11 // W#define KEY_E0X12 // E#define KEY_R0X13 // R#define KEY_T0X14 // T#define KEY_Y0X15 // Y#define KEY_U0X16 // U#define KEY_I0X17 // I#define KEY_O0X18 // O#define KEY_P0X19 // P#define KEY_LBRACKET 0X1A // [#define KEY_RBRACKET 0X1B // ]#define KEY_ENTER 0X1C // ENTER#define KEY_CTRL 0X1D // CTRL#define KEY_A0X1E // A#define KEY_S0X1F // S#define KEY_D0X20 // D#define KEY_F0X21 // F#define KEY_G0X22 // G#define KEY_H0X23 // H#define KEY_J0X24 // J#define KEY_K0X25 // K#define KEY_L0X26 // 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_Z0X2C // Z#define KEY_X0X2D // X#define KEY_C0X2E // C#define KEY_V0X2F // V#define KEY_B0X30 // B#define KEY_N0X31 // N#define KEY_M0X32 // M#define KEY_COMMA 0X33 // ,#define KEY_PERIOD 0X34 // .#define KEY_SLASH 0X35 // /#define KEY_RIGHT_SHIFT0X36 // 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_F10X3B // F1#define KEY_F20X3C // F2#define KEY_F30X3D // F3#define KEY_F40X3E // F4#define KEY_F50X3F // F5#define KEY_F60X40 // F6#define KEY_F70X41 // F7#define KEY_F80X42 // F8#define KEY_F90X43 // F9#define KEY_F10 0X44 // F10#define KEY_NUM_LOCK 0X45 // NUM LOCK#define KEY_SCROLL_LOCK0X46 // SCROLL LOCK#define KEY_HOME 0X47 // HOME#define KEY_UP0X48 // UP#define KEY_PAGE_UP0X49 // 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_10X02 // 1…………………………………………省略了#define KEY_INSERT 0X52 // INSERT#define KEY_DEL 0X53 // DELextern 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 backgroundDrawBar();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++;elsekeyboard_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++;elsekeyboard_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++;elsekeyboard_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标准协议,百度文库有中文版,我查阅的是英文版,网址如下:

puter-/ps2mouse/

无滚轮数据包:

有滚轮数据包:

无论使用了什么鼠标,默认都是无滚轮的,若要使用滚轮,则需要用0xF3这个设置鼠标采样率的命令,按如下的顺序进行操作,协议给出了详细的操作步骤,下边是简化:

1. 设置鼠标采样率为200

2. 设置鼠标采样率为100

3. 设置鼠标采样率为80

这之后,如果你的鼠标是个滚轮鼠标,那么,它将转到滚轮鼠标下进行工作,这个时候,主机向它发送0xF2(获得鼠标类型ID)命令,你的工作在滚轮鼠标下的鼠标将向主机返回它的类型ID,但如果你的鼠标不支持滚轮鼠标,即如果你的鼠标只是一个二维鼠标,它返回给主机的类型ID将是0,这样,主机就能够知道现在你用的鼠标是什么类型的鼠标,并由此知道应当接受3个还是4个数据包了。

(由于我这里没有滚轮鼠标,我用的笔记本进行的编程,先不增加滚轮功能,之后我会补上,上边的信息足够自己做了,有兴趣的自己做一下)

2、中断具体实现

感觉鼠标的难点应该在数据解析上。

鼠标的中断是IRQ 12,这个可以从第六讲中查到,配置中断的步骤应该已经烂熟于心了吧。

按照键盘的中断配置过程。

首先在functions.s中建立

MouseIntCallBack: clipushalpushfl//调用键盘中断处理函数call IntMousepopflpopalstiiret

然后修改中断向量表,先看看IRQ12对应的是中断向量表的第几个,0x20+12=0x2c

//mouseidt[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++;elsekeyboard_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++;elsemouse_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++;elsemouse_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 packetcursor_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 packetcursor_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;

但是由于我们没有制作图层,所以会出现这种情况

慢慢来吧!

如果觉得《从零开始搭建环境编写操作系统 ATT GCC (八)使用键盘和滚轮鼠标》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。