所谓Video BIOS,其核心就是前面曾经提到过的INT 10H中断服务程序。在上一章中我们曾使用过10H中断的0EH功能在屏幕上显示字符,这个功能的能力还是很强的,但有一点令人不太满意的地方,那就是这个功能不能使字符具有颜色。那么有什么方法可以使字符有颜色呢?在讨论这个问题前我们先要讨论一个术语--字符属性。
直觉告诉我们显示在屏幕上的那些字符是具有"个性"的。最明显的,不同的字符可以有不同的颜色,有时候我们会在屏幕上看到一闪一闪的字符,如果使用一块老式的MDA卡,还能看到具有下划线的字符。
这里所说的"个性"指得就是每个字符可以具有的各种不同显示形式,这些不同的形式就是一般所说的"字符属性"。对于一个具体的字符是如何表示它的属性呢?在PC机的显示系统中,属性是由一个字节的数据表示的(图6-3):
上一节提到过各种颜色都是由RGB三种基本颜色合成的,所以属性字节中的低3位指出了组成字符颜色的RGB值。如果这三位是001,那么表示字符的颜色是蓝色;若这三位是011,那么字符的颜色就是蓝+绿,即青色。
"I"位是字符加亮位,这一位置1时则字符的颜色会变亮。如果认为"RGB"位控制了CRT电子枪的开关,那么"I"位相当于控制了加在电子枪上的信号电压,"I"位置1时信号电压加强了一倍,所以屏幕上的字符颜色会更亮。
Bit4-Bit6位控制了背影颜色的RGB值,"F"位的定义和"I"位不同,它表示字符是否闪烁,若F位为1,则屏幕上的字符将一闪一闪的。下面不妨来看两个实际属性字节:
(1)00100110 (2)11011010
第一个字节反映了字符的颜色是红+绿=棕色,不加亮;背影颜色是绿色,字符正常显示。第二个字节反映了字符的颜色是加亮的绿色;背影颜色是红+蓝=洋红,字符闪烁显示。这些只是理论上的分析,如果想验证这些分析带还要学习一个新的BIOS INT 10H功能调用--09H功能。
功能号:09H
用 途:在当前光标位置写有属性的字符
参 数:AL=将要显示字符的ASCII码
BH=显示页号
BL=字符的属性字节
CX=显示字符的个数
调 用:INT 10H
返 回:无
此功能并不难理解,但有一点需要解释--什么是显示页号?
这个问题同显示卡上的RAM有关。不同的显示卡所带的RAM量是不同的,MDA卡有4KB RAM,CGA卡有16KB RAM,标准EGA和VGA有256KB RAM。程序要显示一个字符,必须送两个字节数据到显示RAM中,第一个字节是字符的ASCII码,第二个字节就是属性字节。
如果当前屏幕上最多可以显示80列×25行即2000个字符,那么显示卡就必须具有4KB的RAM才可以放下2000个字符的数据。MDA卡上的4KB RAM就是由此而来,而且由于这4KB存储器仅够存储一个屏幕的字符,所以MDA卡没有分显示页的问题。
而CGA卡则不同,它有16KB存储器,按刚才讨论的标准它的RAM可以装下4个屏幕的字符数据,所以我们将CGA的存储器分成4个区,每一个区就称为一个显示页。程序可以向不同的显示页中写入字符及属性,不过只有写入当前正被显示卡电路处理的那个显示页的字符才能出现在屏幕上,这个显示页就称为"当前显示页"。
系统启动时默认当前显示页为第0页,即系统默认使用显示RAM最前面的4KB,当然我们可以通过程序改变当前显示页,这将在后面讨论。
下面这个程序演示了属性字节的应用,运行这个程序可以观察到不同RGB组合成的色彩,也可以看到属性字节中I位和F位的作用。
data segment assume ds:data attrib db 00000001b ;属性字节表 db 00000010b db 00001010b db 00000011b db 00100110b db 01010011b db 01011011b db 10000011b db 10001011b db 11010011b db 11011010b data ends code segment assume cs:code main proc far mov ax,data ;初始化DS寄存器指向数据段 mov ds,ax mov al,41h ;准备显示大写字母A mov ah,09h ;10H中断的09H功能 mov bh,0 ;显示0页 mov cx,11 ;第一行显示11个字符 mov si,offset attrib ;SI寄存器指向属性字节表 disploop: mov bl,byte ptr [si] ;BL寄存器取得一个属性字节 call outattr ;调用OUTATTR子过程显示属性值 int 10h ;调用10H中断显示字符 call waitkey ;等待键盘输入 inc si ;SI寄存器指向下一个属性字节 loop disploop ;继续显示字符 mov ah,4ch ;结束程序 int 21h main endp outattr proc near ;OUTATTR子过程 push ax ;保存将要使用的寄存器 push bx push cx mov cx,8 ;显示8个数位 outloop: shl bl,1 ;属性字节左移1位,将最高位移入CF jc out_1 ;若移出的位为1则转OUT_1输出"1" mov al,30h ;AL寄存器置入字符"0"的ASCII码 jmp out_char ;输出字符"0" out_1: mov al,31h ;AL寄存器置入字符"1"的ASCII码 out_char: mov ah,0eh ;利用10H中断0EH功能 int 10h ;输出AL寄存器中的字符 loop outloop ;继续输出其它数位 mov al,0dh ;输出回车符 int 10h mov al,0ah ;输出换行符 int 10h pop cx ;恢复寄存器 pop bx pop ax ret ;返回主过程 outattr endp waitkey proc near ;WAITKEY子过程 push ax ;保存将要使用的寄存器 push dx mov ah,0 ;利用16H中断的0号功能 int 16h ;从键盘接收字符 mov ax,0e0dh ;输出回车符 int 10h mov al,0ah ;输出两个换行符 int 10h int 10h pop dx ;恢复寄存器 pop ax ret ;返回主程序 waitkey endp code ends end main
关于指令JC我们在第四章讨论JA/JB指令时略有提及,它实际是指令JB的另一个形式:
助记符:JC(Jump if Carry)
用 途:依据CF标志状态转移
格 式:JC 目的地址
执 行:如果CF位为1则此指令完成转移,反之CF=0则不发生转移。
在讨论指令"SHL"时曾经提到过移出寄存器的一位将进入CF标志位,在此程序里这个特性得到了应用。