Pic16 Timer0-Puzzle

EINFÜHRUNG

Der springende Punkt bei einem Timer mit Überlauf-Interrupt ist, dass der Interrupt in einem genau getakteten Intervall ausgelöst wird - solange der im Interrupt ausgeführte Code nicht länger dauert als das Timer-Intervall.

DAS PROBLEM

Auf einem PIC16LF1823 bin ich auf das folgende Verhalten gestoßen und verstehe es nicht. Kurz gesagt, eine meiner Routinen wurde innerhalb des Interrupts aufgerufen und verursachte, dass der Interrupt das nächste Mal ausgelöst wurde . Es ist, als ob die Routine irgendwie bewirkt, dass Timer0 anhält, während es läuft. Ich habe den Problemcode so weit wie möglich reduziert.

ANFANGEN MIT WAS FUNKTIONIERT

Zuerst setzt der Basiscode ohne Schnickschnack den Prozessor auf 2 MHz, startet Timer0 ohne Prescaler und in der Interrupt-Routine wird der Test-Pin (C3) umgeschaltet, um zu überprüfen, ob alles funktioniert. (Ich verwende übrigens mikroC). Dieser Code funktioniert wie erwartet:

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


void interrupt(void){

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;

          LATC3_bit = ~LATC3_bit;
          //Delay_us(100); //<<<<Uncommenting this widens the pulse, but the pulses are still 132uS apart
          LATC3_bit = ~LATC3_bit;
      }
}

Ich habe überprüft, dass dies funktioniert. Pin C3 schaltet einmal alle 132 µs mit oder ohne Verzögerung um.

WIE MAN ES BRICHT

Im folgenden Code wird der Interrupt jedoch jetzt alle 136 µs statt 132 µs ausgelöst. Der einzige Unterschied besteht in der Hinzufügung von zwei Funktionen (routine1 und routine2) und der Deklaration eines unsigned short. Da ich diese Frage stelle, behebt das Entfernen des initialisierten Werts ('=0') das Problem.

void main() {
    //2MHz
    SPLLEN_bit = 0;
    SCS1_bit = 1;
    SCS0_bit = 0;
    IRCF3_bit = 1;
    IRCF2_bit = 1;
    IRCF1_bit = 0;
    IRCF0_bit = 1;

    //Oscilloscope probe on C3
    ANSC3_bit = 0;
    TRISC3_bit = 0;
    LATC3_bit = 1;

    //Start Timer0
    TMR0CS_bit = 0; //Internal clock
    PSA_bit = 1;    //No prescaler assigned to Timer0

    //Enable timer interrups
    TMR0IE_bit = 1;

    //Enable general interupt

    PEIE_bit = 0;
    GIE_bit = 1;
    LATC0_bit = ~LATC0_bit;
}


unsigned short mode=0; //<<<<<<<<<<Removing the initiator ('=0') fixes the problem!!!
void routine1(void){
    unsigned short result;

    mode = 0;
    switch(mode) {
    case 99:
        //result = someFunction();
        if (result == 0xFF) {
            //Do something
        }
        break;
    }
}

void routine2(void){
    unsigned int result;

    if (result == 0xFF) {
       //Do something
    }
}

void interrupt(void){
int tmp;

      if (TMR0IF_bit) {
         //The interrupt fires every 132uS
          TMR0 = 255 - 121;
          TMR0IF_bit = 0;


          LATC3_bit = ~LATC3_bit;
          //Delay_us(100);
          LATC3_bit = ~LATC3_bit;


          routine2();
      }
}

Kann das jemand erklären?


EDIT1:

KORREKTUR, der Takt ist auf 4MHz eingestellt, also läuft der Takt mit 1uS (1 / Befehlstakt). Der obige Codekommentar ist falsch.

Der Kommentar von Bruce und m.Alin über die magische Zahl brachte mich zum Nachdenken. Ich sollte in der Lage sein, 132 fest zu codieren. Und dann fiel mir im ursprünglichen Code ein:

void interrupt(void){

    if (TMR0IF_bit) {
        TMR0 = 255 - 121; //Produces 132uS, but proves unpredictable       
        TMR0IF_bit = 0;

        LATC3_bit = ~LATC3_bit;
        LATC3_bit = ~LATC3_bit;
    }
}

die Linie:

TMR0 = 255 - 121;

ist ein Versuch, das richtige Timing hinzubekommen, wobei ich alles berücksichtigt habe, was nach dem Auslösen des Interrupts passiert, aber bevor ich die Chance habe, den Timer zurückzusetzen - daher der magische Fudge-Faktor von 11. Mit anderen Worten, ich versuche vorherzusagen , was Der Wert des Timers entspricht dem Zeitpunkt, zu dem ich ihn voreingestellt habe.

Tatsache ist, dass ich den Wert kenne – er wird in TMR0 gehalten. Also versuchte ich als nächstes die folgende Zeile:

TMR0 = TMR0 - 132; //Predictable but off by 3uS

Dies erzeugte aufgrund der if-Anweisung und der Subtraktion 135-uS-Impulse. Also habe ich 3 von 132 abgezogen:

TMR0 = TMR0 - 132 - 3; //This produces 142uS pulses (132uS + 10uS for the extra operation)

Aber das hat natürlich nicht ganz funktioniert, denn während ich die Zeit um 3 reduziert habe, habe ich auch ein paar Operationen hinzugefügt, die noch mehr Zeit in Anspruch genommen haben. Also ich habe folgendes probiert...

TMR0 = TMR0 - 129; //This produces 132uS pulses (132 - 3)

Dies scheint das Problem behoben zu haben. Dies muss bedeuten, dass der Interrupt nicht jedes Mal genau gleich ausgelöst wird. Irgendetwas muss den Interrupt manchmal abhalten. Obwohl dies ein Fix ist, weiß ich nicht genau, wie der Timer-Interrupt-Mechanismus funktioniert.

Irgendwelche Ideen?

Sie benötigen ein while(1)am Ende Ihrer Hauptfunktion. Es ist wahrscheinlich, dass Ihr Code den uC jedes Mal einfach zurücksetzt, wenn er das Ende dieser Funktion erreicht. Achten Sie auch auf den Watchdog.
Das while(1) hat nicht geholfen. Außerdem war es für den einfachen Testcode nicht notwendig.
Wenn Sie den Timer-Wert zurücksetzen, müssen Sie die Taktticks berücksichtigen, die zwischen dem Timer-Interrupt und dem Zurücksetzen auftreten
@LanceBeasley das ist falsch. Es ist immer notwendig, die Ausführung zu stoppen, wenn das Ende von main() erreicht wird, oder der Prozessor wird zurückgesetzt.
@LanceBeasley, eigentlich, wenn am Ende von main nichts steht, ist das Ergebnis unvorhersehbar - die Ausführung läuft einfach am Ende der Anweisungen am Ende von main ab - vielleicht in Ihren Interrupt-Handler, vielleicht auch nicht. Nur weil der Code zu funktionieren scheint, funktioniert er nicht unbedingt so, wie Sie es erwarten ...

Antworten (2)

Bei der Verwaltung von Hardware auf niedriger Ebene wie dieser müssen Sie:

  1. Lesen Sie das Datenblatt.

  2. Siehe Punkt 1.

  3. Stellen Sie sicher, dass Sie wissen, welche Maschinenbefehle verwendet werden. Der einfachste Weg, dies zu tun, besteht darin, kritischen Code in Assembler zu schreiben.

  4. Siehe nochmal Punkt 1.

Sie haben Punkt 1 und 3 nicht bestanden. Wenn Sie den Abschnitt über Timer 0 sorgfältig gelesen hätten, hätten Sie gesehen, dass er nach jedem Schreiben zwei Zyklen lang anhält. Dies ist auf Seite 175, Abschnitt 20.1.1 8-Bit-Timer-Modus , Abschnitt 2, klar beschrieben :

Wenn TMR0 geschrieben wird, wird das Inkrement unmittelbar nach dem Schreiben für zwei Befehlszyklen gesperrt.

Wie Sie schließlich herausgefunden haben, möchten Sie TMR0 nicht auf einen festen Wert im Interrupt setzen, sondern ihm einen festen Wert hinzufügen. Da die Addition relativ zum aktuellen Wert ist, spielt es keine Rolle, wo in der Interrupt-Routine die Addition durchgeführt wird, solange die Interrupt-Routine die gewünschte Zeitgeberperiode nicht überschreitet.

Beim Addieren muss der Inkrementverlust von drei Zyklen berücksichtigt werden. Anders ausgedrückt, wenn dem Zeitgeber jede Periode hinzugefügt wird, wird die Basisperiode 259 Zyklen, nicht die 256 Zyklen, wenn der Zeitgeber allein gelassen wird. Der dem Timer hinzugefügte Wert ist, um wie viele Zyklen die Periode von 259 verkürzt wird. Wenn Sie eine Periode von 132 Zyklen wünschen, dann addieren Sie 256 + 3 - 132 = 127 zu TMR0 für jede Periode.

Dies muss natürlich in Assembler erfolgen, um sicherzustellen, dass der Timer inkrementell richtig geschrieben wird. Sie haben keine Garantie, welche Anweisung genau der C-Code ist

TMR0 = TMR0 + (256 + 3 – Periode);

wird generieren. Zum Beispiel könnte es ein Durcheinander geben:

         movf tmr0, w
         addlw 256 + 3 - Punkt
         movwf tmr0

Du willst das selbst schreiben:

         movlw 256 + 3 - Punkt
         addwf tmr0

Noch besser ist es, all dies in ein Makro zu packen. Das dokumentiert die Absicht in Ihrem Interrupt-Code besser und macht es zu etwas, das Sie nur einmal sorgfältig herausfinden müssen, um es dann als vorgefertigte Ressource für neue Projekte zu verwenden. Ich habe das vor langer Zeit getan. Hier mein Makro:

;********************
;
; Makro TIMER0_PER cy
;
; Aktualisiere den Zeitgeber 0, so dass er als nächstes CY-Zyklen von dem vorherigen Umlauf umbricht. Das
; kann in einer Timer-0-Interrupt-Routine nützlich sein, um die genaue Anzahl festzulegen
; Zyklen bis zum nächsten Timer 0 Interrupt. Es wird davon ausgegangen, dass Timer 0 läuft
; von der Unterrichtsuhr. Der entsprechende Wert wird in Timer 0 addiert,
; Daher muss dieses Makro nicht mit einer festen Verzögerung nach dem letzten aufgerufen werden
; Timer 0 Wrap. CY muss eine Konstante sein.
;
; Der Timer setzt sein Interrupt-Flag, wenn er von 255 an zählt, was zurückspringt
; auf 0. Wenn er allein gelassen wird, hat der Zeitgeber daher eine Periode von 256 Befehlen
; Fahrräder. Beim Addieren eines Werts in den Timer geht das Inkrement währenddessen verloren
; der Additionsbefehl, und der Zeitgeber wird nicht um zwei weitere erhöht
; Zyklen, wenn in das TMR0-Register geschrieben wird. Dies fügt effektiv 3 weitere hinzu
; Zyklen bis zur Wrap-Periode des Timers 0. Diese zusätzlichen Zyklen werden genommen
; bei der Berechnung des zu TMR0 hinzuzufügenden Werts berücksichtigt werden.
;
timer0_per Makro cy
         dbankif tmr0
         movlw 256 + 3 - (cy)
         addwf tmr0
         endm

Dies ist eines der vielen Utility-Makros in STD.INS.ASPIC in meiner PIC-Entwicklungsumgebung.

Danke für den Einblick. Zu diesem Projekt habe ich ein solides BUCH mit Datenblättern!

Diese Linie:-

TMR0 = 255 - 121;

ist dein Problem. Timer0 tickt in 1us-Intervallen, zählt bis 255 und generiert dann einen Interrupt, wenn er auf 0 übergeht. Das Setzen von Timer0 auf 256-132 würde einen Interrupt 132 Ticks später verursachen, aber es gibt keine 132 in Ihrem Code. Stattdessen sehen wir eine 'magische' Zahl, 121. Wie bekommt das alle 132 us einen Interrupt? Ihr Kommentar sagt uns nichts.

So sollte Ihr Code aussehen:

// fudge factor to account for interrupt latency - determined by experiment 
#define MY_FUDGE_FACTOR 11

//The interrupt fires every 132uS
TMR0 = 255 - (132 - MY_FUDGE_FACTOR);     

Jetzt können wir das Problem sehen. Abhängig davon, welchen Code der Compiler erstellt und welche Anweisung die MCU zu diesem Zeitpunkt ausführt, kann die Anzahl der Zyklen zwischen einer Unterbrechungsauslösung und der Ausführung Ihres Codes variieren. Um dies zu kompensieren, subtrahieren Sie die Anzahl der Zyklen, die Sie benötigen, um dorthin zu gelangen. Diese Zahl kann jedoch nicht zuverlässig bestimmt werden, da Sie keine Kontrolle darüber haben, welchen Maschinencode der Compiler erzeugt.

Der Interrupt wird in einem genau getakteten Intervall ausgelöst - solange der im Interrupt ausgeführte Code nicht länger dauert als das Timer-Intervall.

Dies gilt nur, wenn die Interrupt-Latenz sicher weniger als 1 Tick beträgt (nicht das gesamte zeitgesteuerte Intervall) oder wenn Sie den Timer nie neu laden. Wenn Sie den Timer neu laden müssen, um das gewünschte Intervall zu generieren, und er schneller tickt als zum Neuladen benötigt wird, kann er empfindlich auf das Ausführungstiming Ihres Codes reagieren.

Der einfachste Weg, dies zu umgehen, besteht darin, den Timer alle 256 Ticks überlaufen zu lassen und den Prescaler zu verwenden, um das gewünschte Zeitintervall einzustellen. Leider bedeutet das, dass Sie mit einem 4-MHz-Systemtakt keine 132 us erreichen können, aber mit 8 MHz 128 us.

"Aber es gibt keine 132 in Ihrem Code. Stattdessen sehen wir eine 'magische' Zahl, 121. Wie bekommt das alle 132us einen Interrupt?" Kommt das 132 us nicht von 255 - 121 = 134 (was fast 132 ist)?