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接口接收返回的缓冲区数据,运行:
按下一个按键,比如a:
我们暂时让按键对应的编码输出了,以后我们再将键盘编码转换为对应的字符。
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命令运行,按下键盘上任意键:
没有问题。
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) */
}
这样键盘鼠标的中断我们都处理了,现在尝试运行一下:
我们移动一下鼠标:
今天的内容大致就这么多。