;单片机电子钟汇编程序
;-----------------------------------
HOU_S BIT P2.7;时十位
HOU_G BIT P2.6;时个位
MIN_S BIT P2.5;分十位
MIN_G BIT P2.4;分个位
SEC_S BIT P2.3;秒十位
SEC_G BIT P2.2;秒个位
H_KEY BIT P3.4;时调整键
M_KEY BIT P3.5;分调整键
LEDBUS EQU P0;显示数据总线
SECOND EQU 30H;秒寄存器
MINUTE EQU 31H;分寄存器
HOUR EQU 32H;时寄存器
TIM_1 EQU 33H;定时器0中断次数
TIM_2 EQU 34H;定时器1中断次数
;-----------------------------------
ORG 0000H
LJMP INIT
ORG 000BH
LJMP TIMER0
ORG 30H
;-----------------------------------
;单片机初始化
INIT:
MOV SECOND,#0;秒置0
MOV MINUTE,#0;分置0
MOV HOUR,#0;开机后显示0
MOV TIM_1,#10;中断10次为0.5秒
MOV TIM_2,#2;0.5*2=1秒
MOV SP,#5FH;堆栈指针指向5FH
MOV TMOD,#01H;定时器0为模式1
MOV TH0,#03CH
MOV TL0,#0B0H;50MS初值(晶振12M)
SETB ET0
SETB TR0
SETB EA
;------------------------------------
;主程序
MAIN:
JNB H_KEY,HT;时调整键按下转到HT
JNB M_KEY,MT;分调整键按下转到MT
ACALL DISP;调用子程序
AJMP MAIN;转到LOOP继续检测控制键的状态
;--------------------------------------
;时间调整
;---------------分调整------------------
MT:
ACALL DISP;调用显示子程序
JNB M_KEY,MT;判断按键是否松开
INC MINUTE;分加一
MOV A,MINUTE
CJNE A,#60,MAIN;判断是否加到60
MOV MINUTE,#0;分到60变为0
MOV SECOND,#0;秒置0
AJMP MAIN
;----------------时调整------------------
HT:
ACALL DISP;调用显示子程序
JNB H_KEY,HT;判断按键是否松开
INC HOUR;时加1
MOV A,HOUR
CJNE A,#24,MAIN
MOV HOUR,#0;判断时是否到24,到24后清0
AJMP MAIN
;-----------------------------------------
;显示子程序
DISP:
MOV DPTR,#LEDTAB;数码管显示首地址送DPTR
MOV A,SECOND ;秒放入ACC
MOV B,#10
DIV AB
MOVC A,@A+DPTR
MOV LEDBUS,A
CLR SEC_S
ACALL D1MS
SETB SEC_S
MOV A,B
MOVC A,@A+DPTR
MOV LEDBUS,A
CPL SEC_G
ACALL D1MS
SETB SEC_G
MOV A,MINUTE;分放入ACC
MOV B,#10;B放入10
DIV AB;A/B,商在A里,余数在B里
MOVC A,@A+DPTR;查表取分十位段码
MOV LEDBUS,A;段码送LEDBUS显示
CLR MIN_S;打开分十位显示
ACALL D1MS;延迟1MS
SETB MIN_S;关闭分十位显示
MOV DPTR,#LEDTAB2
MOV A,B
MOVC A,@A+DPTR;查表取分个位段码
MOV LEDBUS,A
CLR MIN_G;打开分个位显示
ACALL D1MS;延迟1MS
SETB MIN_G;关闭分十位显示
MOV A,HOUR
MOV B,#10
DIV AB;拆分小时的十位和个位
JZ DISP0;十位为0,不显示十位
MOV DPTR,#LEDTAB
MOVC A,@A+DPTR
MOV LEDBUS,A
CLR HOU_S;打开时十位显示
ACALL D1MS
SETB HOU_S
DISP0:
MOV DPTR,#LEDTAB2
MOV A,B
MOVC A,@A+DPTR
MOV LEDBUS,A
CLR HOU_G;打开时个位显示
ACALL D1MS
SETB HOU_G;关闭时个位显示
RET
;--------------------------------------
;定时器0中断服务程序
TIMER0:
PUSH ACC;入栈保护ACC和PSW
PUSH PSW
MOV A,#0B0H;定时器0中断服务子程序
ADD A,TL0;同步修正
MOV TL0,A
MOV TH0,#03CH;重装定时器0初值
DJNZ TIM_1,RETI_1
MOV TIM_1,#10;中断10次为0.5秒
DJNZ TIM_2,RETI_1
MOV TIM_2,#2;1秒时间到
INC SECOND;秒加一
MOV A,SECOND
CJNE A,#60,RETI_1
MOV SECOND,#0;秒到60变0
INC MINUTE;分加1
MOV A,MINUTE
CJNE A,#60,RETI_1
MOV MINUTE,#0;分到60变0
INC HOUR;时加1
MOV A,HOUR
CJNE A,#24,RETI_1
MOV HOUR,#0;时到24变0
RETI_1:
POP PSW
POP ACC
RETI
;----------------------------------------
;延迟子程序
D1MS:
MOV R7,#2;延迟1MS子程序
D_1:
MOV R6,#250;延迟时间估算250*2*2=1000微秒
DJNZ R6,$
DJNZ R7,D_1
RET
;----------------------------------------
;数码管段码表
LEDTAB: ;不带点的数码管字码表
DB 0C0H,0F9H,0A4H,0B0H,99H;0,1,2,3,4
DB 92H,82H,0F8H,80H,90H;5,6,7,8,9
LEDTAB2: ;带点的数码管字码表
DB 40H,79H,24H,30H,19H;0,1,2,3,4
DB 12H,02H,78H,00H,10H;5,6,7,8,9
END
注释:
相信有很多爱好单片机的朋友都用单片机制作过电子钟,这的确是一个很好的锻炼课题。可是当在你享受成功的快乐或是在朋友面前炫耀的时候,你会突然间发现你当初对着电视校准的电子钟的时间竟然变快或是变慢了。于是你就尝试用各种方法来调整它的走时精度,但是最终的效果还是不尽人意,只好每过一段时间手动调整一次了。渐渐的你有点烦了,不再去管它或是直接弃之不用。
我和大家一样对此深有体会,于是我开始查找翻阅资料,试图找出一个解决的好方法。终于有一天……
废话太多——stop
原因分析:
1.单片机电子钟的计时脉冲基准是由外部晶振的频率经过12分频后提供,采用内部的定时/计数器来实现计时功能。所以,外接晶振频率精确度直接影响电子钟计时的准确性。
2. 单片机电子钟利用内部定时/计数器溢出产生中断(12M晶振一般为50ms)再乘以相应的倍率来实现秒、分、时的转换。大家都知道从定时/计数器产生中断请求到响应中断需要3-8个机器周期(如不明白请参考其它资料),定时中断子程序中的数据入栈和重装定时/计数器的初值还需要占用数个机器周期,还有从中断入口转到中断子程序也要占用一定的机器周期。 例如:
ORG 00H
LJMP START
ORG 0BH
LJMP TIMER ;2个机器周期
ORG 30H
START:
MOV 30H, #0
MOV 31H, #0
MOV 32H, #0
MOV 33H, #0
MOV 20H, #10
MOV 21H, #2
MOV SP, #40H
MOV IP, #00H
MOV IE, #82H ;开EA﹑ET0
MOV TMOD, #01H ;定时器模式1
MOV TH0, #03CH ;50MS初裝值
MOV TL0, #0B0H
SETB TR0 ;启动TR0
LOOP:
……
TIMER: ;定時器中断子程序
PUSH ACC ;2个机器周期
PUSH PSW ;2个机器周期
MOV TL0, #0B0H+6+3
MOV TH0, #03CH
……
RETI
END
从上面的例子大家可以看出从中断入口到定时/计数器初值的低8位装入需要占用2+2+2=6个机器周期。所以我们在编程时一般会把这8个机器周期加入定时/计数器的初值。但是从定时/计数器溢出中断请求到执行中断需要几个机器周期(3-8个机器周期)我们很难确定其准确值,因此导致了电子钟计时不准。
解决方法:
1.采用高精度晶振方案
虽然采用高精度的晶振可以稍微提高电子钟计时的精确度,但是其并不是导致电子钟计时不准的主要因素,而且高精度的晶振价格较高,所以不必采用此方案。
2.动态同步修正方案
从程序入手,采用动态同步修正方法给定时/计数器赋初值。动态同步修正方法:由于定时/计数器溢出后又会从0开始自动加数,固在给定时/计数器再次赋值前将定时/计数器低位(TL0)中的值和初始值相加后一并送入定时/计数器中,此时定时/计数器中的值即为动态同步修正后的准确值。