我和大家一样对此深有体会,于是我开始查找翻阅资料,试图找出一个解决的好方法。终于有一天……
废话太多——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)中的值和初始值相加后一并送入定时/计数器中,此时定时/计数器中的值即为动态同步修正后的准确值。例如:
TIMER:
PUSH ACC
PUSH PSW
MOV A, #0B0H
ADD A, TL0 ;初值和TL0中的数相加即为同步修正值
MOV TL0, A ;修正值送定时/计数器低8位
MOV TH0, #03CH
……
RETI
采用了此种方法后相信你的电子钟的精度已经大大提高了。别走开,后面内容更精彩。
3. 自动调整方案
采用了同步修正方案后电子钟的精度虽然提高了很多,但是由于晶振频率的偏差和一些其它未知因素(同一块电路板、同样的程序换了一片单片机后走时误差却不一样,不知是何原因)的影响,时间长了仍然会有积累误差。为此我设计出了此自动调整方案,实际也是一种容错技术。其自动调整原理为:实测出误差1秒所需的时间,然后每隔这样一段时间后就对秒进行加1或减一调整。例如:电子钟每过50小时就慢1秒,其自动调整程序如下:
TIMER: ;定時中斷程序
PUSH ACC ;數據保護
PUSH PSW
……
T_3:
INC A_1
MOV A, A_1
CJNE A, #50, RETI_1 ;到50小时了吗?
INC S_1 ;到50小时秒加1
MOV A_1, #OOH
RETI_1:
POP PSW
POP ACC
RETI
使用此方法调整较费时间,但是效果非常好,经实验一次调整可以将月误差控制在1秒左右,如按此方法再次测出误差1秒所需的天数并进行二次调整,其精度会更高。
电子钟源程序:(修改后)修改前的源程序可以到论坛<单片机技术交流>里看我发的<简单的电子钟源程序>一文
S_1 EQU 30H ;秒寄存器
M_1 EQU 31H ;分寄存器
H_1 EQU 32H ;時寄存器
A_1 EQU 33H ;自动调整寄存器
ORG 00H
LJMP START
ORG 03H
RETI
ORG 0BH ;定時中斷入口
LJMP TIMER
ORG 13H
RETI
ORG 1BH
RETI
ORG 30H
START:
MOV S_1, #0 ;秒、分、時寄存器清0
MOV M_1, #0
MOV H_1, #0
MOV A_1, #0
MOV 20H, #10 ;0.5秒鐘中斷次數,0.5s=500ms=50msx10
MOV 21H, #2 ;2個0.5秒即為1秒
MOV SP, #40H ;堆棧指針設置
MOV IE, #82H ;開定時器0中斷及總中斷
MOV TMOD, #01H ;定時器0模式1
MOV TH0, #03CH ;50ms初值
MOV TL0, #0B0H
SETB TR0 ;啟動定時器0
LOOP:
ACALL DISP ;調用顯示
JNB P3.4, MT ;查詢分調整鍵
JNB P3.5, HT ;查詢時調整鍵
AJMP LOOP
MT: ;分調整
ACALL DISP
JNB P3.4, MT ;鍵消抖
INC M_1 ;分加1
MOV A, M_1
CJNE A, #60, LOOP ;沒到60分返回,到60分清0
MOV M_1, #0
AJMP LOOP
HT: ;時調整
ACALL DISP
JNB P3.5, HT
INC H_1
MOV A, H_1
CJNE A, #24, LOOP
MOV H_1, #0
AJMP LOOP
DISP: ;顯示子程序
MOV DPTR, #NUMTAB ;表地址送數據指針
MOV A, M_1 ;分送A
MOV B, #10
DIV AB ;十進制調整
ADD A, R0 ;查表偏移量調整
MOVC A, @A+DPTR ;查表
MOV P1, A ;分十位送p1口顯示
CLR P3.2 ;開分十位顯示
ACALL D1MS ;延時1ms
SETB P3.2 ;關顯示
MOV A, B ;分個位p1口顯示
ADD A, R0
MOVC A, @A+DPTR
MOV P1, A
CLR P3.3
ACALL D1MS
SETB P3.3
MOV A, H_1 ;時送A
MOV B, #10
DIV AB
ADD A, R0
MOVC A, @A+DPTR
MOV P1, A
CLR P3.0 ;顯示時十位
ACALL D1MS
SETB P3.0
MOV A, B
ADD A, R0
MOVC A, @A+DPTR
MOV P1, A
CLR P3.1 ;顯示時個位
ACALL D1MS
SETB P3.1
RET ;返回
TIMER: ;定時中斷程序
PUSH ACC ;數據保護
PUSH PSW
MOV A, #0B0H ;同步修正
ADD A, TL0
MOV TL0, A ;重置50ms定時值
MOV TH0, #03CH
DJNZ 20H, RETI_1 ;到0.5秒了嗎?
MOV 20H, #10
CPL 25H.0 ;取反秒點閃爍標志位
JNB 25H.0, T_1 ;標志位為0轉T_1
MOV R0, #0 ;查表偏移量寄存器置0(不顯示秒點)
AJMP T_2
T_1:
MOV R0, #10 ;查表偏移量寄存器置10(顯示秒點,秒點每秒閃爍1次)
T_2:
DJNZ 21H, RETI_1 ;到1秒了嗎?
MOV 21H, #2
INC S_1 ;秒加1
MOV A, S_1
CJNE A, #60, RETI_1 ;到60秒了嗎?
MOV S_1, #0 ;到60秒清0
INC M_1 ;分加1
MOV A, M_1
CJNE A, #60, RETI_1 ;到60分了嗎?
MOV M_1, #0
INC H_1 ;時加1
MOV A, H_1
CJNE A, #24, T_3 ;到24小時了嗎?
MOV H_1, #00H
T_3: ;自动调整
INC A_1
MOV A, A_1
CJNE A, #50, RETI_1
INC S_1
MOV A_1, #OOH
RETI_1:
POP PSW
POP ACC
RETI
D1MS: ;1毫秒延時
MOV R7, #2
D_1:
MOV R6, #250
DJNZ R6, $
DJNZ R7, D_1
RET
NUMTAB:
DB 10H,0D3H,48H,41H,83H,21H,20H,53H,00H,01H;不顯示秒點
DB 14H,0D7H,4CH,45H,87H,25H,24H,57H,04H,05H;顯示秒點
END