Um das Problem des Überlaufs der millis()-Funktion beim Arduino nach 49
Tagen zu lösen, verwende ich bei Time-Outs immer folgenden Code:
1
unsignedlongtimeout=millis()+1000;
2
while(((timeout-millis())&0x80000000)==0){
3
...
Ich bin mir aber nicht ganz sicher, ob das wirklich eine gute Idee bzw.
eine gute Formulierung in C ist. Zumindest funktioniert es auch, wenn
millis() sich 4294967295 nähert.
Hat jemand eine effizientere (= erzeugt besseren Maschienencode) oder
schönere (= ist im Sourcecode einfach besser zu verstehen) Lösung, die
auch funktioniert?
Wenn du Timeouts brauchst, dann zähl doch rückwärts von Start bis 0.
Also:
timeout = x
Immer wenn millis() sich verändert hat (new != old) : timeout--
Wenn timeout = 0: Reaktion ...
@ derjaeger: Denkbar, aber dann brauche ich zusätzlich zum Vergleich von
timeout auf 0 oder kleiner (was ja genaugenommen nicht geht, da
unsigned) auch noch einen timeout-- (bei 32 Bit mittelmäßgi aufwändig).
pete schrieb:> @Arduino Fanboy D.: unsigned long currentMillis = millis();>> if(currentMillis - previousMillis > interval) {> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf
Du möchtest was funktionierendes!
Deins funkioniert nicht.
Dieses ja.
Es gibt natürlich noch mehr Wege, sich eine Zeitbasis zu beschaffen.
Aber wenn du millis() nutzen möchtest, dann ist genau DAS der Weg.
pete schrieb:> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf
Bei dir:
4-Byte-Subtraktion
4-Byte And
4-Byte Vergleich
= Noch mehr CPU-Bedarf
Arduino Fanboy D. schrieb:>> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf>> Bei dir:> 4-Byte-Subtraktion> 4-Byte And> 4-Byte Vergleich>> = Noch mehr CPU-Bedarf
Nö, denn nach der 4-Byte-Subtraktion wird einfach auf Overflow-Flag
getestet.
Aber ich hatte draum gebeten, zu zeigen, warum mein Programmausschnitt
nicht funktioniert:
derjaeger schrieb:>>Aha. Und was genau funktioniert nicht?>> Nach 49 Tagen funktionierts nicht.
derjaeger schrieb:> Nach 49 Tagen funktionierts nicht.
Doch. Der Witz ist, dass unsigned-Subtraktionen auch bei Überläufen
funktionieren.
Beispiel, welches das Verhalten von millis() simuliert - hier mal unter
Linux:
1
#include<stdio.h>
2
#include<stdint.h>
3
4
intmain()
5
{
6
uint32_tstart=0xFFFFFFFF;// Start der Messung per millis() - kurz vor Überlauf
7
uint32_tnow=1;// Ende der Messung per millis() - nach Überlauf
8
9
printf("%u\n",now-start);
10
return0;
11
}
Das Programm gibt 2 aus. Das funktioniert auch mit beliebigen anderen
Werten.
Frank M. schrieb:> Doch. Der Witz ist, dass unsigned-Subtraktionen auch bei Überläufen> funktionieren.
Hier eine alternative Variante des gleichen Themas:
(welche korrekt und über den gesamten Wertebereich funktioniert)
pete schrieb:> Um das Problem des Überlaufs der millis()-Funktion beim Arduino nach 49> Tagen zu lösen, verwende ich bei Time-Outs immer folgenden Code:
1
>unsignedlongtimeout=millis()+1000;
2
>while(((timeout-millis())&0x80000000)==0){
3
>...
Ich geh sogar soweit, dass mein Verfahren "beweisbar" richtig ist:
Ich setze für "timeout" die Definition ein, dann folgt für den
Vergleich:
1
while(((millis()+1000-millis())&0x80000000)==0){
und damit:
1
while(((1000)&0x80000000)==0){
Nun ist klar, dass mit forschreitender Zeit, die "1000" kleiner wird,
bis es genau nach dem Wert "0" einen Underflow gibt, bei dem es zu
0xFFFFFFFF und kleineren Werten kommt. Um genau zu sein kommt es fast 25
Tage lang zu Werten, dessen höchstes Bit gesetzt ist.
Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.
Brauchst Du denn überhaupt diesen, langen Zeitraum?
Wenn nicht: Einfach, bei der aktuellen Abfrage, den letzten Wert
speichern und dann abfragen ob der neue Wert kleiner ist. ...und
natürlich berücksichtigen.
pete schrieb:> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.
Die While Schleife blockiert den restlichen Code.
Sowas braucht man doch nicht, oder?
Arduino Fanboy D. schrieb:> Die While Schleife blockiert den restlichen Code.> Sowas braucht man doch nicht, oder?
Doch, z.B. wenn man eine zeitlang (-> timeout) auf eine Antwort warten
muss, sie aber verloren gehen kann und man dann z.B. erneut anfragen
muss ...
pete schrieb:> Ich geh sogar soweit, dass mein Verfahren "beweisbar" richtig ist:>> Ich setze für "timeout" die Definition ein, dann folgt für den> Vergleich: while ( ((millis() + 1000 - millis()) & 0x80000000) == 0> ) {>> und damit: while ( ((1000) & 0x80000000) == 0 ) {
Du kannst die millis() hier nicht einfach so rausstreichen, Du kannst
noch nichtmals timeout durch "millis() + 1000" einfach so ersetzen. Der
erste Rückgabewert von millis() ist konstant. Der zweite Rückgabewert
ändert sich laufend. Von daher halte ich Deinen "Beweis" für äußerst
fragwürdig.
Ob Dein Verfahren tatsächlich weniger CPU-Power braucht als der
klassische Weg:
1
unsignedlongstart=millis();
2
3
while(millis()-start<1000)// ja, das ist überlauf-fest!
4
{
5
;
6
}
kann man nur beurteilen, wenn man sich den Assembler-Output des
Compilers anschaut.
Das hier:
> und damit: while ( ((1000) & 0x80000000) == 0 ) {
ist jedenfalls eine Endlosschleife.
Frank M. schrieb:> Das hier:>>> und damit: while ( ((1000) & 0x80000000) == 0 ) {>> ist jedenfalls eine Endlosschleife.
Klar, das war ja nur die Idee für einen richtigen Beweis. Den müsste ein
Informatiker liefern. Das bin ich nicht.
Aber vielleicht habe ich mir die Frage selbst beantwortet, als ich
vorige Antwort mit folgender Zeile schrieb:
pete schrieb:> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.
Gerade getestet...
Es funktioniert nicht!
Es versagt.
Definitivpete schrieb:> Doch, z.B. wenn man eine zeitlang (-> timeout) auf eine Antwort warten> muss, sie aber verloren gehen kann und man dann z.B. erneut anfragen> muss ...
Wie gesagt, das ist blockierendes Warten.
Meist mehr Nachteile, als Voreile.
Damit meist unbrauchbar.
pete schrieb:> Ich bin mir nur nicht sicher, dass es wirklich funktioniert (Unterschied> allg. Theorie vs. konkrete Realität eines AVR 328)
Einfach mal testen auf dem PC, dafür braucht man keinen AVR:
Arduino Fanboy D. schrieb:> pete schrieb:>> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.> Gerade getestet...> Es funktioniert nicht!> Es versagt.> Definitiv
Man man man. Du lehnst Dich hier aus dem Fenster und behauptest voller
Überzeugung etwas so dermaßen falsches! Ich habe nie verstanden, wo
solche Leute dieses unberechtigte Selbstvertrauen hernehmen (das müßte
man auch mal klären).
Im folgenden ein Arduino-Sketch, der die Funktionsfähigkeit des Timeout
demonstriert:
1
// Test timeout via millis() with 16-bit variable and overflow
2
3
constintledPin=13;
4
charledState=LOW;
5
unsignedshorttimeout,prev_time;
6
7
voidsetup(){
8
Serial.begin(115200);
9
pinMode(ledPin,OUTPUT);
10
timeout=millis()+500;// 0.5s
11
}
12
13
voidloop()
14
{
15
unsignedshorttime=(unsignedshort)millis();
16
17
if((signedshort)(timeout-time)<=0){
18
Serial.println(time-prev_time);
19
Serial.println(time);
20
prev_time=time;
21
timeout=time+500;
22
ledState=(ledState==LOW)?HIGH:LOW;
23
digitalWrite(ledPin,ledState);
24
}
25
}
Damit dürfte geklärt sein, dass meine Lösung aus erstem Post
funktioniert und dass diese Variante für Timeouts bis zu 32 Sekunden
auch funktioniert, und wesentlich weniger CPU Takte kostet.
Meine Frage aus 1. Post ist aber nicht beantwortet.
pete schrieb:> while ( ((timeout - millis()) & 0x80000000) == 0 ) {
Das ist faktisch das gleiche wie < 0 bei signed.
Es gibt zig Möglichkeiten, aus der Differenz signed zu machen, am Ende
sollte man derartige Basics, weil sie immer wieder und mehrfach
gebraucht werden, entsprechend Kapseln. Makros, Inline oder Funktionen,
je nach Vorliebe. Mein Favorit in diesem Kontext: eine signed Funktionen
für eine Differenz:
#dedine diff(t) ((long)(t-millis())
Oder typsicher als (inline-) Funktion.
Ich benutze natürlich als Zeitbasis eigene typedefs.
pete schrieb:> Meine Frage aus 1. Post ist aber nicht beantwortet.
Es ist zu empfehlen (wg. Bitoperation) das Literal explizit auch als
unsigned long zu kennzeichnen ("UL" Postfix).
Die realisierte Verzögerung ist im Worst-case +1 zur Gewünschten
(Beispiel: Eine Verzögerung von 0 wird im Worst-case eine Verzögerung um
1 sein). Das ist evtl. relevant wenn dieser Code mehrfach genutzt wird.
Zum Beispiel 1000x eine Verzögerung von 1 ergibt im Worst-case 2000.
Arduino Fanboy D. schrieb:> pete schrieb:>> unberechtigte Selbstvertrauen>> Ja, ich muss gestehen, dass mein Test fehlerhaft war.> Es schient zu funktionieren.
Du hast ja freundlicherweise Deinen noch großmundigeren Post selbst
gelöscht, weil Du feststellen musstest, dass mein Code doch OK ist.
Lass das "scheint" weg, und wir sind wieder Freunde.
Ich habe in der Zwischenzeit herausgefunden, warum Dein gelöschter
Testcode so merkwürdig arbeitet:
1) hattest Du einen Tippfehler bei (es fehlt eine 0):
> AtomicSection timer0_millis = 0x8000000UL; // versagt
2) Folgende Initialisierung der globalen Variable
> unsigned long timeout = millis() + 1000;
wird zu einem undefinierten Zeitpunkt vor setup() ausgeführt, so dass
die Laufzeit von setup() von der einen Sekunde abgezogen wird.
Und komischerweise braucht die Zeile aus 1) dann so lange, dass der
timeout bereits vorbei ist und Deine LED niemals blinkt.
3) So habe ich den timeout gar nicht gemeint, sondern wie in meinem
Beispielprogramm.
Hier nochmal für long und mit Deiner schönen Idee, den timer zu
verstellen, um zu testen:
A. S. schrieb:> pete schrieb:>> while ( ((timeout - millis()) & 0x80000000) == 0 ) {>> Das ist faktisch das gleiche wie < 0 bei signed.x^2 schrieb:> Es ist zu empfehlen (wg. Bitoperation) das Literal explizit auch als> unsigned long zu kennzeichnen ("UL" Postfix).>> Die realisierte Verzögerung ist im Worst-case +1 zur Gewünschten> (Beispiel: Eine Verzögerung von 0 wird im Worst-case eine Verzögerung um> 1 sein). Das ist evtl. relevant wenn dieser Code mehrfach genutzt wird.> Zum Beispiel 1000x eine Verzögerung von 1 ergibt im Worst-case 2000.
Beides sehr wichtige Kommentare:
1) UL hinzugefügt:
1
unsignedlongtimeout=millis()+1000;
2
while(((timeout-millis())&0x80000000UL)==0){
2) Äquivalent und schöner:
1
unsignedlongtimeout=millis()+1000;
2
while((signedlong)(timeout-millis())>=0){
3)
Beides ist fast 1ms zu lang, daher die bisher beste Variante:
pete schrieb:> Beides ist fast 1ms zu lang, daher die bisher beste Variante:
Das ist eine grundsätzliche Frage, deren Antwort man sich mit dem Aufruf
0 bzw. 1 vergegenwärtigen sollte:
Welche Zeit mit Zeit 0 bzw. 1 erwartet wird:
0: a) 0...999µs oder b)immer sofort?
1: a) 1000 ... 1999µs oder b) 0..999µs?
a: >=0 (bzw. if diff < 0)
b: >0 (bzw. if diff <=0)
Beide Interpretationen haben ihre Berechtigung, und es macht keinen
Sinn, eine als die einzig richtige hinzustellen. Darum sollte man immer
wissen, wie ein Sleep(0) bzw. Sleep(1) in seinem Rtos wirkt.
Ich verweise an dieser Stelle auf die Nutzungsbedingungen. Auch als Gast
sollte man innerhalb eines Threads den Namen beibehalten und nicht
wechseln.
Ich bitte auch, beim Thema zu bleiben.
Soweit ich informiert bin, ist "signed overflow" in C undefiniert,
"unsigned overflow" hingegen nicht. Daher ist ersteres per se schlechter
als letzteres.
Aber macht ihr nur. :-)
Nico W. schrieb:> Den Godbolt gibt es auch für den AVR-GCC...>> Kann ja jeder für sich selbst entscheiden, was er da nehmen möchte.
Schöner Beitrag, aber wenn der gcc so dämlich ist, dann hilf ihm halt
auf die Sprünge und schreib in Deinem Beispiel statt
1
while((int32_t)(timeout-millis())>0);
eben
1
while((int32_t)(millis()-timeout)<0);
Das wird nämlich zu
1
.L3:
2
lds r24,timer
3
lds r25,timer+1
4
lds r26,timer+2
5
lds r27,timer+3
6
sub r24,r20
7
sbc r25,r21
8
sbc r26,r22
9
sbc r27,r23
10
brmi .L3
und ist damit, wie es der gesunde Menschenverstand erwarten lässt, um
einiges effizienter als die von Dir bevorzugte Zeile: