Day7 FIFO与鼠标控制

文章目录

      • [1. harib04a例程(获取按键编码)](#1. harib04a例程(获取按键编码))
      • [2. harib04b例程(加快中断处理)](#2. harib04b例程(加快中断处理))
      • [3. harib04c例程(FIFO缓冲区)](#3. harib04c例程(FIFO缓冲区))
      • [4. harib04d例程(改善FIFO缓冲区)](#4. harib04d例程(改善FIFO缓冲区))
      • [5. harib04e例程(整理FIFO缓冲区)](#5. harib04e例程(整理FIFO缓冲区))
      • [6. harib04f例程(激活鼠标)](#6. harib04f例程(激活鼠标))
      • [7. harib04g例程(从鼠标接收数据)](#7. harib04g例程(从鼠标接收数据))

1. harib04a例程(获取按键编码)

上一章的例程中可以获取到键盘按下一次按键的状态,但是没办法使鼠标移动,其原因主要还是对于鼠标的设定不充足。

程序改善为,按下一个键后不结束,而是把所按键的编码显示在屏幕中。于是,修改int.c中的inthandler21函数:

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,已知发生了IRQ01中断
	data = io_in8(PORT_KEYDAT);

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

在代码中,io_out8(PIC0_OCW2, 0x61);用来通知PIC,已经知道发生了IRQ1中断,如果是IRQ3,则写为0x63,即将0x60+IRQ号码输出给OCW2。执行完这行代码之后,PIC会继续监视IRQ1中断是否发生。反过来如果忘记执行这句话,键盘下次再有动作,操作系统就无法感知到了。

如此以来,按下键和松开键,屏幕上就会显示不同的东西。

2. harib04b例程(加快中断处理)

所谓中断处理,就是打断CPU正在手头的工作,加塞处理另一个事情,所以必须完成的干净利索,而且中断处理进行期间,不再接受其他的中断。如果处理键盘的中断速度太慢就会导致鼠标的运动不连贯或不能从网络中获取数据等情况。

因此,字符显示最好不要出现在在中断处理函数中。字符显示需要花费较大的时间,显示一个字符首先需要做128次if判断,来决定是否往VRAM里描绘该像素;如果需要进行描绘,就会接着执行内存写入指令;为确认往内存哪个地方写,还需要计算地址。

将按键的编码缓存在一个变量中,然后由HariMain偶尔去查看这个变量,如果发现有数据,就把他显示出来。

c 复制代码
/*bootpack.h*/
struct KEYBUF {
	unsigned char data, flag;
};

/*int.c*/
struct KEYBUF keybuf;
void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC,已知发生了IRQ01中断 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.flag == 0) {
		keybuf.data = data;
		keybuf.flag = 1;
	}
	return;
}

其中,flag变量用于表示缓冲区是否为空(0为空,1为缓冲区中有数据)。如果缓冲区中有数据,而此时又来了一个中断,则不做处理,把这个数据丢掉。

在HariMain中偶尔去查看这个变量:

c 复制代码
/*bootpack.h*/
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);
	}
}

首先使用io_cli屏蔽中断,防止在查看已保存的全局变量时,有其他中断进来。如果flag为0,则表示没有中断发生,由于此时已经执行完了io_cli屏蔽中断,所以需要执行STI用于解除中断屏蔽,并HLT,因此可以使用io_stihlt表示同时执行STI和HLT。(根据CPU规范,在STI和HLT两个指令之间的中断,不做处理,因此只能执行io_stihlt,而不能执行io_sti + io_hlt)。

else,即如果flag为1时,把data缓存到变量i中,并把flag清零,最后再通过io_sti取消中断的屏蔽。

但是当前还存在一个有问题的现象,例如,右Ctrl键会产生两个字节的键码,按下时为"E0 1D",松开时为"E0 9D",在这种情况下,因为键盘的内部电路,一次只能发送一个字节,所以一次按下就会产生两次中断,第一次中断发送E0,第二次发送1D。在harib04b中,对这种情况的处理,就会只显示一个E0,因为它会先屏蔽中断,再去检查flag。

3. harib04c例程(FIFO缓冲区)

制作一个先进先出的缓冲区,缓存多个中断。

c 复制代码
/* bootpack.h */
struct KEYBUF {
	unsigned char data[32];
	int next;
};

/* int.c */
void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);
	data = io_in8(PORT_KEYDAT);
	if (keybuf.next < 32) {
		keybuf.data[keybuf.next] = data;
		keybuf.next++;
	}
	return;
}

/* bootpack.c */
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);
	}
}

在inthandler21函数中,对每次中断都记录到缓冲区data中;然后在HariMain中每次访问下标为0的那一个,并把后面的元素往前移。这样就可以在按下右Ctrl时显示1D,松开时显示9D。

但现在存在的一个问题就是,数据往前移动的这个动作,发生在屏蔽中断的时候,如果数据比较多,这也是一个需要花费大时间的操作。

4. harib04d例程(改善FIFO缓冲区)

双指针+循环buffer思想,一个表示读操作的下标,一个表示写操作的下标,并添加一个字段len用于记录缓冲区中当前存在多少个未读取的数据。

c 复制代码
/* bootpack.h */
struct KEYBUF {
	unsigned char data[32];
	int next_r, next_w, len;
};

/* int.c */
void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);
	data = io_in8(PORT_KEYDAT);
	if (keybuf.len < 32) {
		keybuf.data[keybuf.next_w] = data;
		keybuf.len++;	// data写入buf时,len+1
		keybuf.next_w++;
		if (keybuf.next_w == 32) {
			keybuf.next_w = 0;
		}
	}
	return;
}

/* bootpack.c */
for (;;) {
	io_cli();
	if (keybuf.len == 0) {
		io_stihlt();
	} else {
		i = keybuf.data[keybuf.next_r];
		keybuf.len--;	// data读出buf时,len-1
		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);
	}
}

5. harib04e例程(整理FIFO缓冲区)

实际上,鼠标动一下,会发出3个字节的数据。修改FIFO缓冲区,使它称为一个更加通用的先进先出缓冲区。

c 复制代码
/* bootpack.h */
/*
* buf  	: 缓冲区地址
* p		: 可写入位置
* q		: 可读出位置
* size	: 缓冲区总字节数大小
* free	: 可写入的缓冲区字节数大小
* flags	: 记录是否溢出(free == 0)
*/
struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;
};

/* fifo.c */
#define FLAGS_OVERRUN		0x0001
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;
}

/*
* ret	:0, 无溢出;1, 溢出。
*/
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;
}

int fifo8_get(struct FIFO8 *fifo)
{	// 从缓冲区中读出数据
	int data;
	if (fifo->free == fifo->size) {
		/* 缓冲区无数据 */
		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;
}

如此以来,inthandler21则只需要调用fifo8_put即可。HariMain函数中调用fifo8_init初始化fifo,并fifo8_get即可。

c 复制代码
/* int.c */
#define PORT_KEYDAT		0x0060
struct FIFO8 keyfifo;
void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* IRQ-01��t�����PIC���m */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&keyfifo, data);
	return;
}


/* bootpack.c */
extern struct FIFO8 keyfifo;

char s[40], mcursor[256], keybuf[32];
fifo8_init(&keyfifo, 32, keybuf);
for (;;) {
	io_cli();
	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);
	}
}

6. harib04f例程(激活鼠标)

鼠标作为外设接入计算机是较靠后的事情了。最初,如果期望使用鼠标,需要先执行激活鼠标的指令,才能产生鼠标的中断。所谓不产生鼠标中断,就是说即使鼠标发来数据,CPU也不会接收,所以,处于初期状态的鼠标,无论怎么操作,都不会有反应。

因此,如果期望正常使用鼠标,需要先让鼠标控制电路生效,再让鼠标生效。事实上,鼠标控制电路被包含在键盘控制电路中,如果键盘控制电路初始化完成,则鼠标电路控制器的激活也完成了。

c 复制代码
	/* bootpack.c节选 */
#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;
}

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

wait_KBC_sendready函数(keyboard controller)的作用是使键盘控制电路做好准备,以等待控制指令的到来(因为键盘控制电路的速度不如CPU电路快)。如果控制电路可以接收CPU指令了,CPU从0x0064设备读到的低第2bit应该为0,在等到0之前,函数一直在for循环。
init_keyboard函数,一边确认键盘控制电路是否准备好,一边发送指令用于设定模式。设定模式的指令是0x60,使用鼠标模式的指令是0x47。通过在HariMain中调用init_keyboard就可以等待鼠标模式设定完成。
enable_mouse函数,向键盘控制电路中发送0xd4和0xf4,用于激活鼠标(如果向键盘控制电路中发送0xd4,则下一条指令(0xf4)会自动发送给鼠标)。鼠标收到激活指令后会回复0xfa给CPU,表示后续会一直发送鼠标信息。鼠标回复的0xfa是自动回复,也会产生一个中断。

如图,鼠标中断来了。

7. harib04g例程(从鼠标接收数据)

c 复制代码
/* int.c节选 */
struct FIFO8 mousefifo;

void inthandler2c(int *esp)
{
	unsigned char data;
	io_out8(PIC1_OCW2, 0x64);	/* 通知PIC1(slave),IRQ-12的受理已经完成 */
	io_out8(PIC0_OCW2, 0x62);	/* 通知PIC0(master),IRQ-02的受理已经完成 */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&mousefifo, data);
	return;
}

首先通知slavePIC,IRQ12已受理完成(IRQ12是slavePIC的第4号);再通知masterPIC,IRQ02已受理完成。

获取鼠标数据的代码与获取键盘数据的代码完全一样:

c 复制代码
/* bootpack.c节选 */
fifo8_init(&keyfifo, 32, keybuf);
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字节。

当前的逻辑是,如果判断鼠标缓冲区和键盘缓冲区都为空则执行HLT;否则就先检查键盘缓冲区,再检查鼠标缓冲区。

相关推荐
阿图灵2 分钟前
文章记单词 | 第30篇(六级)
学习·学习方法
lalapanda1 小时前
UE学习记录part16
学习
@PHARAOH1 小时前
WHAT - React 进一步学习推荐
前端·学习·react.js
kovlistudio1 小时前
红宝书第四十讲:React 核心概念:组件化 & 虚拟 DOM 简单教程
开发语言·前端·javascript·学习·react.js·前端框架
螺旋小蜗1 小时前
Maven工具学习使用(十二)——extension和depency的区别
学习·extension·depency
却道天凉_好个秋1 小时前
音视频学习(三十四):H264中的宏块
学习·音视频·宏块
却道天凉_好个秋1 小时前
音视频学习(三十三):GOP详解
学习·音视频·gop
键盘敲没电1 小时前
【iOS】UIPageViewController学习
学习·ios·cocoa
cwtlw2 小时前
PhotoShop学习09
笔记·学习·其他·photoshop
一棵开花的树,枝芽无限靠近你2 小时前
【CodeMirror】系列(二)官网示例(六)自动补全、边栏
前端·笔记·学习·编辑器·codemirror