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);
}
}
注意里面用到的j还是要先声明。
如果next不是0,则说明至少有一个数据。
最开始的一个数据肯定是放在data[0]中的,将这个数存入到变量i中去。
这样,数就减少了一个,所以讲next减去1。
如图:
然后遇到的问题就是上面的缓冲区处理需要数据移送,而且还是在中断期间,花时还容易出错。
,这时候就需要改善了。
主要看代码就可以理解这个缓冲区,相当于定义了两个指针,一个指向下一个要读的地方,一个指向下一个要存的地方。
数据读出位置追着数据写入位置,且可以在写入位置到达尽头即缓冲区为空时重新回到0的位置,循环使用缓冲区空间。
Bootpack.h的结构体修改:
structKEYBUF{
unsignedchardata[32];
intnext_r,next_w,len;
};
Int.c中inthandler21函数修改如下:
voidinthandler21(int*esp)
/*来自PS/2键盘的中断*/
{
unsignedchardata;
io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/
data=io_in8(PORT_KEYDAT);
if(keybuf.next<32)
{
keybuf.data[keybuf.next_w]=data;
keybuf.len++;
keybuf.next_w++;
if(keybuf.next_w==32)
{
keybuf.next_w=0;
}
}
return;
}
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);
}
}
这样修改之后没有任何数据移送操作,这个缓冲区可以记录大量数据,执行速度又快。
缓冲区固定为32字节以后改起来就不方便,所以把它定义成可变的。
所以采用了指针的方法*buf(data[32]),将缓冲区的总字节保存在变量size(len)中。
变量free用于保存缓冲区里没有数据的字节数。
缓冲区的地址保存在变量buf里。
p代表下一个数据写入地址next_w,q代表写一个数据读出地址next_r。
结构体修改之后如下:
structKEYBUF{
unsignedchar*buf;
intp,q,size,free,flags;
};
为了初始化缓冲区以及实现对缓冲区的相关操作,新建了一个fifo.c的文件。
/*FIFO*/
#include"bootpack.h"
#defineFLAGS_OVERRUN0x0001
//fifo_init是结构的初始化函数,用来设定各种初始值,也就是设定FIFO8结构的地址以及与结构有关
voidfifo8_init(structFIFO8*fifo,intsize,unsignedchar*buf)
/*初始化FIFO缓冲区*/
{
fifo->size=size;
fifo->buf=buf;
fifo->free=size;/*缓冲区的大小*/
fifo->flags=0;
fifo->p=0;/*下一个数据写入位置*/
fifo->q=0;/*下一个数据读出位置*/
return;
}
//fifo8_put是往FIFO缓冲区存储1字节信息的函数。
如果一出返回-1,没有溢出就返回0
intfifo8_put(structFIFO8*fifo,unsignedchardata)
/*想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--;
return0;
}
//fifo8_get函数是从FIFO缓冲区取出1字节的函数
intfifo8_get(structFIFO8*fifo)
/*从FIFO取得一个数据*/
{
intdata;
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++;
returndata;
}
//报告一下到底积攒了多少数据
intfifo8_status(structFIFO8*fifo)
{
returnfifo->size-fifo->free;
}
然后根据fifo.c中的函数在int.c中修改inthandler21函数如下
structFIFO8keyfifo;
voidinthandler21(int*esp)
/*来自PS/2键盘的中断*/
{
unsignedchardata;
io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/
data=io_in8(PORT_KEYDAT);
fifo8_put(&keyfifo,data);
return;
}
该函数中的&是取地址运算符,用它可以取得结构或变量的地址值。
Fifo8_put接受的第一个参数是内存地址,与之匹配,这里调用时传递的第一个参数也要是内存地址。
HariMain函数内容如下:
chars[40],mcursor[256],keybuf[32];
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);
}
}
4、鼠标中断
分配给鼠标的中断号码是IRQ12,与键盘的IRQ1比起来差了好多代。
要让鼠标信号得到处理,要让下面两个装置有效,一个是鼠标控制电路,一个是鼠标本身。
关于控制电路的设定:
鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,鼠标电路控制器的激活也就完成了。
在bootpack.c后添加如下代码:
#definePORT_KEYDAT0x0060
#definePORT_KEYSTA0x0064
#definePORT_KEYCMD0x0064
#defineKEYSTA_SEND_NOTREADY0x02
#defineKEYCMD_WRITE_MODE0x60
#defineKBC_MODE0x47
//让键盘控制电路(KBC)做好准备动作,等待控制指令的到来。
voidwait_KBC_sendready(void)//等待键盘控制电路准备完毕
{
for(;;){
if((io_in8(PORT_KEYSTA)&KEYSTA_SEND_NOTREADY)==0){
break;/*break语句是从for循环中强制退出*/
}
}
return;
}
voidinit_keyboard(void)//初始化键盘控制电路,然后在HariMain函数调用init_keyboard函数,鼠标控制电路的准备就完成了
{
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_WRITE_MODE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT,KBC_MODE);
return;
}
//然后开始发送激活鼠标的指令。
归根结底还是要向键盘控制器发送指令
#defineKEYCMD_SENDTO_MOUSE0xd4
#defineMOUSECMD_ENABLE0xf4
//这个函数与init_keyboard函数非常相似,不同点在于写入的数据不同。
如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标
voidenable_mouse(void)/*激活鼠标*/
{
wait_KBC_sendready();
io_out8(PORT_KEYCMD,KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT,MOUSECMD_ENABLE);
return;/*顺利的话,键盘控制器会返送回ACK(0xfa)*/
}
鼠标中断现在已经有了,接下来是取出中断数据,int.c中添加代码:
structFIFO8mousefifo;
voidinthandler2c(int*esp)
/*来自PS/2鼠标的中断*/
{
unsignedchardata;
io_out8(PIC1_OCW2,0x64);/*通知PIC1IRQ-12的受理已经完成*/
io_out8(PIC0_OCW2,0x62);/*通知PIC0IRQ-02的受理已经完成*/
data=io_out8(PORT_KEYDAT);
fifo8_put(&mousefifo,data);
return;
}
鼠标和键盘的原理几乎相同,所以程序也就非常相似。
不同之处只有送给PIC的中断受理通知。
IRQ-12时从PIC的第4号(从PIC相当于IRQ-08~IRQ-15),首先要通知IRQ-12受理已完成,然后再通知主PIC。
这是因为主/从PIC的协调不能够自动完成,如果程序不交给主PIC该怎么做,它就会忽视从PIC的下一个中断请求。
鼠标数据取得方法如下:
ifo8_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);
}elseif(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字节,避免溢出。
取得数据的程序中,如果键盘和鼠标的FIFO缓冲区都为空了,就执行HLT。
如果不是两个都空,就先检查keyinfo,如果有数据就取出一个显示出来。
如果keyinfo是空,就再去检查mouseinfo,如果有数据,就取出一个显示出来。
二、遇到的问题及解决方法
1、弄了很久还是对键盘的中断有很多问题,第一个就是作者说的按一下键有两个字节信息,即会产生两个中断,这两个中断究竟怎么处理的。
因为就算到了今天的第四个工程,作者说要搞个聪明的缓冲区,但还是觉得怪怪的,按键的前面那个字节编码一直就没有显示出来,这样做的缓冲区真的聪明吗,还是说真的只要这两个字节编码的后面那个就够了。
在网上查询的资料是说我们按下按键一般会持续50ms左右,我们知道计算机1S达到10的9次方数量级的处理能力,那么这按一下键我们CPU指令的处理至少都有10^6数量级了,这样说这个中断都不知道产生了多少次了。
而事实是我们看见画面只显示了按键的后面那个编码,所以有理由相信,如果持续有中断,那么CPU会优先处理后来的中断,再处理后面那个字节的中断,所以我们缓冲区就会存的是后面那个字节的编码。
2、关于字母的按键信息
还有一个问题就是发现字母的按键信息和其他的真的不同,按键似乎怎么样都只显示一个字符的编码没有出现过其他的,在右Ctrl键可以显示前面那个字节编码(即E0)的情况下,按下A,你仍然见到的是编码1E。
而且字母按键很霸道,按下去再按其他的键就没有反应了,只有按下shift键才行,我猜这个应该就是编辑键和功能键的差别吧。
(后面补充,后来和同学讨论时发现有的键盘的字母键是可以显示两个字节编码的,就可以顺利显示出按下按键表示的内容,且只显示一个。
然后我发现我的电脑有时候按了很多次shift键后再按字母键也可以看见按下和松开的区别了,但一般都是不行的。
然后还发现作者写的是PS/2标准键盘的中断,同学的键盘是的,而我的是增强型,不知道是不是这