目录
一、 实验主要内容
(一) 获取按键编码
鼠标不动原因:
设定不到位
目标:
程序在按下一个后不结束,而是把所按键的编码在画面上显示出来。
【函数解析】
注意看红框部分,这里io_out8(PIC0_OCW2, 0x61);,通知了PIC已经知道发生了IRQ1中断。执行这句话这周,PIC时刻监视IRQ1中断是否发生中断,这样按键输入才能由系统感知。
(二) 加快中断处理
【中断处理是什么】
所谓中断处理,就是打断 cpu本来的工作,加塞要求进行处理。并在中断期间,不在接受别的中断。
并不知道中断会在哪个瞬间到来,所以要先将按键的编码接收下来,保存在变量里,然后由HariMain偶尔去查看这个变量。如果发现这个数据,就把她显示出来。
完成这样的设置之后,考虑到键盘输入时需要缓冲区,定义了一个结构体keybuf。其中flag变量用来表示这个缓冲区是否为空。
【理解HariMain部分函数】
代码注释如下:
【测试运行】
现象:
按下右ctrl键显示1D,松开显示9D。
原因:
当按下右ctrl键时候,会产生两个字节的键码值“E0 1D”,松开之后会产生两个字节的键码值“E0 9D”。在一次产生两个字节键码值的情况下,因为键盘内部电路一次只能发送一个字节,所以一次按键就会产生两次中断,第一次中断发送E0,第二次中断发送1D。
比较:
对于前面在a中,两次中断所发送的值都能收到,瞬间显示E0之后,接着又显示1D或9D。而在b中,HariMain函数在收到E0之前,又收到前一次按键产生的1D或9D,而这个字节就被舍弃了。
(三) 制作FIFO缓冲区
【int.c】
首先我们来理解部分int函数
keybuf.next的起始点是0,所以最初存储的数据是keybuf.data[0]。下一个数据是keybuf.data[1],接着是[2],以此类推,一共有32个存储位置。其中下一个存储位置用next管理。这样就会记住32个数据而不会溢出。但是为了保险,next值变成32后,就舍去不要。
【取数据程序】
如果next不是0,说明至少有一个数据。最开始的一个书肯定是放在data[0]中,将这个数存入到变量i中去,这样数减少了一个,next-1。
for循环的作用就是将数据存放的位置全部向前移送一个位置,如果不移送的话,下一次就不能从data[0]读入数据了。但是数据移送处理,在禁止中断的期间做会有问题。
(四) 改善FIFO缓冲区
基本思路:
不仅要维护下一个要写入数据的位置,还要维护下一个读出数据的位置。这就好像数据读出位置在追着数据写入位置跑一样,这样做就不需要数据移送操作了。数据读出位置追上数据写入位置的时候,就相当于缓冲区为空,没有数据。但是这样,容易进入死胡同,比如缓冲区使用一段时间之后,下一个数据写入位置就会变成31,这时下一个数据读出的位置可能已经是29或者30。当写入位置变成32的时候,就走到死胡同,因为没有地方可以写数据了。
我们只要将下一个数据写入位置和下一个数据读出位置都再置为0就行,就像转回去从头再来一样。
我们对于下一个数据读出位置也做用样的处理,一旦到达32之后,就把她设置为0从头开始继续读取数据。这样32个字节的缓冲区就能一圈一圈不停的循环,只要不溢出就可以一直使用下去。
(五) 整理FIFO缓冲区
鼠标只要稍微一动,就会连续发送3个字节的数据。首先我们要把结构做成如下这样:
我们将缓冲区大小定义为可变的,总的字节数保存在变量size里面。变量free用于保存缓冲区里面没有数据的字节数。缓冲区的地址也保存下来,把他保存在变量buf里。p代表下一个数据写入地址(net_w),q代表下一个数据读出地址(next_r)。
【fifo.c的fifo8_int函数】
fifo8_int是结构的初始化函数,用来设定各种初始值,也就是设定fifo8结构的地址已经与结构有关的各种参数。
【fifo.c的fifo8_put函数】
fifo8_put是往FIFO缓冲区存储1字节信息的函数。用flags这一变量来记录是否溢出。分别用-1和0来表示溢出和没有溢出两种情况。
【fifo.c的fifo8_get函数】
fifo_get函数是从FIFO缓冲区取出一个字节的函数。
【fifo.c的fifo8_status函数】
用来调查缓冲区的状态。
(六) 鼠标
给鼠标分配中断号IRQ12.处于初期状态的鼠标,不管是滑动操作还是点击操作都没有反应。我们来看下面的两个函数。
首先wait_KBC_sendready。他的作用是让键盘控制电路做好准备,等待控制指令的到来。因为虽然CPU覅南路很快,但是键盘控制电路并没有那么快。如果CPU不顾设备的接受数据的能力,有些指令就会得不到执行,从而导致错误执行。如果键盘控制电路可以接受CPU指令,CPU从设备号码0x0064处所读取的数据的倒数第二位应该是0,再确认到这一个位是0之前,程序一直通过for循环来查询。break语句是从for循环中强制退出的语句。退出以后,只有return语句在那里等待执行,所以把break语句换成return语句,结果一样。
紧接着看init_keyboard函数。它需要做到的是一边确认可否往键盘控制电路传送信息,一边发送模式设定指令,指令中包含着要设定为何种模式。模式设定的指令是0x60,利用鼠标模式的模式号码是0x47.这样鼠标控制电路就完成了。
【发送鼠标激活指令】
这个函数与init_keyboard函数非常相似。但是不同之点在于写入的数据不同。如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标。鼠标收到激活指令后,马上就给CPU发送答复信息0xfa。
(七) 从鼠标接受数据
取出中断指令,鼠标和键盘的原理几乎相同,不同之处在于只有送给PIC的中断受理通知。IRQ-12是从PIC的第4号,首先要通知IRQ-12受理已完成,然后再通知主PIC。这是因为主/从PIC的协调不能够自动的完成,如果程序不教给主PIC该怎么做,他就是忽视从PIC的下一个中断请求。
我们可以发现鼠标取得数据的方法与键盘完全一样,传到设备的数据是来自键盘还是鼠标,靠中断号来区分
【取数据的程序】
因为鼠标往往会比键盘更快的送出大量数据,所以要将FIFO缓冲区增加到128字节,这样就不会溢出了。取得数据的程序中,如果键盘和鼠标的FIFO缓冲区都为空,就执行HLT。如果不是两者都空,就先检查keyinfo,如果有数据,就取出一个显示出来。如果keyinfo是空,就再去检查mouseinfo,如果有数据,就取出一个显示出来。
二、遇到的问题及解决方法
问题1、为什么按一下键产生中断,有两个字节却只显示一个
原因:
当按下右ctrl键时候,会产生两个字节的键码值“E0 1D”,松开之后会产生两个字节的键码值“E0 9D”。在一次产生两个字节键码值的情况下,因为键盘内部电路一次只能发送一个字节,所以一次按键就会产生两次中断,第一次中断发送E0,第二次中断发送1D。
比较:
对于前面在a中,两次中断所发送的值都能收到,瞬间显示E0之后,接着又显示1D或9D。而在b中,HariMain函数在收到E0之前,又收到前一次按键产生的1D或9D,而这个字节就被舍弃了。
问题2、怎么让鼠标信号得到处理
虽然作者已经介绍一点了,但是感觉只是浅显的带过了。那么想要鼠标信号得到处理,就要让下面两个装置有效,一个是鼠标控制电路,一个是鼠标本身。关于控制电路的设定:鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,鼠标电路控制器的激活也就完成了。
三、程序设计创新点——姓名名字
【功能:】
① 在屏幕上显示自己的中文名字和学号
② enter按下键盘某个键(自己设置)后名字和学号的颜色开始随时间动态变化。
③ 按下回车之后,消失名字学号
④ 按下右ctrl之后,名字学号再次出现,并且颜色变换
【实现】
① 位置显示,设置姓名学号出现的位置。我这里名字和姓名都出现在最中间,并且名字在第一行,学号在第二行。
② 设置函数,因为只有键盘中断,所以我都移除了鼠标中断的部分。以下我的代码和注解
③ 名字设计
④ 延迟函数,为了让颜色变换慢一点,肉眼可以分辨出来
【现象】
① 初始状态
② 按下回车键,名字颜色变换
③ 按下空格键,消失
④ 按下右ctrl,名字再次出现
四、实验心得体会
在这次实验中,主要需要我们提前预习,然后看会,课堂上做了节点考核,整个过程还是比较紧张刺激的,大多数同学都选择了第三题,但是我觉得我不能在短时间之类完成,所以我选择了第二种。但是我的名字说真的还挺难写的,壁画也不少,所以我将第一个字,分为两部分来写,第一个字会稍微有点大。
总的来说还挺开心,希望明天学到更多。冲呀!!