自制操作系统7-FIFO与鼠标控制

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字节。

下面试运行一下

今天就先到这了,明天让鼠标指针在屏幕上动起来!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lor :)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值