一、实验主要内容
内容1:获取按键编码
将目光转移到“io_out8(PIC0_OCW2,0x61)”,这句话上,这句话用来通知PIC“已经知道IRQ1中断”。如果是IRQ3,则写成0x63。执行这句话后,PIC继续时刻监视IRQ1中断是否发生。
在运行之后,按下键盘上任意一个按键,都会有对应的数字编码显示出来。比如A就会显示出1E。
内容2:加快中断处理
字符显示的内容被放在了中断处理程序中。所谓中断处理,基本上就是打断CPU本来的工作,加塞要求进行处理。所以应该完成的干净利落。这样子会不连贯。
另一方面,字符显示是要花大块的时间来进行的处理。这期间谁也不知道其它中断会在哪个瞬间到来。很可能在键盘输入的同时,就有数据正在从网上下载,而PIC还在等待键盘中断处理的结束。
先将按键的编码接收下来,保存到变量。然后让Harimain偶尔去查看这个变量。如果发现有数据,就把它显示出来。
代码部分如下:
考虑到键盘输入时需要缓冲区,我们定义了一个构造体,命名为keybuf。其中的flag变量用于表示这个缓冲区是否为空。如果flag为0,表示缓冲区为空;如果flag为1,则表示缓冲区中存有数据。那么,如果缓冲区中存有数据,而这时候又来了一个中断,应该怎么办?在此先不做任何处理,先就当作扔了这个数据。
Harimain函数如下:
用io_cli指令屏蔽中断,为防止在执行其后的处理时,如果有中断进来,那可就乱套了。
如果flag==0,说明键还没有被按下,keybuf.data没有值被保存下来。因为已经执行了屏蔽中断,如果之后无事可做执行HLT的花,程序就不再会有任何反应。所以执行了io_stihlt,这样的话,收到PIC通知后,CPU就可以被唤醒了。
进行测试了之后,可以发现可以顺利执行,但是在按下右ctrl时,不管按下还是松开,屏幕上显示的都是E0,正常应该是按下去显示1D,松开时显示9D。
查询资料得知,当按下右ctrl时,会产生两个字节的键码值“E0,1D”松开时是“E0,9D”。所以一次按键就会有两次中断。在收到E0之前,又收到前一次按键产生的1D或者9D,而这个字节被舍弃了。
内容3:制作FIFO缓冲区
上面会出现错误,是因为之前只能接收一个中断发来的数据,所以中断发生的太快,有超过一个字节的数据就会被忽略,所以如下这样做:
增加变量
data用数组存储
显然,用数组更方便一些,我们在使用这些缓冲数据时,要采取FIFO策略,也就是先入先出策略,程序实现:
for循环中相当于把数据存放位置全部都向前移送一个位置。如果不移送的话,下一次就不能从data[0]读入数据了。
运行之后可以发现按下右Ctrl键运行正常,但并没有OK,有些地方还不尽如人意。就是这样的移送数据在数据过多的时候,还是挺麻烦的。
内容4:改善FIFO缓冲区
既维护下一个要写入数据的位置,也维护下一个要读出数据的位置
但是当下一个数据写入位置到达缓冲区终点时,数据读出位置也恰好到达缓冲区的终点,也就是说缓冲区正好变空。我们只要将下一个数据写入位置和下一个数据读出位置都再置0即可,就像从头再来一样。但是还是会有数据读出位置没有追上写入位置的情况,就还要数据移送,我们还是想尽量避免。
当下一个数据写入位置到达缓冲区最末尾时,缓冲区开头部分应该变空了。因此如果下一个数据写入位置到了32之后,就强制性将之设置为0,这样一来,下一个数据写入位置就跑到了下一个数据读出位置的后面,让人觉得怪怪的。但不会有什么问题。
程序如下:
读出数据程序如下:
内容5:整理FIFO缓冲区
将结构做成一下这样:
如果我们将缓冲区大小固定为32字节的话,以后改起来就不方便,所以将之定义为可变的,几个字节都行。缓冲区的总字节数保存在变量size里。变量free用于保存缓冲区里没有数据的字节数。缓冲区的地址当然也必须保存下来。我们将之保存在变量buf中。p代表下一个数据写入地址,q代表下一个数据读出地址。
下面是结构的初始化函数
关于往FIFO缓冲区存储1字节信息的函数如下。
这里出现的新东西,应该就是return语句后面跟着数字的写法。如果有人调用以上函数,写出类似于“i=fifo8_put(fifo,data);”这种语句,我们就可以通过这种方式指定赋给i的值。为了能够简单明了地确认到底有没有发生溢出,笔者将之设定在-1或0,分别表示有溢出和没有溢出这两种情况。
下面是从缓冲区取出1字节的函数。
调查缓冲区状态的函数:
运行之后依旧运行正常。
内容6:总算讲到鼠标了
键盘控制电路部分:
其中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。
测试之后,可以对鼠标中断做出相应。
内容七:FIFO与鼠标控制
基本与键盘的原理和程序差不多。
中断函数:
数据取得方法,和键盘完全相同,如下:
也许是因为键盘控制电路中含有鼠标控制电路,才造成了这种结果。
最后运行之后,发现键盘和鼠标的功能都可以相应。鼠标的滚动也会相应出对应数据。
二、遇到的问题及解决方法
问题1:
在设计圆的边界问题的时候,因为在设计的时候涉及到了速度的变化,那么速度如果不是1的话,在判断边界的时候,就有可能越界。解决越界问题的方式如下:
即判断到下一次移动如果要移出屏幕或者正好到边界的话,就设定好这次到的位置。然后对这个圆进行打印。这样的话,不管再怎么移动,也不会移出屏幕。
问题二:
还是在这个部分,当时验收的时候,老师看到我在移动到右边界的时候,左边界多出来一个像素点,后来我想了一下,整个屏幕是320200,但是我按的两个边界是0-320,也就是321200了,所以实际上是越界了,而整个VGA实际上是一个一维数组而已,所以自然就显示到了下一行。修改方式就是定义的位置左移一下就行了:
三、创新点
创新点一:
关于速度的变化,只要每一次改变圆移动的步长,也就改变了速度,所以定义一个变量speed,在按下w或s的时候speed变化就行了。
创新点二:
修改圆的颜色,函数中COL的部分用变量代替,然后再按下对应按键的时候更改这个颜色变量即可。