51定时器减少系统长时间运行的时间误差累积
【注:文章相对接近原理,可能相对枯燥乏味,建议对常用51汇编指令有了解再阅读。】
1、部分概念
51:51单片机。
时间误差累积:在此定义为每一个计时周期所出现的时间误差的累加。
2、前言
实际上是不存在100%无误差的系统,误差只能无限减小。
3、问题
假如在51上获得系统运行时间,正常来说是使用定时器,但单纯的使用定时器是会出现一个问题,那就是时间误差累积。
具体是在系统运行的时间显示会出现明显的滞后或者超前的现象。如果没有进行误差校正的话,该51系统与手机的秒表(参考)运行一两个小时后会发现51系统的时间和手机秒表时间存在1s以内的误差(与实际代码相关),这个误差肉眼可以观察。
对于该时间误差累积如何减少我如下思路。
4、思路及原理
对于本人能想出的方法十分有限,这里就分为静态和动态的方法。
写过汇编的伙计们都知道,指令执行是需要时间的,然而这个定时器的误差大部分源于中断里面计时指令前面所执行的指令花费的时间。知道中断机制的伙计们也知道,51中断是有中断响应、定时器初值重装载(部分存在)、执行中断内容、中断断点返回(恢复现场)四个阶段。由于计时操作是在第三阶段,所以很明显,时间误差累积来自前两个阶段所消耗时间的累积,减小这两个阶段误差原理其实就是通过补偿前面两个阶段执行指令的时间即可。
中断响应的时间是随机的,为什么呢?因为不同指令的执行机器周期不一定相同,也就导致定时器到时间后中断响应需要等待一条指令执行完后才真正响应(这个响应时间是有个范围的,具体可以看一些书籍来查看,我也不记得具体值是多少个机器周期了),响应完后初值溢出继续计数。
定时器初值重装载的时间其实是看定时器模式是否为自动重装载来判断是否需要考虑,如果是非自动重装载,那么就需要两条MOV指令来重装载TH和TL,这也会占用一定时间,具体多少个机器周期就看你怎么写MOV指令的源操作数和目的操作数了(正常是4个机器周期,如MOV TH0,高8数据 MOV TL0,低8数据,这里给的是直接地址内容,每个MOV就是2个机器周期)。
定时器初值重装载的前面一般会存在压栈操作(PUSH指令),这个也需要操作时间,具体多少看自己的汇编代码。我这里就挺多个的,如下图4.1。
图4.1 中断部分汇编代码示例
其实听完上面几个阶段的大致分析,各位应该也知道怎么补偿了吧。静态补偿就每条指令考虑到,并计算出这些指令的总和(响应阶段的机器周期有个范围,没有非常具体的值);动态补偿有细心的会发现,响应后初值寄存器会继续计数(溢出后继续计数),这个方法就是利用这两个寄存器的内容来算出误差的。
4.1 静态补偿
单个中断造成的时间误差 = 误差响应时间(范围)+压栈时间+重装载时间
这种方法是最傻最笨的了,其实我实际运行过几个小时,误差确实减小了。但响应的时间误差不确定,这样非常局限,时间再长点的话那时间累积误差也就慢慢变大了。
4.2 动态补偿
动态补偿就是在每次中断重装载时,进行一个取差赋值即可,如下:
定时器装载寄存器内容 = 重装载值 -(+) 定时器装载寄存器溢出值
这里的-(+)具体看定时器是向上还是向下计数,这样写是比较通用。
虽然原理是这样,但实际上还是有些细节需要注意的,比如C语言写的这种形式需要考虑到转换为汇编是否存在其他中间指令,如果有那也要考虑进去进行补偿,只不过不像静态补偿那样存在时间误差范围。
END:
其实画图就很好理解这个误差细节的(本人就随性发挥写写文章,没啥高水平,也懒得画图了),而且因为以前只知道单纯实现功能不在意细节和严谨性,导致本人很多底层没有学到位。其实51挺多东西很妙的,比如指令时序这些和硬件设计结合起来那就可以达到非常高效的执行一些操作;还有51的压栈出栈细节和注意问题,这些哪天心血来潮再写吧。