显存文本模式详解 ———《x86汇编语言:从实模式到保护模式》读书笔记补遗02

时间:2021-06-07 01:25:56

文章修改记录

修改日期 修改内容
2018-2-4 修改了一处错别字;增加了表格的使用方法

今天我们讨论如何编程以在屏幕上显示出彩色的文字。

为了显示文字,通常需要两种硬件——显示器和显卡。
显卡的作用是为显示器提供要显示的内容,并且控制显示器的模式和状态。
显示器的作用是把那些内容以人们可见的方式呈现在屏幕上。

1.显存

每个显卡都有自己的存储器,因为它位于显卡上,所以称为显示存储器,简称“显存”。和其他存储器一样,显存并没有什么特殊的地方,也是一个按字节访问的存储器件。

2.显卡的两种工作模式

显卡最基本的两种工作模式是文字(也称为文本)模式和图形模式。在不同的模式下,显卡对显存内容的解释是不同的。要想设置显卡的显示模式,可以用指令访问显卡,也可以直接调用BIOSint 10h中断。

3.BIOS调用之设置显示模式

功能号:AH = 00H
用 途:设置显示模式
参 数:AL = 显示模式号
调 用:INT 10H
返 回:无

AL的取值说明:

AL 文字/图形 分辨率 颜色
00H 文字 40*25 2
01H 文字 40*25 16
02H 文字 80*25 2
03H 文字 80*25 16
04H 图形 320*200 2
05H 图形 320*200 4
06H 图形 640*200 2

需要说明的是:计算机在加电自检后会自动初始化到AL=03H的文字模式。在这种模式下,一屏幕可以显示25行,每行80个字符,总共是80*25=2000个字符。

4.文本模式下,显存到内存的映射

0xB80000xBFFFF这段物理地址被映射到显存。也就是说,写这些物理地址,就可以控制显示内容。

显存和每个字符(假入从0开始数,那就是0~1999)的对应关系,如下图所示。

显存文本模式详解 ———《x86汇编语言:从实模式到保护模式》读书笔记补遗02

5.关于属性

bit [7] [6:4] [3:0]
含义 1:字闪烁;0:字不闪烁 背景色 前景色

5.1 背景色

因为[6:4]决定背景色,所以取值是0~7。根据我判识色彩的能力,总结如下。

[6:4]取值 0 1 2 3 4 5 6 7
颜色 深蓝 绿 粉红 灰白

5.2 前景色

因为[3:0]决定前景色,所以取值是0x0~0xF。根据我判识色彩的能力,总结如下。

[3:0]取值 0 1 2 3 4 5 6 7 8 9 A B C D E F
颜色 深蓝 绿 粉红 灰白 亮蓝 亮绿 亮青 亮红 亮粉红 亮白

6.编程实践——遍历所有颜色

关于颜色,眼见为实。也许我眼中的青色,在你眼中就是蓝色。不妨编程看看,显示在屏幕上的到底是什么颜色。

思路:在不考虑闪烁的情况下,前景色搭配背景色,共有16*8=128种可能,我们的目的是提供一个表格,每一行表示前景色的不同取值,每一列表示背景色的不同取值。

关于要显示的内容,可以选择一个字符串(考虑到一行最多显示80个字符,80/16=5,所以字符串长度不宜超过5个),一共显示128次。

6.1 通过调用BIOS中断实现

    jmp near start

message db 'KARL '   ;字符串任意,但是不要超过5个字符
                     ;取KARL是因为KARL是我徒弟的英文名


;int 10h (video service)
;AH=13h, 在teletype模式下显示字符串
;入口参数:
;   AL[1:0]=显示方式
;     [0]: 0表示不移动光标,1表示移动光标
;     [1]: 0表示字符串中仅包含字符,不包含属性,属性在BL中;1表示字符串中包含属性
;   BH=页码
;   BL=属性
;   CX=字符串长度
;   DH=行
;   DL=列
;   ES:BP=指向字符串
;
;出口参数:无


start:
    mov ax,0x7c0    ;设置ES段的段地址 
    mov es,ax     
    mov bp,message  ;ES:BP指向字符串
    mov ah,0x13     ;在teletype模式下显示字符串
    mov al,1   ;显示方式,表示字符串中仅包含字符,不包含属性,属性在BL中,移动光标
    mov bl,0   ;属性初始值
    mov bh,0   ;页码
    mov dh,0   ;从0行开始


    mov cx,8   ;循环8次,从0行到7行

put_0_8: ;------------------------------------外层循环

    push cx   ;因为内层循环也要用CX控制循环次数,所以压栈保护
    mov dl,0  ;从0列开始
    mov cx,16 ;循环16次,从0列到15列

put_0_F:         ;------------内层循环
    push cx   ;因为循环体中要用到CX,所以压栈保护
    mov cx,5  ;设置字符串长度
    int 0x10  ;BIOS中断调用
    inc bl    ;改变属性,属性值增加1
    add dl,5  ;改变列,列值增加5 
    pop cx
    loop put_0_F ;------------内层循环

    pop cx
    inc dh    ;改变行,行增加1  
    loop put_0_8  ;----------------------------外层循环

    jmp near $   ;使陷入死循环


times 510-($-$$) db 0
                 db 0x55,0xaa

运行结果如下图
显存文本模式详解 ———《x86汇编语言:从实模式到保护模式》读书笔记补遗02

6.2 通过自己写过程实现

    jmp near start

message db 'KARL '
        db 0       ;本程序的过程规定以0结尾

start:
    mov ax,0x7c0   ;设置数据段的段基地址 
    mov ds,ax

    mov bx,message ;使DS:BX指向字符串
    mov al,0       ;属性初始值

    mov dh,0       ;从0行开始
    mov cx,8       ;循环8次,从0行到7行

put_0_8:         ;----------------------------外层循环

    push cx
    mov dl,0     ;列的初始值
    mov cx,16    ;循环16次,从0列到15列

put_0_F:         ;--------内层循环

    call put_string
    inc al       ;改变属性,属性值增加1
    add dl,5     ;改变列,列值增加5 
    loop put_0_F ;--------内层循环

    pop cx
    inc dh       ;改变行,行增加1  
    loop put_0_8 ;----------------------------外层循环

    jmp near $ 
;-------------------------------------  
;功能:在某位置显示字符串      
;入口参数:
;   AL=属性
;   DH=行
;   DL=列
;   DX:BX=指向字符串,字符串必须以0结尾
;出口参数:无         
put_string:

    push ax
    push bx
    push cx
    push dx
    push di
    push es

    push ax          ;AX中是属性,因为下面要用AX,所以先进栈保护起来
    mov ax,0xb800
    mov es,ax
                     ; xy列,换算成偏移是:(x*80+y)*2
    mov al,80
    mul dh           ;ax=al*dh (计算出x*80,结果在ax中)
    xor dh,dh        ;dh清零
    add ax,dx        ;计算出(x*80+y),结果在ax中
    shl ax,1         ;计算出(x*80+y)*2,结果在ax中

    mov di,ax        ;用di保存偏移
    pop ax           ;得到属性
put_char:
    mov cl,[bx]      ;取要显示的字符到cl中
    cmp cl,0         ;和0比较
    jz end           ;等于0则跳转
    mov [es:di],cl   ;写字符的ASCII码到显存
    inc di
    mov [es:di],al   ;写字符的属性到显存
    inc di           ;di指向显存中的下一个位置
    inc bx           ;bx指向下一个字符
    jmp put_char
 end:   
    pop es
    pop di
    pop dx
    pop cx
    pop bx
    pop ax

    ret              ;返回
;-------------------------------------------

times 510-($-$$) db 0
                 db 0x55,0xaa

6.3 将实验结果制作成表格

显存文本模式详解 ———《x86汇编语言:从实模式到保护模式》读书笔记补遗02

可以看到,当前景色和背景色取值相同时,就看不到字了。所以,属性组合不是128种,而是128-8=120种。

查询表格的时候,最左边一列的数字表示背景色,最上面一行的数字表示前景色(即字的颜色)。

  • 举例1:0x02——黑底绿字
  • 举例2:0x04——黑底红字
  • 举例3:0x24——绿底红字

7.使用LOOP要注意什么

上面的代码使用了LOOP实现循环,对于初学者,用LOOP时需要注意的是:

  • 循环之前的初始化,比如循环次数(CX)、变量的初始值等
  • 标号的位置
  • 循环体内变量的自增/自减
  • 对于嵌套的LOOP,尤其要注意CX的压栈出栈和其他寄存器(如果需要)的压栈、出栈

【完】