本帖最后由 LBQ691477940 于 2014-7-12 16:10 编辑
相信有很多爱好单片机的朋友都用单片机制作过电子钟,这的确是一个很好的锻炼课题。可是当在你享受成功的快乐或是在朋友面前炫耀的时候,你会突然间发现你当初对着电视校准的电子钟的时间竟然变快或是变慢了。于是你就尝试用各种方法来调整它的走时精度,但是最终的效果还是不尽人意,只好每过一段时间手动调整一次了。渐渐的你有点烦了,不再去管它或是直接弃之不用。 原因分析: 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 ;2个机器周期 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 ;数据保护;2个机器周期 PUSH PSW ;2个机器周期 CLR C ;1个机器周期 MOV A, #0B7H ;同步修正本身应0B0但由于中断跳入和堆栈占了几个周期故为0B7 ADD A, TL0 ;初值和TL0中的数相加即为同步修正值 MOV TL0, A ;结果赋给TL0 MOV A, #03CH ADDC A, TH0 ;如果产生进位连进位也加上 MOV TH0, A ;结果赋给TH0 ;C语言为: ; CY = 0; ; TL0 += 0Xb7; //同步修正本身应0B0但由于中断跳入和堆栈占了几个周期故为0B7 ; TH0 = 0x3C+( unsingend char )CY 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
|