简介
鼠标发送的数据要三个字节一起解读,所以我们的内核要等待鼠标发送足够的数据后才可以采取行动。当鼠标被激活后,它会立马给内核发送一个字节数据,数值为0xfa, 当内核收到这个数据后,就可以开始积攒数据,每接收三个字节后,根据数据绘制鼠标。
这三个字节数据是有一定特点的,第一个字节0xmn, m的数值必须在0-3这个范围内,所以这意味着该字节的第6、7两个比特位必须为0,n的值必须在8-F之间,这意味着该字节数据对应的第4个比特位必须为1。左键,滚轮,右键被按下时,n的最低3位会被置1。
第二个字节用来表示鼠标的左右移动,对该字节进行相应处理后,可以得到鼠标平移的坐标变换。
第三个字节的数据表示鼠标的上下移动,对该字节进行相应处理后,可以得到鼠标垂直移动时的坐标变换。
目标
1.os.h 文件定义MouseDes处理鼠标移动数据类型,os.h文件如下:
//*******************************相关数据类型声明*************************//定义调色板颜色
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15//屏幕宽度
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200//定义缓冲区
typedef struct _FIFO8{//指向缓冲区char* buf;//r:读索引,w:写索引//len:存储数据长度int r, w, size, len, flag;
}FIFO8;//定义鼠标移动处理模型
//鼠标处理需要连续处理3字节
//phase 表三处理的字节阶段
//offX,offY 表示当前鼠标的偏移
//x,y 表示鼠标当前所在的坐标位置
typedef struct _MouseDes{char buf[3], phase;int offX,offY;int x, y, btn;
}MouseDes;//*******************************函数声明*************************//初始化调色板
void initPallet();/***绘制矩形*x 矩形左上角x坐标*y 矩形左上角y坐标*width 宽度*height 高度*colIndex pallet_color 类型调色板颜色索引,即矩形颜色*/void fillRect(int x,int y,int width,int height,char colIndex);//绘制桌面背景
void drawBackground();/***绘制字体*@param addr 绘制的起始显存地址*@param x 绘制的x坐标*@param y 绘制的y坐标*@param col 绘制颜色*@param pch 绘制的字符数组8*16,每一行共8位,共16行*@param screenWidth 屏幕宽度*/
void putChar(char *addr,int x,int y,char col,unsigned char *ch,int screenWidth);/**初始化鼠标指针*@param vram 绘制的起始显存地址*@param x 绘制鼠标指针最左上角x坐标*@param y 绘制鼠标指针最左上角y坐标*@param bc 绘制的矩形填充颜色,和背景色一样将能看到鼠标指针*/
void drawMouseCursor(char *vram,int x,int y,char bc);/**char 类型数据转换为16进制字符数据*@param val 待转化为16进制的数值*@param arr 保存16进制字符串数据的数组*/
void char2HexStr(unsigned char val,char *arr);//初始化鼠标硬件
void init_mouse();//缓存初始化
void fifo8_init(FIFO8 *fifo,int size,char *buf);
//缓冲区存放数据
int fifo8_put(FIFO8 *fifo,char data);
//缓冲区读取数据
int fifo8_get(FIFO8 *fifo);//鼠标移动处理
//@param mdec 鼠标移动处理模型
//@param bc 绘制鼠标的背景色
void mouseCursorMoved(MouseDes *mdec,char bc);
2.os.c 文件如下:
// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//#include "os.h"
#include "io.h"
#include "ascii_font.h"static char keybuf[32];
static char mousebuf[128];static FIFO8 keybufInfo;
static FIFO8 mousebufInfo;//鼠标移动模型
static MouseDes mouseDes;static int num = 0;//操作系统C语言入口函数--可以指定为其他
void init_main() {io_sti();initPallet();drawBackground();fifo8_init(&keybufInfo,32,keybuf);fifo8_init(&mousebufInfo,128,mousebuf);mouseDes.x = (320-16)/2;mouseDes.y = (200-16)/2;mouseDes.phase = 0;drawMouseCursor((char *)0xa0000,mouseDes.x,mouseDes.y,COL8_008484);init_mouse();for(; ;){if(keybufInfo.len>0){io_cli();static char arr[4] = {'0','x'};unsigned char *ascii = ascii_array;for(int t=0;t<keybufInfo.len;t++){char data = fifo8_get(&keybufInfo);char2HexStr(data,arr);for(int i=0;i<4;i++){int x = (num)%32*10;int y = (num)/32*20;putChar((char *)0xa0000,x,y,COL8_FFFFFF,ascii+(arr[i]-0x20)*16,SCREEN_WIDTH);num++;}}io_sti();}else if(mousebufInfo.len>0){io_cli();for(int t=0;t<mousebufInfo.len;t++){mouseCursorMoved(&mouseDes,COL8_008484);}io_sti();}else{io_hlt();}}}void initPallet(){//定义调色板static char table_rgb[16*3] = {0x00, 0x00, 0x00, /* 0:黑色*/0xff, 0x00, 0x00, /* 1:亮红*/0x00, 0xff, 0x00, /* 2:亮绿*/0xff, 0xff, 0x00, /* 3:亮黄*/0x00, 0x00, 0xff, /* 4:亮蓝*/0xff, 0x00, 0xff, /* 5:亮紫*/0x00, 0xff, 0xff, /* 6:浅亮蓝*/0xff, 0xff, 0xff, /* 7:白色*/0xc6, 0xc6, 0xc6, /* 8:亮灰*/0x84, 0x00, 0x00, /* 9:暗红*/0x00, 0x84, 0x00, /* 10:暗绿*/0x84, 0x84, 0x00, /* 11:暗黄*/0x00, 0x00, 0x84, /* 12:暗青*/0x84, 0x00, 0x84, /* 13:暗紫*/0x00, 0x84, 0x84, /* 14:浅灰蓝*/0x84, 0x84, 0x84, /* 15:暗灰*/};unsigned char *rgb = (unsigned char *)table_rgb;int flag = io_readFlag();io_cli();io_out8(0x03c8, 0);for(int i=0;i<16;i++){io_out8(0x03c9,rgb[0] / 4);io_out8(0x03c9,rgb[1] / 4);io_out8(0x03c9,rgb[2] / 4);rgb += 3;}io_writeFlag(flag);
}void fillRect(int x,int y,int width,int height,char colIndex){char *vram = (char *)0xa0000;for(int i=y;i<=y+height;i++){for(int j=x;j<=x+width;j++){vram[i*SCREEN_WIDTH+j] = colIndex;}}
}void drawBackground(){fillRect(0,0,SCREEN_WIDTH-1,SCREEN_HEIGHT-29, COL8_008484);fillRect(0,SCREEN_HEIGHT-28,SCREEN_WIDTH-1,28, COL8_848484);fillRect(0,SCREEN_HEIGHT-27,SCREEN_WIDTH,1, COL8_848484);fillRect(0,SCREEN_HEIGHT-26,SCREEN_WIDTH,25, COL8_C6C6C6);fillRect(3,SCREEN_HEIGHT-24,56,1, COL8_FFFFFF);fillRect(2,SCREEN_HEIGHT-24,1,20, COL8_FFFFFF);fillRect(3,SCREEN_HEIGHT-4,56,1, COL8_848484);fillRect(59,SCREEN_HEIGHT-23,1,19, COL8_848484);fillRect(2,SCREEN_HEIGHT-3,57,0, COL8_000000);fillRect(60,SCREEN_HEIGHT-24,0,19, COL8_000000);fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-24,43,1, COL8_848484);fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-23,0,19, COL8_848484);fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-3,43,0, COL8_FFFFFF);fillRect(SCREEN_WIDTH-3,SCREEN_HEIGHT-24,0,21, COL8_FFFFFF);
}void putChar(char *addr,int x,int y,char col,unsigned char *pch,int screenWidth){for(int i=0;i<16;i++){char ch = pch[i];int off = (y+i)*screenWidth;//显示的字形最左边的是低地址,右侧的是高地址。例如:0x80,则高地址部分显示在内存的低地址,//最低位的应该偏移7if((ch & 0x01) != 0){addr[off+x+7] = col;}if((ch & 0x02) != 0){addr[off+x+6] = col;}if((ch & 0x04) != 0){addr[off+x+5] = col;}if((ch & 0x08) != 0){addr[off+x+4] = col;} if((ch & 0x10) != 0){addr[off+x+3] = col;}if((ch & 0x20) != 0){addr[off+x+2] = col;}if((ch & 0x40) != 0){addr[off+x+1] = col;} if((ch & 0x80) != 0){addr[off+x+0] = col;}}
}void drawMouseCursor(char *vram,int x,int y,char bc){//16*16 Mouse //鼠标指针点阵static char cursor[16][16] = {"*...............","**..............","*O*.............","*OO*............","*OOO*...........","*OOOO*..........","*OOOOO*.........","*OOOOOO*........","*OOOOOOO*.......","*OOOO*****......","*OO*O*..........","*O*.*O*.........","**..*O*.........","*....*O*........",".....*O*........","......*........."};for (int i = 0; i < 16; i++) {for (int j = 0; j < 16; j++) {int off = (i+y)*SCREEN_WIDTH+x+j;if (cursor[i][j] == '*') {vram[off] = COL8_000000;}if (cursor[i][j] == 'O') {vram[off] = COL8_FFFFFF;}if (cursor[i][j] == '.') {vram[off] = bc;}}}}void char2HexStr(unsigned char val,char *arr) {unsigned char tmp = val >> 4;if(tmp>=10){arr[2] = 'a'+tmp-10;}else{arr[2] = '0'+tmp;}tmp = val & 0x0f;if(tmp>=10){arr[3] = 'a'+tmp-10;}else{arr[3] = '0'+tmp;}
}/**8259A 键盘中断调用**/
void int_keyboard(char *index){//0x20是8259A控制端口//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号io_out8(0x20,0x21);//读取8259A 0x60端口键盘扫描码char data = io_in8(0x60);fifo8_put(&keybufInfo,data);
}#define PORT_KEYDAT 0x60
#define PORT_KEYSTA 0x64
#define PORT_KEYCMD 0x64
#define KEYCMD_WRITE_MODE 0x60
#define KBC_MODE 0x47//鼠标电路对应的一个端口是 0x64, 通过读取这个端口的数据来检测鼠标电路的状态,
//内核会从这个端口读入一个字节的数据,如果该字节的第二个比特位为0,那表明鼠标电路可以
//接受来自内核的命令,因此,在给鼠标电路发送数据前,内核需要反复从0x64端口读取数据,
//并检测读到数据的第二个比特位,直到该比特位为0时,才发送控制信息
void waitKBCReady(){for( ; ;){if((io_in8(PORT_KEYSTA) & 0x02)==0){break;}}
}#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4//初始化键盘控制电路,鼠标控制电路是连接在键盘控制电路上,通过键盘电路实现初始化
void init_mouse(){waitKBCReady();//0x60让键盘电路进入数据接受状态io_out8(PORT_KEYCMD,KEYCMD_WRITE_MODE);waitKBCReady();//数据0x47要求键盘电路启动鼠标模式,这样鼠标硬件所产生的数据信息,通过键盘电路端口0x60就可读到io_out8(PORT_KEYDAT,KBC_MODE); waitKBCReady();io_out8(PORT_KEYCMD,KEYCMD_SENDTO_MOUSE);waitKBCReady();//0xf4数据激活鼠标电路,激活后将会给CPU发送中断信号io_out8(PORT_KEYDAT,MOUSECMD_ENABLE);
}/**8259A 鼠标中断调用**/
void int_mouse(char *index){//当中断处理后,要想再次接收中断信号,就必须向中断控制器发送一个字节的数据io_out8(0x20,0x20);io_out8(0xa0,0x20);//读取鼠标数据char data = io_in8(0x60);fifo8_put(&mousebufInfo,data);
}void fifo8_init(FIFO8 *fifo, int size,char *buf){fifo->buf = buf;fifo->r = 0;fifo->w = 0;fifo->size = size;fifo->len = 0;fifo->flag = 0;
}int fifo8_put(FIFO8 *fifo,char data){if (fifo->len == fifo->size) {return -1;}fifo->buf[fifo->w] = data;fifo->w++;if (fifo->w == fifo->size) {fifo->w = 0;}fifo->len++;return 0;
}int fifo8_get(FIFO8 *fifo) {if (fifo->len == 0) {return -1;}int data = fifo->buf[fifo->r];fifo->r++;if (fifo->r == fifo->size) {fifo->r = 0;}fifo->len--;return data;
}int mouse_decode(MouseDes *mdec,unsigned char dat){ int flag = -1;if (mdec->phase == 0) { if (dat == 0xfa) { mdec->phase = 1; } flag = 0;} else if (mdec->phase == 1) { if ((dat & 0xc8) == 0x08) { mdec->buf[0] = dat; mdec->phase = 2; } flag = 0;} else if (mdec->phase == 2) { mdec->buf[1] = dat; mdec->phase = 3; flag = 0;} else if (mdec->phase == 3) { mdec->buf[2] = dat; mdec->phase = 1; mdec->btn = mdec->buf[0] & 0x07; mdec->offX = mdec->buf[1]; mdec->offY = mdec->buf[2]; if ((mdec->buf[0] & 0x10) != 0) { mdec->offX |= 0xffffff00; } if ((mdec->buf[0] & 0x20) != 0) { mdec->offY |= 0xffffff00; } //鼠标y坐标偏移和平面y坐标相反mdec->offY = -mdec->offY; flag = 1; } return flag;
}//鼠标移动处理
void mouseCursorMoved(MouseDes *mdec,char bc){unsigned char val = fifo8_get(&mousebufInfo);//表示处理到第3步,需要绘制鼠标光标if(mouse_decode(mdec,val) == 1) {fillRect(mdec->x,mdec->y,16,16,bc);mdec->x += mdec->offX;mdec->y += mdec->offY;if(mdec->x < 0){mdec->x = 0;}if(mdec->x > SCREEN_WIDTH-16/2){mdec->x = SCREEN_WIDTH-16/2;}if(mdec->y < 0 ){mdec->y = 0;}if(mdec->y > SCREEN_HEIGHT - 16){mdec->y = SCREEN_HEIGHT - 16;}drawMouseCursor((char *)0xa0000,mdec->x,mdec->y,bc);}
}
3.编译并加载floppy.img 文件,让虚拟机捕获鼠标后效果如同pc 上移动鼠标一样真切!如下图为鼠标从平面中间移动后的位置,平面上的文字可不用理会(笔者在Ubuntu 操作系统下开发,使用VirtualBox虚拟机在Ubuntu下加载floppy.img 文件,在截屏使需要按下 左 shift 键导致的输出)
说明
当收到字节0xfa的时候,系统进入数据收集阶段,当收到第一个数据时,判断字节是否符合前面所说的要求,符合的话,进入第二阶段,接收第二个字节,当进入第三阶段,接收完第三个字节后,开始对坐标数据进行处理,btn存储的是第一字节的低3位,它表示当前鼠标哪个按键被按下了,接着看第一字节的第4,第5个比特位,如果第4、5个比特位为1,那么第二、三字节需要做一些处理,也就是从第8位开始全部设置为1。