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?
Bei der Verwaltung von Hardware auf niedriger Ebene wie dieser müssen Sie:
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.
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.
David
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.Lanze Beasley
Scott Seidmann
David
Sean Houlihane