8086汇编语言实现8253计时器对时且实时定点显示时间(全注释)

作为微机原理终极大作业的一部分,个人感觉这个模块是最难的,花了我一个晚上+一个上午。因为有计时中断,也没办法开调试,Debug难度上了一个新高度,而且有时代码一点没动,运行的结果还不一样,甚至会闪退卡死,就很迷,好在最后还是被我碰出来了。

题目要求如下:
在这里插入图片描述
输入时分秒处理好做,输出也好做,难点在于计时和定点显示,还有返回重新对时。

使用8253芯片做计时

首先先关闭计时器中断
在这里插入图片描述
然后设置中断向量,将08H中断服务地址更改为我们自己的代码段INT08H,该代码段是中断处理
在这里插入图片描述
对计时器分频10ms,设置控制字。注意需要在中断处理处再次分频100,才能做到1s一显示。因为AX最大65536,没法直接分频到1s
在这里插入图片描述
最后在收完键盘数据和显示完提示字符串之后,开启中断,自动计时显示
在这里插入图片描述
其中,DX是中断内部计时数,100分频。
参考文章:点这里

定点显示

使用BIOS显示服务中断10H控制光标位置,在定时器中断代码段INT08H中显示当前时间。
在输出字符前线记录光标信息入栈中:
在这里插入图片描述
在显示完数字后,设置光标位置回去
在这里插入图片描述
注意MOV BH,0这个代码必须要写上,之前参考一篇博客没有这个代码,我实际运行时光标根本没有移动,这个bug找了两个小时。

当然,每次显示完一次之后要对秒数加1,然后判断进位,相对简单,不再赘述。

键盘判断返回重新对时

这块是最迷的,到现在我也没搞明白,代码10分钟敲完,却Debug了一上午。

我的想法:AH=0BH的DOS中断检测键盘状态,若无输入则循环,若有输入,使用AH=08H的DOS中断检测键盘回显输入,判断是否为ESC,若是则跳出,若不是则重新对时。

现实:直接重新对时。

前面写的检测逻辑代码根本没用!

折腾了几个小时,没办法开调试模式-t,最后我试着把检测到的字符直接输出,发现是个ASCII码为252的一个字符,导致键盘什么操作也没有,系统却认为我按下键了。
在这里插入图片描述
就是红圈里的这个坏小子,甚至不是标准ASCII码

BUG找到了,写两行代码直接拍死:
在这里插入图片描述
如果是这个奇怪的字符,就接着循环等待键盘输入,如果不是,说明用户按下键盘了,就按照正常逻辑处理。

最终代码如下:

DATAS SEGMENT
    SINPUT DB 'Please enter hours, minutes, and seconds with spaces: $' 
    SOUTPUT DB 'Any key to again and ESC to return. Now time is : $' 
    BUF1 DB 20H
    	 DB  0
    	 DB 20H DUP(0)
    TIMEHOUR DW 23
    TIMEMINUTE DW 56
    TIMESECOND DW 45
    NUM DW ?
DATAS ENDS

STACKS SEGMENT PARA STACK
   DW 30H DUP(0)
STACKS ENDS

CODES SEGMENT

    ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
    MOV AX,DATAS
    MOV DS,AX
	
	MOV AL,11111101B;关定时器中断
	OUT 21H,AL;中断屏蔽寄存器数据传回去
	
	PUSH DS;保存数据段地址
	MOV AX,SEG INT08H;SEG取得标号的段地址
	MOV DS,AX;获得08H号中断的段地址,放在中断向量中
	MOV DX, OFFSET INT08H;获得08H号中断的偏移地址,放在中断向量中
	MOV AL,08H;设置中断号
	MOV AH,25h;设置中断向量
	int 21h;调用系统dos中断
	POP DS;恢复数据段地址
	
	MOV AL,00110110B;设置通道0的方式3
	OUT 43H,AL;输出控制字,43H是8253定时器芯片的控制寄存器地址
	MOV AX,11932;定时器的时钟频率为1.1931817MHz,计数初值=1193182/100,10ms中断一次
	OUT 40H,AL;40H为计数器地址
	MOV AL,AH;先输出低位,再输出高位
	OUT 40H,AL;40H为计数器地址
TIMING:
	;换行
	MOV DL,0DH;CR
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
 	MOV DL,0AH;LF
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
 	
    MOV AH,09H;显示字符串
    LEA DX,SINPUT;取段内偏移地址
    INT 21H;调用系统dos中断
   
    MOV AH,0AH;键盘输入到缓冲区
    LEA DX,BUF1;取段内偏移地址
    INT 21H;调用系统dos中断
    CALL INPUTTIME;调用子程序处理字符串
    
    MOV AH,09H;显示字符串
    LEA DX,SOUTPUT;取段内偏移地址
    INT 21H;调用系统dos中断
    
    MOV DX,100;初始化为0
    MOV AL,11111100B;开键盘和定时器中断
	OUT 21H,AL;中断屏蔽寄存器数据传回去

RE:    
    MOV AH,08H;无回显键盘输入到AL
    INT 21H;调用系统dos中断
    ;MOV AH,0;
    ;MOV NUM,AX;
    
    ;MOV AH,03H;获取光标位置信息
	;INT 10H;BIOS中断
	;ADD DL,10;
	;MOV BH,0;显示到第一页
	;MOV AH,02H;设置光标位置
	;INT 10H;BIOS中断
    ;CALL SHOWNUM
    CMP AL,27;按键是否ESC
    JE DO_ESC;是退出 
    CMP AL,252;按键是否是奇怪的那个字符
    JE RE;是奇怪的,循环
    
    ;MOV AH,0BH;检测键盘状态,有输入AL=00,无输入AL=FF
    ;INT 21H;调用系统dos中断
    ;CMP AL,0FFH;判断按键无输入
    ;JE RE;确实无输入,重复
 
    MOV AL,11111101B;关定时器中断
	OUT 21H,AL;中断屏蔽寄存器数据传回去
    JMP TIMING;不是就重新对时
DO_ESC:
	MOV AL,11111101B;关定时器中断
	OUT 21H,AL;中断屏蔽寄存器数据传回去
    ;退出代码
    MOV AH,4CH
    INT 21H
    
;这是一段子程序,用来输入数字
INPUTTIME PROC 
	;初始化
	MOV DX,0
	MOV BX,10
	MOV SI,2
	MOV NUM,0
	MOV AX,0
	MOV CX,0;
LOP:
    MOV AL,BUF1[SI];寄存器相对寻址,从缓冲区取一个字符
    CMP AL,0DH;是否是CR
    JE  FINAL;等于就跳转,JNE相反
    SUB AL,30H;减48,从ASCII码转数字
    CMP NUM,0;与0比较,相当于判断初始化
    JE  DO_DEAL;等于就跳转,JNE相反
    PUSH AX;当前数字压入栈中
    MOV AX,NUM;当前数送入运算寄存器中
    MUL BX ;隐含寻址,在AX中,相当于NUM乘以10
    MOV  NUM,AX;运算结果存进NUM中  
    POP AX;之前的数据弹出
DO_DEAL:
    ADD NUM,AX;加上之前的数据
    MOV AX,0;清零
    INC SI;自加1
    MOV AL,BUF1[SI];寄存器相对寻址,从缓冲区取一个字符
    CMP AL,' ';是否是空格
    JE  DO_SPACE;等于就跳转,JNE相反
    JMP LOP;跳转,处理下一个
DO_SPACE:
	INC SI;偏移量再自加1,跳过空格
	MOV DX,NUM;把NUM存起来
	MOV NUM,0;原先数清零
	INC CX;标志自加1
	CMP CX,1;与1比较,判断是不是时
    JE  HOUR;等于就跳转,JNE相反
    CMP CX,2;与2比较,判断是不是分
	JE MINUTE;等于就跳转,JNE相反
	JMP LOP;跳转,处理下一个
HOUR:
	MOV TIMEHOUR,DX;把时传送到变量
	JMP LOP;跳转,处理下一个
MINUTE:
	MOV TIMEMINUTE,DX;把分钟传送到变量
	JMP LOP;跳转,处理下一个
FINAL: 
	MOV DX,NUM;把NUM存起来
	MOV TIMESECOND,DX;把秒传送到变量
	;换行
	MOV DL,0DH;CR
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
 	MOV DL,0AH;LF
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
RET;子程序退出重置堆栈
INPUTTIME ENDP


;这是一段子程序,用来显示数字NUM
SHOWNUM PROC 
	;其实正常这里显示不到第一位,懒得改了
	MOV BL,100;%先除100
	MOV AX,NUM;送入运算寄存器
	DIV BL;除法,AX隐含寻址
	ADD AL,30H;商转换为ASCII码
	PUSH AX;将余数先压入栈中
	CMP AL,30H;看一看首位是不是0
	JE BITTWO;若是,则不显示直接跳转到下一个
	MOV DL,AL;传送字符
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
BITTWO:	
	POP AX;将余数取出
	
	;第二位
	MOV AL,AH;送入运算寄存器
	MOV AH,0;高位清零
	MOV BL,10;%再除以10
	DIV BL;除法,AX隐含寻址
	ADD AL,30H;商转换为ASCII码
	PUSH AX;将余数先压入栈中
	MOV DL,AL;传送字符
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
 	POP AX;将余数取出
 	
 	;第三位
 	ADD AH,30H;余数转换为ASCII码
 	MOV DL,AH;传送字符
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
 	
RET;子程序退出重置堆栈
SHOWNUM ENDP

INT08H PROC NEAR;08H计数器中断
 	;保护现场
 	PUSH AX
 	PUSH BX
 	PUSH CX
 	CMP DX,100;;计数达到100,执行一次,相当于1s一次
 	JGE COUNT;大于等于100就跳转
 	JMP ENDINT;没达到就结束中断响应
COUNT:
	MOV DX,0;计数值清零
	PUSH DX;保存计数值
 	MOV AH,03H;获取光标位置信息
	INT 10H;BIOS中断
	PUSH DX;将获得的信息压入栈中
	MOV AX,TIMEHOUR;传送指令
	MOV NUM,AX;传送指令
	CALL SHOWNUM;调用子程序显示数字
	MOV DL,':'
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
	MOV AX,TIMEMINUTE;传送指令
	MOV NUM,AX;传送指令
	CALL SHOWNUM;调用子程序显示数字
	MOV DL,':'
 	MOV AH,2;显示一个字符
 	INT 21H;调用系统dos中断
	MOV AX,TIMESECOND;传送指令
	MOV NUM,AX;传送指令
	INC AX;秒数自加1
	MOV TIMESECOND,AX;传送指令
	CALL SHOWNUM;调用子程序显示数字
	POP DX;将之前的光标信息从栈中取出
	MOV BH,0;显示到第一页
	MOV AH,02H;设置光标位置
	INT 10H;BIOS中断
	POP DX;
	
	;进位处理
	CMP TIMESECOND,60;大于等于60,进一位
	JGE SECONDCARRY;秒数进位
	JMP ENDINT;中断返回
SECONDCARRY:
	MOV TIMESECOND,0;清零
	INC TIMEMINUTE;分钟自加一
	CMP TIMEMINUTE,60;大于等于60,进一位
	JGE MINUTECARRY;分钟进位
	JMP ENDINT;中断返回
MINUTECARRY:
	MOV TIMEMINUTE,0;清零
	INC TIMEHOUR;小时自加一
	CMP TIMEHOUR,24;大于等于24,进一位
	JGE HOURCARRY;小时进位
	JMP ENDINT;中断返回
HOURCARRY:
	MOV TIMEHOUR,0;清零
	JMP ENDINT;中断返回	
	
ENDINT:	
 	;中断返回
 	MOV AL,20H;
 	OUT 20H,AL;发送中断结束指令
 	;恢复现场
 	POP AX
 	POP BX
 	POP CX
 	INC DX;计数加一
 	IRET;;中断返回
INT08H ENDP
CODES ENDS
    END START

在这里插入图片描述

  • 12
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
汇编语言定时器时钟程序的主要实现步骤如下: 1. 初始化:首先需要在程序中定义一个计数器,用于记录时钟的过去时间。可以使用寄存器或者内存变量来存储计数器的值。然后需要设置一个新的计时器中断,通过将计数器与中断触发阈值进行比较来实现时钟计时。 2. 配置中断向量表:在汇编语言程序中,需要配置中断向量表。中断向量表是一个包含中断处理程序入口地址的数据结构。可以通过修改中断向量表中的指针,将定时器中断与自定义的时钟处理程序连接起来。 3. 设置计时器参数:需要设置计时器的频率和定时时间计时器的频率决定了计时器的中断触发速率,而定时时间决定了时钟显示的精度。可以通过写入特定的值到计时器的控制寄存器来配置这些参数。 4. 编写中断处理程序:中断处理程序是一个能够在计时器中断触发时被调用的子程序。在中断处理程序中,需要更新计数器的值,并且将计数器的值转换成时钟显示格式(例如:HH:MM:SS)。 5. 链接和运行程序:最后,将汇编语言代码编译成可执行文件,并进行链接和运行。在程序运行期间,计时器将以设定的频率触发中断,并通过中断处理程序来更新时钟的显示。 通过以上步骤,就可以实现一个简单的汇编语言定时器时钟程序。可以根据需要对程序进行更复杂的功能和优化,例如添加闹钟功能、调整时钟显示格式等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值