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

来源:互联网 发布:wine mac下载 编辑:程序博客网 时间:2024/05/17 07:26

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

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

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)的对应关系,如下图所示。

这里写图片描述

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 startmessage 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

运行结果如下图
这里写图片描述

6.2 通过自己写过程实现

    jmp near startmessage 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 将实验结果制作成表格

这里写图片描述

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

7.使用LOOP要注意什么

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

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

【完】

1 0