Spezielle Frage bzgl. millis()-Überlauf

Hallo,

da das Thema millis-Überlauf in diesem Forum ebenso regelmäßig auftaucht wie Verweise auf den berühmten blink without delay Sketch habe ich hier mal eine ganz konkrete Frage bzgl. millis-Überlauf, die ich für mich gedanklich nach wie vor nicht gelöst bekomme.

Der Wert, den millis() zurückgibt ist endlich und fängt in einer Schleife immer wieder bei 0 an.

Gehen wir der Einfachheit halber einmal davon aus, dass der höchste Wert den millis() zurückgeben kann der Wert 10000 ist.

Um Fehler, die aus dem millis-Überlauf resultieren könnten zu vermeiden, wird oftmals folgendes Vorgehen empfohlen:

if(millis() - previousMillis >= interval)

Stellen wir uns nun folgendes Beispiel vor:

Alle drei Sekunden soll durch den folgenden Code ein Befehl gesendet werden. Wir gehen davon aus, dass command() permanent von void loop() aus aufgerufen wird.

void command()
{
  int interval = 3000;
  static unsigned long previousMillis = 0;
  
  if(millis() - previousMillis >= interval)
  {
    send_command();  // <- Befehl senden
    previousMillis = millis();
  }
}

Unmittelbar nachdem millis() wieder auf null zurückgesprungen ist, kommt es zu folgendem Umstand:

if(0 - 9000 >= 3000)

Da unsigned null minus 9000 zunächst einmal null ergibt, passiert erstmal gar nichts.

Erst nachdem millis() den Wert 3000 angenommen hat, wird erneut der Befehl gesendet.

Folge: Der Befehl wird anstatt der gewünschten Pause drei Sekunden in diesem einen Sonderfall erst nach einer Pause von vier Sekunden gesendet.

Um diese und ähnliche Probleme zu vermeiden, wird oftmals auf diese Seite verlinkt.

Wie ich durch Verwendung von Herrn Faludis Code mein Problem lösen könnte, erschließt sich mir leider nicht, da dort offenbar lediglich die Anzahl der millis()-Durchläufe gezählt werden.

Nun zur zentralen Frage:

Wie umgeht ihr das millis()-Überlauf-Problem?

Gruß Chris

Da unsigned null minus 9000 zunächst einmal null ergibt,......

Das ist nicht richtig!

Gehen wir der Einfachheit halber einmal davon aus, dass der höchste Wert den millis() zurückgeben kann der Wert 10000 ist.

Gut, ich kann in deinem Zahlensystem rechnen: Dann ist 10000 + 1 = 0 Dann ist auch 0 - 1 = 10000 und damit ist 0 - 9000 = 1001

Du siehst: Der Überlauf findet auch beim subtrahieren statt.

Schön genutzt wird das bei signed int/long. Und einen Namen hat es da auch: Zweierkomplement

Da unsigned null minus 9000 zuunächst einmal null ergibt, passiert erstmal gar nichts.

Das ergibt nicht null sondern theoretisch -9000. Da der Wertebereich den die Variable in diesem Beispiel annehmen kann aber von 0 bis 10000 geht gibt es keine -9000 sondern der Wert ist 1000 (10000 - 9000 mit einem Übertrag von 1 der aber verlorengeht und nicht als Übertrag zur nächsten Stelle dazu bzw weggezählt wird). Auch im Fall des Überlaufs ist die Bedingung nach 3000 Milisekunden erreicht (bei millis() = 2000)
(kann sein daß ich um +/-1 falsch gerechnet habe weil 0 bis 10000 genau 10001 Werte sind und nicht 10000, aber das ist für das Verständnis in diesem Fall vernachläßigbar).

Bei Arduino Millis() Rollover Handling wird eine weitere Variable verwendet und die Überläufe gezählt ( praktisch die Zahl von 32 Bit auf 64 Bit erweitert) So erhält man viel später einen neuerlichen Überlauf. Dies ist nur notwendig wenn der Intervall größer als 49,5 Tage sein soll (1 Überlauf)
Beispiel: Treffen wir uns um 12:00. Das ist keine eindeutige Angabe weil 12:00 heute, morgen oder in 3 Monaten sein kann. Durch hinzufügen weiterer Daten (des Datums) wird es eindeutig.

Wie umgeht ihr das millis()-Überlauf-Problem?

indem wir richtig rechnen :wink: :wink: :wink: :wink:

Grüße Uwe

Danke für die Aufklärung! 8)

Gruß Chris