发新帖本帖赏金 3.00元(功能说明)我要提问
12下一页
返回列表
打印
[51单片机]

【已推翻结论】只使用单片机定时器是无法100%精确时间的

[复制链接]
9362|37
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
cov0xt|  楼主 | 2016-1-5 17:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 cov0xt 于 2016-1-7 17:04 编辑

我用单片机写了个时钟程序,想节约成本,不使用专门的时钟芯片,只用单片机内置的定时器中断,发现了如下问题:


如果只用一个定时器T0,用来走秒,数码管的刷新和显示全放在main的while里,走时还算比较准确,呆看了一分钟,没有明显的误差感觉。


可是,正常应用场景,功能越来越多,程序代码越来越多,8个数码管的刷新消隐,不能放在main里,否则代码多了,显示会有明显闪烁。


所以数码管的显示,放在了定时器T1,1毫秒刷新消隐显示一个数码管。


为了保证时间的精度,走秒的T0比显示的T1的优先级要高,且T0里面的代码非常精简,只计算秒数,不进行其他业务逻辑。


可是即使如此谨慎,两个定时器都运行的话,时间却出现了明显的误差!

通过呆看观察,大概每走10-15秒左右,丢失1秒……


我估计应该是两个定时器,争用CPU时间造成的。


T0是每50毫秒触发,计数20次,加1秒;T1是每1毫秒触发。


所以每50毫秒的时候,T0将会与T1有一次争夺,争夺造成的时间损失,大概等于T0+T1的代码运行所需要的时间……


这就很郁闷了,代码中有各种选择、循环,根本无法准确预测到底需要多少时间。


所以,我的结论是,只用定时器的话,是无法精确时间的!

如果只是小制作的话,用定时器计算时间还可以。


要是重要的工业应用的话,还是要用专业的时钟芯片来计算时间吧。




相关帖子

来自 2楼
cov0xt|  楼主 | 2016-1-7 17:02 | 只看该作者
本帖最后由 cov0xt 于 2016-1-7 17:41 编辑

我已找到问题所在,对于草率做出的结论,表示惭愧……


只使用单片机定时器,并同时启用多个定时器,是不会相互干扰,并能够准确计时的!

请看如下代码:


//定时器初始化
void TimerInit()
{
        //晶振=12MHz
        //90C516RD+ 是12T的
        //时钟周期=1/12
        //机器周期=12 * 1/12 = 1us
        
        //T0和T1都是16位定时器
        TMOD=0x11;
        
        //定时器0设置成50ms=50000us(用于走秒)
        //(65536-50000)/256=60=0x3C
        TH0=0x3C;
        //(65536-50000)%256=176=0xB0
        TL0=0xB0;
        
        //定时器1设置成1ms=1000us(用于显示)
        //(65536-1000)/256=252=0xFC
        TH1=0xFC;
        //(65536-1000)%256=24=0x18
        TL1=0x18;
        
        //T0设置为抢占优先级
        PT0=1;
        
        //T0和T1启动
        TR0=1;
        TR1=1;
        
        //T0和T1允许启动
        ET0=1;
        ET1=1;
        
        //中断总开关开启
        EA=1;
        
        //定时器0计数器清零
        timer0Counter=0;
}


之前得出错误的结论,是因为没有设置抢占优先级。


单片机默认优先级,一个中断或定时器,不论优先级高低,都会先运行完一个,再运行另一个。


优先级的高低,只会影响准备进入中断或定时器的顺序。


单片机可以设置为抢占优先级模式,最关键的一句话就是PT0=1;


就是因为这几个字母,程序效果有了翻天覆地的变化!


抢占优先级模式就是,在其他中断或定时器正在运行的时候,另一个具有抢占优先级的中断或定时器可以打断它,提前运行。


PT0 表示 定时器0 的抢占优先级,所有中断/定时器的抢占优先级默认为0,设置成1,就可以打断别的中断了。


现在的走秒程序,人已经感觉不出来了误差了……

========


附加提示:


许多人以为,设置 IP(中断优先级控制寄存器),跟如下的默认优先级是一回事:


interrupt 0 -> INT 0
interrupt 1 -> Timer 0
interrupt 2 -> INT 1
interrupt 3 -> Timer 1
interrupt 4 -> UART
interrupt 5 -> Timer 2
interrupt 6 -> INT 2
interrupt 7 -> INT 3


其实不对,以上的默认优先级,你是改不了的。


设置IP,是附加另一套优先级规则,来改变最终的优先级顺序结果。


PX0 ~ PX3, PT0 ~ PT2 等,都只能设置0或1,表示是否抢占。默认值都是0。


在设置为0的时候,优先级还是按照默认优先级顺序来运行。

使用特权

评论回复

打赏榜单

21ic小喇叭 打赏了 3.00 元 2016-01-18

板凳
wh6ic| | 2016-1-5 18:13 | 只看该作者
你的问题在于刷新显示1mS间隔偏小。    所谓的争夺可以设置优先级来解决
  更关键的是你需要明白你的各个中断的服务时间,完全玩C,用软件仿真,也可以清楚大概的代码运行时间的。

使用特权

评论回复
地板
cov0xt|  楼主 | 2016-1-5 18:35 | 只看该作者
wh6ic 发表于 2016-1-5 18:13
你的问题在于刷新显示1mS间隔偏小。    所谓的争夺可以设置优先级来解决
  更关键的是你需要明白你的各个中 ...

一共8个数码管,每个数码管1ms,总共就是8ms,据说10ms刷新就是所谓的,高级电视100Hz刷新无闪烁……

如果不用1毫秒的定时器,真想不到该用什么了……

使用特权

评论回复
5
wh6ic| | 2016-1-5 18:50 | 只看该作者
1、你的8位显示可以稳定至少100mS吧,你的主循环能不能做到100mS内循环一次?如果能做到,可以在主循环中处理相关的解码等工作,在1mS中断内处理IO端口这种方式来扫描,那么应该可以做到在顶多几十个uS内完成一位的扫描工作,否则你需要将扫描代码中无谓的消耗排除在中断外。
2、视觉暂留约100mS, 用5mS扫描间隔,刷新周期约25Hz,肉眼应该观察不到闪烁。
3、如果一定要1mS扫描,硬件上如果可以采取措施,可以考虑将8位扫描改成两路4位扫描,降低扫描速度。

使用特权

评论回复
评分
参与人数 1威望 +3 收起 理由
cov0xt + 3 3条建议,每条都是精华!
6
cov0xt|  楼主 | 2016-1-7 07:43 | 只看该作者
给我自己一个提醒,后来看教程得知,默认优先级和抢占优先级是不一样的,估计要把T0设置成抢占优先级,效果会更好~

使用特权

评论回复
7
yhn1973| | 2016-1-7 11:29 | 只看该作者
这明显是程序做的不行。定时器要用自动重装定时器,要不就要做补偿。

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 我已找到问题,并推翻了结论,请看2楼~.
8
受不了了| | 2016-1-7 12:18 | 只看该作者
yhn1973 发表于 2016-1-7 11:29
这明显是程序做的不行。定时器要用自动重装定时器,要不就要做补偿。

嗯,二姨家关于这个定时器的精度,一直是月经贴,只是最近估计二姨快到更年期了才讨论得少些。楼主这结论太武断

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 我已找到问题,并推翻了结论,请看2楼~.
9
datouyuan| | 2016-1-7 14:33 | 只看该作者
本帖最后由 datouyuan 于 2016-1-7 14:37 编辑

用有自动重载功能的定时器,没有累加误差,应该可以精确定时.
51单片机的T2,是16bit重载.T1是8bit重载.

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 赞一个!
10
cov0xt|  楼主 | 2016-1-7 15:27 | 只看该作者
datouyuan 发表于 2016-1-7 14:33
用有自动重载功能的定时器,没有累加误差,应该可以精确定时.
51单片机的T2,是16bit重载.T1是8bit重载.

我原来一直以为T2不能用呢……原来还有这高级功能,我去研究研究……

使用特权

评论回复
11
PIGYONG801| | 2016-1-7 16:46 | 只看该作者
程序太菜了,好好规划一下,

看到别人说STM32定时不好,我开始也这样想,

后来改进一下,可以用到2PPM以下,不错了

使用特权

评论回复
12
cov0xt|  楼主 | 2016-1-7 16:48 | 只看该作者
PIGYONG801 发表于 2016-1-7 16:46
程序太菜了,好好规划一下,

看到别人说STM32定时不好,我开始也这样想,

是啊,我已找到问题了,正在整理发布……

使用特权

评论回复
13
cainiao_123| | 2016-1-7 17:13 | 只看该作者
cov0xt 发表于 2016-1-7 16:48
是啊,我已找到问题了,正在整理发布……

补偿做的好的话也可以

使用特权

评论回复
14
datouyuan| | 2016-1-8 09:06 | 只看该作者
本帖最后由 datouyuan 于 2016-1-8 09:27 编辑
cov0xt 发表于 2016-1-7 17:02
我已找到问题所在,对于草率做出的结论,表示惭愧……
之前得出错误的结论,是因为没有设置抢占优先级。

楼主还没有完全找到问题所在.
51响应中断的时间是不同的,即使你把该中断设为最高抢断优先级.
你很难做出正确的补偿,所以还是不是100%精确.

想要100%精确,必须要有自动重载功能的定时器.

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 正在研究T2中,16位自动重载…….
15
datouyuan| | 2016-1-8 09:25 | 只看该作者
本帖最后由 datouyuan 于 2016-1-8 09:29 编辑

介绍一种很特别的自动重载的方法.

1:51mcu的晶振选择11.0592M.这样50毫秒定时间隔的TL0的值为0.
2:中断响应后,立刻给TH0赋值,不给TL0赋值.
3:再把该中断设为最高抢断优先级.

通过上述3点,就可以100%精确.误差只和晶振的精度有关了.

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 精辟啊,我研究下~
16
cov0xt|  楼主 | 2016-1-8 11:12 | 只看该作者
本帖最后由 cov0xt 于 2016-10-26 09:22 编辑
datouyuan 发表于 2016-1-8 09:25
介绍一种很特别的自动重载的方法.

1:51mcu的晶振选择11.0592M.这样50毫秒定时间隔的TL0的值为0.

晶振是11.0592MHz

机器周期= 11.0592/12 = 0.9216


50ms:0.9216 * 50000 = 46080


TH:(65536-46080)/256 = 76 = 0x4C


TL:(65536-46080)%256 = 0


这真是太巧了,看来你真是经验丰富才能发现这个啊!


太感谢了:handshake

============


2016年10月26日订正:以上内容有逻辑计算错误,下面才是正确的计算步骤。


晶振=11.0592MHz


时钟周期=1/11059200s


89C52机器周期=12/11059200s


50ms是多少个机器周期:


0.05/(12/11059200)=0.05*11059200/12=46080个


定时器初值应该装入多少:


65536-46080=19456=0x4c00


所以:


TH0=0x4c


TL0=0


赋值的时候,不用给TL0赋值,可以少一步操作,节约了时间,提高了精度

使用特权

评论回复
17
datouyuan| | 2016-1-8 11:49 | 只看该作者
11.0592M=256*12*4*3*3*100
所以只要是1/(4*3*3*100)秒的整数倍,低8位都是0.

使用特权

评论回复
18
cov0xt|  楼主 | 2016-1-8 16:25 | 只看该作者
datouyuan 发表于 2016-1-8 11:49
11.0592M=256*12*4*3*3*100
所以只要是1/(4*3*3*100)秒的整数倍,低8位都是0.

:dizzy: 有点复杂了……

在实际中,晶振可能无法强制要求11.0592,秒数可能无法要求整数倍……

总结一下大概三点,精度就已经很够用了:

1、优先使用T0计时
2、使用自动重装功能
3、T0设置最高抢占优先级

使用特权

评论回复
19
coody| | 2016-1-8 18:14 | 只看该作者
那是因为LZ你没设计好程序。
有自动重装的,更没有问题,没有自动重装的,不改变低字节,也没有问题。我近来就用STC11L02E设计了几种LED数码管时钟。
使用4.096MHZ做主频,定时器0工作于1T 16位模式,下面是中断。
void timer0 (void) interrupt 1
{
    TH0 = 0xE0;        // 4096000 / 8192 = 500HZ,
       
    if(++cnt_8ms >= 250)  // 500ms
    {
       cnt_8ms = 0;
       B_HalfSecond = 1;  // 0.5秒到
    }
    B_2ms = 1;    //2ms时隙
}
中断重装了TH0,TL0不改变,则定时精度就依赖于晶振。

我用一个1ppm的有源时钟,则年误差为30秒内。用普通晶体,程序有误差校准,年误差也能保证在60秒内。

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 你厉害!我已经知错了,改进后的程序刚刚的.
20
qinweixing| | 2016-1-16 09:00 | 只看该作者
小白

使用特权

评论回复
发新帖 本帖赏金 3.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

200

帖子

3

粉丝