AVR-Timer-Beschleunigung auf ATmega328

Beim Betrieb mit Clock Prescaler von 64 auf ATmega328 beschleunigt einer meiner Timer aus unbekannten Gründen zu einem bestimmten Zeitpunkt in der Ausführung.

Ich verwende zwei Timer auf ATmega328, um die von TLC5940 benötigte Taktung zu erzeugen (siehe unten, warum; dies ist für die Frage unerheblich). TIMER0erzeugt ein Taktsignal mit Fast PWM on OC0Bund ist wie folgt aufgebaut:

TCCR0A = 0
    |(0<<COM0A1)    // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
    |(0<<COM0A0)    // 
    |(1<<COM0B1)    // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
    |(0<<COM0B0)
    |(1<<WGM01)     // Bits 1:0 – WGM01:0: Waveform Generation Mode
    |(1<<WGM00)
    ;
TCCR0B = 0
    |(0<<FOC0A)     // Force Output Compare A
    |(0<<FOC0B)     // Force Output Compare B
    |(1<<WGM02)     // Bit 3 – WGM02: Waveform Generation Mode
    |(0<<CS02)      // Bits 2:0 – CS02:0: Clock Select
    |(1<<CS01)
    |(0<<CS00)      // 010 = clock/8
    ;
OCR0A = 8;
OCR0B = 4;
TIMSK0 = 0;

TIMER2dreht eine Datenleitung, um alle 256 Zyklen einen Austastimpuls zu erzeugen, TIMER0und ist wie folgt aufgebaut:

ASSR = 0;
TCCR2A = 0
    |(0<<COM2A1)    // Bits 7:6 – COM0A1:0: Compare Match Output A Mode
    |(0<<COM2A0)    // 
    |(0<<COM2B1)    // Bits 5:4 – COM0B1:0: Compare Match Output B Mode
    |(0<<COM2B0)
    |(0<<WGM21)     // Bits 1:0 – WGM01:0: Waveform Generation Mode
    |(0<<WGM20)
    ;
TCCR2B = 0
    |(0<<FOC2A)     // Force Output Compare A
    |(0<<FOC2B)     // Force Output Compare B
    |(0<<WGM22)     // Bit 3 – WGM02: Waveform Generation Mode
    |(1<<CS22)      // Bits 2:0 – CS02:0: Clock Select
    |(0<<CS21)
    |(0<<CS20)      // 100 = 64
    ;
OCR2A = 255;
OCR2B = 255;
TIMSK2 = 0
    |(1<<TOIE2);    // Timer/Counter0 Overflow Interrupt Enable

TIMER2ruft eine ISR bei Überlauf auf (alle 256 Zyklen). Der ISR erzeugt manuell einen Blanking-Impuls und ggf. einen Latch-Impuls:

volatile uint8_t fLatch;

ISR(TIMER2_OVF_vect) {
    if (fLatch) {
        fLatch = 0;
        TLC5940_XLAT_PORT |=  (1<<TLC5940_XLAT_BIT);        // XLAT -> high
        for (int i=0;i<10;i++)
            nop();
        TLC5940_XLAT_PORT &= ~(1<<TLC5940_XLAT_BIT);        // XLAT -> high
    }
    // Blank
    TLC5940_BLANK_PORT |= (1<<TLC5940_BLANK_BIT);
    for (int i=0;i<10;i++)
        nop();
    TLC5940_BLANK_PORT &= ~(1<<TLC5940_BLANK_BIT);
}

Die nop()Verzögerung im obigen Code dient nur dazu, den Impuls auf der Spur des Logikanalysators deutlicher zu machen. So sieht die Schleife in der main()Funktion aus: Senden Sie einige serielle Daten, warten Sie, bis ISR sich um das Latchen kümmert, und wiederholen Sie es dann:

for (;;) {
    if (!fLatch) {
        sendSerial();
        fLatch = 1;
        _delay_ms(1);
    }
    nop();
}

sendSerial()sendet einige SPI ( Code auf Pastebin der Kürze halber ). Mein Problem ist, dass der Zeitgeber nach sendSerial()Abschluss, während er darauf wartet , dass er auf niedrig (verarbeitet) eingestellt wird, beschleunigt wird. fLatchHier ist die Spur des Logikanalysators (ich habe die Bereiche herausgeschnitten, in denen dasselbe Signal die Grafik weiterhin verkleinert):

Geben Sie hier die Bildbeschreibung ein

Auf der linken Seite zeigen die Kanäle 0 und 1 das Ende der gesendeten SPI-Daten. Ebenfalls links, auf Kanal 4, sehen Sie einen Austastimpuls. Auf Kanal 2 tuckert der Takt wie erwartet vor sich hin. Genau dort, wo sich die Lücke im Bild befindet, fLatchwird die Routine auf 1innen gesetzt. main()Und bald darauf TIMER0um etwa den Faktor 4 beschleunigt. Schließlich werden der Austastimpuls und der Verriegelungsimpuls ausgeführt (Kanäle 3 und 4, rechtes Drittel des Bildes), und jetzt nimmt der Taktimpuls wieder seine reguläre Frequenz an, und serielle Daten sind erneut gesendet. Ich habe versucht, die delay_ms(1);Leitung in herauszunehmen main(), aber es werden die gleichen Ergebnisse erzielt. Was ist los? Ich sollte beachten, dass der ATmega von einem 20-MHz-Kristall getaktet und dann mit dem folgenden Code um das 64-fache verlangsamt wird:

CLKPR = 1<<CLKPCE;
CLKPR = (0<<CLKPS3)|(1<<CLKPS2)|(1<<CLKPS1)|(0<<CLKPS0);

Wofür das ist: Ich experimentiere mit der Steuerung des LED-Treibers TLC5940 : Diese Chips erfordern einen externen Takt plus einen Reset am Ende des Taktzyklus.

Wenn Sie einen Debugger haben, versuchen Sie, Ihren Code zu stoppen, wenn der Timer zu schnell ist, und lesen Sie das Konfigurationsregister dieses Timers zurück. Sobald Sie denjenigen mit dem falschen Wert gefunden haben, lösen Sie bei dieser Registeränderung einen Haltepunkt aus und sehen Sie, welcher Teil Ihres Codes falsch funktioniert. Ich vermute, dass sich das Problem in einer externen Bibliothek befindet, die Sie möglicherweise verwenden und die diesen Timer für interne Dinge wie Verzögerungen verwendet.
Zwei Probleme: a) Ich habe keinen JTAG-Programmierer, daher habe ich keine Möglichkeit, den Chip zu debuggen. b) Ich ändere nie den Timer-Registerwert nach dem oben gezeigten Setup, also erwarte ich dies nicht eigentlich ändern. Ist das naiv?
Tatsächlich könnte eine von Ihnen verwendete Bibliothek die UART-Einstellungen ändern. Ich sehe, dass Sie eine sendSerial()-Funktion verwenden. Ist es Teil Ihres Codes oder eine externe Bibliothek? Möglicherweise ändern nicht Sie die Einstellungen, sondern ein Stück Code in einer aufgerufenen Bibliothek. Ich schlage vor, dass Sie Ihre serielle Schnittstelle verwenden, um die Konfigurationsparameter auszugeben und herauszufinden, was geändert wurde. Sie können sich auch die Quelle der verwendeten Bibliotheken (falls vorhanden) ansehen und sicherstellen, dass sie diesen Timer nicht ebenfalls verwenden.
Abgesehen von dem, was @Blup1980 vorgeschlagen hat, könnte es sich lohnen, den TLC5940 zu entfernen, um sicherzustellen, dass er nichts Seltsames mit der Taktleitung macht.
@Blup1980 Ich bin mir nicht sicher, ob ich die Relevanz von UART sehe: Ich verwende USART nicht für SPI, nur die "normalen" SPI-Einrichtungen. sendSerial()ist mein Code, der Daten über SPI sendet: Er berührt nicht die TCCR(Timer-Steuerungs-) Register.
@angelatlarge Entschuldigung, ich meinte "Timer-Einstellungen", nicht "UART-Einstellungen".
@Blup1980 Ah! Ich habe in der Frage und hier auf sendSerial() Code auf Pastebin verlinkt

Antworten (1)

Für eine schnelle Fehlerbehebung würde ich versuchen, dasselbe mit der Arduino-Bibliothek für TLC5940 zu tun und zu sehen, ob es schnell wird oder nicht. Wenn es mit der Bibliothek funktioniert, können Sie seine Quelle überprüfen und mit Ihrer vergleichen. Da Sie mit AVR vertraut sind, sollten Sie die Arduino-Quelle problemlos in nativen AVR konvertieren.

Nur für den Fall, dass Sie nicht wissen, wie Sie kompilierte Arduino-Skizzen in AVR hochladen: Wenn Sie Ihre Skizze kompilieren, wird eine Hex-Datei erstellt (Sie können den genauen Speicherort der Datei sehen, indem Sie den ausführlichen Modus in den Einstellungen aktivieren). Sie können dieses Hex mit Ihrem bevorzugten Programmierer auf Ihren AVR hochladen.

Ich hoffe es hilft