[Rx86OS-IX] 解读鼠标数据 移动鼠标

平台

处理器:Intel Celeron(R) Dual-Core CPU

操作系统:Windows7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.03.23 ]

将《30天自制操作系统》简称为“书”。对于书中的工具,可以专门对其笔记学习。

工具:../toolset/


1 解读鼠标数据

1.1 鼠标3字节数据含义

每当鼠标中断发生时,鼠标会向鼠标控制电路回复3字节数据,CPU通过IN指令从鼠标电路设备端口中读取这些数据。这3字节的含义分别为:

  • 第一字节的低3位表鼠标键状态,bit[0]为1表鼠标左击,bit[1]为1表鼠标右击,bit[2]为1表鼠标点击滑轮;(低4位只在点击鼠标(左中右)时才会发生变化,在8~ F范围内变化,鼠标第一字节的高4位在0~3范围内变化,具体含义见解读鼠标数据的程序);
  • 鼠标的第二字节与鼠标左右移动有关系;
  • 鼠标第三个字节与鼠标上下移动有关系。

判断第一字节高4位是否在0~ 3的范围内,同时判断第一字节低4位是否在8~ F的范围内,这样就可以避免鼠标接触不良产生数据丢失时对错位数据的解析。


1.2 解读鼠标数据

(1) 描述鼠标数据的结构体

struct MOUSE_DEC {
	unsigned char buf[3], phase;    //鼠标3字节数据,用于读取鼠标3字节变量
	int x, y, btn;                 //(x, y)用于存解读到的移动信息,btn存点击信息
};

(2) 读取缓冲区3字节数据解析显示

/*获取鼠标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);
			}
		}
	}

编译运行此时的操作系统程序,得到如下结果:

[Rx86OS-IX] 解读鼠标数据 移动鼠标_第1张图片

Figure1. 解读到的鼠标操作的数据

请根据显示的数据跟鼠标移动结合理解解读出来的数据。它表示鼠标x和y方向上相对于前一刻坐标的变化值。


2 移动鼠标图标

2.1 鼠标坐标处理

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;
}

如果鼠标的坐标超出了屏幕范围,则做特殊处理。


2.2 移动鼠标

//隐藏上次鼠标移动留下的痕迹
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中运行得鼠标移动的结果:

[Rx86OS-IX] 解读鼠标数据 移动鼠标_第2张图片

Figure2. 鼠标移动1

[Rx86OS-IX] 解读鼠标数据 移动鼠标_第3张图片

Figure3. 鼠标移动2

3 总结

[1] 鼠标控制电路在键盘控制电路中,读取鼠标发送数据的端口号为0x0060。

[2] 通过初始化鼠标控制电路激活鼠标时,鼠标会给鼠标控制电路回复oxfa数据,会触发鼠标中断函数。鼠标中断发生时会连续向鼠标控制电路发送3个连续字节。所以,鼠标中断函数内不可包含复杂的处理,否则中断函数往缓冲区存储的数据就不对。

[3] 可以通过PIC指定鼠标中断的中断号。

[4] mouse_move。


[x86OS] Note Over.

[2015.04.17]

你可能感兴趣的:([Rx86OS-IX] 解读鼠标数据 移动鼠标)