鼠标坏了怎么用键盘控制电脑win10(鼠标坏了怎么用键盘控制电脑面)

第5天,我们尝试在屏幕上显示了字符串,并且显示了鼠标

第5天的教程链接:https://www.toutiao.com/a7067012425652060686

第5天的显示程序,让当前这个界面,有点像操作系统的样子了

但是,这个鼠标是静止的,所以。我们下一步就是先让鼠标动起来,让屏幕对鼠标的按键和移动操作有响应。

能对鼠标响应之后,再对键盘有响应。

好了,那我们的目标就确定了:让当前的屏幕对鼠标有响应,分别对鼠标的按键和移动有响应。

要做到对鼠标有响应,需要先设置寄存器GDTR, 并生成一个表GDT,然后设置IDTR,并生成一个表IDT。

这个咱们前一天的教程中已经完成了。

具体的,我们可以把获取鼠标按键,移动等代码放在IDT定义的中断函数中去实现,然后再想办法把移动后的鼠标显示到界面上来。

实际上这个IDT和GDT我们在第5天的代码中已经实现了,所以今天完成鼠标的˙中断函数,在中断函数里控制,响应鼠标动作。

只要能够响应鼠标的动作,就可以用鼠标的动作去改变操作系统显示的内容。比如让鼠标指针随着鼠标的动作而改变。

今天的内容会按照以下顺序展开:

  1. 初始化中断相关硬件:中断控制器
  2. 鼠标的中断函数实现分析
  3. C语言写的中断函数
  4. 对中断函数进行优化:将比较耗时的操作移出中断函数

使用中断功能:需要先初始化中断控制硬件

中断控制有专门的硬件:可编程中断控制器,programmable interrupt controller,简称PIC.

下面咱们直接看程序:

void init_pic(void)
/* PIC初始化设定 */
{
	io_out8(PIC0_IMR,  0xff  ); /* 禁止所有中断 */
	io_out8(PIC1_IMR,  0xff  ); /* 禁止所有中断 */

	io_out8(PIC0_ICW1, 0x11  ); /* 边沿触发 */
	io_out8(PIC0_ICW2, 0x20  ); /* IRQ0-7由INT20-27接收 */
	io_out8(PIC0_ICW3, 1 << 2); /* PIC1由IRQ2接收 */
	io_out8(PIC0_ICW4, 0x01  ); /* 无缓冲区模式 */

	io_out8(PIC1_ICW1, 0x11  ); /* 边沿触发 */
	io_out8(PIC1_ICW2, 0x28  ); /* IRQ8-15由INT28-接受 */
	io_out8(PIC1_ICW3, 2     ); /* PIC1由IRQ2连接 */
	io_out8(PIC1_ICW4, 0x01  ); /* 无缓冲区 */

	io_out8(PIC0_IMR,  0xfb  ); /* 11111011 PIC1以外全禁止 */
	io_out8(PIC1_IMR,  0xff  ); /* 11111111 禁止所有中断 */

	return;
}

程序解读:

PIC0_IMR, 第0个中断控制器的屏蔽寄存器。8位对应8路中断信号。如果某一位为1,那么该位所对应的信号就被屏蔽了,也就是说中断信号产生以后,CPU不会感知到。

这里我们要对中断控制器进行初始化,当然要把中断屏蔽了。所以,我们将PIC0_IMR设置位0xff,将PIC1_IMR设置位0xff.

就是将中断控制器0,中断控制器1的中断信号全部屏蔽了

下面的程序都是有关ICW的设定了。ICW,initial control word,初始化控制数据。也就是把数据写在ICW里,就是对中断控制器初始化。

每个PIC都有4个ICW,ICW1,ICW2,ICW3,ICW4,

ICW1,ICW4主要是设定配线方式。

ICW3设定主从连接方式的,也是8位,如果将其第2位设置位1,那么就是将其第二位作为主从连接用,来接受从中断控制器的中断信号。

ICW2设定中断号,如果设定为0x20,那么当前控制器产生的中断号为:0x20+0,0×20+1,0×20+…,0x20+7,即:0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27.

所以整个init_pic函数主要设定了两个中断控制器PIC0,PIC1,其中,PIC0是主,PIC1是从。

另外,这里使用了OUT指令对PIC的控制字进行操作,因为PIC是CPU的外部设别,CPU只要按照端口去对应写入到控制字就行了。

分别设定了这两个中断控制器的电器连接属性,中断号,以及主从连接属性。

鼠标的中断函数

完成PIC的初始化设定后,硬件上,CPU就可以感知到鼠标的动作了,我们编写中断函数,在感知到鼠标动作后,打印出一些内容就行了。

一般来说,鼠标的中断函数只能在汇编中写,但是这里我们既然用了C语言来写操作系统,自然就想用C语言来写中断函数。

要用C语言写中断函数,就必须用汇编来做桥梁,也就是说在汇编写的中断函数中调用C语言写的中断函数。

汇编写的中断函数只是做一些数据的传递工作,然后调用C语言写的中断函数,不做跟鼠标相关的内容。

我们先来看看汇编写的中断函数,如下:

以下代码摘自:https://gitee.com/camark/MakeComputerOperateSystemin30Days/blob/master/projects/06_day/harib03e/naskfunc.nas

EXTERN	 _inthandler2c   ; 引入C语言写的中断函数
_asm_inthandler2c:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX ; make DS=SS
		MOV		ES,AX ; make ES=SS
		CALL	_inthandler2c
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD
       

可以看到,中间有一句:CALL _inthandler2c

这句代码调用了C语言写的中断函数。

这句代码之外的其他代码,基本上都是push,pop,栈的存入和取出。

也就是说,在这个汇编写的中断函数里,跟鼠标相关的操作,啥都没有做。

这个函数就是调用了C语言中写的中断函数。

如何把汇编中的中断函数放在C语言中实现

如上一段所示,用汇编写一个中断函数,在这个汇编写的中断函数里,调用C写的中断函数就行了。

汇编写的中断函数可以响应系统中断。

因为汇编写的中断函数调用了C语言写的中断函数,所以在它响应系统中断时,C语言写的中断函数就运行起来了。

我们使用C语言写的中断函数来完成对鼠标的响应。

那么C语言写的中断函数如下:


void inthandler2c(int *esp)
/* 此函数受汇编中断函数调用 */
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 2C (IRQ-12) : PS/2 mouse");
	for (;;) {
		io_hlt();
	}
}

这个函数内部先是用boxfill8绘制了长方形,然后又在长方形上写了一串字符串:"INT 2C (IRQ-12) : PS/2 mouse"

那么,如果一切正常,将会显示如下画面:

鼠标坏了怎么用键盘控制电脑win10(鼠标坏了怎么用键盘控制电脑面)

但是,当我们编译完成后,发现并没有出现如上画面。无论是我们滑动鼠标也好,还是点击按钮,都没有反应。

这是因为,鼠标作为计算机的外部设备,也是需要先发送一些控制码,使与鼠标相关的硬件有效。

与鼠标有关的硬件其实就是键盘控制器和鼠标电路了,所以这里要分别给键盘控制器和鼠标发送控制码,如下:

#define PORT_KEYDAT				0x0060
#define PORT_KEYSTA				0x0064
#define PORT_KEYCMD				0x0064
#define KEYSTA_SEND_NOTREADY	0x02
#define KEYCMD_WRITE_MODE		0x60
#define KBC_MODE				0x47

void wait_KBC_sendready(void)
{
	/* 等待键盘控制器准备好 */
	for (;;) {
		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
			break;
		}
	}
	return;
}

#define KEYCMD_SENDTO_MOUSE		0xd4
#define MOUSECMD_ENABLE			0xf4

void enable_mouse(void)
{
	// 等待键盘控制器准备好
	wait_KBC_sendready();
  // 给键盘控制器发送命令
	io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
  // 等待键盘控制器准备好
	wait_KBC_sendready();
  // 给鼠标发送命令
	io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
	return; 
}

将这些代码放在C语言中一起编译,然后运行,就会成功的到如下画面:

鼠标坏了怎么用键盘控制电脑win10(鼠标坏了怎么用键盘控制电脑面)

到现在为止,系统对鼠标可以响应了:显示字符:PS/2 mouse.

这个正是在我们用C写的中断函数里实现的。

提升中断函数的执行效率:

所谓中断处理,就是打断CPU本来的工作,加塞要求进行处理。

所以必须完成得干净利索。而且进行中断处理期间,不再接受新的中端。

所以如果中断处理程序运行的太慢,它会占用CPU,CPU就无法及时响应新产生的中断。

无法产生新的中断,鼠标如果有新的动作,CPU就无法捕捉到

这将会使CPU无法及时响应用户对鼠标,键盘的操作。

这种现象是我们要极力去避免的。

那么如何做呢?

原则就是中断程序内,只做最简单的事情。

显示一般比较耗时的,

所以,把最耗时的显示程序,放在中断程序外面。

中断程序内,就只是把鼠标的数据保存在某个变量里。

这样,中断程序只完成一个鼠标动作数据的传递功能,对数据的解读,显示等功能,交给main函数就行了。

这样,中断程序就会非常快速地完成,干净利索,cpu就可以更高的频率去捕捉鼠标的动作。

中断程序改成如下形式:


#define PORT_KEYDAT 0x0060

// 定义一个结构体
// 这个结构体用来存放鼠标数据
struct KEYBUF {
    unsigned char data, flag;
};

struct KEYBUF keybuf;

void inthandler2c(int *esp)
{
    unsigned char data;
    io_out8(PIC1_OCW2, 0x64); /* 通知PIC1已经处理完成了IRQ-12的中断 */
    io_out8(PIC0_OCW2, 0x62); /* 通知PIC1已经处理完成了IRQ-02的中断 */
    data = io_in8(PORT_KEYDAT);
    if (keybuf.flag == 0) {
        keybuf.data = data;
        keybuf.flag = 1;
    }
    return;
}

注意到,我们定义了一个结构体KEYBUF,专门用来存放鼠标的端口PORT_KEYDAT传过来的数据。

然后我们再在主程序中,把KEYBUF里的内容显示出来,代码如下:

// bootpack.c
void HariMain(void)
{
    ...
    ...
    ...
    // 前面是其他带代码
    for (;;) {
        io_cli();
        if (keybuf.flag == 0) {
            io_stihlt();
         } else {
             i = keybuf.data;
             keybuf.flag = 0;
             io_sti();
             sprintf(s, "%02X", i);
             boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
             putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
          }
      }
}

以上代码就实现了把原本在中断函数inthandler2c中的显示部分代码,利用keybuf,转移到了HariMain函数中。

不仅实现了对鼠标动作的响应,还实现了中断函数内部的精简。

编译运行成功后,我们发现,我们对鼠标进行各种操作,屏幕上都会成功打印出一个字符出来。

移动鼠标,屏幕出现字符。

点击鼠标按键,屏幕出现字符。

鼠标的响应非常迅速。

但是,屏幕上的鼠标并没有移动。

要想让鼠标移动,就必须在鼠标移动后的位置,去重新绘制一鼠标,

也就是说,我们要完成两个步骤:

1. 获取鼠标的位置

2. 重新绘制鼠标。

那么,如何获取鼠标的位置?

要从keybuf中的data去找。

怎么找?

没办法找,因为data只有一个数值。

鼠标的位置坐标有连个数值:x和y.

所以,data如果是个数组的话,我们就可以把鼠标坐标相关的数据一次性的放入data了。

我们就把data改为数组。然后去按照这个数组来重新绘制鼠标就行了。

那么鼠标返回的数据,难道就只有x,y坐标么?还有其他值么?

有其他值的,比如鼠标按下的是左键,还是右键?鼠标移动时,是每隔一个很短的时候就发送一组x,y的,所以, 鼠标会在很短的时间发送大量的数据。

那么如此大量的数据,显然,如果data这个数组是需要把他们都存储下来的,所以我们需要把data数组优化为一个FIFO的缓冲区

FIFO缓冲区正式为应对鼠标这种短时间大量数据,来不及处理的情况而设计的数据结构,

今天的内容已经很多了,由于FIFO需要讲的比较多,所以我们放在下一天的教程中讲

总结:

今天我们开始对鼠标产生响应了,我们写了对鼠标硬件的控制程序,然后程序能够成功的响应鼠标的移动和按键。我们还为了提高中断函数的效率,把鼠标相关的显示程序放在了HariMain函数中。

但是我们还没有实现把鼠标的移动过程画出来。

因为要把鼠标移动的过程画出来,就要一次性接收大量的数据。我们需要把接收数据的变量data改造成FIFO数据缓冲区,然后再对数据逐一解读,解读出鼠标的坐标变化,然后才能依据解读出来的坐标,把鼠标指针画在屏幕上。

下一天的教程我们将会完成这个内容。

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 sumchina520@foxmail.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.yiheng8.com/36922.html