Präzises Timing mit einem PIC18-Mikrocontroller?

Ich versuche, eine serielle Softwareimplementierung zu schreiben, und habe Probleme mit dem Timing. Ich verwende Timer0 auf einem PIC18F242 und aus irgendeinem Grund scheint es nicht sehr genau zu sein.

Ich habe versucht, das Problem zu debuggen, indem ich einen Pin am Mikrocontroller umschaltete und die Frequenz auf meinem Oszilloskop beobachtete. Das Oszilloskop zeigt, dass die Periode größer ist als sie sein sollte, und ich weiß nicht warum.

Mit meinem vollständigen Code war die Periode ungefähr 10 us zu lang, wenn ich mich richtig erinnere, aber selbst mit einem sehr einfachen Beispiel, wie dem folgenden, ist das Timing nicht richtig.

Hier ist ein Beispiel:

#include <p18f242.h>

// fuse configurations
#pragma config OSC = HSPLL
#pragma config OSCS = OFF 
#pragma config PWRT = OFF 
#pragma config BOR = OFF 
#pragma config WDT = OFF
#pragma config LVP = OFF 
#pragma config CP0 = OFF

// CALCULATIONS:
// 8Mhz crystal with PLL enabled
// FOSC = 4*8Mhz = 32MHz
// TOSC = 1/FOSC = 31.25ns
// TCY = 4*TOSC = 125 ns
// timer has 1:8 prescaler, therefore timer increments every 8*TOSC = 1us.

main(void)
{
    // port configuration
    TRISBbits.RB1 = 0; // make RB1 an output

    // configure Timer0
    T0CONbits.TMR0ON = 0; // disable (stop) the timer
    T0CONbits.T08BIT = 0; // 16-bit timer
    T0CONbits.T0CS   = 0; // use internal instruction clock
    T0CONbits.PSA    = 0; // use prescaler (prescaler assigned below)
    T0CONbits.T0PS0  = 0; // 010 = 1:8 prescaler
    T0CONbits.T0PS1  = 1;
    T0CONbits.T0PS2  = 0;
    T0CONbits.TMR0ON = 1; // enable (start) the timer

    while(1) 
    {
        if(TMR0L >= 100)
        {
            PORTBbits.RB1 ^= 1;

            // reset the timer
            TMR0H = 0; // MUST SET TMR0H FIRST
            TMR0L = 0;
        }   
    }
}

Mein Oszilloskop sagt, dass die Periode dieses Signals 204,0 us beträgt, was bedeutet, dass der Pin alle 102,0 us umschaltet. Wenn mein Oszilloskop korrekt ist, schaltet der Pin jedes Mal 2us zu spät um.

Interessanterweise führt ein Wechsel if(TMR0L >= 100)zu if(TMR0L >= 102)Ergebnissen im selben Zeitraum, aber ein Unter- 100oder Überschreiten 102dazu, dass der Zeitraum abnimmt bzw. zunimmt.

Warum passiert das?

Wie ich bereits erwähnt habe, scheint zusätzlicher Code in der Hauptschleife das Problem zu verschlimmern. Warum sollte das sein?

Antworten (6)

Wenn Sie ein genaues Timing wünschen, verwenden Sie Interrupts (dh setzen Sie den Timer-Wert in seiner Interrupt-Routine zurück).
So wie Sie es haben, überprüft es den Timer-Wert nur einmal pro Schleife. Wenn Sie also Code in die Schleife einfügen, werden die Überprüfungen verteilt.
Der Interrupt wechselt den Kontext, sobald der Timer überläuft, es spielt also keine Rolle, wie viel Code sich dann in Ihrer Schleife befindet.

Was Sie hier sehen, ist die Verzögerung zwischen dem Moment, in dem der Timer 100 erreicht hat (was bedeutet, dass Ihre if-Bedingung wahr ist) und dem Zeitpunkt, an dem Sie ihn zurücksetzen. In Ihrem Fall sind dies 2 Timer-Ticks, was bedeutet, dass Sie einen längeren Zeitraum erhalten. Wie bereits gesagt, wenn Sie ein präzises Timing haben möchten, verwenden Sie Interrupts. Alternativ können Sie Ihren Timer so konfigurieren, dass er genau im richtigen Moment überläuft. Auf diese Weise müssen Sie nur prüfen, ob ein Überlauf auftritt, und müssen ihn nicht zurücksetzen.

Dies ist nicht die effektivste Art, einen Timer zu verwenden. Wenn Sie ein genaues Timing wünschen, legen Sie einen Interrupt fest, wenn der Timer überläuft. Die Interrupt-Routine sollte ein Timer-Flag setzen und alles andere tun, was schnell erledigt werden muss, und den Timer-Zähler zurücksetzen (den aktuellen Timer-Wert lesen, damit Sie Verzögerungen berücksichtigen können, die mit der Interrupt-Behandlung verbunden sind). Alles, was nicht schnell ist, fügen Sie in eine if-Schleife ein, die das Timer-Flag überprüft. Das erste, was Sie in dieser Schleife tun, ist das Timer-Flag zurückzusetzen und dann alle nicht zeitkritischen Dinge zu tun, die mit Ihrer Schleife verbunden sind.

Die zusätzliche Verzögerung ist auf die Zeit zurückzuführen, die zum Ausführen der Anweisungen while, if Compare, Toggle und Reset benötigt wird, denn wenn Sie den Timer auf Null setzen, verlieren Sie bis zu all diese Zählwerte, je nachdem, wann genau der Timer in diesem bestimmten Zyklus 100 erreicht hat . Tcy beträgt 125 ns, sodass nur 16 Assembler-Anweisungen benötigt werden, um die zusätzliche Verzögerung von 2 us zu berücksichtigen. Werfen Sie einen Blick auf die Montageliste und Sie werden erkennen, wie viel Overhead Sie hinzufügen.

Verwenden Sie, wie andere gesagt haben, einen Interrupt für geringste Unsicherheit und Jitter. Anstatt den Timer zurückzusetzen, lassen Sie ihn überlaufen und zählen Sie weiter, auf diese Weise werden die Zählungen nicht auf Null gesetzt, die zur Gesamtzeit hinzugefügt werden.

Wenn Sie keine Interrupts verwenden möchten (was Sie aber tun sollten), stellen Sie auch die Periode so ein, dass sie überläuft, und überprüfen Sie das Überlauf-Flag. Dadurch können Sie den Timer nicht auf Null setzen und den berechneten Zeitraum beibehalten, aber Sie werden Jitter haben.

Wenn Sie einen Vorteiler mit Timer 0 verwenden, ist es nicht möglich, Interrupts bei Raten, die keine Zweierpotenz-Vielfachen von 256 sind, genau zu generieren. Wenn Sie eine Rate erzeugen möchten, die kein Zweierpotenz-Vielfaches von 256 ist , verwenden Sie wahrscheinlich am besten Timer 2, der für eine beliebige Zahl der Form (A*B*C) konfiguriert werden kann, wobei A eine beliebige Zahl von 1 bis 256, B eine beliebige Zahl von 1 bis 16 und C eine zulässige Potenz ist von zwei (ich habe vergessen, welche erlaubt sind). Andernfalls, wenn Sie den Timer 0 effizient im 8-Bit-Modus ohne Präskalar verwenden möchten, sagen SieTMR0L += Nwird den Timer um N-3 Zyklen vorrücken. Wenn Sie also möchten, dass der Timer 0 alle 200 Zyklen umbricht, können Sie nach jedem Umbruch (259-200) Zyklen hinzufügen (da das Vorrücken des Timers um 56 Zyklen nach dem Umbruch 256-56 Zyklen übrig lassen würde, dh 200). Beachten Sie, dass es bei Verwendung dieser Technik nicht darauf ankommt, wann genau das "Hinzufügen" stattfindet, vorausgesetzt, es tritt früh genug auf, damit es den Timer nicht "über den Rand" schiebt.

Es kann mühsam sein, wenn Sie Assembler nicht kennen, aber das Einfügen von Assembler-Routinen in C-Code kann Ihnen helfen, ein genaues Timing zu erreichen. Wenn ich mich richtig erinnere, um die Baugruppe in PIC C einzufügen, die Sie verwenden

_asm

Geben Sie Ihren Montagecode ein

_endasm