您的位置:首页 > 其它

从零开始搭建环境编写操作系统 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中使用它的地址。

  

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;




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



  慢慢来吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐