Verständnisfrage zu millis ()

Wenn ich die Funktion millis verwende, um einen Code nicht per delay auszubremsen, was passiert dann nach ca. 49,7 Tagen? Z.B. bei einer Abfrage eines Sensors? Angenommen, die Abfrage soll alle 10s passieren und währenddessen setzt sich die Funktion auf 0 zurück, dauert die Abfrage dann dieses eine mal länger?

Kommt darauf an wie du sie verwendest

Wenn man immer "millis() - previousMillis > interval" macht, läuft es einfach weiter. Da ein kleiner millis() Wert (nach dem Überlauf) Minus ein großer alter Wert wieder eine sehr hohe Zahl ergibt. Und damit die Bedingung wahr ist

millis() zählt die Millisekunden seit Prozessorstart. Es ist eine Variable vom Typ unsigned long und kann deshalb von 0 bis 4294967295 zählen. Das bedeutet dass sie nach „fast 50 Tagen“ (wie es im Arduino-Manual heißt) überläuft, um genau zu sein nach 49,71 Tagen, denn:
(2^32 – 1) / 1000 / 60 / 60 / 24 = 49,71
(49 Tage, 17 Stunden … etc.)

Häufig wird millis() zum messen von Zeitintervallen verwendet (dafür ist es ja auch gedacht). Wenn man also Rechenoperationen mit millis() durchführt kann der Überlauf ein Problem sein, aber nur wenn man es falsch macht.

Richtig macht man es so:

  • unsigned long für Zeitvariable verwenden (und nicht long)
  • einen Startzeitpunkt festhalten
  • den Startzeitpunkt von der aktuellen Zeit subtrahieren um auf die Differenz zu kommen
  • überprüfen ob diese Differenz größer ist als das gewünschte Intervall

z.B.: Intervall ist 1 Minute (60 000 millis)

unsigned long startTime = millis();
unsigned long interval  = 60000;

void setup()
{   ...
}

void loop()
{   ...
    if ( millis() - startTime >= interval )
    {
          // mach was
    }
   ...
}

So wird selbst bei Überlauf richtig gezählt.

Überlauf von Millis „provozieren“
Wer seinen Code testen will, ob er auch richtig auf einen Überlauf von Millis reagiert, aber nicht 49 Tage warten will, kann so Millis auf einen Wert knapp vor dem Überlauf stellen:

extern volatile unsigned long timer0_millis;
void setMillis(unsigned long millis)  {
  byte sreg = SREG;
  cli();
  timer0_millis = millis;
  SREG = sreg;
}
void setup()  {
  Serial.begin(9600);
  setMillis(0xffffff00UL);                   
}
void loop()  {
  Serial.println(millis());  
  delay(5000);
}

(siehe: http://forum.arduino.cc/index.php?topic=366969.msg2529469#msg2529469)

Danke, jetzt ist es klarer.

uxomm:
Richtig macht man es so:

  • unsigned long für Zeitvariable verwenden (und nicht long)
  • einen Startzeitpunkt festhalten
  • den Startzeitpunkt von der aktuellen Zeit subtrahieren um auf die Differenz zu kommen
  • überprüfen ob diese Differenz größer ist als das gewünschte Intervall

z.B.: Intervall ist 1 Minute (60 000 millis)

(siehe: http://forum.arduino.cc/index.php?topic=366969.msg2529469#msg2529469)

warum subtrahieren ? habe das bei einem wait ohne delay auch schon gesehen.
Meiner Ansicht ist kurz nach dem Systemstart wenn millis zB. noch 20000 ist eine falsche Wartezeit 20000 minus 60000 ist nämlich 0 ?
Also besser die 60000 addieren und abfragen ob millis größer ist.

20000 minus 60000 ist nämlich 0 ?

Mein Arduino rechnet das anders.

Also besser die 60000 addieren und abfragen ob millis größer ist.

Und damit stürzt du in die Überlauffalle.

Sicherheitshalber initialisiert man die zu vergleichende Zeit am Ende von setup() mit millis().

combie:
Mein Arduino rechnet das anders.
Und damit stürzt du in die Überlauffalle.

Ohne aufwändiges abfangen ist man mit beiden Methoden in der Überlauffalle. Nach meiner Erfahrung mit einen ATMEGA 128 (Conrad C-Control Pro) erfolgt beim Einsatz eines exteren watchdogs ohnehin alle paar Tage ein Zwangsreset.

habs noch nicht probiert, aber von einer Unsign Long Variable einen größeren Betrag abzuziehen gibt entweder null oder eine mega riesige Zahl - in beiden Fällen gibt’s ein gewaltiges Funktionsproblem!! bei der Addition nur ein Überlaufproblem. Noch dazu kann die Addition bei der Festlegung des “Endwertes” gleich gemacht werden und in der Abfrageschleife erfolgt dann nur der Vergleich von millis mit dem Endwert (ohne einer zusätzlichen Rechenoperation)

@DrDietrich:
auch am Ende des Setups ist nur ein geringer Unterschied.

von einer Unsign Long Variable einen größeren Betrag abzuziehen gibt entweder null oder eine mega riesige Zahl

von einer Unsign Long Variable einen größeren Betrag abzuziehen gibt entweder null oder eine mega riesige Zahl
Und das rettet dich vor der Überlauffalle.

Tipp:
Werte zu millis() addieren, macht einen Überlauf(manchmal) und das will abgehandelt werden.
Werte von millis() zu subtrahieren, macht evtl einen Unterlauf, aber den kann man ganz entspannt ignorieren.

in beiden Fällen gibt's ein gewaltiges Funktionsproblem!!

Nöö....
Solange die Intervalle kleiner als 49,xx Tage sind, kein Problem.
Alles gut...

Noch dazu kann die Addition bei der Festlegung des "Endwertes" gleich gemacht werden und in der Abfrageschleife erfolgt dann nur der Vergleich von millis mit dem Endwert (ohne einer zusätzlichen Rechenoperation)

Überlaufabhandlung vergessen?

Nach meiner Erfahrung mit einen ATMEGA 128 (Conrad C-Control Pro) erfolgt beim Einsatz eines exteren watchdogs ohnehin alle paar Tage ein Zwangsreset.

Das dürfte ein ganz anderes Thema sein.

@combie
jetzt hab ichs durschaut, kostet zwar eine Rechenoperation, damit gibt's aber dann kein Überlaufproblem. (also auch für micros() einsetzbar.

Ernst16:
@combie
jetzt hab ichs durschaut, kostet zwar eine Rechenoperation, damit gibt's aber dann kein Überlaufproblem. (also auch für micros() einsetzbar.

Ja. Nur daß der Überlauf früher stattfindet, nach ca 70 Minuten und daß die Auflösung von micros 4µS ist (bei einem 16Mhz Arduino).
Das problem ist daß wie oben gezeigt ein Überlauf kein Problem ist, aber ein intervall größer der max Zeitspanne schon da nicht ersichtlich ist wie oft ein Überlauf erfolgt ist.
Dementsprechend micros kann für Intervalle bis 70 minuten verwendet werden, millis bis Intervalle von ca 49,7Tagen.

Grüße Uwe

Ernst16:
habs noch nicht probiert, aber von einer Unsign Long Variable einen größeren Betrag abzuziehen gibt entweder null oder eine mega riesige Zahl - in beiden Fällen gibt's ein gewaltiges Funktionsproblem!!

Du solltest etwas mehr über binäre Zahlenssteme und Arithmetik lernen. Dann halbieren sich schon mal die Probleme, die Du vermutest :wink:

Danke, jetzt kann ich meine wait ohne Delay Routinen berichtigen.
(habe eine "Verzögerungszeit" Routine mit 10 unabhängigen Modulen gemacht, wobei diese auch einzeln als Schrittschaltwerk eingesetzt werden können)

/*
StatusWarten[erste] = Startstep[erste] dann wird die Wartezeit gestartet, sofort mit Start erhöht sich der Status um eins
StatusWarten[erste] = Startstep[erste] +1 laufende abfrage ob Zielwert erreicht - dann plus eins
StatusWarten[erste] = Startstep[erste] +2 Zielwert erreicht ... im Programm Aktionen durchführen und Status auf Fertig
bzw. auf Schritt ausgeführt setzen.
StatusWarten[erste] = Startstep[erste] +3 Aktion bereits durchgeführt, Schritt abgeschlossen - keine Aktion.

StatusWarten[erste] = 0 ... keine Aktionen auf Abfrage kann auch verzichtet werden
StatusWarten[erste] = 1 ... Startphase eingeleitet (Step 1) (bei Nachlauffunktion für neusetzen des Zielwertes)
StatusWarten[erste] = 2 ... Wartezeit abgelaufen (Step 1)
StatusWarten[erste] = 3 ... Aktion durchführen und neuen Status einstellen zB 0 oder +1 = Schritt erledigt bereit für nächsten
StatusWarten[erste] = 4 ... Step 1 fertig ausgeführt, keine Aktion, bereit für Startphase Step 2
StatusWarten[erste] = 5 ... Startphase eingeleitet (Step 2)
StatusWarten[erste] = 6 ... Wartezeit abgelaufen (Step 2)
StatusWarten[erste] = 7 ... Aktion durchführen und neuen Status einstellen zB 0 oder +1 = Schritt erledigt bereit für nächsten
StatusWarten[erste] = 8 ... Step 2 fertig ausgeführt, keine Aktion, bereit für Startphase Step 3
StatusWarten[erste] = 9 ... Startphase eingeleitet (Step 3)
StatusWarten[erste] =10 ... Wartezeit abgelaufen (Step 3)
StatusWarten[erste] =11 ... Aktion durchführen und neuen Status einstellen zB 0 oder +1 = Schritt erledigt bereit für nächsten
StatusWarten[erste] =12 ... Step 3 fertig ausgeführt, keine Aktion, bereit für Startphase Step 4
StatusWarten[erste] =13 ... Startphase eingeleitet (Step 4)
StatusWarten[erste] =14 ... Wartezeit abgelaufen (Step 4)
StatusWarten[erste] =15 ... Aktion durchführen und neuen Status einstellen zB 0 oder +1 = Schritt erledigt bereit für nächsten
StatusWarten[erste] =16 ... Step 4 fertig ausgeführt, keine Aktion, bereit für Startphase Step 5
eventuell Error - Überwachung wenn StatusWarten[erste] zu hoch wird ?

*/