Verzögerungszeit); vs if (millis () - vorherige> Zeit); und treiben

8

Als ich ein altes Projekt durchging, hatte ich Code auf zwei Arduino Due, der so aussah

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

Ich habe mir den größten Teil der Literatur über die Verwendung zu Herzen genommen und delay();dies als neu kodiert

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

Dies scheint jedoch zu einer Situation geführt zu haben, in der die beiden Geräte über einen Zeitraum hinweg driften, in dem sie dies zuvor nicht getan haben

Meine Frage ist zweifach:

  1. Warum sollte if(millis()-PrevTime>time)mehr Drift verursachen als delay(time)?
  2. Gibt es eine Möglichkeit, diese Abweichung zu verhindern, ohne zurück zu gehen delay(time)?
ATE-ENGE
quelle
1
Was ist die Größenordnung der "Zeitspanne", in der Sie die Drift bemerken? Befinden sich die beiden Geräte am selben Ort und somit auf derselben Temperatur? Laufen sie auf einem Kristalloszillator oder einem Keramikresonator?
Jose Can uc
Persönlich bevorzuge ich Majenkos Lösung und verwende sie immer (ich stelle das Inkrement vor die anderen Anweisungen, aber dies ist nur eine Präferenz). Beachten Sie jedoch, dass dieses Timing GENAU 100 ms beträgt, während der andere Code ( foo; delay;) einen Zeitraum von mehr als 100 ms hat (es ist 100 ms + Zeit von foo). Sie werden also eine Drift erleben (aber es ist die delayimplementierte SW, die driftet). Denken Sie auf jeden Fall daran, dass selbst vollkommen gleiche Implementierungen "driften", da die Uhren nicht gleich sind. Wenn Sie eine vollständige Synchronisierung benötigen, verwenden Sie ein Signal, um die beiden Programme zu synchronisieren.
frarugi87
Die beiden Geräte stehen nebeneinander, nachdem sie von Freitag um 17:00 Uhr bis Montag um 9:00 Uhr gelaufen waren, gab es eine 4-minütige Drift. Ich entscheide, dass ich einen digitalen Pin verwenden werde, um die Eingänge gemäß Ihrem Vorschlag zu synchronisieren
ATE-ENGE
"Die beiden Geräte liegen nebeneinander, ..." Das bedeutet nicht, dass der Zeitsteuerungsmechanismus nicht genau ist. Sie sprechen von einer Fehlerrate von ~ 800 ppm, hoch für zwei Kristalloszillatoren, aber angemessen für sogar einen Keramikresonator. Sie müssen es mit einem einigermaßen genauen Timing-Standard vergleichen, um sicherzugehen: Kristalle liegen normalerweise innerhalb von 20 ppm, und tcxos können unter 1 ppm arbeiten. das wäre meine Art, es zu tun.
Dannyf

Antworten:

10

Es gibt eine wichtige Sache, an die Sie denken müssen, wenn Sie mit der Zeit an einem Arudino jeglicher Form arbeiten:

  • Jede Operation braucht Zeit.

Ihre Funktion foo () wird einige Zeit in Anspruch nehmen. Was diese Zeit ist, können wir nicht sagen.

Der zuverlässigste Weg, mit der Zeit umzugehen, besteht darin, sich nur auf die Zeit für das Auslösen zu verlassen und nicht darauf, wann die nächste Auslösung erfolgen sollte.

Nehmen Sie zum Beispiel Folgendes:

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

Die Variable lastist die Zeit, die die Routine ausgelöst hat * plus die Zeit, doSomethingdie zum Ausführen benötigt wurde. Angenommen, es intervalist 100, und doSomethingdie Ausführung dauert 10 ms. Sie erhalten Auslösungen bei 101 ms, 212 ms, 323 ms usw. Nicht die erwarteten 100 ms.

Eine Sache, die Sie tun können, ist, immer dieselbe Zeit zu verwenden, unabhängig davon, indem Sie sich an einen bestimmten Punkt erinnern (wie Juraj vorschlägt):

uint32_t time = millis();

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

Jetzt hat die Zeit, die doSomething()benötigt wird, keine Auswirkung auf irgendetwas. Sie erhalten also Auslösungen bei 101 ms, 202 ms, 303 ms usw. Immer noch nicht ganz die 100 ms, die Sie wollten - weil Sie nach mehr als 100 ms suchen, die vergangen sind - und das bedeutet 101 ms oder mehr. Stattdessen sollten Sie verwenden >=:

uint32_t time = millis();

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

Angenommen, in Ihrer Schleife passiert nichts anderes, erhalten Sie Auslösungen bei 100 ms, 200 ms, 300 ms usw. Beachten Sie jedoch das Bit: "Solange in Ihrer Schleife nichts anderes passiert" ...

Was passiert, wenn eine Operation, die 5 ms dauert, bei 99 ms stattfindet ...? Ihre nächste Auslösung wird auf 104 ms verzögert. Das ist ein Drift. Aber es ist leicht zu bekämpfen. Anstatt zu sagen "Die aufgezeichnete Zeit ist jetzt", sagen Sie "Die aufgezeichnete Zeit ist 100 ms später als sie war". Das bedeutet, dass unabhängig von den Verzögerungen, die Sie in Ihrem Code erhalten, Ihre Auslösung immer in Intervallen von 100 ms erfolgt oder innerhalb eines Ticks von 100 ms driftet.

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

Jetzt erhalten Sie Auslösungen bei 100 ms, 200 ms, 300 ms usw. Oder wenn es Verzögerungen bei anderen Codebits gibt, erhalten Sie möglicherweise 100 ms, 204 ms, 300 ms, 408 ms, 503 ms, 600 ms usw. Es wird immer versucht, sie so nahe wie möglich auszuführen das Intervall wie möglich unabhängig von Verzögerungen. Und wenn Sie Verzögerungen haben, die größer als das Intervall sind, wird Ihre Routine automatisch so oft ausgeführt, dass Sie die aktuelle Zeit einholen können.

Bevor du driftest . Jetzt hast du Jitter .

Majenko
quelle
1

Weil Sie den Timer nach dem Vorgang zurücksetzen.

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}
Juraj
quelle
Nein. Beachten Sie, dass PrevTime statisch ist.
Dannyf
4
@dannyf, ja und?
Juraj
Wenn Sie wüssten, was "statisch" bedeutet, würden Sie wissen, warum Ihre Antwort nicht korrekt ist.
Dannyf
Ich weiß, was Statik mit lokaler Variable macht. Ich verstehe nicht, warum meine Antwort meiner Meinung nach etwas mit Statik zu tun hat. Ich habe gerade den aktuellen Millis-Wert verschoben, bevor foo () aufgerufen wird.
Juraj
-1

Für das, was Sie versuchen, ist delay () der geeignete Weg, um den Code zu implementieren. Der Grund, warum Sie if (millis ()) verwenden möchten, besteht darin, dass Sie zulassen möchten, dass die Hauptschleife weiter wiederholt wird, damit Ihr Code oder ein anderer Code außerhalb dieser Schleife eine andere Verarbeitung ausführen kann.

Zum Beispiel:

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();
   }
}

Dies würde foo () in dem angegebenen Intervall ausführen, während die Schleife dazwischen weiter ausgeführt werden kann. Ich habe die Berechnung von next_trigger_time vor den Aufruf von foo () gestellt, um die Drift zu minimieren, aber das ist unvermeidlich. Wenn die Drift ein wichtiges Problem darstellt, verwenden Sie einen Interrupt-Timer oder eine Art Takt / Timer-Synchronisation. Denken Sie auch daran, dass millis () nach einiger Zeit herumläuft und ich dies nicht berücksichtigt habe, um das Codebeispiel einfach zu halten.

ThatAintWorking
quelle
Ich hasse es, dies zu erwähnen: Rollover-Problem in 52 Tagen.
Ich habe das Rollover-Problem bereits am Ende meiner Antwort erwähnt.
ThatAintWorking
Nun, löse es.
Meine Standard-Beratungsgebühr beträgt 100 USD / Std., Wenn Sie möchten, dass ich Code für Sie schreibe. Ich denke, was ich geschrieben habe, ist ausreichend relevant.
ThatAintWorking
1
Ist dir bewusst, dass Majenko eine vollständigere und bessere Antwort als deine gepostet hat? Ist Ihnen bewusst, dass Ihr Code nicht kompiliert wird? Das long m - millis()macht nicht das, was Sie vorhaben? Das ist auf dem Haus.
-4

Ihr Code ist korrekt.

Das Problem, auf das Sie stoßen, ist millis (): Es wird leicht unterzählt (die maximale Unterzählung beträgt nur knapp 1 ms pro Aufruf).

Die Lösung ist mit feineren Zecken wie micros () - aber das wird auch leicht unterzählen.

dannyf
quelle
2
Bitte geben Sie einige Beweise oder Hinweise für " es wird leicht unterzählen ".
Edgar Bonet