7 FIFO与鼠标控制
2020.10.19
今天的任务主要是学习如何获取中断数据并制作FIFO缓冲区来暂存数据,还有对鼠标的激活。
1. 获取按键编码
文档:hiarib04a
今天继续加油!在解决鼠标问题前,还是先利用键盘多练手。
现在我们按下不同的键却还是只显示相同的信息,我们需要改善一下程序,获取按键的编码并在画面上显示出来。
//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);//将按键编码存入data变量中
sprintf(s, "%02X", data);//将data放入s缓冲区中
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中断了”,将“0x60+IRQ编号”输出给OCW2就可以了,例如是IRQ3则输出0x63。如果不这样写的话,PIC就不再监视IRQ1中断,再次按下键盘系统也感知不到了。
#define PORT_KEYDAT 0x0060从编号为0x0060获取的8位信息是按键编码。(IBM的大佬们设定的,没有为什么。)
下面测试一下程序。
测试程序后,我们发现不仅是按下按键时显示字符,松开按键也会显示字符。相同按键按下和松开也是不一样的。加快中断处理
文档:hiarib04b
中断处理,基本上就是打断CPU本来的工作,加塞要求进行处理,所以必须要完成得干净利索。
而我们字符显示就要花许多时间,仅仅画一个字符,就要执行8*16次if语句。
那要如何加快中断处理呢?
简单,将按键的编码接收下来,到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); /* 通知PIC IRQ-01已经受理完毕 */
data = io_in8(PORT_KEYDAT);
if (keybuf.flag == 0) {
keybuf.data = data;
keybuf.flag = 1;
}
return;
}
定义了一个结构体KEYBUF,char作为缓冲区存放按键编码,flag记录缓冲区是否为空,flag为1时表示缓冲区有数据。
(如果缓冲区中已经有数据,这时又来了一个中断,只能把这个数据扔掉,这个问题后面我们需要改善。)
接下来看看HariMain函数如何对接收的按键编码进行处理。
//bootpack.c中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为0,就没啥事做,我们执行HLT。由于之前CLI了,如果不开中断,又执行HLT,我们即使按了键程序也不会有反应。所以STI和HLT都要执行,注意这里要调用执行这两个指令的函数io_stihlt。如果用“io_sti();io_hlt();",就会有点问题。如果STI后产生了中断,keybuf中就会存入数据,这时候中断返回执行HLT就察觉不到缓冲区中的数据。根据CPU的规范,如果机器语言的STI指令之后紧接着HLT,那么就暂不受理这两条指令之间的中断。
如果flag为1,就把缓冲区中的数据读入变量i里,然后开中断后悠哉游哉地显示字符。
下面测试一下程序
顺利执行。但是我们按下键盘的右Ctrl会发现,无论松开还是按下都是显示“E0”。再试试原来的程序,按下显示“E0”然后紧接着又显示“1D",松开显示“E0”然后紧接着又显示“9D"。原来是在修改后的程序中,收到E0之后,又收到一次前一次按键产生的1D或者9D,而这个字节被舍弃了。
这么一说之后,不能代表修改前的程序更好,原来的程序有点让硬件勉为其难。
我们现在的缓冲区只能接收一个字节,所以我们需要对缓冲区进行改善。
2. 制作FIFO缓冲区
文档:hiarib04c
为了按照输入的顺序输出数据,我们需要建立FIFO型缓冲区。并且在原来缓冲区只能存储一个字节的基础上增大缓冲区容量。
缓冲区修改如下
//int.c 节选
struct KEYBUF {
unsigned char data[32];
int next;
};
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;
}
缓冲区下一个存储位置用变量next来管理。当next为32时,下一个数据就舍弃不要。
接下里看看HariMain中对缓冲区接收到的数据的处理
//int.c-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);
}
}
从data[0]中取出数据,并将后面的数据前移一个位置。
目前来说,程序运行是没问题的,但当数据量大的时候数据移动就需要耗费太多时间了。
改善FIFO缓冲区
文档:hiarib04d
使用一个写入指针和读出指针维护要写入和读出数据的位置,这样做就不要数据移送操作了。
并且要注意当数据走到终点时,要能回到缓冲区起点。
下面是修改后的程序。
//bootpack.h节选
struct KEYBUF {
unsigned char data[32];
int next_r, next_w, len;//len指已记录数据的长度
};
//int.c节选
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); /* 通知 IRQ-01已经受理完毕 */
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;
}
//bootpack.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);
}
}
整理FIFO缓冲区
文档:hiarib04e
整理一下缓冲区,使它具有通用性。比如鼠标缓冲区也能使用它。
面向对象思想:将整理好的函数写入fifo.c文件中
struct FIFO8 {
unsigned char *buf;
int p, q, size, free, flags;
};
size表示缓冲区字节数,p是写入指针,q是读出指针,free指缓冲区剩余空间,buf存放缓冲区地址。
fifo.c的初始化函数。
//fifo.c的fifo8_init函数
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;
}
往FIFO缓冲区存储1B信息的函数。
//fifo.c的fifo8_put函数
#define FLAGS_OVERRUN 0x0001
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;
}
从FIFO缓冲区里取出1B字节的函数。
//fifo.c的fifo8_get函数
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;
}
查看缓冲区状态的函数。
//fifo.c的fifo8_status函数
int fifo8_status(struct FIFO8 *fifo)
/* 报告一下到底积攒了多少数据 */
{
return fifo->size - fifo->free;
}
使用以上函数后的程序段
//int.c节选
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);
return;
}
//bootpack.c-MariMain函数节选:
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);
}
}
修改好后试运行一下
运行正常!
3. 总算讲到鼠标了
文档:hiarib04f
终于回归鼠标了,先解释一下鼠标不能用的原因。
从计算机的历史来看,鼠标属于新兴产物。早期的计算机一般都不配置鼠标。从中断号就能比较出,鼠标的中断号码是IRQ12,和键盘的IRQ1比起来可是差了很多了。
当鼠标刚开始被使用时,几乎所有的操作系统都不支持它。只是稍微动一动鼠标就会产生中断,就只好先把鼠标拔掉了。IBM的大佬们认为这对于使用计算机的人来说非常不方便。所以,虽然在主板上做了鼠标用的电路,但只要不执行激活鼠标的指令,就不产生鼠标的中断信号。
不产生中断信号,即使鼠标传送了数据,CPU也不予理会。所以鼠标也没必要传送数据了。
总结一下就是,想要使用鼠标要让两个装置有效,一是鼠标控制电路,二是鼠标本身。
控制电路的设定
鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,鼠标电路控制器的激活也就完成了。
//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;
}
先来看看函数wait_KBC_senready,他的作用就是让键盘控制电路(keyboard controller,KBC)做好准备,等待控制指令的到来。CPU的电路很多,但键盘控制电路并没有那么快,需要防止CPU一个劲儿的发指令给KBC而KBC处理不过来。如果KBC可以接收CPU指令了,CPU从设备号0x0064处所读取的数据的第1位应该是0。
再看看init_keyboard,它的任务很简单就是先确认KBC是否准备好了,然后发送模式设定指令。模式设定的指令是0x60,利用鼠标模式的模式号码是0x47。
这样,在HariMain中调用init_keyboard函数,鼠标控制电路的准备就做完成了。
激活鼠标
发送鼠标激活指令其实就是向键盘控制器发送指令。
//bootpack.c节选
#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)*/
}
与init_keyboard函数非常类似。往KBC发送指令0xd4,下一个数据就会自动送给鼠标。
跃跃欲试的鼠标在收到激活指令后,立马就会给CPU回复信息了0xfa,所以即使我们保持不动,也会产生一个鼠标中断。
下面测试一下
鼠标中断终于出来了,已经很棒了!
从鼠标接收数据
文档:hiarib04g
既然中断来了,那我们可以把中断数据取出来了。
鼠标和键盘的原理几乎相同,所以程序也非常相似。
//int.c节选
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;
}
IRQ12是从PIC的第4号,而从PIC连接到主PIC的第2号上。
//bootpack.c节选
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);
}
}
}
鼠标取得方法和键盘完全相同。
鼠标往往会比键盘更快地送出大量数据,所以将它的FIFO缓冲区增加到了128字节。
下面试运行一下
今天就先到这了,明天让鼠标指针在屏幕上动起来!