Timer 1 (16 Bit): Warum wird der Überlauf-Interrupt manchmal übersehen?

Initialisierung von Timer 1, dem 16-Bit-Timer auf dem ATmega328:

TCCR1A = 0; // normal operation
TCCR1B = bit(CS10); // no prescaling
OCR1A = 0;
OCR1B = 0;
TIMSK1 |= bit(TOIE1); // Timer/Counter 1, Overflow Interrupt Enable

16-Bit-Überläufe erhöhen einen Überlaufzähler:

ISR(TIMER1_OVF_vect) {
    timer1OverflowCount ++;
}

In einer Schleife wird geprüft, ob die beiden 16-Bit-Zähler korrekt hochzählen:

void loop() {
  static uint16_t lastOc = 0, lastC = 0;
  uint16_t oc, c;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    oc = timer1OverflowCount;
    c = TCNT1;
  }

  if (c < lastC && oc == lastOc) {
    print(oc, c, lastOc, lastC);
  }

  lastOc = oc;
  lastC = c;
}

Beispielausgabe an die serielle Konsole:

Bad overflow: oc = 31, c = 49, lastOc = 31, lastC = 65440
Bad overflow: oc = 58, c = 49, lastOc = 58, lastC = 65440
Bad overflow: oc = 66, c = 49, lastOc = 66, lastC = 65440
Bad overflow: oc = 118, c = 49, lastOc = 118, lastC = 65440
Bad overflow: oc = 127, c = 49, lastOc = 127, lastC = 65440

Warum wird der Überlaufzähler manchmal nicht erhöht?

Ich verstehe, dass die Schleife häufig auf den Überlaufzähler zugreift. Während eines Zugriffs werden Interrupts mit abgeschaltet ATOMIC_BLOCK(ATOMIC_RESTORESTATE). Der Zugriff ist jedoch schnell, und ich erwarte, dass der Überlauf-Interrupt in die Warteschlange gestellt wird, damit er nie verpasst wird.

Vollständiger Code für den Arduino Pro Mini ATmega328P (5 V, 16 MHz), kompatibel mit der Arduino IDE 1.8.1:

#include <util/atomic.h>

volatile uint16_t timer1OverflowCount = 0;

ISR(TIMER1_OVF_vect) {
  timer1OverflowCount ++;
}

void setup() {
  TCCR1A = 0; // normal operation
  TCCR1B = bit(CS10); // no prescaling
  OCR1A = 0;
  OCR1B = 0;
  TIMSK1 |= bit(TOIE1); // Timer/Counter 1, Overflow Interrupt Enable

  Serial.begin(9600);
}

void print(uint16_t oc, uint16_t c, uint16_t lastOc, uint16_t lastC) {
  Serial.print("Bad overflow: ");
  Serial.print("oc = ");
  Serial.print(oc);
  Serial.print(", c = ");
  Serial.print(c);
  Serial.print(", lastOc = ");
  Serial.print(lastOc);
  Serial.print(", lastC = ");
  Serial.println(lastC);
}

void loop() {
  static uint16_t lastOc = 0, lastC = 0;
  uint16_t oc, c;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    oc = timer1OverflowCount;
    c = TCNT1;
  }

  if (c < lastC && oc == lastOc) {
    print(oc, c, lastOc, lastC);
  }

  lastOc = oc;
  lastC = c;
}
Welcher Assemblercode wird von der ATOMIC_BLOCK(..){..}-Anweisung ausgegeben?
oc wird inkrementiert .. also, was ist hier das Problem
@MITURAJ Sehen Sie sich die Beispielausgabe an. oc = 31, c = 49, lastOc = 31, lastC = 65440bedeutet, dass TCNT1es übergelaufen ist (65440→49), aber der Überlaufzähler ist gleich geblieben (31).
@Spehro Pefhany Weitere Informationen finden Sie hier .
Eine asm-Auflistung nach dem Ersetzen des Druckaufrufs durch einige NOPs wäre interessant, um das Timing zu sehen.
Ein paar Dinge, die Sie ausprobieren sollten: Schalten Sie einen Pin in der ISR um, schalten Sie einen Pin ein, wenn Sie TCNT1 lesen, bringen Sie das Hardware-Überlaufsignal an einem Pin heraus und verbinden Sie das alles mit einem Oszilloskop oder Logikanalysator. Das sollte Ihnen ein anständiges Bild davon geben, was intern passiert. Nehmen Sie auch das Interrupt-Flag am Ende Ihres Atomblocks und drucken Sie es aus, um zu sehen, ob ein Interrupt innerhalb des Atomblocks angefordert wurde, wie dies hier wahrscheinlich der Fall ist.
@feklee hast du versucht, die Anweisungen wie in meiner Antwort zu ändern? Zuerst TCNT1 lesen, dann timer1OverflowCount?
@Dorian Ja, aber es funktioniert nicht.
@feklee Platzieren Sie jede Anweisung in einem eigenen ATOMIC-Block, siehe die geänderte Antwort. Es ist auch Thread-sicher.
Ein Thread-Problem, wenn Interrupts nicht wie von Sphero vorgeschlagen deaktiviert werden, tritt auch bei TMR1 auf, das gepufferte höherwertige Byte kann durch einen Interrupt geändert werden, der auch TMR1 liest.
Was ist Ihr Zweck, etwas im Hauptcode auf TMR1 zum Überlaufen zu bringen oder einfach zu überprüfen, ob es richtig funktioniert?

Antworten (6)

Sofern Interrupts vom Lesen eines Werts bis zu seiner Verwendung nicht vollständig deaktiviert sind, sollte man im Allgemeinen die Idee berücksichtigen, dass ein Versuch, einen Wert von etwas zu lesen, das sich ändert, jeden Wert ergeben kann, den das Ding zu jeder Zeit hatte während des Versuchs, ohne sich Gedanken darüber zu machen, wann genau abgetastet wird.

Eine einfache allgemeine Art, einen 32-Bit-Lesevorgang zu handhaben, besteht darin, einen Wert zweimal in n1 und n2 zu lesen (lesen Sie die Bytes in beliebiger Reihenfolge, vorausgesetzt, dass der letzte Lesevorgang für n1 dem ersten Lesevorgang für n2 vorangeht) und nicht. Machen Sie sich keine Sorgen über das Deaktivieren von Interrupts) und sagen Sie dann:

if ((uint8_t)((n1 >> 24) ^ (n2 >> 24)) // MSB has changed
  n2 &= 0xFF000000;
else if ((uint8_t)((n1 >> 16) ^ (n2 >> 16))
  n2 &= 0xFFFF0000;
else if ((uint8_t)((n1 >> 8) ^ (n2 >> 8))
  n2 &= 0xFFFFFF00;

Wenn sich das obere Byte des Timers von xx zu yy ändert, bedeutet dies, dass der Wert des Timers höchstens xxFFFFFF war, wenn das obere Byte xx war, und wenn der Wert yy war, der Wert des Timers mindestens yy000000 war. Daher muss der Wert irgendwann zwischen dem Lesen von xx und dem Lesen von yy yy000000 gewesen sein. Eine ähnliche Logik gilt für die anderen Bytes des Timers.

Beachten Sie, dass die zum Ausführen dieses Codes erforderliche Zeit begrenzt ist, selbst wenn eine starke Unterbrechungsbelastung vorliegt, vorausgesetzt, dass die Belastung weniger als 100 % beträgt. Beachten Sie, dass der Code keinen Versuch unternimmt, z. B. zwischen dem Fall zu unterscheiden, in dem der Timer 0x01FF0000 vor dem Code und 0x02000000 nach [mehr als 65.000 Zyklen in Interrupts verbracht hat] oder 0x01FFFFFF davor und 0x02010000 danach war. Es wird in beiden Fällen 0x02000000 gemeldet, aber das würde einen Wert darstellen, den der Timer irgendwann während der Ausführung dieses Codes gehalten hat.

Sauberer Trick und unabhängig von der verwendeten MCU! Ich habe es erfolgreich mit der Implementierung getestet, die ich meiner Antwort hinzugefügt habe .

Das Problem besteht darin, dass Sie das eigentliche Problem mit der ATOMIC-Anweisung nicht beseitigen – der Zähler wird in der Hardware erhöht und es wird Zeiten geben, in denen ein Interrupt ansteht, aber nicht abgeschlossen ist. Es ist schlimmer, weil die Cint-Operationen auf einem 8-Bit-Prozessor viele Zyklen benötigen, aber es würde sogar in engem ASM-Code auftauchen.

Wenn Sie keine anderen Interrupts von langer Dauer haben, müssen Sie nicht einmal Interrupts beenden, Sie können einfach die Zählung korrigieren.

Die allgemeine Vorgehensweise ist wie folgt:

  1. read timer1OverflowCount, erste Probe von timer1OverflowCount
  2. Lesen Sie den Hardware-Zähler TCNT1 <- genau hier ist, wenn Sie mit hoher Auflösung abtasten
  3. Lesen Sie timer1OverflowCount erneut, zweites timer1OverflowCount-Beispiel

Wenn sich timer1OverflowCount geändert (erhöht) hat, sehen Sie sich das Beispiel von TCNT1 an. Wenn es MSB = 0 hat, verwenden Sie den späteren Timer1OverflowCount. Wenn es MSB=1 hat, verwenden Sie den früheren Timer1OverflowCount.

Es ist nicht erforderlich, Interrupts auszuschalten, es sei denn, es gibt andere Interrupts mit langsamen ISRs, die dazu führen könnten, dass sich die Gesamtzeit für das Obige der Zeit annähert, in der der Hardwarezähler die Hälfte des vollen Zählwerts erreicht. Es ist fast immer unerwünscht, Interrupts auszuschalten, es sei denn, es ist absolut notwendig.

Bearbeiten: In Ihrem Fall haben Sie einen int für den Überlauf, also müssen Sie das Lesen der Variablen innerhalb der ISR atomar ändern und sich mit dem obigen Problem befassen.

Das scheint zu funktionieren, danke! Aber sollte ich Interrupts beim Lesen nicht deaktivieren timer1OverflowCount? Ich nehme an, dass es möglich ist, dass timer1OverflowCountmitten im Lesen hochgezählt wird, also zwischen mehreren LDSAnweisungen.
Weil es nicht nötig ist (der Messwert wird in jedem Fall korrigiert). Das globale Ausschalten von Interrupts kann die Latenz zu Interrupts mit höherer Priorität in Prozessoren erhöhen, die solche unterstützen; Es ist einfach schlechter Stil, wenn Sie es nicht müssen.
Beispiel: (a) timer1OverflowCountist ein 16-Bit-Wert von: 11111111 00000000(b) Das erste Byte wird gelesen ( LDS): 11111111(c) Der Überlauf-Interrupt wird inkrementiert 11111111 0000000000000000 00000001. (Little Endian) (d) Das zweite Byte wird gelesen ( LDS): 00000001. Der gelesene Gesamtwert ist 0xb111111111 = 760495542545, statt 0xb100000000 = 760209211392. Das ist falsch. Oder liege ich falsch? Ich sehe nicht, wie Ihr Vorschlag diese Rennbedingung korrigiert.
Ah, okay, ja, das sollten Sie in diesem Fall tun, wenn es sich um eine 16-Bit-Variable auf einem 8-Bit-Prozessor handelt. Sie können auch einen ähnlichen Trick mit doppelten Lesevorgängen spielen, wenn Sie Interrupts eingeschaltet lassen müssen.

Der ATOMIC_BLOCK wird ohne Unterbrechung ausgeführt, aber die Anweisungen darin werden nicht gleichzeitig ausgeführt.

Von "oc = timer1OverflowCount;" zu "c = TCNT1;" TCNT1 wird inkrementiert und ist nicht dasselbe wie beim Lesen von oc.

Der Fehler zeigt, wenn der Überlauf innerhalb des ATOMIC-Blocks auftritt und timer1OverflowCount nicht aktualisiert werden kann und sogar die Interrupts deaktiviert wurden, zeigt TCNT1 einen späteren Wert als timer1OverflowCount.

Tauschen Sie die beiden Anweisungen aus, platzieren Sie jede in einem eigenen ATOMIC-Block und sehen Sie sich das Ergebnis an. Es ist ein „Falsch-Positiv“. Ich bin davon ausgegangen, dass Sie nur überprüfen möchten, ob Ihr TCNT1-Überlauf-Interrupt gut funktioniert. Er funktioniert, aber der verwendete Code zeigt Fehler an, wo sie nicht sind.

Auf OP-Anfrage füge ich die Erklärung zum Lesen von TCNT1 in seinem eigenen Atomblock hinzu.

Das Lesen des 16-Bit-TCNT1-Registers ist sicher in Bezug auf seine Inkrementierung zwischen zwei 8-Bit-LSB- und MSB-Registerlesungen. Beim Lesen von LSB wird das MSB-Register gepuffert und alle weiteren Lesungen zeigen den gleichen Wert in dem Moment, in dem LSB gelesen wurde.

Aber wenn zwischen den beiden Lesevorgängen ein Interrupt vorhanden ist, der auch TCNT1 liest, wird der Wert des gepufferten MSB mit dem neuen Wert aktualisiert, der unterschiedlich sein kann.

Ich weiß nicht, ob dies hier zutrifft, aber andere 16-Bit-Register teilen möglicherweise denselben Puffer wie TCNT1.

Sie können die Überlaufzählung für eine feste Zeit beobachten, um zu sehen, ob es zu Verzögerungen kommt.

Der Code ist schnell, wird aber auch ohne Unterbrechung zwischen den Schleifen ausgeführt.

In der Schleife haben Sie mehr oder weniger fünf Anweisungen, zwei Übertragungen im Atomblock und drei außerhalb. Kann einen Sprung hinzugefügt werden und Interrupts deaktivieren. Die Wahrscheinlichkeit, dass dieser Überlauf während der Ausführung des atomaren Blocks auftritt und den Fehler auslöst, ist ziemlich hoch.

Nehmen wir also an, der Prozessor gibt 2 us für den ATOMIC-Block und 4 us für den Rest der Schleife aus, dann wieder 2 us im ATOMIC-Block und wieder 4 außerhalb.

Wenn der Überlauf im ATOMIC-Block auftritt (was eine Wahrscheinlichkeit von 1/3 darstellt), während die Interrupts deaktiviert sind und timer1OverflowCount nicht erhöht werden kann, haben Sie eine ziemlich gute Chance, einen falschen Fehler auszulösen.

Die Wahrscheinlichkeit ist viel geringer, weil der Compiler etwas Code für die Schleife hinzufügt und auch innerhalb des ATOMIC-Blocks der Überlauf auftreten muss, bevor TCNT1 LSB gelesen wird.

Dies ist der Arbeitscode, der von OP im Kommentar gepostet wurde:

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { c = TCNT1; } 
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { oc = timer1OverflowCount; }

Ich möchte hinzufügen, dass dieser Code mit den Bedingungen aus dem IF-Abschnitt funktioniert

if (c < lastC && oc == lastOc)

Aber wenn Sie überprüfen

if (c >= lastC && oc != lastOc)

Das heißt, wenn Sie die Überlaufzahl ohne einen Überlauf ändern, werden Sie auch falsche Fehler haben.

Wenn es sich in diesem Fall nicht nur um eine Codeprüfung handelt, sollten Sie so etwas wie Spehros Antwort verwenden, indem Sie den TCNT1 erneut lesen (auch in seinem ATOMIC-Block) und den niedrigsten Wert von TCNT1 im Falle einer Änderung der Timer-Zählung verwenden.

Wenn Sie jedoch etwas tun müssen, während die Timer-Zählung aktualisiert wird, überprüfen Sie nur die Timer-Zählung (threadsicher). Es funktioniert gut.

Danke, das funktioniert: ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { c = TCNT1; } ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { oc = timer1OverflowCount; }Man fragt sich vielleicht: Warum ist es notwendig, c = TCNT1;in einen eigenen Atomblock zu platzieren? Immerhin geschieht der 16-Bit-Lesezugriff auf TCNT1 mit Hilfe des TEMP-Registers. Siehe Datenblattversion DS40001984A, Seiten 154 und 155. Interessanterweise wird explizit angegeben: „Die folgenden Codebeispiele zeigen, wie auf die 16-Bit-Zeitgeberregister zugegriffen werden kann, vorausgesetzt, dass keine Interrupts das temporäre Register aktualisieren.“ Wird möglicherweise ISR(TIMER1_OVF_vect)während des Lesens von TCNT1 ausgelöst.
Könnten Sie diese Überlegungen der Antwort hinzufügen, um sie gründlicher zu machen? Es wäre auch schön, wenn Sie erklären würden, was Sie mit „falsch positiv“ meinen. Ich verstehe die zweite Hälfte Ihrer Antwort nicht.

Wenn wir uns die „schlechten“ Ausdrucke ansehen, sehen wir lastC = 65440und c = 49, was darauf hinweist, dass zwischen jedem Lesen des Timers in der Hauptschleife 96 Zählerticks aufgetreten sind. Ein Überlauf-Interrupt hätte bei 65536 auftreten sollen, was etwa die Hälfte dieser Zeit ist.

Aber Sie lesen auch timer1OverflowCountungefähr die Hälfte dieser Zeit, so dass es abhängig vom genauen Zeitpunkt des Interrupts vorkommen kann oder nicht, wenn Sie den Überlaufzähler lesen. Wenn Sie den Timer lesen, ist der Interrupt aufgetreten und hat timer1OverflowCount sich erhöht, aber Sie verwenden den historischen Überlaufzähler, der möglicherweise vor dem Überlauf des Timers vorhanden war.

Um eine bessere Vorstellung davon zu bekommen, was passiert, könnten Sie direkt nach jedem "schlechten" einen weiteren Ausdruck machen, dann sollten Sie sehen, dass timer1OverflowCountsich vor dem nächsten Interrupt "auf mysteriöse Weise" erhöht hat!

Um das Problem zu beheben, lesen Sie nicht timer1OverflowCountbis c< lastC. Dann wissen Sie, dass gerade ein Überlauf-Interrupt hätte auftreten müssen (und der nächste ist ~65000 Ticks entfernt), sodass Sie die Überlaufzählung zuverlässig testen können.

Sehr einfache Lösung, danke! Ich habe meiner Antwort eine Implementierung hinzugefügt .

Durch Kombinieren von Teilen aus Antworten mit Informationen aus der ATmega328 -Datenblattversion DS40001984A habe ich die folgende Lösung gefunden, die ich für robust halte:

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  c = TCNT1; // TCNT1 increases all the time during the following instructions
  bool timerDidOverflow = TIFR1 & 1; // TOV1 Timer/Counter 1, Overflow Flag
  byte msb = c >> 15;
  if (msb == 0 && timerDidOverflow) {
    timer1OverflowCount ++;
    TIFR1 &= 1; // Write to TOV1 to clear it and prevent triggering TIMER1_OVF
  }
  oc = timer1OverflowCount;
}

Die obigen Zeilen ersetzen:

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  oc = timer1OverflowCount;
  c = TCNT1;
}

Da in den Kommentaren Bedenken hinsichtlich der Mikrooptimierung geäußert wurden, ist hier der Assemblercode pro Ausgabe von avr-objdump:

   c = TCNT1; // TCNT1 increases all the time during the following instructions
606:    c0 91 84 00     lds    r28, 0x0084    ; 0x800084 <__stack+0x7ff785>
60a:    d0 91 85 00     lds    r29, 0x0085    ; 0x800085 <__stack+0x7ff786>
   bool timerDidOverflow = TIFR1 & 1; // TOV1 Timer/Counter 1, Overflow Flag
60e:    86 b3           in    r24, 0x16    ; 22
610:    81 70           andi    r24, 0x01    ; 1
   byte msb = c >> 15;
   if (msb == 0 && timerDidOverflow) {
612:    d7 fd           sbrc    r29, 7
614:    0e c0           rjmp    .+28         ; 0x632 <main+0x144>
616:    88 23           and    r24, r24
618:    61 f0           breq    .+24         ; 0x632 <main+0x144>
     timer1OverflowCount ++;
61a:    80 91 4c 01     lds    r24, 0x014C    ; 0x80014c <timer1OverflowCount>
61e:    90 91 4d 01     lds    r25, 0x014D    ; 0x80014d <timer1OverflowCount+0x1>
622:    01 96           adiw    r24, 0x01    ; 1
624:    90 93 4d 01     sts    0x014D, r25    ; 0x80014d <timer1OverflowCount+0x1>
628:    80 93 4c 01     sts    0x014C, r24    ; 0x80014c <timer1OverflowCount>
     TIFR1 &= 1; // Write to TOV1 to clear it and prevent triggering TIMER1_OVF
62c:    86 b3           in    r24, 0x16    ; 22
62e:    81 70           andi    r24, 0x01    ; 1
630:    86 bb           out    0x16, r24    ; 22
   }
   oc = timer1OverflowCount;
632:    e0 90 4c 01     lds    r14, 0x014C    ; 0x80014c <timer1OverflowCount>
636:    f0 90 4d 01     lds    r15, 0x014D    ; 0x80014d <timer1OverflowCount+0x1>

Randnotiz: Der 16-Bit-Lesezugriff auf TCNT1geschieht mit Hilfe des TEMPRegisters. Der Zugriff liefert garantiert einen konsistenten Wert. Siehe Seiten 154 und 155 in der Datenblattversion DS40001984A . Ohne die Gefahr von Unterbrechungszugriffen TCNT1ist es nicht erforderlich, Lese-/Schreibzugriffe auf TCNT1in einen atomaren Block zu legen.

Zum Vergleich hier eine Implementierung der Lösung aus der Antwort von @supercat , die beim Lesen überhaupt keine Atomarität erfordert:

uint16_t c0 = TCNT1, oc0 = timer1OverflowCount;
uint16_t c = TCNT1, oc = timer1OverflowCount;

if ((oc0 >> 8) ^ (oc >> 8)) {
  oc &= 0xff00;
  c = 0;
} else if ((uint8_t)(oc0 ^ oc)) {
  c = 0;
} else if ((c0 >> 8) ^ (c >> 8)) {
  c &= 0xff00;
}

Terse ist die Lösung von @Bruce Abbot , die ich mit folgender Implementierung erfolgreich getestet habe:

static uint16_t oc = 0;
uint16_t c;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  c = TCNT1;
}

bool timerDidOverflow = c < lastC;
if (timerDidOverflow) {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    oc = timer1OverflowCount;
  }
}
Als Option gewählt, aber eigentlich ist der Code so schlechter. Die Interrupt-Latenz ist viel höher. Für TCNT1 unterbrechen Sie die Zeile "bool ..." bis zum Ende und für andere unterbrechen Sie den gesamten Block. Bei drei kleinen Atomblöcken ist die Latenz so hoch wie beim längsten.
Ersetzen Sie zumindest "msb = c >> 15", was in mehr als 32 Schritten oder 7 erfolgt, wenn der Compiler einige Optimierungen vornimmt und das untere Byte ignoriert, durch "msb = c & 0x1000" oder überspringen Sie es einfach und ersetzen Sie "if(msb= =0 &&..." mit "if(c<0x1000 &&...", das weniger Verarbeitungszeit benötigt. Es gibt keinen wirklichen Gewinn, nur einen Block zu verwenden.
Übrigens, warum puffern Sie das Überlauf-Flag auf, da es keine Rolle spielt, wann es abgetastet wird? Sie können "if ((c<0x1000)&&(TIFR1 & 1)){..." verwenden.
@Dorian sbrc r29, 7macht nicht annähernd 32 Schritte. In Bezug auf das Aufteilen des Atomblocks: Zwischen den Blöcken TIMER1_OVFkann ein Feuer oder eine andere Unterbrechung von unbekannter Dauer auftreten. Zwischen jeweils zwei Schritten hat dies potenziell katastrophale Folgen.
Der Rechtsverschiebungsoperator sollte durch Klammern vom Exklusiv-Oder-Operator getrennt werden.
@supercat oc0 >> 8 ^ oc >> 8ist dasselbe wie die (oc0 >> 8) ^ (oc >> 8)folgende C-Operatorpriorität.
Dann ist der Compiler schlauer. Es tut, was ich vorgeschlagen habe, hinter deinem Rücken. :-)
Natürlich wurde mir nicht vorgeschlagen, Ihren Code aufzuteilen, sondern eine Lösung mit kleineren Blöcken zu verwenden. Kannst du auch den ASM-Code für meine Version mit zwei Blöcken posten?
@feklee: Das stimmt, aber viele Compiler haben optionale Warnungen zur Verwendung bestimmter Kombinationen von Operatoren ohne Klammern, und das Hinzufügen von Klammern macht den Code klarer und stellt sicher, dass er ohne Beschwerden kompiliert wird, selbst wenn solche Optionen aktiviert sind.
Kein perfekter Compiler, hart, er hat das Lesen und Testen von TIFR1 nicht optimiert (wie für c in msb verschoben und dann thested), weil er Ihren Kommentar übersehen hat, dass der Moment des Lesens von TIFR1 nicht wichtig ist.
@Dorian Ich habe so etwas nie gesagt. Note TIFR1ähnelt einer flüchtigen C-Variablen. Sein Wert kann sich jederzeit ändern. Der Compiler wäre schlecht beraten, die Position des Zugriffs auf zu ändern TIFR1.
@supercat Ich habe gerade ein paar Compiler mit allen aktivierten Warnungen ausprobiert (gcc, clang, vc). Keine gibt gerade in diesem Fall eine Warnung aus. Trotzdem habe ich die Klammern hinzugefügt. (Tatsächlich füge ich normalerweise viele Klammern hinzu, weil ich mich nicht an alle Fälle von Operatorvorrang erinnern kann. Hier habe ich mir die Zeit genommen, es nachzuschlagen.)
Ich weiss. Es war nur ein harmloser Scherz. Es ist besser, von Anfang an guten Code zu schreiben. Ein bisschen enttäuscht über Ihre Wahl, immer noch Ihr q
Ihre Frage wurde immer noch nicht bearbeitet und die richtige und vollständige Antwort lautet: "Es wurde aktualisiert, Ihr Testcode ist falsch" und eine gute Möglichkeit, dies zu überprüfen, indem Sie die Überlaufzählung auf lange Sicht beobachten. Aber es ist ok, es ist deine Wahl, nicht die beste.
@feklee: Vielleicht gibt dieser spezielle Fall keine Warnung aus, aber es scheint, dass die meisten Kombinationen von Links- / Rechtsverschiebungen mit anderen Operatoren Warnungen ausgeben, und guter Code hat die Eigenschaft, dass er nicht nur das tut, was der Programmierer beabsichtigt, sondern verlässt ein menschlicher Leser ohne Zweifel, dass es dies tun wird. Das Hinzufügen von Klammern hilft bei letzterem.

Das ist aber nur meins. Sie überprüfen es gelegentlich nicht in einem festen Intervall.

Es braucht Zeit, um die Schleife zu verarbeiten und Zeichen über UART zu senden! Machen wir eine Vermutung:

Sie verwenden die Baudrate von 9600, Ihr Arduino sendet etwa 57 Zeichen pro Schleife, sodass die Mindestzeit zum Beenden jeder Schleife beträgt:

               57 * 8 / 9600  =  47.5 ms  ( Rough approximation)

Wie viel Zeit läuft Timer1 über? (Basierend auf Ihrer Einstellung)

               65440 / 160000004 ms

Sie sehen, es kann nicht jeden einzelnen Überlauf auffangen.

Sie sollten Ihren Code ändern und einen anderen Timer verwenden, um ein festes Gating-Intervall zu erstellen, um zu zählen, wie viel timer1OverflowCountin einer festen Zeit.

Ich habe Ihren Code nicht gründlich analysiert, da ich jetzt erschöpft bin (und auch faul). Dies ist wahrscheinlich eher ein Softwarefehler als ein Hardwaredefekt.

Er verwendet uart erst, nachdem das fehlende Udate gefunden wurde, sodass es nicht zur Schleifenzeit zählt.
Wie gesagt: "Es kann nicht jeden einzelnen Überlauf auffangen. ".
Dies bedeutet, dass das Problem etwas schlimmer ist. Aber selbst wenn die Druckanweisung nicht gepuffert ist, versäumt er es, nur einen Überlauf zu prüfen, nachdem ein Fehler gefunden wurde.
Entschuldigung, ich habe bearbeitet. 47.5 msnicht 6 ms.
Habe gerade den Fehler Bauds vs. Bytes bemerkt. Ihrer Antwort fehlt immer noch die Antwort selbst. Es zeigt nur, dass das Problem vorhanden ist, erklärt aber nicht die Ursache. Habe nicht runtergestimmt, aber es sollte eher ein Kommentar sein.