NASM(四)显示控制


NASM(四)

任务:向屏幕输送大量文本

屏幕光标控制

光标在屏幕上的位置保存在显卡内部的两个光标寄存器中,每个寄存器是8位的,合起来形成一个16位的数值。标准VGA文本模式是25行,每行80个字符。当光标在屏幕右下角时,该值为25×80-1=1999

显卡操作十分复杂,寄存器多,寄存器的访问只能通过索引寄存器间接访问。索引寄存器的端口号是0x3d4,可以向它写入一个值,用来指定内部的某个寄存器。两个8位的光标寄存器,其索引值分别是14(0x0e)和15(0x0f),分别用于提供光标位置的高8位和低8位。指定了寄存器之后,通过数据端口0x3d5来进行进行读写。

get_cursor:
    mov dx, 0x3d4  ; 索引寄存器
    mov al, 0x0e   ; 0x0e光标寄存器
    out dx, al     ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5  ; 读数据
    in al, dx      ; 读
    mov ah, al     ; 放到高8位

    mov dx, 0x3d4  ; 索引寄存器
    mov al, 0x0f   ; 0x0f光标寄存器
    out dx, al     ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5  ; 读数据
    in al, dx      ; 读
    ; mov bx, ax     ; 把光标信息存储在bx中
set_cursor:
    mov dx,0x3d4    ; 索引寄存器
    mov al,0x0e     ; 0x0e光标寄存器
    out dx,al       ; 选择0x0e光标寄存器, 高8位
    mov dx,0x3d5    ; 选择0x3d5,准备写
    mov al,bh       ; 准备数据
    out dx,al       ; 写
    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    mov al,bl
    out dx,al

回车符与换行符

回车符0x0d,将光标移动到当前行的行首。$(光标位置 // 80) * 80$。

    mov bl, 80     ; 判断行列
    div bl         ; 被除数存储在"AX"寄存器中 "AL"中得到的是当前行的行号。
                   ; 商(结果的整数部分)将存储在"AL"寄存器中
                   ; 余数(结果的小数部分)将存储在"AH"寄存器中
    mul bl         ; 乘数应存储在AL寄存器中
                   ; 结果的低8位存储在"AL"寄存器中
                   ; 结果的高8位存储在"AH"寄存器中
    mov bx, ax     ; 保存到bx中

换行符0x0a, 换行的意图是向下挪一行,$光标位置+80$。

  add bx, 80      ; 换行

换行后,若超出显示范围,需要进行回滚,空出最后一行。

    cmp bx, 2000
    jl set_cursor   ; 屏幕只能显示2000个字符, 多余的滚行
    mov ax,0xb800
    mov ds,ax
    mov es,ax
    cld             ; 清除标志位,使movsw向正方向前进
    mov si,0xa0
    mov di,0x00
    mov cx,1920     ; 位置前挪,空出最后一行
    rep movsw       ; 该指令需要配合源地址寄存器(SI),目标地址寄存器(DI)和计数寄存器(CX)一起使用
                    ; 它会根据CX寄存器中的计数值重复执行movsw指令
clean:              ; 最后一行全部置为空格
    mov bx, 3840    ; 屏幕上第25行第1列在显存中的偏移地址是3840
    mov cx, 80
    mov word[es:bx], 0x0720  ; 清除第25行的内容
    add bx, 2
    loop clean
    mov bx,1920     ; 设置光标

文字输出

将文字输出到屏幕,此代码在主引导扇区中运行,注意主引导扇区为512字节。

init:
    mov ax, 0x7c0
    mov ds, ax  ; 设置数据段
    mov ax, msg0
    mov si, ax  ; 设置源数据地址
    mov ax, 0
    mov ss, ax  ; 设置栈段
    mov sp, ax

    mov bx, 0        ; 先将鼠标指针设置为0
    call set_cursor  ;

    mov di, 0   ; 偏移
    mov ax, end_data - data

put:
    mov cl, [ds:si]  ; 拿字符
    add si, 1        ; 偏移+1
    push si          ; 入栈保存
    push ax          ; 入栈保存
    call put_char
    pop ax
    pop si
    dec ax
    cmp ax, 0        ; 判断数据是否传输完
    jne put

exit:
    jmp near exit


get_cursor:
    mov dx, 0x3d4  ; 索引寄存器
    mov al, 0x0e   ; 0x0e光标寄存器
    out dx, al     ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5  ; 读数据
    in al, dx      ; 读
    mov ah, al     ; 放到高8位

    mov dx, 0x3d4  ; 索引寄存器
    mov al, 0x0f   ; 0x0f光标寄存器
    out dx, al     ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5  ; 读数据
    in al, dx      ; 读
    ; mov bx, ax     ; 把光标信息存储在bx中
    ret

put_char:          ; 接受参数cl, 用于提供要显示的ASCII码。
    call get_cursor
    cmp cl, 0x0d   ; 判断是否是0x0d
    jne check_0x0a ; 判断是否是0x0a
    mov bl, 80     ; 判断行列
    div bl         ; 被除数存储在"AX"寄存器中 "AL"中得到的是当前行的行号。
                   ; 商(结果的整数部分)将存储在"AL"寄存器中
                   ; 余数(结果的小数部分)将存储在"AH"寄存器中
    mul bl         ; 乘数应存储在AL寄存器中
                   ; 结果的低8位存储在"AL"寄存器中
                   ; 结果的高8位存储在"AH"寄存器中
    mov bx, ax     ; 保存到bx中

    jmp set_cursor

check_0x0a:
    cmp cl, 0x0a
    jne put_c
    add bx, 80      ; 换行
    cmp bx, 2000
    jl set_cursor   ; 屏幕只能显示2000个字符, 多余的滚行
    mov ax,0xb800
    mov ds,ax
    mov es,ax
    cld             ; 清除标志位,使movsw向正方向前进
    mov si,0xa0
    mov di,0x00
    mov cx,1920     ; 位置前挪,空出最后一行
    rep movsw       ; 该指令需要配合源地址寄存器(SI),目标地址寄存器(DI)和计数寄存器(CX)一起使用
                    ; 它会根据CX寄存器中的计数值重复执行movsw指令
clean:              ; 最后一行全部置为空格
    mov bx, 3840    ; 屏幕上第25行第1列在显存中的偏移地址是3840
    mov cx, 80
    mov word[es:bx], 0x0720  ; 清除第25行的内容
    add bx, 2
    loop clean
    mov bx,1920     ; 设置光标

set_cursor:
    mov dx,0x3d4    ; 索引寄存器
    mov al,0x0e     ; 0x0e光标寄存器
    out dx,al       ; 选择0x0e光标寄存器, 高8位
    mov dx,0x3d5    ; 选择0x3d5,准备写
    mov al,bh       ; 准备数据
    out dx,al       ; 写
    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    mov al,bl
    out dx,al
    ret

put_c:
    mov ax,0xb800
    mov es,ax
    shl bx,1         ; bx 乘2
    mov byte [es:bx], cl
    inc bx
    mov byte [es:bx], 0x07  ; 白字黑底
    dec bx
    shr bx,1          ; bx 除2
    add bx,1
    jmp set_cursor



data:
    msg0 db 'This is NASM - the famous Netwide Assembler. ',0x0d,0x0a
         db 'Back at SourceForge and in intensive development! ',0x0d,0x0a
         db 'Get the current versions from http://www.nasm.us/.',0x0d,0x0a
         db 0x0d,0x0a,0x0d,0x0a
         db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
         db '     xor dx,dx',0x0d,0x0a
         db '     xor ax,ax',0x0d,0x0a
         db '     xor cx,cx',0x0d,0x0a
         db '     ...'

end_data:
    times 510-($-$$) db 0
    db 0x55
    db 0xaa

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - NASM(四)显示控制


我的朋友,理论是灰色的,而生活之树是常青的!