键盘输入
1,前面讲过, int 9 中断例程负责对键盘输入进行处理,具体为:从 60h 端口读出扫描码,并将其转化为相应的ASCII码或状态信息,一起存储到内存的指定空间(键盘缓冲区或状态字节)中。
其中键盘缓冲区一共有16个字单元,可以存储15个按键的扫描码和对应的ASCII码。这是因为采用了循环队列来实现的,会有一个单元被浪费掉。
例如:当摁下 A、B、C、shift_A、A 时键盘缓冲区的状态如下:
当摁下 A 键时,int 9 例程从 60h 端口读取A键的通码;检测状态字节判断是否有控制键(Ctrl、Shift)按下,发现没有被按下时,将A键的扫描码 1eh 和对应的字母 ’a’ 的ASCII码 61h写入键盘缓冲区中。高位字节存储扫描码,低位字节存储ASCII码。
当摁下 B、C 键时进行类似的操作。
当摁下 Shift_A 键时,(1)当按下左 Shift 键时,发生键盘中断,int 9 中断例程接收左 Shift 键的通码,设置 0040:17 处的状态字节的第1位为1,表示左 Shift 键按下。(2)当按下 A 键时,int 9 例程从 60h 端口读出A键的通码;检测状态字节发现左 Shift 键按下,则将 A 键的扫描码 1Eh 和 Shift_A 对应的大写字母 ‘A’ 的ASCII码 41h 写入键盘缓冲区。(3)当松开左 Shift 键时,发生键盘中断,int 9 中断例程接收左 Shift 键的断码,设置0040:17处的状态字节的第1位为0,表示左 Shift 键松开。
最后再次按下 A 键时的操作同上。
5次按键的键盘缓冲区图示
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1, | 1E61 | |||||||||||||||
2, | 1E61 | 3062 | ||||||||||||||
3, | 1E61 | 3062 | 2E63 | |||||||||||||
4, | 1E61 | 3062 | 2E63 | 1E41 | ||||||||||||
5, | 1E61 | 3062 | 2E63 | 1E41 | 1E61 |
2,使用 int 16h 中断例程的 0 号子程序可以从键盘缓冲区中读取一个键盘输入,并将其从缓冲区中删除。具体步骤如下:
(1)检测键盘缓冲区中是否有数据。
(2)没有则继续做第1步。即一直在等待,等待有数据被输入到键盘缓冲区中。
(3)读取缓冲区中最早进入的键盘输入,从队头开始读取。
(4)将读取的扫描码送入 ah,ASCII码送入 al。
(5)将已读取的键盘输入从缓冲区中删除。
对上面的键盘缓冲区执行:
mov ah,0
int 16h
缓冲区中的结果为
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3062 | 2E63 | 1E41 | 1E61 |
ah 中的内容为 1eh,al 中的内容为 61h。
总结:int 9 和 int 16h 可以相互配合使用,int 9 是在有键按下的时候向键盘缓冲区中写入数据,int 16h 是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。当我们需要对某一个特定的按键实现一个特别的功能时,有两种方法,一种是通过 Int 9 中断例程在处理键盘输入时来实现;另一种是通过调用 int 16h 从键盘缓冲区中读取键盘输入来实现。后者的优点在于不需要重写编写 int 9 中断例程。
练习1
编写程序,接收用户的键盘输入,输入 ‘r’,将屏幕上的字符设置为红色;输入 ’g‘,将将屏幕上的字符设置为绿色;输入 ’b’,将屏幕上的字符设置为蓝色。
关于在屏幕上显示颜色的细节可参考 https://blog.csdn.net/Little_ant_/article/details/108227058
assume cs:code
code segment
;调用 16h 号中断例程的 0 号子程序来读取一个键盘输入。
start: mov ah,0
int 16h
;处理已经读取的键盘输入,ah中存放扫描码,al 中存放字符码(可直接来进行比较)。
我们用 ah 来保存颜色属性,01h表示蓝色,02h表示绿色,04h表示红色。
mov ah,1 ;初始设置颜色为蓝色,通过左移来改变颜色,这点需要注意。
cmp al,'r'
je red
cmp al,'g'
je green
cmp al,'b'
je blue
jmp short sret
red: shl ah,1
green: shl ah,1
blue: 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
练习2
编写字符串输入程序,需具备下面的功能。
(1)在输入的同时需要显示这个字符串。
(2)一般在输入回车符后,字符串输入结束。
(3)能够删除已经输入的字符。
因为需要显示字符串,故,子程序的参数为:(dh)、(dl) = 字符串在屏幕上显示的行、列位置;ds:si 指向字符串的存储空间,字符串以0为结尾符。
首先,我们采用栈的方式来管理字符串的存储空间,即字符串的存储空间实际上是一个字符栈。输入字符时,字符入栈;删除字符时,字符出栈;显示字符时,从栈底到栈顶来显示。其次,输入回车符后,可以在字符串中加入 0,表示字符串结束。最后,每次当有新的字符输入或删除字符时,都应该重新显示该字符串。
程序的处理过程如下:
1,调用 int 16h 读取键盘输入
2,如果是字符,进入字符栈,显示字符栈中的所有字符,继续执行 1。
3,如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符,继续执行1。
4,如果是回车键,向字符栈中压入 0,返回。
因为包含多次字符入栈、出栈和显示,我们将它们编写为子程序。
其中用(ah)=功能号,0 表示入栈、1 表示出栈、2 表示显示。用ds:si 指向字符栈空间。在入栈时,(al)= 入栈字符;出栈时,(al)= 出栈字符;显示时,(dh)、(dl)为屏幕上显示的行、列位置。
charstack: jmp short charstart
table dw charpush,charpop,charshow ;直接定址表
top dw 0 ;用数据标号top中的内容来表示栈顶,初始栈顶为0
charstart: push bx
push dx
push di
push es
cmp ah,2
ja sret ;功能号ah大于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 short sret ;字符入栈结束,返回
charpop: cmp top,0 ;若top为0,此时栈为空,返回
je sret
dec top
mov bx,top
mov al,[si+bx] ;al 保存出栈字符
jmp short sret
charshow: mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh
mov di,ax ;将(al)*(dh)的结果放在di中
add dl,dl
mov dh,0
add di,dx ;将最终的显示地址放在di中。即es:di作为目的地址。
mov bx,0
charshows: cmp bx,top
jne noempty
mov byte ptr es:[di],' ' ;字符栈为空时显示空格符;或在字符串末尾再显示一个空格符。
jmp short sret
noempty: mov al,[si+bx]
mov es:[di],al
mov byte ptr es:[di+2],' ' ;总是在字符串输出的末尾加上空格符
inc bx
add di,2
jmp charshows
sret: pop es
pop di
pop dx
pop bx
ret
编写完整的接受字符串输入的子程序如下:(注意:在显示栈中字符的时候,需要清除屏幕上一次显示的内容):
getstr: push ax
getstrs: mov ah,0
int 16h ;接收键盘输入,ah中放扫描码,al中放字符码
cmp al,20h
jb nochar ;ASCII码小于20h,说明不是字符
mov ah,0
call charstack ;存放在(al)中的字符入栈,ah表示调用0号子程序
mov ah,2
call charstack ;显示栈中的字符串
jmp short getstrs
nochar: cmp ah,0eh ;退格键的扫描码
je backspace
cmp ah,1ch ;回车键的扫描码
je enter
jmp short getstrs
backspace: mov ah,1
call charstack ;字符出栈
mov ah,2
call charstack ;显示栈中字符
jmp short getstrs
enter: mov al,0
mov ah,0
call charstack ;将0入栈
mov ah,2
call cahrstack ;显示栈中的字符
pop ax ;退出
ret