Verwenden von millis () und micros () in einer Interruptroutine


13

Die Dokumentation für attachInterrupt()sagt:

... millis()basiert auf Interrupts, um zu zählen, sodass es in einem ISR niemals inkrementiert wird. Da delay()Interrupts erforderlich sind, funktioniert es nicht, wenn es in einem ISR aufgerufen wird. micros()Funktioniert anfangs, verhält sich jedoch nach 1-2 ms unregelmäßig. ...

Wie unterscheidet micros()sich von millis()(außer natürlich für ihre Präzision)? Bedeutet die obige Warnung, dass die Verwendung micros()innerhalb einer Interruptroutine immer eine schlechte Idee ist?

Kontext - Ich möchte eine niedrige Pulsbelegung messen , also muss ich meine Routine auslösen, wenn sich mein Eingangssignal ändert, und die aktuelle Zeit aufzeichnen.

Antworten:


16

Die anderen Antworten sind sehr gut, aber ich möchte näher darauf eingehen, wie das micros()funktioniert. Es liest immer den aktuellen Hardware-Timer (möglicherweise TCNT0), der ständig von der Hardware aktualisiert wird (tatsächlich alle 4 µs aufgrund des Prescaler von 64). Anschließend wird die Anzahl der Timer 0-Überläufe hinzugefügt, die durch einen Timer-Überlauf-Interrupt (multipliziert mit 256) aktualisiert wird.

Somit können Sie sich auch innerhalb eines ISR auf die micros()Aktualisierung verlassen. Allerdings , wenn Sie zu lange warten , dann verpassen Sie den Überlauf zu aktualisieren, und dann wird das Ergebnis zurück nach unten gehen (dh Sie 253 erhalten wird, 254, 255, 0, 1, 2, 3 usw.)

Dies ist micros()- etwas vereinfacht, um Definitionen für andere Prozessoren zu entfernen:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Der obige Code ermöglicht den Überlauf (er überprüft das TOV0-Bit), damit er mit dem Überlauf fertig wird, wenn die Interrupts ausgeschaltet sind, aber nur einmal - es ist nicht vorgesehen, zwei Überläufe zu behandeln.


TLDR;

  • Verzögern Sie nicht innerhalb eines ISR
  • Wenn du sie machen musst, kannst du das dann mit micros()aber nicht millis(). Auch delayMicroseconds()ist eine Möglichkeit.
  • Verzögern Sie nicht länger als 500 µs oder Sie werden einen Timer-Überlauf verpassen.
  • Selbst kurze Verzögerungen können dazu führen, dass Sie eingehende serielle Daten verpassen (bei 115200 Baud erhalten Sie alle 87 µs ein neues Zeichen).

Nicht in der Lage, die folgende Aussage zu verstehen, die in micros () verwendet wird. Könnten Sie bitte näher darauf eingehen? Da ISR geschrieben ist, wird das TOV0-Flag gelöscht, sobald ISR eingegeben wird, und daher kann es sein, dass die folgende Bedingung nicht erfüllt ist. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh

micros()ist kein ISR. Es ist eine normale Funktion. Mit dem TOV0-Flag können Sie die Hardware testen, um festzustellen, ob der Timer-Überlauf aufgetreten ist (aber noch nicht verarbeitet wurde).
Nick Gammon

Da der Test ohne Unterbrechungen durchgeführt wird, wissen Sie, dass sich das Flag während des Tests nicht ändert.
Nick Gammon

Sie wollen also sagen, dass nach der Funktion cli () in micros () geprüft werden muss, ob ein Überlauf auftritt. Das macht Sinn. Ansonsten, obwohl micros keine Funktion ist, löscht TIMER0_OVF_vect ISR TOV0 in TIFR0, was mein Zweifel war.
Rajesh

Auch warum wird TCNT0 mit 255 verglichen, um festzustellen, ob es weniger als 255 ist? Was passiert nach cli (), wenn TCNT0 255 erreicht?
Rajesh

8

Es ist nicht falsch , eine millis()oder micros()mehrere Interruptroutinen zu verwenden.

Es ist falsch, sie falsch zu verwenden.

Die Hauptsache hier ist, dass, während Sie sich in einer Unterbrechungsroutine befinden, "die Uhr nicht tickt". millis()und micros()wird sich nicht ändern micros().

Sie können also auf jeden Fall die aktuelle Zeit in Ihrem ISR abrufen millis()oder micros()herausfinden, aber erwarten Sie nicht, dass sich diese Zeit ändert.

Es ist der Mangel an Änderungen in der Zeit, auf den in dem von Ihnen angegebenen Zitat hingewiesen wird. delay()hängt davon ab millis(), zu wissen, wie viel Zeit vergangen ist. Da es sich nicht ändert, delay()kann es niemals enden.

Also im Wesentlichen millis()und micros()wird Ihnen sagen, wann Ihr ISR aufgerufen wurde, egal wann Sie sie in Ihrem ISR verwenden.


3
Nein, micros()Updates. Es liest immer das Hardware-Timer-Register.
Nick Gammon

4

Der zitierte Satz ist keine Warnung, sondern lediglich eine Aussage darüber, wie die Dinge funktionieren.

Es ist an sich nichts Falsches daran, eine ordnungsgemäß geschriebene Interrupt-Routine zu verwenden millis()oder zu verwenden micros().

Auf der anderen Seite ist es per definitionem falsch, irgendetwas in einer falsch geschriebenen Interruptroutine zu tun.

Eine Interruptroutine, die mehr als ein paar Mikrosekunden benötigt, um ihre Arbeit zu erledigen, ist höchstwahrscheinlich falsch geschrieben.

Kurz gesagt: Eine ordnungsgemäß geschriebene Interrupt-Routine verursacht oder stößt nicht auf Probleme mit millis()oder micros().

Bearbeiten: In Bezug auf "Warum sich micros ()" unregelmäßig verhält ", wie in einer Webseite " Überprüfung der Arduino micros-Funktion " erläutert , ist micros()Code auf einem gewöhnlichen Uno funktional äquivalent zu

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Dies gibt ein vorzeichenloses Vier-Byte-Long zurück, das aus den drei niedrigsten Bytes von timer0_overflow_countund einem Byte von dem Timer-0-Zählregister besteht.

Das timer0_overflow_countwird vom TIMER0_OVF_vectInterrupt-Handler ungefähr einmal pro Millisekunde inkrementiert , wie in einer Untersuchung der Webseite mit der Arduino-Millis-Funktion erläutert .

Bevor ein Interrupt-Handler gestartet wird, deaktiviert die AVR-Hardware Interrupts. Wenn (zum Beispiel) ein Interrupt-Handler fünf Millisekunden lang mit noch deaktivierten Interrupts ausgeführt wird, werden mindestens vier Timer-0-Überläufe übersehen. [Interrupts, die in C-Code im Arduino-System geschrieben wurden, sind nicht wiedereintrittsfähig (können mehrere überlappende Ausführungen innerhalb desselben Handlers korrekt verarbeiten), aber es könnte ein wiedereintrittsfähiger Assembler-Handler geschrieben werden, der Interrupts wieder aktiviert, bevor ein zeitaufwendiger Prozess beginnt.]

Mit anderen Worten, Timer-Überläufe stapeln sich nicht. Wenn ein Überlauf auftritt, bevor der Interrupt des vorherigen Überlaufs verarbeitet wurde, millis()verliert der Zähler eine Millisekunde, und die Diskrepanz timer0_overflow_countmacht sich wiederum auch micros()um eine Millisekunde negativ bemerkbar.

Betrachtet man "kürzer als 500 μs" als oberes Zeitlimit für die Interrupt-Verarbeitung, "um zu verhindern, dass der Timer-Interrupt zu lange blockiert", könnte man auf knapp 1024 μs (z. B. 1020 μs) aufsteigen und millis()würde die meisten noch funktionieren Zeit. Ich betrachte einen Interrupt-Handler, der mehr als 5 μs als träge, mehr als 10 μs als träge und mehr als 20 μs als schneckenartig betrachtet.


Könnten Sie bitte erläutern, wie die Dinge funktionieren, insbesondere warum Sie micros()sich unberechenbar verhalten? Und was meinst du mit "richtig geschriebener Interruptroutine"? Ich nehme an, es bedeutet "kürzer als 500us" (um zu verhindern, dass der Timer-Interrupt zu lange blockiert wird), "flüchtige Variablen für die Kommunikation verwenden" und "Bibliothekscode nicht so oft wie möglich aufrufen". Gibt es noch etwas anderes?
Petr Pudlák

@ PetrPudlák, siehe Bearbeiten
James Waldby - jwpat7
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.