Das Festlegen des Ports in der Hauptschleife führt dazu, dass ISR ausgehungert wird

Der Beispielcode

  • initialisiert einen asynchronen Timer, der jede Sekunde ausgelöst wird
  • setzt zwei Ports als Ausgänge (PA4, PA6 - LEDs verbunden)
  • der Timer ISR schaltet Pin PA4 um
  • setzt den Pin PA6 in der main while()-Schleife dauerhaft auf 1

Wenn Pin PA6 mit umgeschaltet wird

PORTA |= (1 << PA6);

alles funktioniert wie erwartet. Die LED an Pin PA4 wird genau jede Sekunde umgeschaltet. Wenn der Port jedoch so eingestellt ist

PORTA |= (1 << PinNumber);

der Pin PA4 wird nur sehr sporadisch umgeschaltet (beobachtet bis zu 22 Sekunden zwischen Umschaltungen). Dies zeigt für mich an, dass der Controller aus irgendeinem Grund entweder "beschäftigt" ist oder das Überlauf-ISR-Flag überhaupt nicht gesetzt ist.

BEARBEITEN: Wenn ich einen anderen zyklusintensiven Code wie ein _delay_us(10); die Situation verbessert sich dahingehend, dass mit zunehmender Verzögerung immer weniger Interrupts fehlen. Dies ist sinnvoll, wenn wir davon ausgehen, dass die Pin-Toggle-Operation irgendwann irgendwie den ISR-Eintrag blockiert.

Da die ISR-Ausführung immer Vorrang vor dem Code in der Hauptschleife haben soll, verstehe ich nicht, warum das passieren kann und wie genau es einen Unterschied macht, ob die Pin-Nummer als Konstante oder als Variable übergeben wird.

Ich verstehe, dass der Compiler wahrscheinlich eine nette Optimierung durchführen kann, wenn eine Konstante verwendet wird, aber die Verwendung einer Variablen sollte nicht zu ISR-blockierendem Code führen.

Dies ist auf GCC 4.8.1.

#include "stdlib.h"
#include "avr/interrupt.h"

uint8_t PinNumber;  

// asynch ISR with external clock source, fires every second
ISR (TIMER2_OVF_vect) {
    PORTA ^= (1 << PA4);
}

int main(void) {        
    DDRA |= (1 << PA6) | (1 << PA4);    // set PA6 and PA4 as output
    PinNumber = PA6;      // variable that just holds PA6 for demonstration

    cli();                      // global interrupt disable during init
    ASSR    |= (1<<AS2);        // Asynchronous Timer/Counter2 from external clock
    TCNT2   = 0x00;
    OCR2A   = 0x00;
    OCR2B   = 0x00;
    TCCR2A  = 0x00;
    TCCR2B  = 0x05;             // set divider for one second
    TIMSK2  |= (1 << TOIE2);    // enable TIMER2_OVF_vect
    sei();                      // global interrupt enable

    while(1) {
        //PORTA |= (1 << PinNumber);    // this somehow causes the interrupt to "starve"
        PORTA |= (1 << PA6);            // this works as expected
    }

}

EDIT: Der praktische Hintergrund ist, dass wir beobachtet haben, dass der ISR beim wiederholten Ändern mehrerer Ausgangsports, bei denen die Pin-Nummer zur Laufzeit variabel ist, nicht korrekt ausgeführt wird.

Sie sagen nicht, welches Mikro Sie verwenden, aber ist es möglich, dass Sie ein Lese-, Änderungs- und Schreibproblem haben? Mit anderen Worten, vielleicht läuft Ihr Interrupt einwandfrei, aber die Anweisung in Ihrer Hauptschleife löscht versehentlich PA4. Es ist nur eine Vermutung, aber möglicherweise kann der Compiler bei Verwendung einer Konstante Code generieren, der das Problem vermeidet (möglicherweise durch Verwendung einer Bit-Set-Anweisung oder ähnlichem), dies jedoch nicht mit einer Variablen kann.
brhans hat recht. In Ihrer main () -Schleife wird PORTA gelesen, dann tritt Ihr Interrupt auf, der PORTA modifiziert, dann zurück in main, die OR-Operation wird mit dem alten Wert von PORTA ausgeführt und wieder in PORTA eingefügt, wobei die Wirkung der ISR verloren geht. Sie müssen den Interrupt vor Ihrer Zeile in main() deaktivieren und danach wieder aktivieren.
Oh, das hätte ich natürlich sehen sollen. Wenn eine Konstante verwendet wird, scheint GCC sie auf eine einzelne sbi-Anweisung zu optimieren. Wenn eine Variable verwendet wird, muss sie den Portstatus "puffern", bevor sie die ODER-Operation ausführt. Wenn die ISR zwischen diesen Anweisungen ausgelöst wird, geht die Änderung von der ISR im Wesentlichen verloren.

Antworten (2)

Wie in den Kommentaren erwähnt, besteht das Problem darin, dass die Lese-, Änderungs- und Schreibsequenz unterbrochen wird, was dazu führt, dass die Anweisung in der Hauptschleife versehentlich PA4 löscht.

Wenn eine Konstante verwendet wird, scheint GCC sie auf eine einzelne sbi-Anweisung zu optimieren. Bei Verwendung einer Variablen wird der Portstatus kopiert, bevor die ODER-Operation ausgeführt wird. Wenn die ISR zwischen diesen Anweisungen ausgelöst wird, geht die Änderung von der ISR im Wesentlichen verloren.

Wahrscheinlich hält der etwas langsamere Speicherzugriff die Sache auf. Vielleicht ist es auch besser, den Portstatus nur an einer Stelle zu ändern, um die in den Kommentaren erwähnte Lese-Änderungs-Schreib-Synchronisierung zu vermeiden. Mein Vorschlag:

#include "stdlib.h"
#include "avr/interrupt.h"

uint8_t PinMask = 1 << PA6;
volatile uint8_t PAMask = 0;  

// asynch ISR with external clock source, fires every second
ISR (TIMER2_OVF_vect) {
    PAMask ^= (1 << PA4);
}

int main(void) {        
    DDRA |= (1 << PA6) | (1 << PA4);    // set PA6 and PA4 as output

    cli();                      // global interrupt disable during init
    ASSR    |= (1<<AS2);        // Asynchronous Timer/Counter2 from external clock
    TCNT2   = 0x00;
    OCR2A   = 0x00;
    OCR2B   = 0x00;
    TCCR2A  = 0x00;
    TCCR2B  = 0x05;             // set divider for one second
    TIMSK2  |= (1 << TOIE2);    // enable TIMER2_OVF_vect
    sei();                      // global interrupt enable

    while(1) {
        PORTA = PAMask;
    }

}
Ihr Beispielcode ist ein gültiger Ansatz. Aber wie Sie sagten, wurde in den Kommentaren darauf hingewiesen, dass es am Ende wirklich darauf ankommt, dass die Lese-, Änderungs- und Schreibsequenz durch die ISR unterbrochen wird.