Wie gehe ich mit dem Rollover von millis () um?

73

Ich muss alle fünf Minuten einen Sensor ablesen, aber da meine Skizze auch andere Aufgaben zu erledigen hat, kann ich nicht einfach delay()zwischen den Ablesungen wechseln. Es gibt das Blink- Tutorial, das ohne Verzögerung vorschlägt, dass ich nach folgenden Grundsätzen codiere:

void loop()
{
    unsigned long currentMillis = millis();

    // Read the sensor when needed.
    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        readSensor();
    }

    // Do other stuff...
}

Das Problem ist, dass millis()es nach ungefähr 49,7 Tagen zu einem Rollover auf Null kommen wird. Da meine Skizze länger ausgeführt werden soll, muss ich sicherstellen, dass der Rollover meine Skizze nicht zum Scheitern bringt. Ich kann den Rollover-Zustand leicht erkennen ( currentMillis < previousMillis), bin mir aber nicht sicher, was ich dann tun soll.

Also meine Frage: Was wäre der richtige / einfachste Weg, um mit dem millis()Überschlag umzugehen?

Edgar Bonet
quelle
5
Anmerkung der Redaktion: Dies ist keine Frage von mir, sondern ein Tutorial in einem Frage / Antwort-Format. Ich habe im Internet (einschließlich hier) viel Verwirrung über dieses Thema erlebt, und diese Site scheint der naheliegende Ort zu sein, um nach einer Antwort zu suchen. Deshalb biete ich dieses Tutorial hier an.
Edgar Bonet
2
Ich würde tun, previousMillis += intervalanstatt previousMillis = currentMilliswenn ich eine bestimmte Häufigkeit von Ergebnissen wollte.
Jasen
4
@Jasen: Das stimmt! previousMillis += intervalwenn Sie eine konstante Frequenz wünschen und sicher sind, dass Ihre Verarbeitung weniger als interval, aber previousMillis = currentMillisfür die Gewährleistung einer minimalen Verzögerung von interval.
Edgar Bonet
Wir brauchen wirklich eine FAQ für solche Dinge.
Einer der "Tricks", die ich benutze, besteht darin, die Last auf dem Arduino zu verringern, indem ich das kleinste int verwende, das das Intervall enthält. Zum Beispiel schreibe ich für maximal 1-minütige Intervalleuint16_t previousMillis; const uint16_t interval = 45000; ... uint16_t currentMillis = (uint16_t) millis(); if ((currentMillis - previousMillis) >= interval) ...
frarugi87

Antworten:

95

Kurze Antwort: Versuchen Sie nicht, den Millis-Rollover zu „handhaben“, sondern schreiben Sie stattdessen einen rollover-sicheren Code. Ihr Beispielcode aus dem Tutorial ist in Ordnung. Wenn Sie versuchen, den Rollover zu erkennen, um Korrekturmaßnahmen zu ergreifen, ist die Wahrscheinlichkeit groß, dass Sie etwas falsch machen. Die meisten Arduino-Programme müssen nur Ereignisse verwalten, die sich über eine relativ kurze Zeitspanne erstrecken, z. B. das Entprellen einer Taste für 50 ms oder das Einschalten einer Heizung für 12 Stunden. Der Millis-Rollover sollte kein Problem sein.

Der richtige Weg, das Rollover-Problem zu verwalten (oder besser gesagt, es zu vermeiden), besteht darin, sich die zurückgegebene unsigned longZahl millis()in modularen Arithmetiken vorzustellen . Für Mathematiker ist eine gewisse Kenntnis dieses Konzepts beim Programmieren sehr hilfreich. Sie können die Mathematik in Aktion in Nick Gammons Artikel millis () overflow sehen ... eine schlechte Sache? . Für diejenigen, die die rechnerischen Details nicht durchgehen möchten, biete ich hier eine alternative (hoffentlich einfachere) Denkweise an. Es basiert auf der einfachen Unterscheidung zwischen Zeitpunkten und Dauern . Solange Ihre Tests nur Vergleichsdauern beinhalten, sollten Sie in Ordnung sein.

Anmerkung zu micros () : Alles, was hier über gesagt wird, millis()gilt gleichermaßen micros(), mit Ausnahme der Tatsache, dass micros()alle 71,6 Minuten ein Rollover ausgeführt wird und die setMillis()unten angegebene Funktion keinen Einfluss hat micros().

Zeitpunkte, Zeitstempel und Dauer

Im Umgang mit der Zeit müssen wir mindestens zwei verschiedene Konzepte unterscheiden: Momente und Dauer . Ein Moment ist ein Punkt auf der Zeitachse. Eine Dauer ist die Länge eines Zeitintervalls, dh der zeitliche Abstand zwischen den Zeitpunkten, die den Beginn und das Ende des Intervalls definieren. Die Unterscheidung zwischen diesen Begriffen ist in der Alltagssprache nicht immer sehr scharf. Wenn ich zum Beispiel sage, dass ich in fünf Minuten zurück sein werde , ist „ fünf Minuten “ die geschätzte Dauer meiner Abwesenheit, während „ in fünf Minuten “ der Zeitpunkt ist von meiner vorhergesagten Rückkehr. Es ist wichtig, die Unterscheidung im Auge zu behalten, da dies der einfachste Weg ist, das Problem des Überschlags vollständig zu vermeiden.

Der Rückgabewert von millis()könnte als Dauer interpretiert werden: die Zeit, die seit dem Start des Programms bis jetzt vergangen ist. Diese Interpretation bricht jedoch zusammen, sobald Millis überläuft. Es ist im Allgemeinen weitaus nützlicher, sich vorzustellen , dass millis()ein Zeitstempel zurückgegeben wird , dh ein „Etikett“, das einen bestimmten Zeitpunkt identifiziert. Es könnte argumentiert werden, dass diese Interpretation unter der Mehrdeutigkeit dieser Etiketten leidet, da sie alle 49,7 Tage wiederverwendet werden. Dies ist jedoch selten ein Problem: In den meisten eingebetteten Anwendungen ist alles, was vor 49,7 Tagen passiert ist, eine alte Geschichte, die uns egal ist. Daher sollte das Recycling der alten Etiketten kein Problem darstellen.

Vergleichen Sie keine Zeitstempel

Es macht keinen Sinn, herauszufinden, welcher der beiden Zeitstempel größer ist als der andere. Beispiel:

unsigned long t1 = millis();
delay(3000);
unsigned long t2 = millis();
if (t2 > t1) { ... }

Naiv würde man erwarten, dass die Bedingung der if ()immer wahr ist. Aber es wird tatsächlich falsch sein, wenn Millis während überläuft delay(3000). T1 und t2 als wiederverwertbare Etiketten zu betrachten, ist der einfachste Weg, um den Fehler zu vermeiden: Das Etikett t1 wurde eindeutig einem Zeitpunkt vor t2 zugewiesen, aber in 49,7 Tagen wird es einem zukünftigen Zeitpunkt zugewiesen. Somit geschieht t1 sowohl vor als auch nach t2. Dies sollte deutlich machen, dass der Ausdruck t2 > t1keinen Sinn ergibt.

Wenn es sich jedoch nur um Etiketten handelt, ist die offensichtliche Frage: Wie können wir mit ihnen nützliche Zeitberechnungen durchführen? Die Antwort lautet: indem wir uns auf die beiden einzigen Berechnungen beschränken, die für Zeitstempel sinnvoll sind:

  1. later_timestamp - earlier_timestampergibt eine Dauer, nämlich die Zeitdauer, die zwischen dem früheren und dem späteren Zeitpunkt verstrichen ist. Dies ist die nützlichste Rechenoperation mit Zeitstempeln.
  2. timestamp ± durationGibt einen Zeitstempel zurück, der einige Zeit nach (wenn + verwendet wird) oder vor (wenn -) dem ursprünglichen Zeitstempel liegt. Nicht so nützlich, wie es sich anhört, da der resultierende Zeitstempel nur für zwei Arten von Berechnungen verwendet werden kann ...

Dank der modularen Arithmetik funktioniert beides garantiert über den gesamten Millis-Rollover hinweg, zumindest solange die Verzögerungen kürzer als 49,7 Tage sind.

Das Vergleichen der Dauer ist in Ordnung

Eine Dauer ist nur die Anzahl der Millisekunden, die in einem bestimmten Zeitintervall verstrichen sind. Solange wir nicht länger als 49,7 Tage arbeiten müssen, sollte jede Operation, die physikalisch sinnvoll ist, auch rechnerisch sinnvoll sein. Wir können zum Beispiel eine Dauer mit einer Frequenz multiplizieren, um eine Anzahl von Perioden zu erhalten. Oder wir können zwei Zeiträume vergleichen, um zu wissen, welcher länger ist. Zum Beispiel sind hier zwei alternative Implementierungen von delay(). Zunächst der Buggy:

void myDelay(unsigned long ms) {          // ms: duration
    unsigned long start = millis();       // start: timestamp
    unsigned long finished = start + ms;  // finished: timestamp
    for (;;) {
        unsigned long now = millis();     // now: timestamp
        if (now >= finished)              // comparing timestamps: BUG!
            return;
    }
}

Und hier ist der Richtige:

void myDelay(unsigned long ms) {              // ms: duration
    unsigned long start = millis();           // start: timestamp
    for (;;) {
        unsigned long now = millis();         // now: timestamp
        unsigned long elapsed = now - start;  // elapsed: duration
        if (elapsed >= ms)                    // comparing durations: OK
            return;
    }
}

Die meisten C-Programmierer würden die obigen Schleifen in einer Terser-Form schreiben, wie z

while (millis() < start + ms) ;  // BUGGY version

und

while (millis() - start < ms) ;  // CORRECT version

Obwohl sie täuschend ähnlich aussehen, sollte die Unterscheidung zwischen Zeitstempel und Dauer deutlich machen, welcher fehlerhaft und welcher korrekt ist.

Was ist, wenn ich Zeitstempel wirklich vergleichen muss?

Versuchen Sie besser, die Situation zu vermeiden. Wenn es unvermeidlich ist, gibt es noch Hoffnung, wenn bekannt ist, dass die jeweiligen Momente nahe genug sind: näher als 24,85 Tage. Ja, unsere maximal handhabbare Verzögerung von 49,7 Tagen wurde gerade halbiert.

Die naheliegende Lösung besteht darin, unser Zeitstempel-Vergleichsproblem in ein Dauer-Vergleichsproblem umzuwandeln. Angenommen, wir müssen wissen, ob der Zeitpunkt t1 vor oder nach t2 liegt. Wir wählen einen Referenzzeitpunkt in ihrer gemeinsamen Vergangenheit und vergleichen die Zeitdauern von dieser Referenz bis sowohl t1 als auch t2. Der Referenzzeitpunkt wird durch Subtrahieren einer ausreichend langen Dauer von entweder t1 oder t2 erhalten:

unsigned long reference_instant = t2 - LONG_ENOUGH_DURATION;
unsigned long from_reference_until_t1 = t1 - reference_instant;
unsigned long from_reference_until_t2 = t2 - reference_instant;
if (from_reference_until_t1 < from_reference_until_t2)
    // t1 is before t2

Dies kann vereinfacht werden als:

if (t1 - t2 + LONG_ENOUGH_DURATION < LONG_ENOUGH_DURATION)
    // t1 is before t2

Es ist verlockend, weiter zu vereinfachen if (t1 - t2 < 0). Dies funktioniert natürlich nicht, da t1 - t2eine vorzeichenlose Zahl nicht negativ sein kann. Dies funktioniert jedoch, obwohl es nicht portabel ist:

if ((signed long)(t1 - t2) < 0)  // works with gcc
    // t1 is before t2

Das signedobige Schlüsselwort ist überflüssig (eine Ebene longist immer signiert), aber es hilft, die Absicht klar zu machen. Das Konvertieren in eine vorzeichenbehaftete Long- LONG_ENOUGH_DURATIONPosition entspricht einer Einstellung von 24,85 Tagen. Der Trick ist nicht portierbar, da gemäß dem C-Standard das Ergebnis in der Implementierung definiert ist . Aber da der GCC-Compiler verspricht, das Richtige zu tun , funktioniert er zuverlässig auf Arduino. Wenn wir implementierungsdefiniertes Verhalten vermeiden möchten, ist der oben angegebene Vergleich mathematisch äquivalent zu:

#include <limits.h>

if (t1 - t2 > LONG_MAX)  // too big to be believed
    // t1 is before t2

mit dem einzigen problem, dass der vergleich rückwärts schaut. Es ist auch äquivalent zu diesem Einzelbittest, solange Longs 32-Bit sind:

if ((t1 - t2) & 0x80000000)  // test the "sign" bit
    // t1 is before t2

Die letzten drei Tests werden von gcc tatsächlich in genau denselben Maschinencode kompiliert.

Wie teste ich meine Skizze gegen den Millis-Rollover?

Wenn Sie die obigen Vorschriften befolgen, sollten Sie alle gut sein. Wenn Sie dennoch testen möchten, fügen Sie diese Funktion Ihrer Skizze hinzu:

#include <util/atomic.h>

void setMillis(unsigned long ms)
{
    extern unsigned long timer0_millis;
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
        timer0_millis = ms;
    }
}

und Sie können jetzt Ihr Programm per Zeitreise aufrufen setMillis(destination). Wenn Sie möchten, dass es immer wieder durch den Millis-Überlauf geht, wie Phil Connors, der den Groundhog Day noch einmal durchlebt, können Sie dies hier einfügen loop():

// 6-second time loop starting at rollover - 3 seconds
if (millis() - (-3000) >= 6000)
    setMillis(-3000);

Der negative Zeitstempel über (-3000) wird vom Compiler implizit in einen vorzeichenlosen Wert konvertiert, der 3000 Millisekunden vor dem Rollover entspricht (er wird in 4294964296 konvertiert).

Was ist, wenn ich wirklich sehr lange Zeiträume erfassen muss?

Wenn Sie ein Relais drei Monate später ein- und ausschalten müssen, müssen Sie die Millis-Überläufe wirklich nachverfolgen. Es gibt viele Möglichkeiten, dies zu tun. Die einfachste Lösung könnte darin bestehen, einfach millis() auf 64 Bit zu erweitern:

uint64_t millis64() {
    static uint32_t low32, high32;
    uint32_t new_low32 = millis();
    if (new_low32 < low32) high32++;
    low32 = new_low32;
    return (uint64_t) high32 << 32 | low32;
}

Dies zählt im Wesentlichen die Rollover-Ereignisse und verwendet diese Zählung als die 32 höchstwertigen Bits einer 64-Bit-Millisekunden-Zählung. Damit diese Zählung ordnungsgemäß funktioniert, muss die Funktion mindestens alle 49,7 Tage einmal aufgerufen werden. Wenn es jedoch nur einmal alle 49,7 Tage aufgerufen wird, ist es in einigen Fällen möglich, dass die Prüfung (new_low32 < low32)fehlschlägt und der Code eine Zählung von nicht besteht high32. Die Verwendung von millis (), um zu entscheiden, wann der einzige Aufruf dieses Codes in einem einzigen "Wrap" von millis (einem bestimmten 49,7-Tage-Fenster) erfolgen soll, kann abhängig von der Ausrichtung der Zeitrahmen sehr gefährlich sein. Wenn Sie mithilfe von millis () ermitteln, wann die einzigen Aufrufe von millis64 () erfolgen sollen, sollten aus Sicherheitsgründen mindestens zwei Aufrufe in jedem 49,7-Tage-Fenster erfolgen.

Beachten Sie jedoch, dass 64-Bit-Arithmetik auf dem Arduino teuer ist. Es kann sich lohnen, die Zeitauflösung zu reduzieren, um bei 32 Bit zu bleiben.

Edgar Bonet
quelle
2
Sie sagen also, dass der in der Frage geschriebene Code tatsächlich korrekt funktioniert?
Jasen
3
@Jasen: Genau! Ich habe mehr als einmal versucht, das Problem zu beheben, das es überhaupt nicht gab.
Edgar Bonet
2
Ich bin froh, dass ich das gefunden habe. Ich habe diese Frage schon einmal gehabt.
Sebastian Freeman
1
Eine der besten und nützlichsten Antworten auf StackExchange! Danke vielmals! :)
Falko
Dies ist eine so erstaunliche Antwort auf die Frage. Ich komme im Grunde einmal im Jahr auf diese Antwort zurück, weil ich paranoid bin, Überschläge durcheinander zu bringen.
Jeffrey Cash
17

TL; DR Kurzfassung:

An unsigned longist 0 bis 4 294 967 295 (2 ^ 32 - 1).

Sagen wir previousMillisalso 4.294.967.290 (5 ms vor dem Überschlag) und currentMillis10 (10 ms nach dem Überschlag). Dann currentMillis - previousMillisist es tatsächlich 16 (nicht -4.294.967.280), da das Ergebnis als vorzeichenloses Long berechnet wird (was nicht negativ sein kann, so dass es sich selbst dreht ). Sie können dies einfach überprüfen, indem Sie:

Serial.println( ( unsigned long ) ( 10 - 4294967290 ) ); // 16

Der obige Code funktioniert also einwandfrei. Der Trick besteht darin, immer die Zeitdifferenz zu berechnen und nicht die beiden Zeitwerte zu vergleichen.

Gerben
quelle
Wie wäre es mit 15 ms vor dem Überschlag und 10 ms nach dem Überschlag (dh 49,7 Tage danach ). 15> 10 , aber der 15ms Stempel ist fast anderthalb Monate alt. 15-10> 0 und 10-15> 0 unsigned Logik, so dass hier nichts zu gebrauchen ist!
ps95
@ prakharsingh95 10 ms-15 ms werden ~ 49,7 Tage - 5 ms, was der richtige Unterschied ist. Die Mathematik funktioniert so lange, bis millis()zweimal ein Rollover ausgeführt wird. Es ist jedoch sehr unwahrscheinlich, dass dies bei dem betreffenden Code der Fall ist.
BrettAM,
Lass mich umformulieren. Angenommen, Sie haben zwei Zeitstempel von 200 ms und 10 ms. Woran erkennt man, was überrollt ist (sind)?
ps95
@ prakharsingh95 Der in gespeicherte Wert previousMillismuss zuvor gemessen worden sein currentMillis, wenn currentMillisalso ein kleinerer Wert als previousMillisein Überschlag aufgetreten ist. Es hat sich herausgestellt, dass Sie nicht einmal darüber nachdenken müssen, es sei denn, es sind zwei Überschläge aufgetreten.
BrettAM
1
Ach ja ok wenn Sie das tun t2-t1, und wenn Sie garantieren können , t1bevor gemessen wird , t2dann ist es gleichbedeutend mit unterzeichnet (t2-t1)% 4,294,967,295 , damit die Auto - Wraparound. Nett!. Was aber, wenn es zwei Überschläge gibt oder interval> 4.294.967.295?
ps95
1

Wickeln Sie die millis()in einer Klasse!

Logik:

  1. Verwenden Sie IDs anstelle von millis()direkt.
  2. Vergleichen Sie Umkehrungen mit IDs. Dies ist sauber und überschlagsunabhängig.
  3. Um für bestimmte Anwendungen die exakte Differenz zwischen zwei IDs zu berechnen, müssen Sie die Umkehrungen und Stempel nachverfolgen. Berechnen Sie die Differenz.

Rückbuchungen nachverfolgen:

  1. Aktualisieren Sie einen lokalen Stempel regelmäßig schneller als millis(). Auf diese Weise können Sie feststellen, ob millis()ein Überlauf aufgetreten ist.
  2. Die Zeitdauer des Timers bestimmt die Genauigkeit
class Timer {

public:
    static long last_stamp;
    static long *stamps;
    static int *reversals;
    static int count;
    static int reversal_count;

    static void setup_timer() {
        // Setup Timer2 overflow to fire every 8ms (125Hz)
        //   period [sec] = (1 / f_clock [sec]) * prescale * (255-count)
        //                  (1/16000000)  * 1024 * (255-130) = .008 sec


        TCCR2B = 0x00;        // Disable Timer2 while we set it up

        TCNT2  = 130;         // Reset Timer Count  (255-130) = execute ev 125-th T/C clock
        TIFR2  = 0x00;        // Timer2 INT Flag Reg: Clear Timer Overflow Flag
        TIMSK2 = 0x01;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
        TCCR2A = 0x00;        // Timer2 Control Reg A: Wave Gen Mode normal
        TCCR2B = 0x07;        // Timer2 Control Reg B: Timer Prescaler set to 1024

        count = 0;
        stamps = new long[50];
        reversals = new int [10];
        reversal_count =0;
    }

    static long get_stamp () {
        stamps[count++] = millis();
        return count-1;
    }

    static bool compare_stamps_by_id(int s1, int s2) {
        return s1 > s2;
    }

    static long long get_stamp_difference(int s1, int s2) {
        int no_of_reversals = 0;
        for(int j=0; j < reversal_count; j++)
        if(reversals[j] < s2 && reversals[j] > s1)
            no_of_reversals++;
        return stamps[s2]-stamps[s1] + 49.7 * 86400 * 1000;       
    }

};

long Timer::last_stamp;
long *Timer::stamps;
int *Timer::reversals;
int Timer::count;
int Timer::reversal_count;

ISR(TIMER2_OVF_vect) {

    long stamp = millis();
    if(stamp < Timer::last_stamp) // reversal
        Timer::reversals[Timer::reversal_count++] = Timer::count;
    else 
        ; // no reversal
    Timer::last_stamp = stamp;    
    TCNT2 = 130;     // reset timer ct to 130 out of 255
    TIFR2 = 0x00;    // timer2 int flag reg: clear timer overflow flag
};

// Usage

void setup () {
    Timer::setup_timer();

    long s1 = Timer::get_stamp();
    delay(3000);
    long s2 = Timer::get_stamp();

    Timer::compare_stamps_by_id(s1, s2); // true

    Timer::get_stamp_difference(s1, s2); // return true difference, taking into account reversals
}

Timer Credits .

ps95
quelle
9
Ich habe den Code bearbeitet, um die maaaaany-Fehler zu entfernen, die das Kompilieren verhinderten. Dieses Zeug kostet Sie ungefähr 232 Bytes RAM und zwei PWM-Kanäle. Es wird auch nach get_stamp()51-maliger Beschädigung des Speichers beginnen . Der Vergleich von Verzögerungen anstelle von Zeitstempeln ist mit Sicherheit effizienter.
Edgar Bonet,
1

Ich habe diese Frage geliebt und die tollen Antworten, die sie hervorgebracht hat. Zuerst ein kurzer Kommentar zu einer vorherigen Antwort (ich weiß, ich weiß, aber ich habe noch keinen Repräsentanten, um einen Kommentar abzugeben. :-).

Edgar Bonets Antwort war erstaunlich. Ich programmiere seit 35 Jahren und habe heute etwas Neues gelernt. Danke. Das heißt, ich glaube, der Code für "Was ist, wenn ich wirklich sehr lange Zeiträume verfolgen muss?" bricht ab, es sei denn, Sie rufen millis64 () mindestens einmal pro Rollover-Zeitraum auf. Wirklich pingelig, und es ist unwahrscheinlich, dass es in einer realen Implementierung zu Problemen kommt, aber los geht's.

Wenn Sie wirklich Zeitstempel für einen vernünftigen Zeitbereich benötigen (64-Bit-Millisekunden sind nach meiner Einschätzung etwa eine halbe Milliarde Jahre), scheint es einfach, die vorhandene millis () -Implementierung auf 64-Bit zu erweitern.

Diese Änderungen an attinycore / wiring.c (ich arbeite mit dem ATTiny85) scheinen zu funktionieren (ich gehe davon aus, dass der Code für andere AVRs sehr ähnlich ist). Siehe die Zeilen mit den Kommentaren // BFB und der neuen Funktion millis64 (). Es ist klar, dass es sowohl größer (98 Byte Code, 4 Byte Daten) als auch langsamer sein wird, und wie Edgar betonte, können Sie Ihre Ziele mit ziemlicher Sicherheit mit einem besseren Verständnis der vorzeichenlosen ganzzahligen Mathematik erreichen, aber es war eine interessante Übung .

volatile unsigned long long timer0_millis = 0;      // BFB: need 64-bit resolution

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
    // copy these to local variables so they can be stored in registers
    // (volatile variables must be read from memory on every access)
    unsigned long long m = timer0_millis;       // BFB: need 64-bit resolution
    unsigned char f = timer0_fract;

    m += MILLIS_INC;
    f += FRACT_INC;
    if (f >= FRACT_MAX) {
        f -= FRACT_MAX;
        m += 1;
    }

    timer0_fract = f;
    timer0_millis = m;
    timer0_overflow_count++;
}

// BFB: 64-bit version
unsigned long long millis64()
{
    unsigned long long m;
    uint8_t oldSREG = SREG;

    // disable interrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
    m = timer0_millis;
    SREG = oldSREG;

    return m;
}
Brainbarker
quelle
1
Du hast recht, mein millis64()funktioniert nur, wenn es häufiger aufgerufen wird als die Überlaufzeit. Ich habe meine Antwort bearbeitet, um auf diese Einschränkung hinzuweisen. Ihre Version hat dieses Problem nicht, weist jedoch einen weiteren Nachteil auf: Sie führt 64-Bit-Arithmetik im Interrupt-Kontext aus , wodurch sich die Wartezeit beim Reagieren auf andere Interrupts gelegentlich erhöht.
Edgar Bonet