Der Beispielcode
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.
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;
}
}
brhans
Tut
Rev