汇编语言实验 14:16h 中断

448 阅读6分钟

1. 预备知识

  1. 前面介绍了使用 9 号中断处理键盘输入,一般的键盘输入,在 CPU 执行完该中断例程后都放在键盘缓冲区中。键盘缓冲区有 16 个字单元,可以存储 15 个按键的扫描码和对应的 ASCII 码。

  2. int 16h 中断例程中包含的一个重要功能是从键盘缓冲区中读取一个输入,并将其从缓冲区中删除;如果键盘缓冲区没有内容,则循环等待,该功能的编号为 0。

mov ah,0
int 16h
;调用中断例程后,(ah)=扫描码、(al)=ASCII码

2. 实验任务 1:键盘控制字符颜色

编程,接受用户的键盘输入,输入 r 将屏幕上的字符设置为红色;输入 g 将屏幕上的字符设置为绿色;输入 b 将屏幕上的字符设置为蓝色。

2.1 实验分析

基于 int 16h 中断的 0 号功能从键盘读取输入,并根据扫描码判断当前输入内容,如果是执行内容则设置相应的颜色,否则直接退出程序。

cmp al,'r' 
je red 
cmp al,'g' 
je green 
cmp al,'b' 
je blue 
jmp short sret

在字符属性表中,RGB 分别对应最后三位,则可以通过移位运算改变待使用的颜色值:

mov ah,1
...
red:         ;移动一位AH,对应于G
    shl ah,1 
green:       ;移动两位AH,对应于R 
    shl ah,1 
blue:        ;不移动AH,对应于B 
    ...

整体代码为:

assume cs:code
code segment
start:
    mov ah,0
    int 16h		
    ;调用16h中断例程的0号功能从键盘缓冲区读取数据,如果为空则循环等待
    mov ah,1
    ;最后三位依次为RGB
    cmp al,'r'
    je red
    cmp al,'g'
    je green
    cmp al,'b'
    je blue
    jmp short sret
red:		;移动一位AH,对应于G
    shl ah,1
green:		;移动两位AH,对应于R
    shl ah,1
blue:		;不移动AH,对应于B
    mov bx,0b800h
    mov es,bx 
    mov bx,1
    mov cx,2000
s:
    and byte ptr es:[bx],11111000b
    or es:[bx],ah
    add bx,2
    loop s
sret:
    mov ax,4c00h
    int 21h
code ends
end start

2.2 实验结果

r:

g:

b:

3. 实验任务 2:字符串输入

编程,完成字符串输入程序,包含以下功能:(1)在输入的同时显示这个字符串;(2)在输入回车后,字符串输入结束;(3)删除已经输入的字符。

3.1 实验分析

3.1.1 整体子程序功能设计

;(ah)=功能号,0 表示入栈、1 表示出栈、2 表示显示
;ds:si 指向字符栈空间
;对于 0 号功能,(al)=入栈字符
;对于 1 号功能,(al)=栈中返回的字符
;对于 2 号功能,(dh)、(dl)=字符串在屏幕上显示的行和列
charstack:
    jmp short charstart    ;用于显示字符串
    table dw charpush,charpop,charshow

3.1.2 字符串的输入和删除

每个新输入的字符存储在原字符之后,而删除从字符串的末尾进行。字符串的插入和删除类似于栈后进先出的性质,因此考虑使用栈管理字符串的存储空间。基于子程序 charpush 将输入字符压入字符栈:

charpush:
    mov bx,top 
    mov [si][bx],al	;压入字符
    inc top 		;移动栈顶指针

基于子程序 charpop 从字符栈中删除字符:

charpop:
    cmp top,0
    je sret		;栈为空
    dec top 
    mov bx,top 
    mov al,[si][bx]	;出栈并赋值给AL

3.1.3 输入回车结束字符串输入

输入回车后,在字符串的末尾加入 0 表示输入结束。子程序 enter_k 用于完成在字符串的结尾插入空格,并结束字符串的输入:

enter_k:
    mov al,0		;空格的ASCII码,后续用于标识字符串结尾
    mov ah,0
    call charstack
    mov ah,2
    call charstack	;入栈空格并显示

3.1.4 输入的同时显示字符串

每次在输入或删除字符时,以栈底到栈顶的顺序显示所有字符。在输入字符时,结合 0 号功能和 2 号功能实现在输入字符时显示字符串:

getstrs:
    mov ah,0
    int 16h		;从键盘缓冲区读取字符,(ah)=扫描码、(al)=ASCII码
    mov ah,0
    call charstack
    mov ah,2
    call charstack 	;入栈字符并显示

在删除字符时,结合 1 号功能和 2 号功能实现在删除字符串时显示字符串:

backspace_k:
    mov ah,1
    call charstack
    mov ah,2
    call charstack	;出栈并显示 

3.1.5 子程序 charstart

上述子程序通过功能号和 charstack 完成相应的功能。在 charstack 中,首先调用 charstart 中,通过功能号确定相应子程序的入口地址。

charstart:
    cmp ah,2               ;0表示入栈,1表示出栈,2表示显示
    ja sret                ;大于2时直接退出
    mov bl,ah
    mov bh,0
    add bx,bx
    jmp word ptr table[bx] ;根据偏移跳转到对应子程序

3.1.6 字符串的显示

2 号功能显示字符串,基于 charshows 和 noemtpy 显示字符串:

charshows:
    cmp bx,top 
    jne noempty
    mov byte ptr es:[di],' '
    ;字符以\0结尾
    jmp sret
noempty:
    mov al,[si][bx]
    mov es:[di],al	;显示字符
    inc bx 
    add di,2		;偏移2个字节写字符
    jmp charshows	;显示字符串

3.1.7 整体设计

综合以上子程序的内容以及加上某些特殊处理,整体代码执行流程可用以下流程图表示:

整体代码:

;子程序:字符栈的入栈、出栈和显示
;参数说明:(ah)=功能号,0表示入栈,1表示出栈,2表示显示
;ds:si指向字符栈空间
;对于0号功能,(al)=入栈字符
;对于1号功能,(al)=返回的字符
;对于2号功能,(dh)、(dl)=字符串在屏幕上显示的行和列
assume cs:code
code segment
start:
    call getstr
    mov ax,4c00h
    int 21h
getstr:
    push ax 
getstrs:
    mov ah,0
    int 16h		;从键盘缓冲区读取字符,(ah)=扫描码、(al)=ASCII码
    cmp al,20h	
    jb nochar		;ASCII码小于20h时不为字符
    mov ah,0
    call charstack
    mov ah,2
    call charstack 	;入栈字符并显示
    jmp getstrs
nochar:
    cmp ah,0eh		;按下退格键
    je backspace_k
    cmp ah,1ch		;按下回车键
    je enter_k
    jmp getstrs
backspace_k:
    mov ah,1
    call charstack
    mov ah,2
    call charstack	;出栈并显示 
    jmp getstrs
enter_k:
    mov al,0		;空格的ASCII码,后续用于标识字符串结尾
    mov ah,0
    call charstack
    mov ah,2
    call charstack	;入栈空格并显示
    pop ax 
    ret
charstack:
    jmp short charstart
    table dw charpush,charpop,charshow
    top dw 0
charstart:
    push bx 
    push dx 
    push di 
    push es 
    cmp ah,2		;0表示入栈,1表示出栈,2表示显示
    ja sret		;大于2时直接退出
    mov bl,ah 
    mov bh,0
    add bx,bx 
    jmp word ptr table[bx]
    ;根据偏移找到对应子程序的偏移地址
charpush:
    mov bx,top 
    mov [si][bx],al	;压入字符
    inc top 		;移动栈顶指针
    jmp sret		
charpop:
    cmp top,0
    je sret		;栈为空
    dec top 
    mov bx,top 
    mov al,[si][bx]	;出栈并赋值给AL
    jmp sret
charshow:
    mov bx,0b800h
    mov es,bx	;ES指向显示缓冲区
    mov al,160
    mov ah,0
    mul dh 	;写入时的偏移,存放在AX
    mov di,ax 
    add dl,dl 
    mov dh,0
    add di,dx 
    mov bx,0
charshows:
    cmp bx,top 
    jne noempty
    mov byte ptr es:[di],' '
    ;字符以\0结尾
    jmp sret
noempty:
    mov al,[si][bx]
    mov es:[di],al	;显示字符
    inc bx 
    add di,2		;偏移2个字节写字符
    jmp charshows	;显示字符串
sret:
    pop es 
    pop di 
    pop dx 
    pop bx 
    ret
code ends
end start

3.2 实验结果

4. 总结

  1. 本文介绍了基于 int 16h 中断例程的 0 号功能从键盘缓冲区读取内容,如果缓冲区为空,则循环等待直到读取成功

  2. 参考:汇编语言/王爽著.——北京:清华大学出版社,2003