第7天:FIFO与鼠标控制

7.1、获取按键编码

第六天不是做好了中断程序嘛,现在对中断程序进行改进,当有键盘中断产生就显示按键的扫描码。

inthandler21函数改进:

// int.c
#define PORT_KEYDAT		0x0060

void inthandler21(int *esp)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	unsigned char data, s[4];
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC IRQ-01已经受理完毕 */
	data = io_in8(PORT_KEYDAT); // PORT_KEYDAT = 60

	sprintf(s, "%02X", data);
	boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

	return;
}

第六天是产生中断就显示一句话,证明发生中断,今天就可以显示按下按键的扫描码。

// 通知键盘中断已经受理,让PIC继续监控。没有这句的话,下次发生中断就不会提醒发生中断了
// 0x60 + IRQ号码;0x61就是IRQ 01,就是键盘中断
io_out8(PIC0_OCW2, 0x61);

// 从端口0x60端口获取8位电气信号,就是键盘发送的数据(一次只发送一字节数据)
io_in8(PORT_KEYDAT);

7.2、加快中断处理

所谓中断,就是打断CPU本来的工作,加塞要求进行处理,必须完成的干净利索。并且中断处理及进行期间,不再接受别的中断。所以如果处理键盘的中断的速度太慢,就会出现鼠标的运动不连贯、不能从网上接收数据等情况。
另外,处理键盘中断需要在屏幕上显示字符,相对CPU来说,速度很慢。
谁也不知道中断什么时候就会来,可能就是键盘输入的时候,就有数据整在网上下载,但PIC正在等键盘中断处理结束。解决办法,就是先将键盘按键的中断的编码保存下来,然后HariMain函数再抽空去看看有没有数据,有数据就显示出来。

//int.c中断函数修改
struct KEYBUF {
	unsigned char data, flag;
};
#define PORT_KEYDAT		0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* IRQ-01受付完了をPICに通知 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.flag == 0) {
		keybuf.data = data;
		keybuf.flag = 1;
	}
	return;
}
//HariMain函数节选
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);
		}
	}

这次改进设置了一个flag标志,当中断发生了,把按键保存在结构体里,然后让HariMain函数进行处理,(处理:关中断,HariMian检查发现flag为1,不空,则取出数据,开中断,显示数据)。这样以来,就没有在中断中显示字符,提高了中断效率。
关于作者遇到的,按着右Ctrl会卡在E0,自己实验的时候是没有的,作者说是发送第二个字节的时候,HariMain还没处理,从而被忽略了。我觉得现在的电脑处理的挺快的,没有卡住的迹象。但是可以一直按着Ctrl键,你会发现有E0闪动出现。

7.3、制作FIFO缓冲区

以上一次都还只能处理一个字节,如果缓冲区打点就好了,来改一改

// 32字节大小; next用来指示下一个写入位置
struct KEYBUF {
	unsigned char data[32];
	int next;
};

中断程序改成:

#define PORT_KEYDAT		0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC,IRQ-01已经受理 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.next < 32) {
		keybuf.data[keybuf.next] = data;
		keybuf.next++;
	}
	return;
}

只要keybuf没满,就放

HariMain处理的代码:

for (;;) {
		io_cli();
		if (keybuf.next == 0) {
			io_stihlt();
		} else {
			i = keybuf.data[0];
			keybuf.next--;
			for (j = 0; j < keybuf.next; j++) {
				keybuf.data[j] = keybuf.data[j + 1];
			}
			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);
		}
	}

发现一直都是从keybuf.data[0]取的数据,然后通过循环把数据往前移。

极端情况 :中断一直在发生,往keybuf.data中填入数据,但是HariMain处理太慢,就会导致缓冲区存满,之后的中断将无法响应。

7.4、改善缓冲区

在7.3当中,每取一个数据就要移动一次,这样对CPU开销还是很大的,为了解决这个问题,可以引入一个变量去维护读取的位置。

// next_r读取的位置;next_w写入的位置;len缓冲区存放数据的个数
struct KEYBUF {
	unsigned char data[32];
	int next_r, next_w, len;
};

中断处理函数:

#define PORT_KEYDAT		0x0060

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* IRQ-01受付完了をPICに通知 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.len < 32) {
		keybuf.data[keybuf.next_w] = data;
		keybuf.len++;
		keybuf.next_w++;
		if (keybuf.next_w == 32) {
			keybuf.next_w = 0;
		}
	}
	return;
}

HariMain函数:

for (;;) {
		io_cli();
		if (keybuf.len == 0) {
			io_stihlt();
		} else {
			i = keybuf.data[keybuf.next_r];
			keybuf.len--;
			keybuf.next_r++;
			if (keybuf.next_r == 32) {
				keybuf.next_r = 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);
		}
	}

当缓冲区写入位置到末尾后,在来的数据写入到开头位置。当读取到末尾时,从头开始读取。

看一个形象的图:
改善后的缓冲区
这就会想了,如果写入位置从后面追上读出位置时,咋办~,如果真的发生这样的状况,那说明机器去读太慢了,跟不上写入,那就不要以前的数据了,直接写入新的数据。

7.5、整理FIFO缓冲区

为了让缓冲区更通用,再做一个改造。

结构体改造:

// p写入位置,q读出位置,size缓冲区大小;free空闲的缓冲区大小
struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;
};

初始化 缓冲区:

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; /* 空闲 */
	fifo->flags = 0;
	fifo->p = 0; /* 写入 */
	fifo->q = 0; /* 读出 */
	return;
}

把数据 写入 缓冲区,设计一个函数来实现:

#define FLAGS_OVERRUN 0x0001

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向缓冲区填入数据并保存 */
{
	if (fifo->free == 0) {
		/* 缓冲区满了 */
		fifo->flags |= FLAGS_OVERRUN;
		return -1;
	}
	fifo->buf[fifo->p] = data;
	fifo->p++;
	if (fifo->p == fifo->size) {
		fifo->p = 0;
	}
	fifo->free--;
	return 0;
}

发现一个问题 : 第一次满了,flags设置为1,当不满的时候没有置0操作。我猜暂时可能是没有用到吧

从缓冲区 读取 数据,设计函数:

int fifo8_get(struct FIFO8 *fifo)
/* FIFOからデータを一つとってくる */
{
	int data;
	if (fifo->free == fifo->size) {
		/* 缓冲区为空,返回-1 */
		return -1;
	}
	data = fifo->buf[fifo->q];
	fifo->q++;
	if (fifo->q == fifo->size) {
		fifo->q = 0;
	}
	fifo->free++;
	return data;
}

缓冲区 状态 (缓冲区有几个数据),设计函数:

int fifo8_status(struct FIFO8 *fifo)
{
	return fifo->size - fifo->free;
}

那中断处理函数就可以变成这样:

#define PORT_KEYDAT		0x0060

struct FIFO8 keyfifo;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC,IRQ-01已经受理 */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&keyfifo, data); //往缓冲区写入数据,传入的是结构体keybuf的地址,data为写入的数据
	return;
}

HariMain函数:

for (;;) {
		io_cli();
		// fifo8_status返回缓冲区状态(缓冲区有几个数据)
		if (fifo8_status(&keyfifo) == 0) {
			io_stihlt();
		} else {
			i = fifo8_get(&keyfifo);
			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);
		}
	}

7.6、响应鼠标

现在到了让大家期待已久的讲解鼠标的时间了。首先说说, 为什么虽然我们的电脑连着有鼠标,却一直不能用的原因。

从计算机不算短暂的历史来看,鼠标这种装置属于新兴一-族。早期的计算机一般都不配置鼠标。一个很明显的证据就是,现在我们要讲的分配给鼠标的中断号码,是IRQ12,这已经是一个很大的数字了。与键盘的IRQ1比起来,那可差了好多代了。

所以,当鼠标刚刚作为计算机的一个外部设备开始使用的时候,几乎所有的操作系统都不支持它。在这种情况下,如果只是稍微动一动鼠标就产生中断的话,那在使用那些操作系统的时候,就只好先把鼠标拔掉了。IBM的大叔们认为,这对于使用计算机的人来说是很不方便的。所以,虽然在主板上做了鼠标用的电路,但只要不执行激活鼠标的指令,就不产生鼠标的中断信号(1)

所谓不产生中断信号,也就是说,即使从鼠标传来了数据,CPU也不会接收。这样的话,鼠标也就没必要送数据了,否则倒会引起电路的混乱。所以,处于初期状态的鼠标,不管是滑动操作也好,点击操作也好,都没有反应(2)。
鼠标能够使用的条件所以,要想使用鼠标需要做两个工作:
1、激活鼠标控制电路
2、让鼠标有效

  • 激活鼠标电路
#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;
}

void init_keyboard(void)
{
	/* 初期化键盘控制电路 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, KBC_MODE);
	return;
}

wait_KBC_sendready :

该函数是让键盘控制电路做准备。
为什么要做这一项准备工作?
答:因为CPU运行很快,但是键盘控制电路没有那么快,如果CPU不兼顾设备的数据接收能力,可能会使很多命令得不到执行,甚至发生错误。
如果键盘控制电路可以接收CPU指令了,CPU就会从设备号0x0064读取数据,检查该数据倒数第二位(从低位开始数)是不是0。再确认倒数第二位为0之前,就通过for循环一直查询。

init_keyboard

该函数就是边查询是否可以向键盘控制电路发送信息,一边发送模式设定指令,指令中包含着要设定为何种模式。模式设定指令是0x60,向0x64端口发0x60,就可以设定模式了。
如果向0x64端口发0x60发送成功,再次询查可不可以向键盘控制电路发送信息,如果可以,向0x60端口发送0x47,发送完0x47成功,就是利用鼠标模式设定成功。

我画个图理解一下:
电路准备

到此为止,键盘控制电路准备完成。

  • 让鼠标有效

鼠标利用模式已经设定,接着就要发送激活鼠标指令了,激活鼠标指令也是要向键盘控制器发送指令。

#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; /* 顺利的话,键盘控制器会返回ACK(0xfa) */
}

激活鼠标发送指令的过程:
1、向键盘控制电路发送0xd4 -------> 0x64端口,发送成功的话,下一个数据就会发给鼠标。
2、下一个数据0xf4 发往 0x60 后会让鼠标接收,鼠标就被激活了。
3、鼠标通过键盘控制器回复 ACK(0xfa),之后鼠标就会一直发送信息了。

画图解释:

激活鼠标

7.7、从鼠标接收数据

鼠标被激活了,可以使用了,我们接收一下鼠标发来的数据吧

struct FIFO8 mousefifo;

void inthandler2c(int *esp)
/* 来自PS/2鼠标的中断 */
{
	unsigned char data;
	io_out8(PIC1_OCW2, 0x64);	/* 通知PIC1,IRQ-12已经受理 */
	io_out8(PIC0_OCW2, 0x62);	/* 通知PIC0,IRQ-02已经受理 */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&mousefifo, data);
	return;
}

中断处理函数跟键盘中断函数差不多,主要是鼠标的中断需要通知两块PIC。

下面显示一下鼠标发送来的消息,同样在HariMain函数实现:

extern struct FIFO8 keyfifo, mousefifo;
fifo8_init(&mousefifo, 128, mousebuf);
for (;;) {
		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
			io_stihlt();
		} else {
			if (fifo8_status(&keyfifo) != 0) {
				i = fifo8_get(&keyfifo);
				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);
			} else if (fifo8_status(&mousefifo) != 0) {
				i = fifo8_get(&mousefifo);
				io_sti();
				sprintf(s, "%02X", i);
				boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
				putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
			}
		}
	}

因为鼠标的数据更多,这里设定缓冲区为128字节。
另外可以发现,获取数据的方式一摸一样,因为电路都在键盘控制器上吧。但是在中断时,它们有不同的处理函数,把信息写入不同的结构体中,我们可以通过不同的结构体显示数据了。

还有,我要说一下,鼠标会连续发送三个字节数据,这里只会显示最后一次的数据,前两次的都被覆盖了。

回复的0xfa
鼠标即使不动,也会有一个中断,就是激活鼠标时,鼠标的回复ACK(0xfa)

稍微滑动鼠标
滑动鼠标就会有变化,键盘(第一个)也可以响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值