harib04a:
P126 获取按键编码;
让程序在按下键盘的键之后,将键值编码显示出来
修改的是前面编写的鼠标按键的处理键盘中断的函数inthandler21()
这里笔者介绍了怎样把中断号告诉CPU:
1、计算0x60+IRQ号码
2、把结果输出给OCW2寄存器
3、具体方法:调用io_out8(PIC0_OCW2, 0x60+IRQ);
//int.c节选,修改键盘中断处理函数 void inthandler21(int *esp) { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; unsigned char data, s[4]; io_out8(PIC0_OCW2, 0x61); /* 通知PIC已经发生了IRQ1中断 */ 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; }
harib04b:
加快中断处理,上一步获取键盘键值的中断写在了中断处理程序中
这样如果这时有其他的中断来了就佷尴尬了
加速原理:利用缓冲区(笔者使用了一个变量)把读到的键值先保存在缓冲区中,需要时,再由HarMain去查看
注 意:键盘中有的键是一个字节,有的键是两个字节;
//加快中断处理第一步:增加键值缓冲区 struct KEYBUF keybuf{ //键值缓冲区结构体,data:数据位,flag:标志位 unsigned char data,flag; }; void inthandler21(int *esp) { unsigned char data; io_out8(PIC0_OCW2, 0x61); /* 通知PIC已经发生了IRQ1中断 */ data = io_in8(PORT_KEYDAT); //获取键值 if (keybuf.flag == 0) { //标志位为0表示缓冲区空,可以放键值 keybuf.data = data; keybuf.flag = 1; } return; }
//加快中断处理第二步:修改io_halt的无限循环 for (;;) { io_cli(); //io_cli指令屏蔽中断,因为在后面的处理,防止后面的处理中有中断进来,发生不可预料的结果 if (keybuf.flag == 0) { io_stihlt(); //缓冲区空闲,执行STI和HLT指令;这时,PIC准备好接受中断唤醒CPU } 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); } }
harib04c:
P131 制作FIFO缓冲区;
思 考:为什么按下和松开都有键值,CTRL键又不一样;
原 因:我们设定的缓冲区struct KEYBUF keybuf{ unsigned char data,flag};只有一个字节
下面笔者加大了缓冲区的长度,并用FIFO的栈机制:
struct KEYBUF keybuf{ unsigned char data[32]; //缓冲区大小增加为32个字节 int next; //指向缓冲区的下一个,因为是字符型数组 }; //缓冲区数据的程序用FIFO机制做了相应的调整 for (;;) { io_cli(); //io_cli指令屏蔽中断, if (keybuf.next == 0) { io_stihlt(); //缓冲区空闲,执行STI和HLT指令; } else { i = keybuf.data[0]; keybuf.next--; //不断的从缓冲区读和写FIFO 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); } }
harib04d:(这一部分请对照图片看!)
到这里位置数据移送最多只能有32个字节(明显不够)
到上面为止,数据移送都是在禁止中断的情况下:
1、在FOR循环中首先进行的就是中断屏蔽: io_cli();//io_cli指令屏蔽中断;
2、之后才能进行键盘的数据接收;
思 考:如果每次读数据都要先屏蔽中断,这样也太。。。。(麻烦)。。怎么办?
解 决:笔者接下来开发了一个不需要数据移送操作的FIFO缓冲区;大致运用了循环链表的思想:
如上图所示:当写入的位置到达缓冲区末尾,缓冲区开头应该已经开始变空(如果没有变空,说明数据读跟不上数据写,那么只好把部分数据扔掉)。因此,如果下一个数据写入位置到了32以后,就强制性的将其置0;对下一个数据读出位置也做同样的处理,一旦到了32以后,就把它设置从0开始据徐读取数据。这样32字节的缓冲区就能一圈一圈的不断循环(其实就是循环链表;如果看不懂我的解释,请看书本P134内容)
//next_r :下一个读的位置 //next_w :下一个写的位置 // len :缓冲区能记录多少字节的数据 struct KEYBUF { unsigned char data[32]; int next_r, next_w, len; };
接下来做的事:修改中断处理程序(void inthandler21(int *esp))和io_halt的无限循环;下面是io_halt修改的部分:
for (;;) { io_cli(); if (keybuf.len == 0) { io_stihlt(); //这样每一次屏蔽中断的时间有1/2降低为1/32 } 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); } }
harib04e:
我们知道,每次鼠标产生动作,会连续发送3个字节的数据(两个坐标和一个状态信息)
接下来我们进一步修改缓冲区的内容,让他也能适应我们要做的鼠标的移动(重新定义缓冲区):
//1、缓冲区大小改为可变,不再是32字节了 //2、保存缓冲区的总字节数size //3、缓冲区空闲的字节数free //4、缓冲区的地址buf struct FIFO8 { unsigned char *buf; int p, q, size, free, flags; };
作者其实是相当负责的,接着作者写了几个相关的操作函数以便于后续的调用;这些函数都被封装在FIFO.C中(FIFO.C代码较长,我们折叠起来吧!):
/* FIFO.c */ #include "bootpack.h" #define FLAGS_OVERRUN 0x0001 void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf) /* FIFO缓冲区的初始化,用来设定FIFO8的结构地址以及有关的各种参数 */ { 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; //返回-1 溢出了 } fifo->buf[fifo->p] = data; fifo->p++; if (fifo->p == fifo->size) { fifo->p = 0; } fifo->free--; return 0; //返回0,没有溢出 } int fifo8_get(struct FIFO8 *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; }
FIFO.C
接下来做的事,和上面相同:修改中断处理程序(void inthandler21(int *esp));和io_halt的无限循环;
void inthandler21(int *esp) //修改中断处理程序(void inthandler21(int *esp)) { unsigned char data; io_out8(PIC0_OCW2, 0x61); /* PIC打开IRQ-1中断口,告诉CPU */ data = io_in8(PORT_KEYDAT); fifo8_put(&keyfifo, data); //看见木有,直接调用封装在fifo.c中的函数 return; } for (;;) { //MariMain中也做相应调整;io_halt的循环早就没有啦 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); } }
harib04f:
好了,现在我们又要开始折腾鼠标了!
还记得前面笔者给鼠标和键盘分配的PIC的中断号是多少吗?(键盘:IRQ01;鼠标:IRQ12)
笔者在书中这一部分首先给我们普及了一下鼠标是如何兴起的。接着普及了鼠标一些操作的一些变化(一句话:以前的鼠标操作和现在不同)
我们先来看看控制电路是什么情况:
注 意:鼠标控制器和键盘控制器实际上集成在同一个控制电路中。
void wait_KBC_sendready(void) { /* 等待键盘控制电路准备完成 */ //如果键盘端口就绪PROT_KEYSTA & 处于准备发送数据的状态KEYSTA_SEND_NOTREADY //表示键盘控制电路已经准备完毕了。跳出去,返回,不再等待 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; }
接下来向控制器发送指令,激活鼠标:
//激活鼠标的相关程序, //这里和上一部中的void init_keyboard(void)初始化键盘很相似,发现了没有? //没错,就是因为这两个设备的控制器实际上是在同一个控制电路中的 #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) */ }
harib04g:
P142 从鼠标接收数据;原理和上面的从键盘接受数据相同
在 harib04e 中,我们已经修改好了缓冲区,让他同时成为鼠标和键盘数据的缓冲(详见harib04e)
那么我们要做什么事呢?
没错,和键盘接受数据一样,修改两个东西:鼠标的中断程序(函数):inthandler2c() 和 io_halt的无限循环
理解了上面键盘的做法,到这里理解起来就很简单了;直接上代码:
//怎么样。鼠标的中断程序和键盘中断程序inthandler21(int *esp)神似,有木有? void inthandler2c(int *esp) /* PS/2的鼠标中断 */ { unsigned char data; io_out8(PIC1_OCW2, 0x64); /* IRQ-12已经受理完成 */ io_out8(PIC0_OCW2, 0x62); /* IRQ-02已经受理完成 */ data = io_in8(PORT_KEYDAT); fifo8_put(&mousefifo, data); return; } //取数据程序(io_halt的无限循环) //这里和键盘的取数据程序也神似,不同的是,缓冲区开到了128字节; //因为鼠标的数据量更大 fifo8_init(&mousefifo, 128, mousebuf){ 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); } }