汇编语言:键盘中断
友情提示,本篇文章较长,如果仅需要代码可以点击此处下载源代码
一、实验目的
- 了解
Intel8086CPU
的中断处理功能以及IBM-PC
的中断结构 - 了解8259中断控制器的使用。
- 掌握键盘中断的编程,观察中断的执行情况。
二、实验任务
-
基本要求:每按下任意一个键就向CPU发出中断请求信号,该信号由8259的
IRQ1
引入,中断类型号为09
, CPU响应中断后转入执行KEYINTS
中断服务程序,并在屏幕上显示“OK!”,按下10次键后返回DOS。 -
附加功能
- 通过DOS系统功能调用的
25H
,35H
功能实现中断向量的设置和读取; - 在显示”OK!”的前面增加显示按键次数
- 按键10次后,不等25行太阳图标显示完,立即返回DOS;
- 修改显示字符的属性,如,红底白字,蓝底黄字……
- 通过DOS系统功能调用的
三、实验原理
键盘与主机是通过5芯螺旋形的电缆相连的,其中包括数据线、时钟线、复位线、+5v电源线和地线(电缆插入系统板后部的插座)
每当有键按下或释放时,键盘以串行方式向系统板的键盘接口电路传送数据,即扫描码。一个扫描码移位传送完,键盘接口电路便向主机发岀中断请求信号IRQ1
(中断类型码为09H
,此信号送到8259A产生中断请求。
CPU响应中断请求时,查中断向量表,从09H×4开始的连续四个单元中取出中断向量(IRQ1
中断服程序 KEYINTS
的入口地址指针),转去执行中断服务程序 KEYINTS
。
在键盘中断程序 KEYINTS
中,保护现场、开中断之后,就通过8255A
的PA口
(PA口
地址为60H
)读取键盘扫描码,接着从8255A
的PB口
(PB口
地址为61H
)的PB7
输出一个正脉冲(即PB7
先输出髙电平,再输出低电平),先输出的高电平信号反相之后控制键盘状态触发器的清零端,使IRQI
清零,撤消中断请求信号。再输出的低电平信号允许位寄存器输出数据,这样就为传递下一个键盘扫描码作好了准备。
以上内容是为了让大家对程序功能有一些基本认识,接下来开始正式编程
四、编写程序
首先先放几张程序运行时的动图
- 基本程序
- 附加任务二(在显示”OK!”的前面增加显示按键次数):
- 附加任务三(按键10次后,不等25行太阳图标显示完,立即返回DOS)
- 附加任务四(修改显示字符的属性,如,红底白字,蓝底黄字……)
看完程序运行动态图后,我们再通过程序流程图来观察程序是如何运行的。
通过流程图可以看出程序的基本结构,现在将其中几个重要的程序段单独呈现出来
延时程序
DELAY PROC ; 延时程序
PUSH CX
PUSH DX
MOV DX,36H
DL500:MOV CX, 0FFFFH
DL10MS:LOOP DL10MS
DEC DX
JNZ DL500
POP DX
POP CX
RET
DELAY ENDP
对于延时程序也可以使用我写的上一篇文章汇编语言:时钟实验中的延时程序,如果需要修改延时时间的话,直接修改DX
或者CX
的值即可
显示太阳字符
DISP1 PROC FAR ; 显示太阳
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV AH,15 ; 读当前显示状态
INT 10H
MOV AH,0 ; 设置显示方式
INT 10H
MOV DX,0 ; 行号为0,列号为0
REPT: MOV AH,2 ; 设置光标位置
INT 10H
MOV AL,0FH ; OFH一太阳图形的ASCII码
MOV CX,1 ; 显示字符个数
MOV AH,10 ; 写字符
INT 10H
CALL DELAY
SUB AL,AL
MOV AH,0 ; 清除原图形
INT 10H
INC DH ; 行号+1
ADD DL,2 ; 列号+1
CMP DH,25 ; 判断是否到25行,不等继续显示太阳,相等返回
JNE REPT
POP DX
POP CX
POP BX
POP AX
RET
DISP1 ENDP
显示OK!
DISP2 PROC FAR ; 显示OK
PUSH CX
PUSH BX
PUSH AX
MOV CX,3
NEXTC: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTC
POP AX
POP BX
POP CX
RET
DISP2 ENDP
中断程序
KEYINT PROC FAR
PUSH AX
PUSH SI
STI
IN AL,60H ; 通过8255A的PA口(PA口地址为60H)读取键盘扫描码
MOV AH,AL
IN AL,61H ; 从8255APB口(PB口地址为61H)的PB7输出一个正脉冲(即PB7先输出高电平,再输出低电平)
OR AL,80H ; PB7置1
OUT 61H,AL
AND AL,7FH
OUT 61H,AL ; PB7清零
TEST AH,80H ; 相等时代表键被释放,开中断,显示字符
JNE GO ; 不等,中断结束返回
STI
INC KEY
MOV SI,OFFSET BUF ; 初始化SI,在DISP2中不再对SI进行初始化
CALL DISP2
GO:MOV AL,20H
OUT 20H,AL
POP SI
POP AX
IRET
KEYINT ENDP
1. 基本程序
完整的代码如下:
STACK SEGMENT
DW 200H DUP(?)
STACK ENDS
DATA SEGMENT
KEY DB ? ; ?表示占用空间
BUF DB "OK!"
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,SS:STACK,DS:DATA
DELAY PROC ; 延时程序
PUSH CX
PUSH DX
MOV DX,36H
DL500:MOV CX, 0FFFFH
DL10MS:LOOP DL10MS
DEC DX
JNZ DL500
POP DX
POP CX
RET
DELAY ENDP
DELAYL PROC
PUSH CX
PUSH DX
MOV DX,02H
DL500L:MOV CX, 0FFFFH
DL10MSL:LOOP DL10MSL
DEC DX
JNZ DL500L
POP DX
POP CX
RET
DELAYL ENDP
DISP1 PROC FAR ; 显示太阳
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV AH,15 ; 读当前显示状态
INT 10H
MOV AH,0 ; 设置显示方式
INT 10H
MOV DX,0 ; 行号为0,列号为0
REPT: MOV AH,2 ; 设置光标位置
INT 10H
MOV AL,0FH ; OFH一太阳图形的ASCII码
MOV CX,1 ; 显示字符个数
MOV AH,10 ; 写字符
INT 10H
CALL DELAY
SUB AL,AL
MOV AH,0 ; 清除原图形
INT 10H
INC DH ; 行号+1
ADD DL,2 ; 列号+1
CMP DH,25 ; 判断是否到25行,不等继续显示太阳,相等返回
JNE REPT
POP DX
POP CX
POP BX
POP AX
RET
DISP1 ENDP
DISP2 PROC FAR ; 显示OK
PUSH CX
PUSH BX
PUSH AX
MOV CX,3
NEXTC: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTC
POP AX
POP BX
POP CX
RET
DISP2 ENDP
KEYINT PROC FAR
PUSH AX
PUSH SI
STI
IN AL,60H ; 通过8255A的PA口(PA口地址为60H)读取键盘扫描码
MOV AH,AL
IN AL,61H ; 从8255APB口(PB口地址为61H)的PB7输出一个正脉冲(即PB7先输出高电平,再输出低电平)
OR AL,80H ; PB7置1
OUT 61H,AL
AND AL,7FH
OUT 61H,AL ; PB7清零
TEST AH,80H ; 相等时代表键被释放,开中断,显示字符
JNE GO ; 不等,中断结束返回
STI
INC KEY
MOV SI,OFFSET BUF ; 初始化SI,在DISP2中不再对SI进行初始化
CALL DISP2
GO:MOV AL,20H
OUT 20H,AL
POP SI
POP AX
IRET
KEYINT ENDP
START:MOV AX,STACK
MOV SS,AX
MOV AX,DATA
MOV DS,AX
MOV AX,0 ; 将AX置0,后将其赋给ES,进行原09H中断向量的保存
MOV ES,AX
MOV AX,ES:[24H] ; 09H*4=24H
PUSH AX
MOV AX,ES:[26H] ;
PUSH AX
CLI ; 关中断,进行中断向量的设置
MOV AX,OFFSET KEYINT
MOV ES:[24H],AX
MOV AX,SEG KEYINT
MOV ES:[26H],AX
STI ; 同意下方程序接受中断
MOV KEY,0
AGAIN:CAll DISP1 ; 显示太阳
CMP KEY,10 ; 判断显示25行太阳后,是否已经按下了十次键
JB AGAIN
CLI ; 禁止下方程序中断发生,保护代码运行
POP AX
MOV ES:[26H],AX
POP AX
MOV ES:[24H],AX
STI ; 开中断
MOV AH,4CH
INT 21H
CODE ENDS
END START
完整程序中有一个程序段为DELAYL
,该程序段的功能也是延时,但是是在显示0K!
时使用的。
2. 附加任务1
通过DOS系统功能调用(INT 21H)的25H,35H功能实现中断向量的设置和读取
功能号(AH) | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
25H | 设置中断向量 | DS: 段地址 DX: 偏移地址 AL: 中断类型号 | |
35H | 获取中断向量 | AL: 中断类型号 | ES: 段地址 BX: 偏移地址 |
只需要修改START
中的设置和读取中断向量的程序段即可,具体修改如下:
START:MOV AX,STACK
MOV SS,AX
MOV AX,DATA
MOV DS,AX
; MOV AX,0 ; 将AX置0,后将其赋给ES,进行原09H中断向量的保存
; MOV ES,AX
; MOV AX,ES:[24H] ; 中断向量
; PUSH AX
; MOV AX,ES:[26H] ; 中断向量
; PUSH AX
MOV AH, 35H
MOV AL, 09H ; 使用DOS系统功能实现中断向量的读取
INT 21H
PUSH BX
PUSH ES
CLI ; 关中断,进行中断向量的设置
; MOV AX,OFFSET KEYINT
; MOV ES:[24H],AX
; MOV AX,SEG KEYINT
; MOV ES:[26H],AX
PUSH DS
MOV AX, SEG KEYINT ; 使用DOS系统功能实现中断向量的设置
MOV DS, AX
MOV DX, OFFSET KEYINT
MOV AH, 25H
MOV AL, 09H
INT 21H
pop DS
STI ; 同意下方程序接受中断
MOV KEY,0
AGAIN:CAll DISP1 ; 显示太阳
CMP KEY,10 ; 判断显示25行太阳后,是否已经按下了十次键
JB AGAIN
CLI ; 禁止下方程序中断发生,保护代码运行
POP DS
POP DX
MOV AH, 25H
MOV AL, 09H
INT 21H
STI ; 开中断
MOV AH,4CH
INT 21H
CODE ENDS
END START
3. 附加任务2
在显示
0K!
前面加上次数
实现该功能需要修改DISP2
的程序
DISP2 PROC FAR ; 显示OK
PUSH DX
PUSH CX
PUSH BX
PUSH AX
MOV DL, KEY
ADD DL, 30H
CMP DL, 3AH ; 比较DL和3AH,即确定DL是不是10
JAE EQUALTEN
MOV AH,2
INT 21H
MOV CX,3
NEXTC: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTC
POP AX
POP BX
POP CX
POP DX
RET
EQUALTEN: MOV DL, 31H ; 当KEY>=10时
MOV AH, 2
INT 21H
MOV DL, KEY
ADD DL, 26H ; 显示KEY的个位数
MOV AH,2
INT 21H
MOV CX, 3
NEXTCE: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTCE
POP AX
POP BX
POP CX
POP DX
RET
DISP2 ENDP
附加功能2中比较棘手的是需要在按键次数为10时对其进行正确显示,需要将其分为1和0来显示,这里我写的程序其实不够好,因为当按键次数超过20次后,又会出现字符显示错误的情况,不过由于只需要按下10次键盘就停止程序,也就没过多研究了。
4. 附加任务3
按键10次后,直接结束程序
这个功能容易实现,在附加任务2中我们在按键10次时跳转到EQUALTEN
标号处,只需要在结尾加上返回DOS系统的命令即可
DISP2 PROC FAR ; 显示OK
PUSH DX
PUSH CX
PUSH BX
PUSH AX
MOV DL, KEY
ADD DL, 30H
CMP DL, 3AH
JAE EQUALTEN
MOV AH,2
INT 21H
MOV CX,3
NEXTC: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTC
POP AX
POP BX
POP CX
POP DX
RET
EQUALTEN: MOV DL, 31h ; 当KEY==10时
MOV AH, 2
INT 21H
MOV DL, KEY
ADD DL, 26H ; 显示KEY的个位数
MOV AH,2
INT 21H
MOV CX, 3
NEXTCE: LODSB ; 字符串"OK!"在数据段中定义,AL<—[SI]
MOV AH, 0EH ; 写字符, 并移动鼠标
MOV BX,01
INT 10H
CALL DELAYL
LOOP NEXTCE
POP AX
POP BX
POP CX
POP DX ; 此时直接结束程序
CLI ; 禁止下方程序中断发生,保护代码运行
POP DS
POP DX
MOV AH, 25H
MOV AL, 09H
INT 21H
STI ; 开中断
MOV AH,4CH
INT 21H
DISP2 ENDP
与附加任务2中的代码进行比对后可以发现在DISP2
的结尾删除了RET
这一命令,加上了返回DOS系统的命令
5. 附加任务4
修改显示字符的属性,如红底白字,蓝底黄字…
个人感觉这个附加任务并不简单,也有可能是我理解错了,写得比较复杂。
首先先定义了一个宏过程(macro procedure),代码如下:
OUTPUTOK MACRO STR ; 定义了一个宏,STR 为参数
MOV AL, STR
MOV BH, 0
MOV BL, COLOROK
MOV CX, 1
MOV AH, 9
INT 10H
CALL Cursor
CALL DELAYL
ENDM
这里有一个子程序Cursor
是之前没有接触过的,它的功能是为了移动光标,防止字符之间相互覆盖,这一子程序中通过BIOS系统功能调用(INT 10H)的02H,03H功能实现光标位置的设置和读取
功能号(AH) | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
02H | 设置光标位置 | BH: 显示页码 DH: 行 DL: 列 | |
03H | 获取光标位置 | BH: 显示页码 | DH: 行 DL: 列 |
具体程序如下:
Cursor PROC ; 将光标向后移动一位
PUSH DX
PUSH CX
PUSH BX
PUSH AX
MOV AH, 03H
INT 10H
ADD DL, 01H ; 将列数加一后设置光标位置
MOV AH, 02H
INT 10H
POP AX
POP BX
POP CX
POP DX
RET
Cursor ENDP
最后就要修改显示的函数DISP1
和DISP2
了,具体代码如下
DISP1 PROC FAR ; 显示太阳
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV AH,15 ; 读当前显示状态
INT 10H
MOV AH,0 ; 设置显示方式
INT 10H
MOV DX,0 ; 行号为0,列号为0
REPT: MOV AH,2 ; 设置光标位置
INT 10H
MOV AL,0FH ; OFH一太阳图形的ASCII码
MOV BH, 0
MOV BL, COLORSUN
MOV CX,1 ; 显示字符个数
MOV AH,09 ; 写字符
INT 10H
CALL DELAY
SUB AL,AL
MOV AH,0 ; 清除原图形
INT 10H
INC DH ; 行号+1
ADD DL,2 ; 列号+1
CMP DH,25 ; 判断是否到25行,不等继续显示太阳,相等返回
JNE REPT
POP DX
POP CX
POP BX
POP AX
RET
DISP1 ENDP
DISP2 PROC FAR ; 显示OK
PUSH DX
PUSH CX
PUSH BX
PUSH AX
MOV DL, KEY
ADD DL, 30H
CMP DL, 3AH
JAE EQUALTEN
MOV BH, 0
MOV AL, DL
MOV BL, COLORNUM
MOV CX, 1
MOV AH,9
INT 10H
CAll Cursor
OUTPUTOK 'O'
OUTPUTOK 'K'
OUTPUTOK '!'
POP AX
POP BX
POP CX
POP DX
RET
EQUALTEN: MOV CX, 1
MOV AL, 31h ; 当KEY==10时
MOV AH, 09
MOV BL, COLORNUM
MOV BH, 0
INT 10H
CAll Cursor
MOV AL, KEY
ADD AL, 26H ; 显示KEY的个位数
MOV AH,09
MOV BL, COLORNUM
MOV BH, 0
INT 10H
CALL Cursor
OUTPUTOK 'O'
OUTPUTOK 'K'
OUTPUTOK '!'
POP AX
POP BX
POP CX
POP DX ; 此时直接结束程序
CLI ; 禁止下方程序中断发生,保护代码运行
POP DS
POP DX
mov ah, 25H
mov al, 09H
int 21H
STI ; 开中断
MOV AH,4CH
INT 21H
DISP2 ENDP
显示带颜色的字符需要使用BIOS系统(INT 21H)功能号为9的功能
功能号(AH) | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
09H | 在当前光标处 写字符和属性 | BH: 页号 AL: 显示字符的ASCⅡ码 BL: 属性 CX: 显示次数 |
在程序中我重写了显示OK!
的程序,因为如果使用原来的程序,OK!将很难显示属性(当然有可能是我的水平不够,还有更加简单的方法)
在上面的程序和宏过程中大家可能发现了COLOROK
、COLORSUN
、COLORNUM
这三个参数,这三个参数需要提前在数据段中定义:
DATA SEGMENT
KEY DB ? ; ?表示占用空间
COLORSUN DB 42H ; 太阳字符的属性
COLORNUM DB 57H ; 按键次数的属性
COLOROK DB 32H ; OK!的属性
DATA ENDS
对于字符属性,高四位代表文字颜色,低四位代表背景颜色。颜色对照表如下:
十六进制 | 颜色 | 十六进制 | 颜色 |
---|---|---|---|
0 | 黑色 █ | 8 | 灰色 █ |
1 | 蓝色 █ | 9 | 淡蓝色 █ |
2 | 绿色 █ | A | 淡绿色 █ |
3 | 青色 █ | B | 淡青色 █ |
4 | 红色 █ | C | 淡红色 █ |
5 | 紫红色 █ | D | 淡紫红色 █ |
6 | 棕色 █ | E | 淡棕色 █ |
7 | 银色█ | F | 白色█ |
如42H
即文字颜色为红色,背景颜色为绿色
五、结语
本文较长,感谢大家可以看到最后,如果想要源代码,可以在文章开头下载,也可以在百度网盘 上下载,提取码:xhmc