处理器:Intel Celeron(R) Dual-Core CPU
操作系统:Windows7 专业版 x86
阅读书籍:《30天自制操作系统》—川合秀实[2015.03.23 ]
将《30天自制操作系统》简称为“书”。对于书中的工具,可以专门对其笔记学习。
工具:../toolset/
每当鼠标中断发生时,鼠标会向鼠标控制电路回复3字节数据,CPU通过IN指令从鼠标电路设备端口中读取这些数据。这3字节的含义分别为:
判断第一字节高4位是否在0~ 3的范围内,同时判断第一字节低4位是否在8~ F的范围内,这样就可以避免鼠标接触不良产生数据丢失时对错位数据的解析。
struct MOUSE_DEC { unsigned char buf[3], phase; //鼠标3字节数据,用于读取鼠标3字节变量 int x, y, btn; //(x, y)用于存解读到的移动信息,btn存点击信息 };
/*获取鼠标3字节数据,读到3字节数据时解读它们 *mdec存储鼠标3字节数据和解读结果 *dat表示从缓冲区读来的3字节数据 */ int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) { if (mdec->phase == 0) { //激活鼠标时鼠标回复的内容 if (dat == 0xfa) { mdec->phase = 1; } return 0; } if (mdec->phase == 1) { //鼠标第1字节内容 if ((dat & 0xc8) == 0x08) { //11001000, //判断第1个字节高4位是否在0~3范围内, //判断第1个字节低4位是否在8~F范围内 mdec->buf[0] = dat; mdec->phase = 2; } return 0; } if (mdec->phase == 2) { //鼠标第2字节内容 mdec->buf[1] = dat; mdec->phase = 3; return 0; } if (mdec->phase == 3) { //鼠标第3字节内容 mdec->buf[2] = dat; mdec->phase = 1; mdec->btn = mdec->buf[0] & 0x07; //得到鼠标按键状态 mdec->x = mdec->buf[1]; //鼠标x方向移动数据 mdec->y = mdec->buf[2]; //鼠标y方向移动移动 if ((mdec->buf[0] & 0x10) != 0) { mdec->x |= 0xffffff00; //第一字节bit[4]为1时,x方向坐标高24位为1 } if ((mdec->buf[0] & 0x20) != 0) { mdec->y |= 0xffffff00; //第一字节bit[5]为1时,y方向坐标高24位为1 } mdec->y = - mdec->y; //鼠标的y方向与画面符号相反 return 1; } return -1;//错误时mdec->phase != 0时返回 }
修改激活鼠标的函数,使鼠标回应的0xfa数据在这里得到标记。如果不提前让mdec->phase= 0,mouse_decode()函数就会返回-1。
#define KEYCMD_SENDTO_MOUSE 0xd4 #define MOUSECMD_ENABLE 0xf4 void enable_mouse(struct MOUSE_DEC *mdec) { //鼠标有效 wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); //成功后0xfa会被发送过来 mdec->phase = 0; //舍掉0xfa的标记 return; }
将这些函数和结构体加入bootpack.c中,访问缓冲区部分的程序变为:
enable_mouse(&mdec); //不断查询缓冲区,如果有数据,将缓冲区内的数据显示完为止 for (;;) { io_cli(); //查看缓冲区时,屏蔽中断 if (fifo8_status(&mousefifo) == 0) { io_stihlt(); //如果缓冲区内无数据则开启中断并让CPU休眠,直到中断唤醒 } else { data = fifo8_get(&mousefifo); io_sti(); //开启中断 if (mouse_decode(&mdec, data) != 0) { //显示解读到的鼠标数据 sprintf(str, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { str[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { str[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { str[2] = 'C'; } boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 32, 16, 32 + 15 * 8 - 1, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, str); } } }
编译运行此时的操作系统程序,得到如下结果:
Figure1. 解读到的鼠标操作的数据
请根据显示的数据跟鼠标移动结合理解解读出来的数据。它表示鼠标x和y方向上相对于前一刻坐标的变化值。
int mx, my; mx = (binfo->scrnx - 16) / 2; my = (binfo->scrny - 28 - 16) / 2; mx += mdec.x; my += mdec.y; if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 16) { mx = binfo->scrnx - 16; } if (my > binfo->scrny - 16) { my = binfo->scrny - 16; }
如果鼠标的坐标超出了屏幕范围,则做特殊处理。
//隐藏上次鼠标移动留下的痕迹 boxfill8(binfo->vram, binfo->scrnx, COL8_000000, mx, my, mx + 15, my + 15); /*…鼠标坐标处理…*/ sprintf(str, "(%3d, %3d)", mx, my); putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
往bootpack.c中加入鼠标移动的代码,主函数的代码如下(主函数代码过长,要被整理了撒):
void HariMain(void) { int data; int mx, my; char str[30]; char mcursor[256]; char mousebuf[128]; //接收鼠标数据缓冲区 struct MOUSE_DEC mdec; struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; //初始化GDT和IDT init_gdtidt(); //初始化PIC init_pic(); //屏蔽所有中断 io_sti(); fifo8_init(&mousefifo, 32, mousebuf); io_out8(PIC0_IMR, 0xfb); io_out8(PIC1_IMR, 0xef); //初始化鼠标控制电路 init_keyboard(); //初始化调色板 init_palette(); //初始化屏幕颜色 init_screen8(binfo->vram, binfo->scrnx, binfo->scrny); putfonts8_asc(binfo->vram, binfo->scrnx, 110, 60, COL8_FFFFFF, "oh Still Haribote OS."); mx = (binfo->scrnx - 16) / 2; my = (binfo->scrny - 28 - 16) / 2; //初始化鼠标显示 init_mouse_cursor8(mcursor, COL8_000000); //激活鼠标 enable_mouse(&mdec); //不断查询缓冲区,如果有数据,将缓冲区内的数据显示完为止 for (;;) { io_cli(); //查看缓冲区时,屏蔽中断 if (fifo8_status(&mousefifo) == 0) { io_stihlt(); //如果缓冲区内无数据则开启中断并让CPU休眠,直到中断唤醒 } else { data = fifo8_get(&mousefifo); io_sti(); //开启中断 if (mouse_decode(&mdec, data) != 0) { //获得解读到的鼠标坐标 sprintf(str, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { str[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { str[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { str[2] = 'C'; } boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 + 15 * 8 - 1, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, str); boxfill8(binfo->vram, binfo->scrnx, COL8_000000, mx, my, mx + 15, my + 15);//隐藏上次鼠标移动留下的痕迹 mx += mdec.x; my += mdec.y; if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 16) { mx = binfo->scrnx - 16; } if (my > binfo->scrny - 16) { my = binfo->scrny - 16; } sprintf(str, "(%3d, %3d)", mx, my); putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); } } } }
打开“!cons_nt.bat”编译成功后,使用“makerun”命令在QEMU中运行得鼠标移动的结果:
Figure3. 鼠标移动2
[1] 鼠标控制电路在键盘控制电路中,读取鼠标发送数据的端口号为0x0060。
[2] 通过初始化鼠标控制电路激活鼠标时,鼠标会给鼠标控制电路回复oxfa数据,会触发鼠标中断函数。鼠标中断发生时会连续向鼠标控制电路发送3个连续字节。所以,鼠标中断函数内不可包含复杂的处理,否则中断函数往缓冲区存储的数据就不对。
[3] 可以通过PIC指定鼠标中断的中断号。
[4] mouse_move。
[x86OS] Note Over.
[2015.04.17]