找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 8580|回复: 27
收起左侧

基于定时器制作时钟,但是精度有点问题,12分钟慢了10秒

  [复制链接]
ID:232280 发表于 2017-9-13 22:52 | 显示全部楼层 |阅读模式
这是我制作的基于定时器制作的时钟,但是精度有问题,我觉得是程序的延时delay导致的,请大神帮忙改一下,要分秒不差最好;谢谢了。代码如下:

数码管.png 译码器.png
  1. #include<reg52.h>
  2. sbit A1=P2^2;
  3. sbit B1=P2^3;
  4. sbit C1=P2^4;
  5. typedef unsigned char u8;
  6. typedef unsigned int u16;
  7. unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,
  8.                                                         0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,
  9.                                                         0x79,0x71,0x00};
  10. u16 i,k;                  //定义全局变量

  11. void timer0()           //定时器0启动
  12. {
  13.         TMOD=0x01;
  14.         TH0=0xfc;TL0=0x18;                //设置1ms溢出
  15.         EA=1;ET0=1;
  16.         TR0=1;
  17. }
  18. void delay(u8 z)           //延时函数
  19. {
  20.         u8 x,y;
  21.         for(x=z;x>0;x--)
  22.                 for(y=120;y>0;y--);        
  23. }
  24. void time0() interrupt 1        //中断函数
  25. {
  26.         TH0=0xfc;TL0=0x18;           //设置1ms溢出
  27.         i++;                                   
  28.         if(i==1000)        {i=0;k++;}        //i=1000时即一秒过去,k+1
  29. }
  30. void main()
  31. {
  32.         u8 a,b,c,d,e;
  33.         a=0;b=0;c=0;d=0;e=0;
  34.         timer0();
  35.         while(1)
  36.         {                                 
  37.                         A1=0;B1=0;C1=0;P0=table[k];delay(3);  //秒的个位
  38.                         if(k==10){k=0;a++;}
  39.                         A1=1;B1=0;C1=0;P0=table[a];delay(3);  //秒的十位
  40.                         if(a==6){a=0;b++;}
  41.                         A1=0;B1=1;C1=0;P0=table[b];delay(3);   //分的个位
  42.                         if(b==10){b=0;c++;}
  43.                         A1=1;B1=1;C1=0;P0=table[c];delay(3);  //分的十位
  44.                         if(c==6){c=0;d++;}
  45.                         A1=0;B1=0;C1=1;P0=table[d];delay(3);  //时的个位
  46.                         if(d==10){d=0;e++;}
  47.                         A1=1;B1=0;C1=1;P0=table[e];delay(3);         //时的十位
  48.                         if(e==2&&d==4){        k=0;a=0;b=0;c=0;d=0;e=0;}//24小时后归零;                                       
  49.         }
  50. }
复制代码
回复

使用道具 举报

ID:89515 发表于 2017-9-14 01:14 | 显示全部楼层
如果程序没问题。那么要想靠仿真做出高精度时钟,这对电脑,对软件都有要求。所以不要强求,这是毫无意义的。如果你非要误差小,那么至少定时器自动重载。中断里只做标志,不写多余的代码。12分钟10秒是很大了这个误差你那几个delay影响不到,绝对有其他原因。

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:89515 发表于 2017-9-14 01:34 | 显示全部楼层
看了下你的程序,如果可以减少进入中断的次数,就尽量减少,这个道理无论那个时候都合适。所以你的溢出时间能大就大些,不然以后你用多个定时器,多个中断,找不完的问题,还有可能找不到问题,而且中断的事情深得很,你要是把中断玩得很好,以后写复杂些的程序时爽得很。还有你那时间的写法没问题,但是有些时候可以简化就尽量简化,网上的这种例子51的太多了。最后就是你设置一下晶振看看

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:96682 发表于 2017-9-14 01:57 | 显示全部楼层
不是延时的问题就是选择的晶振有问题
回复

使用道具 举报

ID:82765 发表于 2017-9-14 06:12 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

ID:230915 发表于 2017-9-14 07:06 来自手机 | 显示全部楼层
几点建议:1、精确计时一般用时钟芯片;2、仿真上用示波器测试,细调延时;3、如果是实用的程序,可能会含有其它功能,最好把计时功能放到中断里面,利用中断的优先权。
回复

使用道具 举报

ID:213173 发表于 2017-9-14 07:08 | 显示全部楼层
本帖最后由 wulin 于 2017-9-14 08:23 编辑

二楼说得对,靠仿真做高精度时钟毫无意义,在实际运用中首先要选用高质量晶振,程序中定时器要设为自动重装,用查询法不要使用中断法计数,程序在记数时也可以调整精度,程序中要用计数法代替软件延时delay,否则影响精度!
        if(TF0==1)                                                //如果查询定时器溢出标志TF0为1
        {                                                                //定时器溢出周期100us
                TF0=0;                                                //定时器溢出标志TF0清0
                Cnt100us++;                                        //时间变量Cnt100us自+1
                  if(Cnt100us>=10000)                        //在此可以按万分之一秒调整精度
                {
                        Cnt100us=0;                                //时间变量Cnt100us清0
                        Cnt1s++;                                //时间变量Cnt1s自+1
                        if(Cnt1s>=60)                        //如果时间变量Cnt1s>=60
                        {
                                  Cnt1s=0;                        //时间变量Cnt1s清0
                                Cnt1min++;                        //时间变量Cnt1min自+1
                                if(Cnt1min>=60)                //时间变量分钟>=60
                                {
                                        Cnt1min=0;                //时间变量分钟清0
                                        Cnt1hour++;                //时间变量小时自+1
                                        ..........
                                }
                        }
                }
        }
回复

使用道具 举报

ID:232280 发表于 2017-9-14 08:29 来自手机 | 显示全部楼层
mengzhixinheng 发表于 2017-9-14 01:34
看了下你的程序,如果可以减少进入中断的次数,就尽量减少,这个道理无论那个时候都合适。所以你的溢出时间 ...

你好!我是想把if函数放外面的,但是放外面就程序没反应,不知道应该怎么写,另外还有用8位自动重装定时计数器也能提高精度,但是也不知道程序该怎么写,怎么写都不对!还有定时计数器最长能设置多长的溢出时间呢?非常感谢
回复

使用道具 举报

ID:123289 发表于 2017-9-14 08:29 | 显示全部楼层
如果程序写得好,一般晶振做到日差秒内不成问题。
要点:
1、必须知道晶振精确真正的频率,而不是标称的频率。如:标12M,很可能是11.980234M。
2、用中断进行计时,且中断时常数的重装方式很有讲究(这是做到精确的精妙关键之处)。
如果没有测试晶振频率的手段,可以在做成时钟之后,用网络时间对比反推。


回复

使用道具 举报

ID:232280 发表于 2017-9-14 08:30 来自手机 | 显示全部楼层
wc86110 发表于 2017-9-14 01:57
不是延时的问题就是选择的晶振有问题

可能是中断的问题
回复

使用道具 举报

ID:232280 发表于 2017-9-14 08:31 来自手机 | 显示全部楼层
cjjcjj1 发表于 2017-9-14 06:12
差一些的时钟专用芯片一天都有几十秒的误差;
对于定时器来说,就不要要求太高了;
如果定时器做时钟都分 ...

哦哦,好的
回复

使用道具 举报

ID:232280 发表于 2017-9-14 08:34 来自手机 | 显示全部楼层
gaochang000000 发表于 2017-9-14 07:06
几点建议:1、精确计时一般用时钟芯片;2、仿真上用示波器测试,细调延时;3、如果是实用的程序,可能会含有 ...

不是很懂第三点,一直搞不懂中断怎么把程序放外面,以减少进入中断的时间,就像我这个函数里的if语句,放外面就不灵了
回复

使用道具 举报

ID:123289 发表于 2017-9-14 08:51 | 显示全部楼层
我是如何把每个产品上的时钟做到精确的呢?
产品中有一段自动设置中断时常数的程序,程序做以下工作 :
1、先在外端口输入一个标准秒脉冲(自已做的基准,精度也取决于晶振)。
2、依据标准秒脉冲计算出本机所用晶振的准确频率。
3、根据所得到的晶振频率计算出中断时常数。
4、计算时常数需要补偿的部分,并做出补偿方案(不做此步只能做到日差数秒内)
以上这一切都是自动完成的,所以10来秒就可以完成一个产品时钟校准。
如此时钟的准确度就取决于:所用晶振的稳定度和标准秒脉冲发生器的晶振稳定度了。想想如此是否能做到日差秒内呢?1秒/24小时=1/86400=1.16*10^-5,这个级别的要求对于一般晶振稳定度来说,不是问题吧。
回复

使用道具 举报

ID:233011 发表于 2017-9-14 08:53 来自手机 | 显示全部楼层
TMOD=0x01;         TH0=0xfc;TL0=0x18;                //设置1ms溢出         EA=1;ET0=1;         TR0=1;在tr0=1之前的赋值寄存器配置不都是误差吗  积少成多   你可以考虑把这些时间从定时其中抠出去
回复

使用道具 举报

ID:232280 发表于 2017-9-14 09:02 来自手机 | 显示全部楼层
大学小学生 发表于 2017-9-14 08:53
TMOD=0x01;         TH0=0xfc;TL0=0x18;                //设置1ms溢出         EA=1;ET0=1;         TR0=1 ...

这个影响应该可以忽略不计,只有零点零几微秒吧
回复

使用道具 举报

ID:232280 发表于 2017-9-14 09:06 来自手机 | 显示全部楼层
wulin 发表于 2017-9-14 07:08
二楼说得对,靠仿真做高精度时钟毫无意义,在实际运用中首先要选用高质量晶振,程序中定时器要设为自动重装 ...

好的,但是自动重装总是写不对啊,你能写一个例子给我看一下吗,还有你现在写的这个函数是放在哪的呢?主函数里还是中断响应函数里
回复

使用道具 举报

ID:123289 发表于 2017-9-14 09:11 | 显示全部楼层
所以要做一个精准时钟,是要花一些功夫的,花在如何保证一次中断时间的高度准确,做到一个晶振脉冲都不误差是最高境界,普通51:6个晶振脉冲是可控最高境界。
如:楼主设计的中断时间是1ms,一天中断86400000次,如果每次误差6个脉冲(半个机器周期),则日差:518.4M个脉冲。
对于12M晶振,相当于43.2秒!
回复

使用道具 举报

ID:138678 发表于 2017-9-14 09:25 | 显示全部楼层
没有其他中断耽误时间吗?
回复

使用道具 举报

ID:89515 发表于 2017-9-14 10:12 | 显示全部楼层
GOD丿小男孩 发表于 2017-9-14 08:29
你好!我是想把if函数放外面的,但是放外面就程序没反应,不知道应该怎么写,另外还有用8位自动重装定时 ...

声明一个标志位flag放入中断中,每当定时器溢出进入中断flag=1;已flag=1为条件写一个走时间的函数,在内部清零flag。溢出时间最大是多少是由定时器位数和晶振决定的。你用16位定时器0,12M晶振,如果非1T指令的51单片机;那么执行一条单周期指令是1us,这也是定时器的最小溢出时间。那么16位定时器计数65536us,如果你非要用他做时钟,你就设置个50ms溢出多好啊1s进入20次中断,这样与主程序的相互影响也小了。
回复

使用道具 举报

ID:89515 发表于 2017-9-14 10:27 | 显示全部楼层
GOD丿小男孩 发表于 2017-9-14 08:29
你好!我是想把if函数放外面的,但是放外面就程序没反应,不知道应该怎么写,另外还有用8位自动重装定时 ...

8位自动重装定时计数器的用法不懂你就看下资料,顺便把定时器搞懂。随便一份好的教程都会讲的很详细,甚至你下一份STC的手册,上面还有很好的例程。这个比我给你几句程序有用啊!如果觉得看完整的手册烦;你就学哪里看哪里嘛
回复

使用道具 举报

ID:232280 发表于 2017-9-14 11:57 来自手机 | 显示全部楼层
mengzhixinheng 发表于 2017-9-14 10:27
8位自动重装定时计数器的用法不懂你就看下资料,顺便把定时器搞懂。随便一份好的教程都会讲的很详细,甚 ...

好的,谢谢了!我去网上找一下资料学习一下
回复

使用道具 举报

ID:213173 发表于 2017-9-15 07:10 | 显示全部楼层
GOD丿小男孩 发表于 2017-9-14 09:06
好的,但是自动重装总是写不对啊,你能写一个例子给我看一下吗,还有你现在写的这个函数是放在哪的呢?主 ...

//用查询法计时的高精度51单片机时钟程序
//K1键调整小时+,K2键调整分钟+,K3键秒清0,初始状态12:00:00
//定时器初始化程序根据晶振频率选择,走时精度可以控制在日误差5秒
//主程序循环一次必须小于100us,子程序全部要用时间片轮询编写,必须用计数代替软件延时
//数码管采用时分动态显示,约1ms显示1位,按实际驱动IC选择显示程序
#include <reg51.h>
#define uint unsigned int
#define uchar unsigned char
sbit K1 = P3^5;                        //小时+
sbit K2 = P3^6;                        //分钟+
sbit K3 = P3^7;                        //秒清0
//sbit dula=P2^6;                        //段选
//sbit wela=P2^7;                        //位选
sbit A1=P2^2;                        //位A
sbit B1=P2^3;                        //位B
sbit C1=P2^4;                        //位C
uchar code table[]={                //0~F数组
        0x3f,0x06,0x5b,0x4f,
        0x66,0x6d,0x7d,0x07,
        0x7f,0x6f,0x77,0x7c,
        0x39,0x5e,0x79,0x71};
uint Cnt100us;                                //定义100微秒变量
uchar hour=12,min=0,sec=0;//定义时、分、秒变量

/**************************************
        定时器0初始化程序 100微秒@12.000MHz
**************************************/
/*
void Timer0Init()                //100微秒@12.000MHz
{
        TMOD = 0x02;                //设置自动重载模式
        TL0 = 0x9C;                        //设置定时初值
        TH0 = 0x9C;                        //设置定时重载值
        TF0 = 0;                                //清除TF0标志
        TR0 = 1;                                //定时器0开始计时
}
*/
/***************************************
        定时器0初始化程序 100微秒@11.0592MHz
***************************************/
void Timer0Init()                //100微秒@11.0592MHz
{
        TMOD = 0x02;                //设置自动重载模式
        TL0 = 0xA4;                        //设置定时初值
        TH0 = 0xA4;                        //设置定时重载值
        TF0 = 0;                                //清除TF0标志
        TR0 = 1;                                //定时器0开始计时
}
/************************************
        计时子程序
************************************/
void Time()       
{
        if(TF0==1)                                        //如果查询定时器溢出标志TF0为1
        {                                                        //定时器溢出周期100us
                TF0=0;                                        //定时器溢出标志TF0清0
                Cnt100us++;                                //时间变量Cnt100us自+1
                if(Cnt100us>=10000)                //在此增减可以按万分之一秒调整精度
                {
                        Cnt100us=0;                                //变量Cnt100us清0
                        sec++;                                        //秒自+1
                        if(sec>=60)                                //如果秒>=60
                        {
                                sec=0;                                //秒清0
                                min++;                                //分自+1
                                if(min>=60)                        //分>=60
                                {
                                        min=0;                        //分清0
                                        hour++;                  //小时自+1
                                        if(hour>=24)        //小时>=24
                                                hour=0;                //小时清0
                                }
                        }
                }
        }
}
/*************************
          按键扫描程序
*************************/
void key_scan()
{
        static bit key1_sign,key2_sign,key3_sign;        //按键自锁标志变量
        static uchar count1,count2,count3=0;                //消抖计数变量                       

        if(K1==1)                                        //检测按键1没有按下
        {
                key1_sign=0;                        //按键1自锁标志清0
                count1=0;                                //消抖计数1清0
        }
        else                                                        //按键1按下
        {
                count1++;                                //消抖计数1自+1
                if(count1>=200)                        //200次检测按键如果为0
                {                       
                        count1=200;                        //防止溢出
                        if(key1_sign==0)        //按键1自锁标志为0
                        {
                                key1_sign=1;        //按键1自锁标志置1
                                hour++;                        //小时自+1
                                if(hour>=24)        //小时>=24
                                        hour=0;                //小时清0
                        }
                }
        }

        if(K2==1)                                        //检测按键2没有按下
        {
                key2_sign=0;                        //按键2自锁标志清0
                count2=0;                                //消抖计数2清0
        }
        else                                                        //按键2按下
        {
                count2++;                                //消抖计数2自+1
                if(count2>=200)                        //200次检测按键如果为0
                {                       
                        count2=200;                        //防止溢出
                        if(key2_sign==0)        //按键2自锁标志为0
                        {
                                key2_sign=1;        //按键2自锁标志置1
                                min++;                        //分自+1
                                if(min>=60)                //分>=60
                                        min=0;                //分清0
                        }
                }
        }
        if(K3==1)                                        //检测按键3没有按下
        {
                key3_sign=0;                        //按键3自锁标志清0
                count3=0;                                //消抖计数2清0
        }
        else                                                        //按键3按下
        {
                count3++;                                //消抖计数3自+1
                if(count3>=200)                        //200次检测按键如果为0
                {                       
                        count3=200;                        //防止溢出
                        if(key3_sign==0)        //按键3自锁标志为0
                        {
                                key3_sign=1;        //按键3自锁标志置1
                                Cnt100us= 0;        //100微秒变量清0
                                sec = 0;                        //秒清0
                        }
                }
        }
}
/********************************
        显示程序 573+573
********************************/
/*
void display()
{
        static uchar xx=0;        //时分显示变量
        static uchar yy=0;        //计数延时变量
        yy++;       
        if(yy>=60)        //1~255可调,数码管闪烁可减小,有鬼影可加大
        {
                yy=0;
                switch(xx)
                {
                        case 0:
                           dula=0;
                           P0=table[hour/10];                //显示时十位段码
                           dula=1;
                           dula=0;
                       
                           wela=0;
                           P0=0x7e;                                                //显示时十位位码
                           wela=1;
                           wela=0;
                                xx=1;
                         break;       
       
                        case 1:
                           dula=0;
                           P0=table[hour%10];                //显示时个位段码
                           dula=1;
                           dula=0;
                       
                           wela=0;
                           P0=0x7d;                                                //显示时个位位码
                           wela=1;
                           wela=0;
                                xx=2;
                         break;       
       
                        case 2:
                           P0=table[min/10];                        //显示分十位段码
                           dula=1;
                           dula=0;
                       
                           P0=0x7b;                                                //显示时十位位码
                           wela=1;
                           wela=0;
                                xx=3;
                         break;       
       
                        case 3:
                           P0=table[min%10];                        //显示分个位段码
                           dula=1;
                           dula=0;
                       
                           P0=0x77;                                                //显示分个位位码
                           wela=1;
                           wela=0;
                                xx=4;
                         break;       
       
                        case 4:
                           P0=table[sec/10];                        //显示秒十位段码
                           dula=1;
                           dula=0;
                       
                           P0=0xef;                                                //显示秒十位位码
                           wela=1;
                           wela=0;
                                xx=5;
                         break;       
       
                        case 5:
                           P0=table[sec%10];                        //显示秒个位段码
                           dula=1;
                           dula=0;
                       
                           P0=0xdf;                                                //显示秒个位位码
                           wela=1;
                           wela=0;
                                xx=0;
                         break;       
                }
        }
}*/
/********************************
        显示程序  573+138
********************************/
void display()
{
        static uchar xx=0;        //时分显示变量
        static uchar yy=0;        //计数延时变量
        yy++;       
        if(yy>=60)        //1~255可调,数码管闪烁可减小,有鬼影可加大
        {
                yy=0;
                switch(xx)
                {
                        case 0:
                                P0=0x00;
                                A1=1;B1=0;C1=1;                        //显示时十位位码
                                P0=table[hour/10];                //显示时十位段码
                                xx=1;
                        break;       
                       
                        case 1:
                                P0=0x00;
                                A1=0;B1=0;C1=1;                        //显示时个位位码
                                P0=table[hour%10];                //显示时个位段码
                                xx=2;
                        break;       
                       
                        case 2:
                                P0=0x00;
                                A1=1;B1=1;C1=0;                        //显示时十位位码
                                P0=table[min/10];                //显示分十位段码
                                xx=3;
                        break;       
                       
                        case 3:
                                P0=0x00;
                                A1=0;B1=1;C1=0;                        //显示分个位位码
                                P0=table[min%10];                //显示分个位段码
                                xx=4;
                        break;       
                       
                        case 4:
                                P0=0x00;
                                A1=1;B1=0;C1=0;                        //显示秒十位位码
                                P0=table[sec/10];                //显示秒十位段码
                                xx=5;
                        break;       
                       
                        case 5:
                                P0=0x00;
                                A1=0;B1=0;C1=0;                        //显示秒个位位码
                                P0=table[sec%10];                //显示秒个位段码
                                xx=0;
                        break;       
                }
        }
}
/********************************
                主程序
********************************/
void main(void)
{
        Timer0Init();        //初始化定时器
        while(1)
        {
                key_scan();//按键扫描
                Time();                //计时
                display();        //显示
        }
}


回复

使用道具 举报

ID:111634 发表于 2017-9-15 21:13 | 显示全部楼层
单片机时钟可分为2种:一是应用时钟芯片;二是用机内时钟。应用时钟芯片肯定比机内时钟精准,它取决于时钟芯片的晶振频率是否精准。应用机内时钟一般是学生实验,让学生学习定时器中断、计时计次,无实用意义。用机内时钟达到比较精准,必须用中断方式2,自动恢复定时初值,用其他方式或延时肯定不好。用机内时钟时,晶振最好用12MHz或6MHz,便于计算和计时,机器周期分别为1us和2us,定时可取250us和500us,在中断内计次4000和2000就是1s,可建立1s标志,在中断外更新显示和处理。这时这种方式的相对精准度取决于晶振频率的精准度。
回复

使用道具 举报

ID:111634 发表于 2017-9-15 21:14 | 显示全部楼层
本帖最后由 zl2168 于 2017-9-20 20:38 编辑

给你介绍应用上述方法的案例。
实例94  模拟电子钟(由80C51定时器产生秒时基)

Proteus仿真一下,确认有效。
实例94 模拟电子钟(由80C51定时器产生秒时基).rar (43.05 KB, 下载次数: 10)
回复

使用道具 举报

ID:168632 发表于 2019-4-5 11:13 | 显示全部楼层
http://www.51hei.com/bbs/dpj-86306-1.html 可以看我的贴子,不用时钟芯片,日误差可以做到1秒内
回复

使用道具 举报

ID:426861 发表于 2019-4-5 11:38 | 显示全部楼层
你实际测试一下,然后看看定时器需要多少时间要修正一下误差,这样一点点的可以不断修正到你所需要的精度
回复

使用道具 举报

ID:94253 发表于 2023-5-19 12:02 | 显示全部楼层
wulin 发表于 2017-9-15 07:10
//用查询法计时的高精度51单片机时钟程序
//K1键调整小时+,K2键调整分钟+,K3键秒清0,初始状态12:00:0 ...

难得的学习资料,赞赞赞
回复

使用道具 举报

ID:77589 发表于 2023-5-19 14:03 | 显示全部楼层
作为学习例子去折腾一下是没问题的,但无大的实际应用价值。
在有RTC的MCU中,可以用RTC来做时钟;
在无RTC的MCU中,外扩专用时钟芯片。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表