汇编语言程序:时钟显示实验
一、实验目的
- 熟悉系统功能调用INT 21H的有关功能。
- 编写时钟程序
二、实验任务
执行时钟程序时,屏幕上显示提示符“:” ,由键盘输入当前时、分和秒值,即 XX:XX:XX↙,随即显示时间,并不停地计时。
当有键按下时,立即停止计时,返回 DOS。
三、实验原理
首先利用系统调用 INT 21H 中 02H 功能,显示一个提示符“:”,要求用户从键盘输入时钟初值(即当前时间),其输入格式为 XX(时):XX(分):XX(秒) ↙(回车)。然后利用 0AH 功能调用接收从键盘输入的字符串,并将接收的字符串存入到缓冲区。
在利用 0AH 功能调用前要设置一个缓冲区,在调用时,用 DX 作为输入缓冲区的指针,由键盘输入的字符存入该缓冲区,直至遇到回车键为止。
程序中把输入的‘时’、‘分’、‘秒’初值分别从输入缓冲区中取出,各自放在一个寄存器中,然后调用一个延时 1 秒钟的子程序,每过 1 秒使秒值增1,然后检查是否已为 60 秒,若不是则转显示;若是,则使秒值为 0,分值增 1,再检查是否已为 60 分,若不是则转显示,若是,则使分值为 0,时值增 1,接着检查时值是否为 24 小时,若不是则转显示,若是,则使时值为 0,接着也是转显示。
只要有键按下,则程序停止运行,返回 DOS。
四、实验内容
最近在学习微机原理,同时就要学习有关汇编的相关知识,上面的这些内容是我从学校的实验任务书上直接复制粘贴下来的。这次我们需要做一个时钟实验的汇编程序,虽然实验任务书上有答案,但是很可惜答案是错的,所以我就耐着性子将所给的答案看了一遍,并改正了程序,最后使程序得以正常运行。
先放程序运行时的图片:
程序执行时,按下键盘可以发现计时终止。
放代码之前让我们先看一下这段程序的流程图
根据流程图可以编写以下程序
STACK SEGMENT
STACK ENDS
DATA SEGMENT
BUF DB 11 ; BUF即为缓存区
DB ?
DB 10 DUP(?)
DATA ENDS
EXTRA SEGMENT
EXTRA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:EXTRA,SS:STACK
START:
MOV AX,DATA
MOV DS,AX
MOV DL, ':' ; 显示:
MOV AH,2
INT 21H
MOV DX,OFFSET BUF ; 以DX为缓存区指针
MOV AH, 0AH ; 接受用户输入的时间
INT 21H
MOV BX,OFFSET BUF+2 ; 将用户输入的时间的首地址赋给BX
MOV AL,[BX] ;
AND AL,0FH ; AL的高四位置0,0-9的ASCI为30H-39H,高四位置0后,AL中的值即为一般所使用的“数字”,如31H会变成01H,即变成非压缩BCD码
MOV [BX],AL ;
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX ; 两次INC BX 是为了跳过 :
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
MOV BX,OFFSET BUF+2
CALL TOBCD ; 跳转至TOBCD
MOV CH,AL ; 将AL中的值存入CH中,此处AL中的值为小时
ADD BX,3
CALL TOBCD
MOV DH,AL ; 将AL中的值存入DH中,此处AL中的值为分钟
ADD BX,3
CALL TOBCD
MOV DL,AL ; 将AL中的值存入DL中,此处AL中的值为秒
AGAIN: CALL DELAY ; 进入延时程序
MOV AL, DL ; 将秒移入AL中
ADD AL,1 ; 秒+1
DAA ; 调整
CMP AL,60H
JNE SECOND ; 秒不满60跳转
MOV DL,0 ; 当秒达到60时,将秒数置零
MOV AL,DH
ADD AL,1 ; 分钟+1
DAA ; 调整
CMP AL, 60H
JNE MINUTE ; 分钟不满60跳转
MOV DH, 0 ; 分钟满60,将分钟置零
MOV AL, CH
ADD AL, 1
DAA
CMP AL, 24H
JNE HOUR ; 小时不满24跳转
MOV CH, 0 ; 将小时置零
JMP DISPLAY ; 如果不需要JMP SECOND/MINUTE/HOUR 则跳转DISPLAY
SECOND: MOV DL,AL ; 将AL中的值存入DL(秒)中,此处AL中的值为+1后的秒
JMP DISPLAY
MINUTE: MOV DH, AL ; DH(分)
JMP DISPLAY
HOUR: MOV CH, AL ; CH(小时)
JMP DISPLAY
DISPLAY: MOV BX,OFFSET BUF ; 显示程序,用于将目前所存储的时/分/秒显示
MOV AL,0DH ; 添加回车
MOV [BX],AL
INC BX
MOV AL,0AH ;添加换行
MOV [BX],AL
INC BX
MOV AL,CH ; 将CH中的小时值移入AL中
CALL TRAN
INC BX
MOV AL, ':'
MOV [BX],AL
INC BX
MOV AL,DH ; 将DH中的分钟值移入AL中
CALL TRAN
INC BX
MOV AL,':'
MOV [BX], AL
INC BX
MOV AL,DL ; 将DL中的秒值移入AL中
CALL TRAN
INC BX
MOV AL,'$'
MOV [BX], AL ; 以'$'结尾
PUSH BX ; 入栈保护
PUSH CX
PUSH DX
MOV DX, OFFSET BUF
MOV AH,9
INT 21H
MOV AH,06 ; 判断是否有键落下
MOV DL, 0FFH
INT 21H
POP DX
POP CX
POP BX
JNZ BREAK ; 若有键落下 跳转终止程序
JMP AGAIN ; 无键落下,继续程序
BREAK: MOV AH,4CH ; 终止程序,返回DOS
INT 21H
TOBCD PROC ; TOBCD BEGIN
MOV AL,[BX] ; 该段程序是为了将DS段中的连续两个非压缩BCD码转化为一个压缩BCD码
SHL AL,1 ; SHL AL, 4 可以将[BX]中的值转到高四位
SHL AL,1
SHL AL,1
SHL AL,1
OR AL,[BX+1] ; 将[BX+1]中的值转到低四位,
RET ; 这段程序的目的即将用户输入的秒/分钟/小时变成压缩BCD码存入AL中
TOBCD ENDP ; TOBCD END
TRAN PROC ; 将压缩BCD码转换成ASCⅡ码并存入存储器,即将用压缩BCD码存储的秒/分/时转成ASCⅡ码的形式
MOV CL,AL
SHR AL,1
SHR AL,1
SHR AL,1
SHR AL,1
OR AL,30H
MOV [BX],AL
INC BX
MOV AL,CL
AND AL,0FH
OR AL,30H
MOV [BX],AL
RET
TRAN ENDP ; TRAN程序段结束
DELAY PROC ; 延时程序段Begin
PUSH CX
PUSH AX
PUSH BX
MOV BX, 2FH ; 请注意不同电脑的主频的不同,所以给BX的值也会不相同,请根据实际情况进行修改
CIR: MOV CX,0FFFFH
GOON: LOOP GOON
dec BX
JNZ CIR
POP BX
POP AX
POP CX
RET
DELAY ENDP ;延时程序段END
CODE ENDS
END START
上述程序虽然较长,但是和流程图结合来看的话,如果有一定汇编语言基础的话相信时比较容易理解的。
五、改进
1. 取消换行输出,直接在原位置输出时间
这个其实很简单,先看效果图:
需要改动的就是原本的DISPLAY
程序段的内容
DISPLAY: MOV BX,OFFSET BUF ; 显示程序,用于将目前所存储的时/分/秒显示
MOV AL,0DH ; 添加回车
MOV [BX],AL
INC BX
MOV [BX],AL
INC BX
MOV AL,CH ; 将CH中的小时值移入AL中
CALL TRAN
INC BX
MOV AL, ':'
MOV [BX],AL
INC BX
MOV AL,DH ; 将DH中的分钟值移入AL中
CALL TRAN
INC BX
MOV AL,':'
MOV [BX], AL
INC BX
MOV AL,DL ; 将DL中的秒值移入AL中
CALL TRAN
INC BX
MOV AL,'$'
MOV [BX], AL ; 以'$'结尾
只需要删除原本DISPALY
程序段中的MOV AL,0AH ;添加换行
即可。
2. 提供错误提示
这个稍微有点复杂,目前只提供两种错误的提示,即输入不是数字或是”:”的错误,还有输入时间值过大的错误如输入了24:60:60
, 还是先看效果图:
从效果图可以看出当输入的数字过大时会出现TIME ERROR
的错误,当输入其他字符时,会出现Character ERROR
的错误,当然直接按下回车键,也会被判定为Character ERROR
为了实现效果图所展现的功能,首先需要在DATA SEGMENT
中添加以下语句
ERRORINFO1 DB 'Character ERROR', 0DH, 0AH, '$'
ERRORINFO2 DB 'TIME ERROR', 0DH, 0AH,'$'
在代码段中语句应该修改为
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:EXTRA,SS:STACK
START:
MOV AX,DATA
MOV DS,AX
MOV DL, ':'
MOV AH,2
INT 21H
MOV DX,OFFSET BUF
MOV AH, 0AH
INT 21H
JMP ERRCH ; 当用户输入时间后,进行检验,判断是否有错误的字符
ERRCH: LEA BX, BUF+2
PUSH CX
MOV CL, 8
MOV AH,0
CIRCLE:MOV AL, [BX] ;循环判断每一个字符
CALL JUDGECH ; 存在错误AH置1
INC BX
LOOP CIRCLE
CMP AH, 1
JE CHERR ; AH=1 即出错,需打印错误信息,并重新输入
MOV BX,OFFSET BUF+2
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
INC BX
MOV AL,[BX]
AND AL,0FH
MOV [BX],AL
MOV BX,OFFSET BUF+2
CALL TOBCD ; 将数字转化为BCD码
MOV CH,AL ; 将AL中的值存入CH中,此处AL中的值为小时
ADD BX,3
CALL TOBCD
MOV DH,AL ; 将AL中的值存入DH中,此处AL中的值为分钟
ADD BX,3
CALL TOBCD
MOV DL,AL ; 将AL中的值存入DL中,此处AL中的值为秒
MOV AH, 0
CALL JUDGEOVER ; 进入判断时间值是否错误的程序
CMP AH,1 ; 同样当出错时,AH置1
JE TIMEERR
AGAIN: CALL DELAY ; 进入延时程序
MOV AL, DL ; 将秒移入AL中
ADD AL,1 ; 秒+1
DAA ; 调整
CMP AL,60H
JNE SECOND
MOV DL,0 ; 当秒达到60时,将秒数置零
MOV AL,DH
ADD AL,1 ; 分钟+1
DAA ; 调整
CMP AL, 60H
JNE MINUTE
MOV DH, 0 ; 将分钟置零
MOV AL, CH
ADD AL, 1
DAA
CMP AL, 24H
JNE HOUR
MOV CH, 0 ; 将小时置零
JMP DISPLAY ; 如果不需要JMP SECOND/MINUTE/HOUR 则跳转DISPLAY
CHERR: lea dx, ERRORINFO1
mov ah,9
int 21h
jmp start
TIMEERR: LEA DX, ERRORINFO2
MOV AH,9
INT 21H
JMP START
SECOND: MOV DL,AL ; 将AL中的值存入DL中,此处AL中的值为+1后的秒
JMP DISPLAY
MINUTE: MOV DH, AL
JMP DISPLAY
HOUR: MOV CH, AL
JMP DISPLAY
DISPLAY: MOV BX,OFFSET BUF
MOV AL,0DH ; 添加回车
MOV [BX],AL
INC BX
MOV AL, ' '
MOV [BX], AL
INC BX
MOV AL,CH
CALL TRAN
INC BX
MOV AL, ':'
MOV [BX],AL
INC BX
MOV AL,DH
CALL TRAN
INC BX
MOV AL,':'
MOV [BX], AL
INC BX
MOV AL,DL
CALL TRAN
INC BX
MOV AL,'$'
MOV [BX], AL
PUSH BX
PUSH CX
PUSH DX
MOV DX, OFFSET BUF
MOV AH,9
INT 21H
MOV AH,06
MOV DL, 0FFH
INT 21H
POP DX
POP CX
POP BX
JNZ BREAK ; 跳转终止程序
JMP AGAIN ;
BREAK: MOV AH,4CH ; 返回DOS
INT 21H
TOBCD PROC ; TOBCD BEGIN
MOV AL,[BX] ; 该段程序是为了将DS段中的连续两个非压缩BCD码转化为一个压缩BCD码
SHL AL,1 ; SHL AL, 4 可以将[BX]中的值转到高四位
SHL AL,1
SHL AL,1
SHL AL,1
OR AL,[BX+1] ; 将[BX+1]中的值转到低四位
RET
TOBCD ENDP ; TOBCD END
TRAN PROC ; 将压缩BCD码转换成ASCⅡ码并存入存储器,即将用压缩BCD码存储的秒/分/时转成ASCⅡ码的形式
MOV CL,AL
SHR AL,1
SHR AL,1
SHR AL,1
SHR AL,1
OR AL,30H
MOV [BX],AL
INC BX
MOV AL,CL
AND AL,0FH
OR AL,30H
MOV [BX],AL
RET
TRAN ENDP
DELAY PROC ; 延时程序段Begin
PUSH CX
PUSH AX
PUSH BX
MOV BX, 2FH
CIR: MOV CX,0FFFFH
GOON: LOOP GOON
dec BX
JNZ CIR
POP BX
POP AX
POP CX
RET
DELAY ENDP ;延时程序段END
JUDGECH PROC ; 判断是否出现字符错误
CMP AL, 30H
JB lower ; 小于30H跳转
CMP AL, 39H
JA higher ; 大于39H跳转
RET
lower: CMP AL, ':'
JE EQUAL ; 等于':'跳转
MOV AH, 1
RET
higher:CMP AL, ':'
JE EQUAL
MOV AH,1
RET
EQUAL: RET
JUDGECH ENDP
JUDGEOVER PROC ; 判断时间值是否出错
CMP CH, 24H
JAE AE
CMP DH, 60H
JAE AE
CMP DL, 60H
JAE AE
RET
AE:MOV AH,1 ; 任何一个时间值出错,都将跳转至AE处
RET
JUDGEOVER ENDP
CODE ENDS
END START
可以看出与原本的程序相比,增加了一些关于错误判断的程序。
3. 利用DOS系统功能实现延时一秒
主要利用DOS系统中的读取时间功能(INT 21H)实现延时
中断类型号(AH) | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
2CH | 读取时间 | 无 | (CX:DX)=时间 CH=小时(0-23), CL = 分(0-59) DH=秒(0-59), CL = 百分秒(0-99) |
延时程序修改如下:
DELAY PROC ; 延时程序段Begin
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV AH, 2CH
int 21h
ADD DH, 1H
CMP DH, 3CH ; 与60比较,如果不等于60,可以进行下一步
JNE NOTEQU
mov dh, 00h ; 如果相等需要将DH置0
NOTEQU: MOV BL, DH ; 无论是否相等都会执行的程序可以写在跳转的语句中。
COMPARETIME: MOV AH, 2CH
INT 21H
CMP BL, DH
JNE COMPARETIME
pop dx
pop cx
pop bx
pop ax
RET
DELAY ENDP ;延时程序段END
六、结语
用汇编语言编写程序时,存在很多比较麻烦的地方,如在判断时,需要分成多步进行操作等。但是在汇编中直接对数据进行处理的感觉还是不同于高级语言,你能够较清楚地了解数据在哪里,如何进行使用。