可移植性历来是汇编语言所面临的最为严重的问题之一,特别是编程控制显示卡上的端口,这个问题更为突出。原因在于我们现在大量使用的VGA/Super VGA卡没能做到在端口一级兼容早期的CGA卡。这不能不说是一种遗憾,要知道这些卡几乎都做到了在显示缓冲存储器一级兼容CGA卡,只差一小步。因此,这一节将只准备讨论那些适用于各种显示卡的端口寄存器,给出的程序可以在大多数显示卡上运行。首先我们要解决的第一个问题是DX寄存器在端口操作中的应用。
我在前面曾经提到过DX寄存器在端口输入与输出中有特殊的用途,而且在讨论发声程序时也说到过指令"IN AL,??H"的形式只能在端口号小于0FFH时应用,但是显示卡上的端口号都大于0FFH,如何才能访问到这些端口呢?
在给出具体的方法之前,我们还需复习一下有关寻址方式的知识。当我们从内存中取得数据时,我们可以采用直接寻址方式和寄存器间接寻址方式,以MOV指令为例,如果我们从DS:1234处读取一字节数据到累加器,我们可以使用两种方法编制程序:MOV AL,DS:[1234H]或MOV BX,1234H;MOV AL,[BX]。第一种方法就是直接寻址方式,第二种方法就是寄存器间接寻址方式。事实上,端口的访问和内存访问有很多相似的地方,象"IN AL,61H"这样的用法其实就是对端口的直接寻址,那么端口的间接寻址又是什么情形呢?形式与访问内存差不多: MOV DX,端口号 IN 累加器,DX;(或OUT DX,累加器)
当然,区别还是有的:首先,我们只能使用DX寄存器做间接寻址;其次,访问端口无需采用"[DX]"的形式。在后面给出的程序例中你可以看到这种方法的具体应用。
知道如何访问端口,这只是一个开始,我们第二步需要了解的就是显示卡上的端口寄存器的结构。显示卡上的寄存器数量很多,如果给每个寄存器都分配一个端口号那将耗用很多的端口地址资源。而且给设计程序带来不便。所以几乎所有的显示卡都采用了"寄存器堆"的结构。
所谓寄存器堆,其实就是将一些功能相关的寄存器组成一组,分配一个端口号,所有寄存器都从这一个端口输入输出。大家可能会联想起我们在前面讲解定时器时曾经说过,给定时器设定"N"值时要把16位的数据分成两次从同一端口"42H"送进定时器。注意这和现在要讲的知识并不一样。这是因为既然"一堆"寄存器只有一个端口传输数据,那么我们要访问其中一个寄存器就必须有办法"指定"要访问的对象。所以一个寄存器堆实际分配了两个端口,第一个用于指定要访问的寄存器,第二个用于传输数据。
习惯上通常将第一个端口称为"索引"端口,将第二个端口称为"数据"端口。一般情况下索引端口地址与数据端口地址是相临的,如显示卡上有一个被称为CRTC(CRT Controler)的寄存器堆,专门用于控制送到显示器的各种信号,系统分配给这个寄存器堆的两个端口地址是3D4H和3D5H,其中3D4H是"索引"端口,3D5H是"数据"端口。如果程序要访问CRTC的第10个寄存器,那么首先要向端口3D4H送出寄存器号10,此时CRTC的第10个寄存器就和端口3D5H相连通,这时候再用"IN"或"OUT"对端口3D5H进行操作,就可以取得CRTC10号寄存器的数据或将数据送入CRTC10号寄存器中。相应的程序如下:
MOV DX,3D4H ;准备访问"索引"端口3D4H MOV AL,10 ;选择CRTC10号寄存器 OUT DX,AL ;输出寄存器号 MOV DX,3D5H ;准备访问"数据"端口3D5H IN AL,DX ;读取CRTC10号寄存器的值
了解了显示卡上的寄存器结构,第三步就是要详细了解寄存器堆中每个成员的作用了。表6-2列出了CGA卡上CRTC寄存器堆中所有寄存器的功能,共有18个寄存器:
下面对这些寄存器作一些详细的说明:
(1)CRTC0-CRTC3称为水平寄存器,用来控制显示器水平方向的显示特性,比如每行字符数,每个字符的宽度等。
(2)CRTC4-CRTC9称为垂直寄存器,用来控制显示器垂直方向的显示特性,比如全屏显示的字符行数,每个字符占据的扫描线数等。
(3)CRTC16-CRTC17,这两个寄存器用来控制光笔。
(4)CRTC10-CRTC15,这六个寄存器是我们重点要讨论的,通过这六个寄存器,我们可以详细了解BIOS功能调用的执行情况。
寄存器 编 号 |
寄存器名称 | 寄存器 类 型 |
参数单位 | 设定值 | ||
40x25 | 80x25 | 图形模式 | ||||
00 | 水平扫描总时间 | 只写 | 字符 | 38 | 71 | 38 |
01 | 每行字符数 | 只写 | 字符 | 28 | 50 | 28 |
02 | 水平同步位置 | 只写 | 字符 | 2D | 5A | 2D |
03 | 水平同步宽度 | 只写 | 字符 | 0A | 0A | 0A |
04 | 垂直扫描总时间 | 只写 | 字符行 | 1F | 1F | 7F |
05 | 垂直总调节 | 只写 | 扫描线 | 06 | 06 | 06 |
06 | 每帧显示行数 | 只写 | 字符行 | 19 | 19 | 64 |
07 | 垂直同步位置 | 只写 | 字符行 | 1C | 1C | 70 |
08 | 隔行扫描方式 | 只写 | —— | 02 | 02 | 02 |
09 | 最大扫描线地址 | 只写 | 扫描线 | 07 | 07 | 01 |
10 | 光标起始 | 只写 | 扫描线 | 06 | 06 | 06 |
11 | 光标结束 | 只写 | 扫描线 | 00 | 00 | 00 |
12 | 起始地址(高位) | 只写 | —— | 00 | 00 | 00 |
13 | 起始地址(低位) | 只写 | —— | XX | XX | XX |
14 | 光标位置(高位) | 读写 | —— | XX | XX | XX |
15 | 光标位置(低位) | 读写 | —— | XX | XX | XX |
16 | 光笔位置(高位) | 只读 | —— | XX | XX | XX |
17 | 光笔位置(低位) | 只读 | —— | XX | XX | XX |