不准的时钟
本帖最后由 TIGGER 于 2020-3-27 23:04 编辑经过周六、周日两天时间,终于完工了第一个自制时钟
数码管:这个带时钟点的数码管,翻来覆去,用万用表测了好几次,才把管脚弄明白。两个时钟点引脚,阳极是2,阴极是dp。需要开启大电流,才能正常显示。
由于对时不方便,增加了两个按键,一个控制小时,另一个控制分钟,实现了简易的对表功能。
尽管在程序中调整了好多次,走时误差依然较大,每次烧录程序时,单片机的频率也有微小变化,所以精度一直没调上去。
电路图:
代码如下,仅供参考,有不对的,还请大家帮忙指正。
#include <intrins.h>
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sfr P2M0=0x96;//声明 P2引脚模式寄存器
sfr P3M0=0xb2;//声明 P3引脚模式寄存器
sbit a=P3^5;
sbit b=P2^1;
sbit c=P2^3;
sbit d=P3^3;
sbit e=P3^2;
sbit f=P3^6;
sbit g=P2^4;
sbit dp=P2^2;
sbit seg1=P3^4;
sbit seg2=P3^7;
sbit seg3=P2^0;
sbit seg4=P2^5;
sbit key1=P1^6;
sbit key2=P1^5;
uchar key1_action;
uchar key1_old;
uchar key2_action;
uchar key2_old;
void Delay1ms() //@12.000MHz
{
uchar i, j;
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
void Delay_n_ms(uint n) //延时n毫秒函数
{
while(n)
{
Delay1ms();
n=n-1;//每循环一次n减小1
}
}
void display(unsigned char x)//控制数码管显示内容的函数
{
//判断x的值来决定显示什么
if(x==0){a=0;b=0;c=0;d=0;e=0;f=0;g=1; }//显示“0”
if(x==1){a=1;b=0;c=0;d=1;e=1;f=1;g=1;} //显示“1”
if(x==2){a=0;b=0;c=1;d=0;e=0;f=1;g=0;} //显示“2”
if(x==3){a=0;b=0;c=0;d=0;e=1;f=1;g=0;} //显示“3”
if(x==4){a=1;b=0;c=0;d=1;e=1;f=0;g=0;} //显示“4”
if(x==5){a=0;b=1;c=0;d=0;e=1;f=0;g=0;} //显示“5”
if(x==6){a=0;b=1;c=0;d=0;e=0;f=0;g=0;} //显示“6”
if(x==7){a=0;b=0;c=0;d=1;e=1;f=1;g=1;} //显示“7”
if(x==8){a=0;b=0;c=0;d=0;e=0;f=0;g=0;} //显示“8”
if(x==9){a=0;b=0;c=0;d=0;e=1;f=0;g=0;} //显示“9”
}
void main()
{
unsigned int h1,h2,m1,m2,minute,second,m,h;
P2M0=0x3F;
P3M0=0xF4;
h1=0;h2=0;m1=0;m2=0;m=0;h=9;
h1=h/10;
h2=h%10;
key1_old=0; //按键没按下去,是低电平
key2_old=0;
while(1)
{
if(h1==1)
{
seg1=1;seg2=0;seg3=0;seg4=0;
display(h1);
Delay_n_ms(1);
}
seg1=0;seg2=1;seg3=0;seg4=0;
display(h2);
Delay_n_ms(1);
seg1=0;seg2=0;seg3=1;seg4=0;
display(m1);
Delay_n_ms(1);
seg1=0;seg2=0;seg3=0;seg4=1;
display(m2);
Delay_n_ms(1);
if(key1==0)
{
if(key1_old==1)
{
key1_action=1;
m=m+1;
}
}
key1_old=key1;
m1=m/10;
m2=m%10;
if(key2==0)
{
if(key2_old==1)
{
key2_action=1;
h=h+1;
}
}
key2_old=key2;
h1=h/10;
h2=h%10;
if(h>12){h=0;}
second=second+1;
if(second>310)//1、每1秒慢27.8ms,调整为325-7=318,2、慢32.59ms/S,调整为318-8=310
{
second=0;
dp=~dp;
minute=minute+1;
if(minute>60)
{
minute=0;
m=m+1;
m1=m/10;
m2=m%10;
if(m>60)
{
m=0;
h=h+1;
h1=h/10;
h2=h%10;
if(h>12){h=0;}
}
}
}
}
}
时钟需要使用外部高精度晶振,及时程序写在中断里面。也可以外挂一个RTC芯片,可以连万年历都有了 本帖最后由 田不辣 于 2020-3-31 13:43 编辑
我之前也做过,其实跟频率不准有一定的关系,最主要的是要用内部中断走时,你用main函数写,Delay时间会延长。而且代码运行本来也是需要花时间的。
用外部晶振会稍微准点,再者用内部中断。你用定时器0写走时程序,定时器2写显示程序。主程序只写初始化,循环验证个低电压,蜂蜜器啥的。这样代码也简单,程序也准确。
感谢分享请联系moz8com领取论坛赠品 34.44354354135753735735735537357357 楼主牛皮
厉害了{:1_12:}{:1_12:} 本帖最后由 TIGGER 于 2020-3-28 21:53 编辑
2、进一步对时钟进行校准
第一轮时钟精度很低,误差达到27MS/S,为了进一步提高精度,做了如下改进:
首先,采用中断的方式,产生时钟频率。即程序编写中采用T0定时器的方式2,即8位定时器,自动重装初值,以降低用软件反复设置初值,带来的误差。
定时器设置:TL0初值设为6,每250次,产生一次中断。在晶振频率12M下,为250us中断一次。程序中累计4000次为1S。烧录程序时,设定频率12.000MHz,实际烧录频率为12.011MHz。经校准,程序累计次数改为3999后,时钟精度达到:0.2ms/s(手动测量,精度有限),即每小时误差0.72s,一天误差17.28s。
另外,发现两处计算错误,一处为秒循环,一处为分钟循环。应该循环到59,清零,而不是60。
程序如下,如有更好的提高时钟精度的方法,希望加入讨论。
#include <intrins.h>
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sfr P2M0=0x96;//声明 P2引脚模式寄存器
sfr P3M0=0xb2;//声明 P3引脚模式寄存器
sbit a=P3^5;
sbit b=P2^1;
sbit c=P2^3;
sbit d=P3^3;
sbit e=P3^2;
sbit f=P3^6;
sbit g=P2^4;
sbit dp=P2^2;
sbit seg1=P3^4;
sbit seg2=P3^7;
sbit seg3=P2^0;
sbit seg4=P2^5;
sbit key1=P1^6;
sbit key2=P1^5;
uchar key1_action;
uchar key1_old;
uchar key2_action;
uchar key2_old;
unsigned int second;
void Delay1ms() //@12.000MHz
{
uchar i, j;
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
void Delay_n_ms(uint n) //延时n毫秒函数
{
while(n)
{
Delay1ms();
n=n-1;//每循环一次n减小1
}
}
void display(unsigned char x)//控制数码管显示内容的函数
{
//判断x的值来决定显示什么
if(x==0){a=0;b=0;c=0;d=0;e=0;f=0;g=1; }//显示“0”
if(x==1){a=1;b=0;c=0;d=1;e=1;f=1;g=1;} //显示“1”
if(x==2){a=0;b=0;c=1;d=0;e=0;f=1;g=0;} //显示“2”
if(x==3){a=0;b=0;c=0;d=0;e=1;f=1;g=0;} //显示“3”
if(x==4){a=1;b=0;c=0;d=1;e=1;f=0;g=0;} //显示“4”
if(x==5){a=0;b=1;c=0;d=0;e=1;f=0;g=0;} //显示“5”
if(x==6){a=0;b=1;c=0;d=0;e=0;f=0;g=0;} //显示“6”
if(x==7){a=0;b=0;c=0;d=1;e=1;f=1;g=1;} //显示“7”
if(x==8){a=0;b=0;c=0;d=0;e=0;f=0;g=0;} //显示“8”
if(x==9){a=0;b=0;c=0;d=0;e=1;f=0;g=0;} //显示“9”
}
void main()
{
unsigned int h1,h2,m1,m2,minute,m,h;
P2M0=0x3F;
P3M0=0xF4;
h1=0;h2=0;m1=0;m2=0;m=0;h=9;
h1=h/10;
h2=h%10;
key1_old=0; //按键没按下去,是低电平
key2_old=0;
TMOD=0x02;//使用T0中断的模式2,自动装初值,减小时钟误差
TH0=6; //装初值,
TL0=6;
EA=1;
ET0=1;
TR0=1;
while(1)
{
if(h1==1)
{
seg1=1;seg2=0;seg3=0;seg4=0;
display(h1);
Delay_n_ms(1);
}
seg1=0;seg2=1;seg3=0;seg4=0;
display(h2);
Delay_n_ms(1);
seg1=0;seg2=0;seg3=1;seg4=0;
display(m1);
Delay_n_ms(1);
seg1=0;seg2=0;seg3=0;seg4=1;
display(m2);
Delay_n_ms(1);
if(key1==0)
{
if(key1_old==1)
{
key1_action=1;
m=m+1;
}
}
key1_old=key1;
m1=m/10;
m2=m%10;
if(key2==0)
{
if(key2_old==1)
{
key2_action=1;
h=h+1;
}
}
key2_old=key2;
h1=h/10;
h2=h%10;
if(h>12){h=1;}
if(second>3999)//中断一次250us,4000次,1秒(1、慢0.4814MS/S,增减少计数:481/250=1.9256,2、快0.2023MS/S,增加计数:202.3/250=0.8091,不到1 )
{
second=0;
dp=~dp;
minute=minute+1;
if(minute>59) //60次,1分钟
{
minute=0;
m=m+1;
m1=m/10;
m2=m%10;
if(m>59)//60次,1小时
{
m=0;
h=h+1;
h1=h/10;
h2=h%10;
if(h>12){h=1;}
}
}
}
}
}
void T0_time() interrupt 1
{
second=second+1;
}
lz有恒心 鼓励 哈哈,要上时钟芯片才行啊 还记得上大学弄过这个,还是用汇编写的 田不辣 发表于 2020-3-31 13:40
我之前也做过,其实跟频率不准有一定的关系,最主要的是要用内部中断走时,你用main函数写,Delay时间会延 ...
谢谢!程序又修改了一下,把显示部分的延时去掉了,全部用T0中断来控制。但还是不理想。另外,想外接一个时钟晶振,但时钟晶振频率是32.768KH ,与单片机的机器周期(12),无法进行15分频得到秒。 加油,完美了可以用泰勒公式去计算周几 加个外部晶振不香么 用ESP8266他不香吗,都不用你对时,自己网络获取时间
我也考虑过自己做一个,你可以再加一个6块钱的时钟芯片,一天误差0.45秒以内,我属于那种可以折腾,但折腾后这东西要可以长久稳定运行,我会弄个8266wifi模块矫时或者gps模块校对,之后用个弄个电池作为备用电源接移动电源板 准不准无所谓,关键是学习过程 单片机学习都是这样开始的,反正初级阶段还是要学习一下
不知道提高芯片工作频率能不能提高一点精度? 本帖最后由 田不辣 于 2020-4-6 17:46 编辑
看你一直在更新这个帖子,特别看了你的代码,你参考:1、定时器选为12T,最长单次中断是60ms,这样中断次数很少了。
2、走时计算尽量写在中断里面,原因是你的中断second变量已经变成N+X了,但是main函数还在运行second的第N次,可以把时间运算全部放在中断0中。
3、display放到定时器2里面,每20ms切换一次显示位(具体是不是20MS不记得了),这样来保证显示准确。
4、主函数除了按钮程序,尽量空出来。其实按钮函数可以一并放在中断2里面(这个看个人,但是这样就可以不再使用delay函数,delay函数一般只是程序初始化的时候使用)。
5、变量定义过多,可以将key,keyaction设为bit型,减少U16类型(比如second),将hour,min,second设为全局变量(u8即可)!
这样一个定时器管走时,一个定时器管显示。相对独立,各不干扰。程序也相对轻便。
田不辣 发表于 2020-4-6 17:24
看你一直在更新这个帖子,特别看了你的代码,你参考:1、定时器选为12T,最长单次中断是60ms,这样中断次数 ...
根据MR.田的建议,将走时的计算,写进了T0中断程序里,第一次测试,中断4000次为1s,经24小时测量,快1.9MS/S; 经再次计算,调整为中断4007次为1s后,走时精度达到了一天快2秒,即 0.023 MS/S。上一次的最好成绩是0.2MS/S, 此次走时精度提高了10倍。
在此对MR.甜不辣 表示感谢!!!
中断程序代码如下:
void T0_time() interrupt 1
{
us=us+1;
ds=ds+1;
if(us>(4007))//中断一次250us,4000次,1秒(测量1、快1.9MS/S,增计数:1900/250=7.6; 测量2、快0.023MS/S)
{
us=0;
dp=~dp;
s=s+1;
if(s>59) //60次,1分钟
{
s=0;
m=m+1;
m1=m/10;
m2=m%10;
if(m>59)//60次,1小时
{
m=0;
h=h+1;
h1=h/10;
h2=h%10;
if(h>12){h=1;}
}
}
}
}
页:
[1]
2