延迟时间); vs if(millis()-previous> time); 和漂移


8

经历一个老项目,我在两个Arduino Due上有代码,看起来像这样

void loop()
{
  foo();
  delay(time);
}

考虑到大多数有关使用的文献delay();我将其重新编码为

void loop()
{
  static unsigned long PrevTime;
  if(millis()-PrevTime>time)
  {
    foo();
    PrevTime=millis();
  }
}

但是,这似乎造成了以下情况:这两个设备在一段时间内会漂移,而此前它们并没有

我的问题是双重的:

  1. 为什么会if(millis()-PrevTime>time)引起更多的漂移 delay(time)呢?
  2. 有没有办法防止这种漂移而无需返回delay(time)

1
您注意到漂移的“时间段”的数量级是多少?两个设备是否在同一位置,因此温度相同?它们运行在晶体振荡器还是陶瓷谐振器上?
jose can uc

就我个人而言,我更喜欢Majenko的解决方案,并且始终使用它(我将增量放在其他说明的前面,但这只是一个偏好)。但是请注意,此时间精确为100毫秒,而其他代码(foo; delay;)的周期大于100毫秒(100毫秒+时间foo)。因此,您将体验到漂移(但这是- delay实现的SW在漂移)。无论如何,请记住,即使完全相等的实现也会“漂移”,因为时钟不相等。如果您需要完全同步,请使用信号来同步两个程序。
frarugi87

从星期五的17:00运行到星期一的9:00运行之后,这两个设备彼此相邻,漂移了4分钟。我决定,我要去用数字引脚的输入按照您的建议同步
ATE-ENGE

“这两个设备彼此相邻,...”并不意味着时序机制不准确,您所说的错误率约为800ppm,对于两个晶体振荡器来说很高,但对于一个陶瓷谐振器来说也是合理的。您必须将其与合理准确的时序标准进行比较,以确保:晶体通常在20ppm以内,而tcxo的晶体可以做到1ppm以下。那就是我的方式。
dannyf '17

Answers:


10

在任何形式的Arudino上工作时,需要记住一件事:

  • 每次操作都需要时间。

您的foo()函数将花费一些时间。那是什么时候,我们不能说。

处理时间的最可靠方法是仅依赖于触发时间,而不是依赖于确定下一次触发的时间。

例如,采取以下措施:

if (millis() - last > interval) {
    doSomething();
    last = millis();
}

该变量last将是例程触发的时间*加上doSomething运行所花费的时间。假设interval是100,并且doSomething需要10毫秒才能运行,您将在101毫秒,212毫秒,323毫秒等时间内获得触发。而不是您期望的100毫秒。

因此,您可以做的一件事就是始终使用相同的时间,而不必每次都记住特定点(如Juraj所建议的):

uint32_t time = millis();

if (time - last > interval) {
    doSomething();
    last = time;
}

现在,doSomething()花费的时间将对任何事物都没有影响。因此,您将在101ms,202ms,303ms等处获得触发。仍然不是您想要的100ms-因为您正在寻找超过 100ms的时间-这意味着101ms或更多。相反,您应该使用>=

uint32_t time = millis();

if (time - last >= interval) {
    doSomething();
    last = time;
}

现在,假设循环中没有其他事件发生,您将在100ms,200ms,300ms等处获得触发。但是请注意一点:“只要循环中没有其他事件发生” ...

如果花费5毫秒的操作恰好发生在99毫秒...处会发生什么?您的下一次触发将延迟到104ms。那是一个漂移。但这很容易战斗。而不是说“现在是录制的时间”,而是说“录制的时间比现在晚100ms”。这意味着无论您在代码中遇到什么延迟,触发都将始终以100ms的间隔进行,或者在100ms的刻度内漂移。

if (millis() - last >= interval) {
    doSomething();
    last += interval;
}

现在,您将在100ms,200ms,300ms等处获得触发。或者,如果其他代码位存在延迟,您可能会得到100ms,204ms,300ms,408ms,503ms,600ms等。它总是尝试将其运行到接近不考虑延迟的时间间隔。而且,如果延迟时间大于间隔时间,它将自动运行您的例程足够的时间以赶上当前时间。

在你漂泊之前。现在,您有抖动


1

因为您在操作后重置了计时器。

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}

没有。注意PrevTime是静态的。
dannyf '17

4
@dannyf,是的,还有吗?
Juraj

如果您知道“静态”的含义,那么您就会知道为什么答案不正确。
dannyf

我知道static与局部变量有什么关系。.我不明白为什么您认为我的答案与static有关。在调用foo()之前,我只是移动了当前的millis读数。
Juraj

-1

对于您要尝试执行的操作,delay()是实现代码的适当方法。您要使用if(millis())的原因是,如果您想允许主循环继续循环,以便您的代码或该循环之外的其他代码可以执行其他处理。

例如:

long next_trigger_time = 0L;
long interval = DESIRED_INTERVAL;

void loop() {
   do_something(); // check a sensor or value set by an interrupt
   long m = millis();
   if (m >= next_trigger_time) {
       next_trigger_time = m + interval;
       foo();
   }
}

这将以指定的时间间隔运行foo(),同时允许循环在两者之间继续运行。我将对next_trigger_time的计算放在对foo()的调用之前,以帮助最小化漂移,但这是不可避免的。如果漂移是一个重要问题,请使用中断定时器或某种时钟/定时器同步。还要记住,millis()将在一段时间后重新出现,为了使代码示例简单,我没有考虑这一点。


讨厌提及这一点:52天后出现过渡问题。

我已经在回答的结尾提到了过渡问题。
ThatAintWorking

好吧,解决它。

如果您希望我为您编写代码,我的标准咨询费为$ 100 /小时。我认为我写的内容足够相关。
ThatAintWorking

1
您是否知道Majenko发布了比您更完整,更好的答案?您是否知道您的代码无法编译?那long m - millis()不符合您的意图吗?那是在房子上。

-4

您的代码是正确的。

您遇到的问题与millis()有关:它将稍微计数不足(每次调用最大计数不足1ms)。

解决方案是使用更细的滴答滴答声,例如micros()-但这也将略有不足。


2
请提供一些证据或参考,以“ 它会稍微低估 ”。
Edgar Bonet
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.