視訊記憶體文字模式詳解 ———《x86組合語言:從真實模式到保護模式》讀書筆記補遺02
文章修改記錄
修改日期 | 修改內容 |
---|---|
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
4.文字模式下,視訊記憶體到記憶體的對映
0xB8000
到0xBFFFF
這段實體地址被對映到視訊記憶體。也就是說,寫這些實體地址,就可以控制顯示內容。
視訊記憶體和每個字元(假入從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 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
執行結果如下圖
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
; x行y列,換算成偏移是:(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種。
查詢表格的時候,最左邊一列的數字表示背景色,最上面一行的數字表示前景色(即字的顏色)。
- 舉例1:0x02——黑底綠字
- 舉例2:0x04——黑底紅字
- 舉例3:0x24——綠底紅字
7.使用LOOP
要注意什麼
上面的程式碼使用了LOOP
實現迴圈,對於初學者,用LOOP
時需要注意的是:
- 迴圈之前的初始化,比如迴圈次數(CX)、變數的初始值等
- 標號的位置
- 迴圈體內變數的自增/自減
- 對於巢狀的LOOP,尤其要注意CX的壓棧出棧和其他暫存器(如果需要)的壓棧、出棧
【完】