汇编网首页登录博客注册
unixandlinux的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
最新评论
最新留言
好友圈
友情链接

[2023-06-25 17:01] 第9章 实验9 根据材料编程

实验 9 根据材料编程

80×25 彩色字符模式显示缓冲区(以下简称为显示缓冲区)的结构:
内存地址空间中,B8000H~BFFFFH 共 32KB 的空间,为 80×25 彩色字符模式的显示缓冲区。向这个地址空间写入数据,写入的内容将立即出现在显示器上。
内存地址空间 B8000H~BFFFFH,其段地址为 B0000H,偏移地址范围 8000H~FFFFH;可见偏移地址数量计算方法如下:
FFFFH-8000H+1=7FFFH+1=8000H=8×16³=2³×(2⁴)³=2³×2¹²=2¹⁵=2⁵×2¹⁰=2⁵KB=32KB
在 80×25 彩色字符模式下,显示器可以显示 25 行,每行 80 个字符,每个字符可以有 256 种属性(背景色、前景色、闪烁、高亮等组合信息)。由于 256=100H,也就说可以用 00H~FFH 表示这 256 种属性,而这恰好就是一个字节的存储空间,可见可以用一个字节(8 个 bit 位)的存储空间来存储属性特征。
于是,一个字符在显示缓冲区中就要占两个字节,分别存放字符的 ASCII 码和属性(一个字节存储 ASCII 码,一个字节存储属性特征)。80×25 模式下一屏内容在显示缓冲区中共占 4000 个字节。即一屏显示 25 行,每行 80 个字符,即 80×25=2000 个字符,每个字符占用 2 个字节,所以 2000 个字符就占用 4000 个字节。
显示缓冲区分为 8 页,每页 4KB(显示缓冲区空间共 32KB,分 8 页,每页 32÷8=4KB),显示器可以显示任意一页的内容。一般情况下,显示第 0 页的内容。也就是说通常情况下, 8000H~B8F9FH 中的 4000 个字节的内容将出现在显示器上。
在一页显示缓冲区中:
(1) 偏移 000~09F 对应显示器上的第 1 行(80 个字符占 160 个字节:160=A0H)
(2) 偏移 0A0~13F 对应显示器上的第 2 行
(3) 偏移 140~1DF 对应显示器上的第 3 行
依此类推,可知,偏移 F00~F9F 对应显示器上的第 25 行。
一个字符占两个字节的存储空间(一个字),低位字节存储字符的 ASCII 码,高位字节存储字符的属性。
即在一行中:
(1) 00~01 单元对应显示器上的第 1 列
(2) 02~03 单元对应显示器上的第 2 列
(3) 04~05 单元对应显示器上的第 3 列
依此类推,可知,9E~9F 单元对应显示器上的第 80 列。
例如,在显示器的 0 行 0 列显示黑底绿色的字符串“ABCDEF”(字符'A' 的 ASCII 码值为 41H,02H 表示黑底绿色),显示缓冲区里的内容为:
      00 01 02 03 04 05 06 07 08 09 0A 0B ... 0E 0F
B800:0000 41 02 42 02 43 02 44 02 45 02 46 02 ... ...
B800:00A0 ... ...
可以看出,在显示缓冲区中,偶地址存放字符,奇地址存放字符的颜色属性。

一个在屏幕上显示的字符,具有前景(字符颜色)和背景(底色)两种颜色,字符还可以以高亮度和闪烁的方式显示。前景色、背景色、闪烁、高亮等信息被记录在属性字节中。
属性字节的格式:
      7  6   5   4   3   2   1   0
含义    BL  R   G   B   I   R   G   B
      闪烁    背景     高亮     前景
R:红色
G:绿色
B:蓝色
属性字节格式的说明:
属性用一个字节表示,属性字节包含 8 个 bit,表示为:7 6 5 4 3 2 1 0
这些数字的每个都表示一个 bit 位,具体含义如下:
(1) 数字 0:最低位,即第 1 位,表示前景色蓝色。
(2) 数字 1:第 2 位,表示前景色绿色。
(3) 数字 2:第 3 位,表示前景色红色。
(4) 数字 3:第 4 位,表示高亮。
(5) 数字 4:第 5 位,表示背景色蓝色。
(6) 数字 5:第 6 位,表示背景色绿色。
(7) 数字 6:第 7 位,表示背景色红色。
(8) 数字 7:最高位,即第 8 位,表示闪烁。
当具有某种属性时,对应的属性位被置 1,否则置 0。
另外,黑色用红、绿、蓝对应的位都置 0 来表示;白色用红、绿、蓝对应的位都置 1 来表示。
可以按位设置属性字节,从而配出各种不同的前景色和背景色。比如:
(1) 红底绿字
底色属性占用数字位 4、5、6 三个位,红色底则数字位 6 置 1,数字位 4 和 5 置 0。
字体颜色即前景色,占用数字位 0、1、2 三个位,绿字体则数字位 1 置 1,数字位 0 和 2 置 0。
其他属性(闪烁和高亮)都置 0(数字位 7 和数字位 3,均置 0)。
所以其属性字节存储的数据为 01000010。
(2) 红底闪烁绿字
底色属性占用数字位 4、5、6 三个位,红色底则数字位 6 置 1,数字位 4 和 5 置 0。
闪烁属性占用数字位 7,因此数字位 7 置 1。
字体颜色即前景色,占用数字位 0、1、2 三个位,绿字体则数字位 1 置 1,数字位 0 和 2 置 0。
其他属性(高亮)置 0(数字位 3 置 0)。
所以其属性字节存储的数据为 11000010。
(3) 红底高亮绿字
底色属性占用数字位 4、5、6 三个位,红色底则数字位 6 置 1,数字位 4 和 5 置 0。
字体颜色即前景色,占用数字位 0、1、2 三个位,绿字体则数字位 1 置 1,数字位 0 和 2 置 0。
高亮属性占用数字位 3,因此数字位 3置 1。
其他属性(闪烁)置 0(数字位 7 置 0)。
所以其属性字节存储的数据为 01001010。
(4) 黑底白字
底色属性占用数字位 4、5、6 三个位,黑色底则这三个数字位都置 0。
字体颜色即前景色,占用数字位 0、1、2 三个位,白字体则这三个数字位都置 1。
其他属性(闪烁和高亮)都置 0(数字位 7 和 3 都置 0).
所以其属性字节存储的数据为 00000111。
(5) 白底蓝字
底色属性占用数字位 4、5、6 三个位,白色底则这三个数字位都置 1。
字体颜色即前景色,占用数字位 0、1、2 三个位,蓝字体则数字位 0 置 1。
其他属性(闪烁和高亮)都置 0(数字位 7 和 3 都置 0)。
所以其属性字节存储的数据为 01110001。

例:在显示器的 0 行 0 列显示红底高亮闪烁绿色的字符串"ABCDEF"(红底高亮闪烁绿色:底色属性占用数字位 4、5、6 三个位,红色底则数字位 6 置 1;闪烁属性占用数字位 7,因此数字位 7 置 1;字体颜色即前景色,占用数字位 0、1、2 三个位,绿字体则数字位 1 置 1;高亮属性占用数字位 3,因此数字位 3 置 1;所以其属性字节存储的数据为 11001010,即 CAH)。显示缓冲区里的内容为:
     00 01 02 03 04 05 06 07 08 09 0A 0B ... 9E 9F
B800:0000 41 CA 42 CA 43 CA 44 CA 45 CA 46 CA ... ...
B800:00A0 ...... 
注意,闪烁的效果必须在全屏 DOS 方式下才能看到。

编写程序,以实现在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串"welcome to masm!"。对此程序功能的理解有两种:
1. 显示一行字符串,其中“welcome”属性为“绿色”,“to”属性为“绿底红色”,“masm!”属性为“白底蓝色”。
2. 显示三行“welcome to masm!”字符串,三行字符串的属性分别为“绿色”、“绿底红色”、“白底蓝色”。
首先确认显示缓冲区中与屏幕中间位置对应的内存地址,然后确定所显示的字符串相应的字符 ASCII 码及其属性码,最后设计程序的具体汇编指令以实现将字符串的码值(包括其 ASCII 码和属性码)传送到显示缓冲区内存地址中,从而在屏幕上显示这些字符串。

以下叙述按程序功能的第 1 种理解方式进行操作。
一、确认内存地址
由于通常情况下,显示器显示的是显示缓冲区中第 0 页的内容,即内存地址 B8000H~B8F9FH 范围的 4000 个字节内容;而显示器每屏显示 25 行、每行 80 个字符(每个字符占 1 列,所以也就是 80 列)。

(一)计算所要显示的内容在屏幕的具体行、列位置
程序要求将字符串"welcome to masm!"(共计 16 个字符)显示到屏幕的中间位置,因此应该显示在当前屏幕的第 13 行第 33~49 列,计算方法如下:
(1) 行数
对 25÷2 的计算结果取整,即对 12.5 取整得到 13,也就是第 13 行。
(2) 列数
字符串中包含字符数量为 16 个,所以显示行中无字符的列数为 80-16=64,由于要在屏幕中间显示,所以无字符的列应该平均分布在所显示字符的两侧,也就是在显示的 16 个字符两侧各分布 64÷2=32 列无字符列。即在显示行的第 33 列开始显示字符串"welcom to masm!",而 33+16=49,因此字符串应显示在第 33~49 列。
        
(二)确定所要显示内容的具体显示缓冲区内存地址
1. 第一种方法
由于每个字符占用内存空间为 2 个字节(一个字),所显示的字符串位于屏幕的第 13 行第 33~49 列,其起始位置为当前屏幕的第 12×80+33=993 个字符位置,即 993×2=1986 字节,转换为十六进制数为 7C2H;所以起始地址为 B8000H+7C2H-2=B87C0H(由于一个字符占用 2 个字节,所以此处要减 2 而不是减 1,即减 1 个字符所占用的字节数,也就是减 2 个字节),16 个字符占用的内存空间为 32 字节,转换为十六进制数为 20H,因此其终止地址为 B87C0H+20H=B87E0H。可见要显示的字符串所在的显示缓冲区内存地址为 B87C0H~B87E0H。
2. 第二种方法
(1) 确定行地址
偏移 000~09F 对应显示器上的第 1 行(80 个字符占 160 个字节:160=A0H)
偏移 0A0~13F 对应显示器上的第 2 行
偏移 140~1DF 对应显示器上的第 3 行
偏移 1E0~27F 对应显示器上的第 4 行
偏移 280~31F 对应显示器上的第 5 行
偏移 320~3BF 对应显示器上的第 6 行
偏移 3C0~45F 对应显示器上的第 7 行
偏移 460~4FF 对应显示器上的第 8 行
偏移 500~59F 对应显示器上的第 9 行
偏移 5A0~63F 对应显示器上的第 10 行
偏移 640~6DF 对应显示器上的第 11 行
偏移 6E0~77F 对应显示器上的第 12 行
偏移 780~81F 对应显示器上的第 13 行
偏移 820~8BF 对应显示器上的第 14 行
偏移 8C0~95F 对应显示器上的第 15 行
偏移 960~9FF 对应显示器上的第 16 行
偏移 A00~A9F 对应显示器上的第 17 行
偏移 AA0~B3F 对应显示器上的第 18 行
偏移 B40~BDF 对应显示器上的第 19 行
偏移 BE0~C7F 对应显示器上的第 20 行
偏移 C80~D1F 对应显示器上的第 21 行
偏移 D20~DBF 对应显示器上的第 22 行
偏移 DC0~E5F 对应显示器上的第 23 行
偏移 E60~EFF 对应显示器上的第 24 行
偏移 F00~F9F 对应显示器上的第 25 行
可见第 13 行的偏移范围为 780H~81FH —— 第 13 行的偏移起始值为 780H。
(2) 确定列地址
00H~01H 单元对应显示器上的第 1 列
02H~03H 单元对应显示器上的第 2 列
04H~05H 单元对应显示器上的第 3 列
...
40H~41H 单元对应显示器上的第 33 列
42H~43H 单元对应显示器上的第 34 列
44H~45H 单元对应显示器上的第 35 列
...
5CH~5DH 单元对应显示器上的第 47 列
5EH~5FH 单元对应显示器上的第 48 列
60H~61H 单元对应显示器上的第 49 列
...
9EH~9FH 单元对应显示器上的第 80 列
可见第 33~49 列对应的内存单元为 40H~60H。
综合上述行、列偏移量可知,第 13 行第 33~49 列的偏移量为 780H+40H~780H+60H=7C0H~7E0H。
由于显示器的显示缓冲区第 0 页地址为 B8000H~B8F9FH,因此可以认为第 0 页的段地址为 B8000H,偏移地址范围为 0000H~0F9FH,于是偏移量 7C0H~7E0H 的偏移地址范围为 07C0H~07E0H,所以第 13 行第 33~49 列的地址范围为 B87C0H~B87E0H。
以上两种计算方法所得结果相同。

二、确定所要显示字符串的 ASCII 码和属性特征码
所要显示的字符串为“welcome to masm!”,其属性为“绿色、绿底红色、白底蓝色”,即:字符串“welcome”为“绿色”,字符串“to”为“绿底红色”,字符串“masm!”为“白底蓝色”。

(一)字符串“welcome”
1. ASCII 码
(1) w:77H
(2) e:65H
(3) l:6CH
(4) c:63H
(5) o:6FH
(6) m:6DH
(7) e:65H
2. 属性特征码
绿色属性的编码为 00000010B,也就是 02H。

(二)字符串“to”
1. ASCII 码
(1) t:74H
(2) o:6FH
2. 属性特征码
绿底红色属性的编码为 00100100B,也就是 24H。

(三)字符串“masm!”
1. ASCII 码
(1) m:6DH
(2) a:61H
(3) s:73H
(4) m:6DH
(5) !:21H
2. 属性特征码
白底蓝色属性的编码为 01110001B,也就是 71H。

(四)空格字符
1. ASCII 码
' '(空格字符):20H
2. 属性特征码
黑底黑色的编码为 00000000B,也就是 00H。

综合上述所有字符串 ASCII 码和属性特征码如下:
(1) welcom
77 02 65 02 6C 02 63 02 6F 02 6D 02 65 02 20 00
(2) to
74 24 6F 24 20 00
(3) masm!
6D 71 61 71 73 71 6D 71 21 71
注意,上面“welcome”和“to”编码中最末位“20 00”显示的是空格字符。

三、设计汇编程序指令代码

注意,本实验程序代码在 Ubuntu 18.04 的 DOSBox-X 中运行时,不能实现改变显示缓冲区内存地址数据,但运行和编译、链接都不会报错,原因不明。在 Ubuntu 的 DOSBox 及 Windows 10 的 DOSBox-X 中都能正常运行。

1. 声明一个存放着需要显示在显示器上的字符串的数据段 dis,并读取其中的数据。
dis segment
  db  77h,02h,65h,02h,6Ch 02h,63h,02h,6Fh,02h,6Dh,02h 65h,02h,20h,00h
  db  74h,24h,6Fh,24h,20h,00h
  db  6Dh,71h,61h,71h,73h,71h,6Dh,71h,21h,71h
dis ends
mov ax,dis
mov ds,ax
mov bx,0

2. 确定显示缓冲区目标段地址为 B8000h 和起始偏移地址 07C0h。
mov ax,0B800h
mov es,ax
mov si,07C0h

3. 经 32 次循环,将 dis 段中的数据逐个读入到显示缓冲区目标地址(16 个字符占 32 个字节,一次传送 1 个字节数据)。
        mov cx,32
s:        mov al,[bx]
        mov es:[si],al
        inc bx
        inc si
        loop s

4. 将上面的代码组合成完整的程序代码。

assume cs:code,ds:dis

dis segment
  db  77h,02h,65h,02h,6Ch 02h,63h,02h,6Fh,02h,6Dh,02h 65h,02h,20h,00h
  db  74h,24h,6Fh,24h,20h,00h
  db  6Dh,71h,61h,71h,73h,71h,6Dh,71h,21h,71h
dis ends

code segment

start:        mov ax,dis
        mov ds,ax
        mov bx,0
        mov ax,0B800h
        mov es,ax
        mov si,07C0h

        mov cx,32
s:        mov al,[bx]
        mov es:[si],al
        inc bx
        inc si
        loop s

        mov ax,4c00h
        int 21h

code ends

end start

上述代码实现了显示一行字符串"welcome to masm!",其中"welcome"属性为绿色,"to"属性为绿底红色,"masm!"属性为白底蓝色。

如果按程序功能的第 2 种理解,则只需将显示缓冲区内存地址由第 13 行改为第 12、13、14 行,即:
偏移 6E0~77F 对应显示器上的第 12 行
偏移 780~81F 对应显示器上的第 13 行
偏移 820~8BF 对应显示器上的第 14 行
列不变,仍为 40H~60H;其偏移量由780H+40H~780H+60H=7C0H~7E0H 改为:
第 12 行:6E0H+40H~6E0H+60H=720H~740H
第 13 行:780H+40H~780H+60H=7C0H~7E0H
第 14 行:820H+40H~820H+60H=860H~880H
同样地,由于显示器的显示缓冲区第 0 页地址为 B8000H~B8F9FH,因此第 0 页的段地址为 B8000H,偏移地址范围为 0000H~0F9FH,于是上述偏移量的偏移地址范围为:
第 12 行:0720H~0740H
第 13 行:07C0H~07E0H
第 14 行:0860H~0880H
所以第 12、13、14 行第 33~49 列的地址范围分别为:
第 12 行:B8720H~B8740H
第 13 行:B87C0H~B87E0H
第 14 行:B8860H~B8880H

根据上述情况,可以有两种编程方法,代码如下:

1. 程序运行过程中按行显示字符串,即每显示完一行字符串,再显示下一行。
运行本程序时,如果直接输入程序名 EXE 文件,将直接显示全部的运行结果在显示器上;如果通过 Debug 来运行本程序,则应该使用 Debug 的 G 命令运行,这要分两次运行:
(1) 输入 loop s1 指令的后一条指令的 IP 作为 G 命令的参数来运行,即可显示第 1 行字符串。
(2) 输入 loop s0 指令的后一条指令的 IP 作为 G 命令的参数来运行,即可同时显示后面的两行字符串。
或者执行上述 (1) 的 G 命令后,再使用 T 命令执行一次程序,接着再重使用 (1) 所用的 G 命令执行,如此反复执行,即可逐行显示三行字符串。

assume cs:code,ds:dis

dis segment
  db  'welcome to masm!'
  db  02h,24h,71h
dis ends

code segment

start:        mov ax,dis
                mov ds,ax
                mov di,16
                mov ax,0B800h
                mov es,ax
                mov si,0720h

                mov cx,3
s0:                mov dx,cx
                mov bx,0
                mov bp,0
                mov cx,16

s1:                mov al,[bx]
                mov es:[bp+si],al
                inc bp
                mov al,[di]
                mov es:[bp+si],al
                inc bx
                inc bp
                loop s1

                add si,0A0h
                inc di
                mov cx,dx
                loop s0

                mov ax,4c00h
                int 21h

code ends

end start

2. 程序运行过程中是按列(区别于上面按行显示的程序)显示字符串,即每显示完一列字符,再显示下一列。
运行本程序时,如果直接输入程序名 EXE 文件,将直接显示全部的运行结果在显示器上;如果通过 Debug 来运行本程序,则应该使用 Debug 的 G 命令运行,这要分两次运行:
(1) 输入 loop s1 指令的后一条指令的 IP 作为 G 命令的参数来运行,即可显示第 1 列字符。
(2) 输入 loop s0 指令的后一条指令的 IP 作为 G 命令的参数来运行,即可同时显示所有剩余的字符串。
或者执行上述 (1) 的 G 命令后,再使用 T 命令执行一次程序,接着再重使用 (1) 所用的 G 命令执行,如此反复执行,即可逐列显示所有字符串。

assume cs:code,ds:dis

dis segment
        db        'welcome to masm!'
        db        02h,24h,71h
dis ends

code segment

start:        mov ax,dis
                mov ds,ax
                mov bx,0
                mov ax,0B800h
                mov es,ax
                mov bp,0

                mov cx,16
s0:                mov dx,cx
                mov cx,3
                mov di,16
                mov si,0720h

s1:                mov al,[bx]
                mov es:[bp+si],al
                inc bp
                mov al,[di]
                mov es:[bp+si],al
                add si,0A0h
                inc di
                dec bp
                loop s1

                inc bx
                add bp,2
                mov cx,dx
                loop s0

                mov ax,4c00h
                int 21h

code ends

end start
评论次数(0)  |  浏览次数(110)  |  类型(课程实验) |  收藏此文  | 
 
 请输入验证码  (提示:点击验证码输入框,以获取验证码