1 获取按键编码

如何让用户输入的键盘按键转换为对于的字符,只需使用汇编调用bios中断即可实现,我们在naskfuc.nas中编写好的大量in out接口尝试调用,修改后的int.c中inthandler函数:

#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);	/* 通知IRQ-01已经处理完毕 */
	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;
}

我们往键盘上按下一个键的时候,键盘外设会向CPU发送一个外中断,并将按键的区位码发送,我们使用io_in8接口接收返回的缓冲区数据,运行:

一键BIOS制作_#define

按下一个按键,比如a:

一键BIOS制作_一键BIOS制作_02

我们暂时让按键对应的编码输出了,以后我们再将键盘编码转换为对应的字符。

2 制作FIFO(First In First Out)缓冲区

我们在bookpack.h头文件中声明一个名为KEYBUF的缓冲区,用于存放更多的字符编码:

/* int.c */
struct KEYBUF {
	unsigned char data[32];
	int next;
};

修改一下int.c中的inthandler函数,使其能不断的读取键盘码,其中next为数组指针:

struct KEYBUF keybuf;

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

 bookpack.c中主函数HariMain尾部添加读取缓冲区键盘码代码:

/* bootpack主函数 */

#include "bootpack.h"
#include <stdio.h>

extern struct KEYBUF keybuf;

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256];
	int mx, my, i, j;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PIC的初始化结束后解除CPU的中断禁止 */

	io_out8(PIC0_IMR, 0xf9); /* PIC1允许键盘(11111001) */
	io_out8(PIC1_IMR, 0xef); /* 允许鼠标(11101111) */

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /*以成为画面中央的坐标计算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	for (;;) {
		io_cli();
		if (keybuf.next == 0) {
			io_stihlt();
		} else {//如果数据不为0,读取第一个数据
			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);
		}
	}
}

这样当键盘连续输入的时候,会不断的刷新显示的按键编码。

3 改进缓冲区的读取

上面缓冲区的设计的弊端就是不能存储大量的数据,每次只能读取一个数据。并且每次读取都需要进行一次一位操作,时间复杂度是O(N),我们尝试设计一个O(1)复杂度的读取操作,并且使它能读取更多的数据,修改后bookpack.h中的KEYBUF结构体:

/* int.c */
struct KEYBUF {
	unsigned char data[32];
	int next_r, next_w, len;
};

其中定义的data存放数据,下面的三个指针next_r是数据读取指针,next_w是数据写入指针,len是缓冲区已有数据个数。

修改后int.c的inthandler函数对键盘编码的写入:

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

bookpack.c的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);
		}
	}

 这样就不需要频换的进行移位操作。

4 整理缓冲区

我们优化后的缓冲区只能读取32个字节,这样后面用到更多读写操作时又需要频换修改,我们将缓冲区的读写独立为一个fifo.c函数。

修改后的结构体:

/* fifo.c */
struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;/*p为下一个写入地址,q为下一个读出地址,free为当前已有数据数量,flags为溢出标志*/
};

新增的fifo.c文件:

/* FIFO库 */

#include "bootpack.h"

#define FLAGS_OVERRUN		0x0001

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* FIFO结构体初始化函数 */
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; /* 缓冲区大小 */
	fifo->flags = 0;
	fifo->p = 0; /* 下一个数据写入位置 */
	fifo->q = 0; /* 下一个数据读出位置*/
	return;
}

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO传送数据并保存 */
{
	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)
/* 从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;
}

此时在bookpack.c的HariMain主函数中:

/* bootpack主函数 */

#include "bootpack.h"
#include <stdio.h>

extern struct FIFO8 keyfifo;

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256], keybuf[32];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PIC的初始化结束后解除CPU的中断禁止 */

	fifo8_init(&keyfifo, 32, keybuf);//初始化一个32字节的缓冲区
	io_out8(PIC0_IMR, 0xf9); /* 允许PIC1和键盘(11111001) */
	io_out8(PIC1_IMR, 0xef); /* 允许鼠标(11101111) */

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /* 以成为画面中央的坐标计算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

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

我们使用make run命令运行,按下键盘上任意键:

一键BIOS制作_一键BIOS制作_03

没有问题。

5 鼠标的中断处理

对鼠标的中断处理跟键盘大致差不多,对于鼠标我们将缓冲区字节大小修改为128字节,修改后的bookpack.c:

/* bootpack主函数 */

#include "bootpack.h"
#include <stdio.h>

extern struct FIFO8 keyfifo, mousefifo;
void enable_mouse(void);
void init_keyboard(void);

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256], keybuf[32], mousebuf[128];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PICの因为初始化结束了,所以解除了CPU的中断禁止 */
	fifo8_init(&keyfifo, 32, keybuf);/*键盘输入缓冲区*/
	fifo8_init(&mousefifo, 128, mousebuf);/*鼠标输入缓冲区*/
	io_out8(PIC0_IMR, 0xf9); /* PIC1允许键盘(11111001) */
	io_out8(PIC1_IMR, 0xef);

	init_keyboard();

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /* 以成为画面中央的坐标计算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	enable_mouse();

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

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

这样键盘鼠标的中断我们都处理了,现在尝试运行一下:

一键BIOS制作_#define_04

我们移动一下鼠标:

一键BIOS制作_数据_05

今天的内容大致就这么多。