汇编课程设计——时钟

任务二

一、任务内容 

要求:

  1. 在屏幕特定位置显示当前系统日期和时间;
  2. 显示的日期和时间要求即时更新,即设计为跳动的时钟;
  3. 可自定义电子钟的显示区域、显示位置、背景和前景色或以图形化方式显示;
  4. 当按下某个键,如“Q”时,程序返回DOS,数字钟消失。

提高:

        再设计一款倒计时,倒计时时间由用户设定(如30分钟),时间到有提示(如时间背景、前景色变化或文本提示等)。

二、设计思路  

 三、实现代码(该部分代码来自于一个博主文章)

//文件名2.asm

;===========
;数据段data
;===========
data 	segment
	count	dw	1
	info	db	'Current time is:',0dh,0ah,'$'		;提示语
	datestr	db	'00-00-0000',0dh,0ah,'$'		    ;日期显示信息:month-day-year
	timestr	db	'00:00:00',0dh,0ah,'$'		        ;时间显示信息:hour:minute:second
data	ends
;===========
;代码段code
;===========
code 	segment
	assume 	cs:code,ds:data
main 	proc 	far
start:
	mov	ax,data
	mov	ds,ax

;调色、位置(时钟日期显示的位置以及背景色、字体颜色)
	mov	ah,0		
	mov 	al,3	
	int	10h
	mov	ah,6
	mov 	al,0		;al=0 全屏幕为空白
	mov	bh,00000100b	;背景色0000黑色,0100红色(黑底红字)
	mov	ch,0		    ;左上角行号
	mov	cl,0		    ;左上角列号
	mov	dh,50h		    ;右下角行号
	mov	dl,50h		    ;右下角列号
	int 10h	

;保存原中断向量
	mov	al,1ch
	mov	ah,35h
	int 	21h		;获取1ch中断向量到es:bx
	push 	es
	push 	bx
	push	ds		;保存

;设置新的中断向量
	mov	dx,offset intpro
	mov	ax,seg intpro
	mov	ds,ax
	mov	al,1ch
	mov	ah,25h
	int	21h		            ;设置中断向量ds:dx
	pop	ds
	in 	al,21h		        ;读中断屏蔽寄存器
	and 	al,11111110b	;开定时器中断
	out 21h,al			    ;写中断屏蔽寄存器
	sti			            ;开中断

;等待中断
s:
	mov	ah,1
	int 	21h
	cmp 	al,'q'		;'q'退出
	jz 	quit
	jmp 	s

;恢复1ch原中断向量
quit:	
	pop 	dx
	pop 	ds
	mov	al,1ch
	mov 	ah,25h
	int 	21h

;调色、位置(跳出时钟效果后的背景色以及字体颜色)
	mov 	ah,00h
	mov 	al,03h
	int 	10h
	mov	ah,06h
	mov 	al,00h		;al=0 全屏幕为空白
	mov	bh,00000111b	;背景色0000黑色,
0111白色(黑底白字)
	mov	ch,0h		;左上角行号
	mov	cl,0h		;左上角列号
	mov	dh,50h		;右下角行号
	mov	dl,50h		;右下角列号
	int 	10h	
	
	mov 	ax,4c00h
	int 	21h
		
main 	endp
;------------------------------------------------------
;中断处理intpro
;------------------------------------------------------
intpro 	proc	near
;保护现场
	push 	ds
	push 	ax
	push 	cx
	push	dx
	mov 	ax,data
	mov 	ds,ax
	sti
	dec 	count
	jnz 	exit

;显示提示("current time is:"的显示位置)
	mov	ah,02h
	mov	bh,0h		    ;页号
	mov	dh,0ah		    ;行号
	mov 	dl,20h		;列号
	int	10h
	mov	 dx,offset info
	mov 	ah,09h
	int 	21h

	call 	dispdate
	call 	disptime
	mov 	count,18

exit:
	cli
	pop 	dx
	pop 	cx
	pop 	ax
	pop 	ds
	iret
intpro 	endp
;----------------------------------------------------------
;显示日期子程序dispdate
;----------------------------------------------------------
dispdate 	proc	near
	push 	ax		;al星期
	push 	cx		;cx年
	push 	dx;		;dh月  dl日
	push 	si
;获取系统日期:入口:ah<-2ah  出口:cx<-year   dh<-month  dl<-day	
	mov 	ah,2ah		;2ah取系统日期
	int 	21h
	mov 	ax,cx		;年份转4为ASCII码
	mov 	bx,10		
	lea 	si,datestr+9	;指向字符串年份最后一位
	mov 	cx,4		;年份4位,循环4次
	push 	dx		    ;保存月日
y:
	xor 	dx,dx		;dx清零,保存余数即个位
	div 	bx		    ;商存ax,余数存dx
	add 	dl,30h
	mov 	[si],dl
	dec 	si
	loop 	y
		
	pop 	dx		    ;还原dx,处理日
	mov 	al,dl
	xor 	ah,ah
	div 	bl		    ;求日的个位,存ah,十位存al
	add 	ax,3030h    ;分别转ASCII码
	sub 	si,2
	mov 	[si],ax		;存日
		
	mov 	al,dh		;处理月
	xor 	ah,ah
	div 	bl		    ;求月的个位,存ah,十位存al
	add	ax,3030h		;分别转ASCII码
	sub 	si,3
	mov 	[si],ax		;存日

;显示提示(月-日-年显示位置)
	mov	ah,02h
	mov	bh,0h		    ;页号
	mov	dh,0bh		    ;行号
	mov 	dl,22h		;列号
	int	10h
;显示日期字符串	
	mov 	dx,offset datestr
	mov 	ah,09h
	int 	21h

	pop	si
	pop 	dx
	pop 	cx
	pop 	ax
	ret
dispdate 	endp
;----------------------------------------------------------
;显示时间子程序disptime
;----------------------------------------------------------
disptime	proc	near
	push	ax
	push	cx
	push	dx
	push	si
;获取系统时间:入口:ah<-2ch,出口:ch<-hour,cl<-minute,dh<-second
	mov	ah,2ch
	int	21h
	
	mov	bl,10
	lea	si,timestr
	mov	al,ch		;处理小时
	xor	ah,ah

	div	bl
	add	ax,3030h
	mov	[si],ax
		
	add	si,3
	mov	al,cl		;处理分
	xor	ah,ah
	div	bl
	add	ax,3030h
	mov	[si],ax

	add	si,3
	mov	al,dh		;处理秒
	xor	ah,ah
	div	bl
	add	ax,3030h
	mov	[si],ax
;显示提示(时:分:秒显示的位置)
	mov	ah,02h
	mov	bh,0h		    ;页号
	mov	dh,0ch		    ;行号
	mov 	dl,23h		;列号
	int	10h
;显示时间字符串		
	mov	dx,offset timestr
	mov	ah,09h
	int 	21h
		
	pop	si
	pop	dx
	pop	cx
	pop	ax
	ret
disptime 	endp
code 	ends
	end 	start

四、调试结果及分析

分析:任务二不管在代码量上还是在思路上都比任务一难很多。还好老师给出了一部分的代码,不然真的不知道怎么下手。根据老师代码只需要将不断输出的时间改为在同一位置输出跳动。比如秒需要在同一位置跳动,就需要不断清空数据,然后输出数据,不断循环从而达到时钟实时跳动的效果。同时设置时钟显示的位置以及颜色。当输入’q’后,就恢复1ch原中断向量,重新设置跳出时钟效果后的背景色以及字体颜色(00000111黑底白字)。

操作指令: 

 运行结果: 

加分题:

倒计时:设计一款倒计时,倒计时时间由用户设定(如30分钟),时间到有提示(如时间背景、前景色变化或文本提示等)。

设计思路: 

 代码部分(该部分代码来源于一个博主):

;================
;数据段data
;================
data	segment
    sinput		db	'Please enter hours, minutes, and seconds : $' 
    soutput		db 	'Any key to again and ESC to return.Time is : $' 
    buf 		db 	20h
    		db  	0
    		db 	20h 	dup(0)
    timehour	dw	0	;时
    timeminute 	dw 	0	;分
    timesecond 	dw 	0	;秒
    num		dw	?
data	ends
;===============
;堆栈段stack
;===============
stack	segment	para	stack
	dw	30h	dup(0)
stack	ends
;===============
;代码段CODE
;===============
code	segment
	assume	cs:code,ds:data,ss:stack
start:
	mov	ax,data
	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,buf			;取段内偏移地址
	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中断

	cmp	al,27		;按键是否ESC
	je	do_esc		;是退出 
	cmp	al,252		;按键是否是指定字符
	je	re			;是循环
    
	mov	al,11111101b	;关定时器中断
	out	21h,al			;中断屏蔽寄存器数据传回去
	jmp	timing			;不是就重新对时
do_esc:
	mov	al,11111101b	;关定时器中断
	out	21h,al			;中断屏蔽寄存器数据传回去
;退出代码
	mov	ah,4ch
	int	21h
;----------------------
;输入数字inputtime
;----------------------
inputtime	proc
	;初始化
	mov	dx,0
	mov	bx,10
	mov	si,2
	mov	num,0
	mov	ax,0
	mov	cx,0
lop:
	mov	al,buf[si]	;寄存器相对寻址,从缓冲区取一个字符
	cmp	al,0dh		;是否是cr
	je	final		;等于就跳转,JNE相反
	sub	al,30h		;减30h,从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,buf[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
;-----------------------------
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
;-------------------------
;08h计数器中断 int08h
;------------------------
int08h	proc	near
 	;保护现场
 	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		    ;传送指令
	dec	ax		        ;秒数自减1
	mov	timesecond,ax	;传送指令
	call	shownum		;调用子程序显示数字
	
	pop	dx		    ;将之前的光标信息从栈中取出
	mov	bh,0		;显示到第一页
	mov	ah,02h		;设置光标位置
	int	10h		    ;BIOS中断
	pop	dx
	
	;进位处理
	cmp	timesecond,0	;小于等于0,退一位
	jle	secondcarry	    ;秒数
	jmp	endint		    ;中断返回
secondcarry:
	mov	timesecond,59	;秒数得到借位
	dec	timeminute	    ;分钟自减1
	cmp	timeminute,0	;小于等于0,退一位
	jle	minutecarry	    ;分钟退位
	jmp	endint		    ;中断返回
minutecarry:
	mov	timeminute,59	;分钟得到借位
	dec	timehour		;小时自减1
	cmp	timehour,0	    ;小于等于0,清零退出
	jle	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
code	ends
	end	start

运行结果:

 分析:在网上找了很多关于计时器的代码,一个个试,终于找到了一个可以运行成功的计时器,然后通过对代码的理解(有些代码还是不能理解)。之后我直接在计时器代码上修改,只改变了计算时间部分的代码实现了递减的效果。

  • 20
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这个问题可以用汇编语言来解决。下面是一个可能的解决方案,仅供参考。 首先,我们需要定义一个数组,用于存储每个月份的缩写。可以用如下方式定义: ``` months db 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec' ``` 接下来,我们需要获取用户输入的数字,可以使用 DOS 中断 21h 中的功能来实现。具体步骤如下: 1. 调用中断 21h 的 01h 功能,显示提示信息 "Please enter a number (1-12): ",并等待用户输入。 2. 调用中断 21h 的 0Ah 功能,将用户输入的字符串读入到一个缓冲区中。 3. 将缓冲区中的字符串转换为数字,并保存到一个寄存器中。 代码如下: ``` mov ah, 09h ; 01h 功能:显示字符串 lea dx, prompt ; 提示信息 int 21h mov ah, 0Ah ; 0Ah 功能:输入字符串 lea dx, buffer ; 缓冲区 int 21h mov al, buffer[2] ; 第三个字符是用户输入的数字 sub al, '0' ; 将 ASCII 码转换为数字 ``` 接下来,我们需要检查用户输入的数字是否在 1-12 的范围内。如果不是,则显示错误信息并退出程序。否则,将对应月份的缩写输出到屏幕上。 代码如下: ``` cmp al, 1 jl error cmp al, 12 jg error mov dl, months[al-1] ; 获取对应月份的缩写 mov ah, 02h ; 02h 功能:显示字符 int 21h mov dl, months[al] int 21h mov dl, months[al+1] int 21h jmp exit error: mov ah, 09h lea dx, errmsg int 21h exit: mov ah, 4Ch ; 4Ch 功能:退出程序 int 21h prompt db 'Please enter a number (1-12): $' buffer db 3, 0 errmsg db 'Invalid input!$' ``` 完整代码如下: ``` .model tiny .code org 100h start: mov ah, 09h ; 01h 功能:显示字符串 lea dx, prompt ; 提示信息 int 21h mov ah, 0Ah ; 0Ah 功能:输入字符串 lea dx, buffer ; 缓冲区 int 21h mov al, buffer[2] ; 第三个字符是用户输入的数字 sub al, '0' ; 将 ASCII 码转换为数字 cmp al, 1 jl error cmp al, 12 jg error mov dl, months[al-1] ; 获取对应月份的缩写 mov ah, 02h ; 02h 功能:显示字符 int 21h mov dl, months[al] int 21h mov dl, months[al+1] int 21h jmp exit error: mov ah, 09h lea dx, errmsg int 21h exit: mov ah, 4Ch ; 4Ch 功能:退出程序 int 21h prompt db 'Please enter a number (1-12): $' buffer db 3, 0 errmsg db 'Invalid input!$' months db 'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec' end start ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值