Hallo,
ich frage mich gerade ob, wenn man einen intern (!) getakteten Timer mit
Interrupt verwendet, irgendwelche Pins dadurch beeinflusst werden bzw.
irgendwelche Pins den Timer/Interrupt beeinflussen. Was ich erlebe, ist
genau das. Einige Pins sind ja mit den Timer/Countern verbunden.
Ein konkretes Beispiel: wenn ich den Timer 2 im CTC und eine ISR auf
TIMER2_COMPA_vect definiere, habe ich einen sauber laufenden Interrupt.
Ein in der ISR getoggleter Pin (PD3) erzeugt im Oszilloskop ein stabiles
Rechtecksignal.
PD3 im Oszi:
1
_ _ _ _
2
| |_| |_| |_| |_
Wenn ich nun im main loop das Bit PD5 (welches als Output gesetzt ist)
regelmäßig ändere, fängt das Rechtecksignal an zu zucken. Genauer
betrachtet sieht man, dass anscheinend ISR-Aufrufe verschluckt werden.
PD3 im Oszi:
1
___ _ ___ _
2
| |_| |___| |_| |_
oder auch
1
___ ___ ___
2
| |___| |___| |___
Das Output-Pin (D5) funktioniert wie erwartet.
Die zusätzliche Funktionen für PD5 sind "T1/OC0B/PCINT21". PD3 hat
"INT1/OC2B/PCINT19"
Hier die Timer-Initialisierung:
Gibt es dafür eine schnelle Erklärung? Falls nicht kann ich gerne mehr
Infos bzw. Sourcecode liefern.
Das ganze passiert im ATmega328 auf einem Arduino Duemilanove.
Danke und Gruß
Jonas
Vielleicht sollte ich noch erwähnen, dass das beschriebene Verhalten
nicht bei allen Pins auftritt. Ändere ich z.B. PD6 oder auch PB0 im main
loop, tritt das Problem nicht auf. Bei PD5 oder PD7 schon.
Jonas
Das ist offenbar ein read-modify-write Problem.
Das passiert, wenn die Pins nicht mit setbit oder clearbit geschaltet
werden, sondern der Port in ein Pufferrgister eingelesen wird, ein Bit
im Pufferregister geändert wird (z.B. getoggelt) und der Registerwert
dann wieder auf den Port geschrieben wird.
Wenn der Interrupt eine read-modify-write Anweisung der Hauptschleife
unterbricht und den Portwert selbst ändert, weiß die Hauptschleife davon
nichts und schreibt einen veralteten Portwert aus dem Pufferegister auf
den Port raus. Die Änderungen, die der Interrupt am Port vorgenommen
hat, sind damit nach sehr kurzer Zeit überschrieben.
Schau dir mal das Assemblerlistung von deinem Programm an, ich bin mir
fast zu 100% sicher, dass es genau so ein Problem ist.
Stichwort "atomarer Registerzugriff"!
Damit das funktioniert, muss im Hauptprogramm bei jedem nicht atomaren
Zugriff auf Register, die der Interrupt verändern darf, der
entsprechende Interrupt für die Zeit des Zugriffs gesperrt werden.
Grüße,
Peter
Oh ja, mit cli() vor und sei() nach dem ändern der Bits in der
Hauptschleife bleibts sauber!
Dann fehlten nicht wie ich vermutete Interrupts, sondern das
"Rechteck-Bit" lieferte ein falsches Signal ans Oszilloskop.
Vielen Dank! Ich war die ganze Zeit auf der falschen Fährte...
Werd mir das Assemblerlisting noch ansehen.
Gruß
Jonas
Ist mir inzwischen wie Schuppen von den Augen gefallen.
Die Zeile:
1
PORTD|=(0<<PD5);
bedeutet ja:
1
PORTD=PORTD|(0<<PD5)
Also read-modify-write, wie du erkannt hast, Peter. Und der Interrupt
funkte regelmäßig irgendwo ins 'modify' rein. Beim 'write' wurden dann
die Änderungen des Interrups zunichte gemacht.
Das auch nur, weil es auf dem gleichen Port passierte, wenn auch
(vermeintlich) verschiedene Bits betraf.
Danke nochmal!
Bei neueren AVRs (ATmega328P ist neu genug) gibt es noch eine andere
Möglichkeit, wenn der Pin 5 nicht explizit auf 0 bzw. 1 gesetzt, sondern
zwischen den beiden Zuständen umgeschaltet werden soll:
1
PIND=1<<PD5;
Das gilt natürlich nur für den in main() gesetzten Pin, im Interrupt
würde das nicht helfen.
Beachte: es ist volle Absicht, dass da kein '|' drinsteht!
Andreas
Jonas P. schrieb:> Ist mir inzwischen wie Schuppen von den Augen gefallen.>> Die Zeile:>>
1
>PORTD|=(0<<PD5);
2
>
Die Zeile sollte eigentlich
1
PORTD|=(1<<PD5);
heissen. Mit einer 0 ist das recht sinnfrei.
Macht man es aber richtig, so erkennt der Compiler die Absicht und der
Optimizer ersetzt das komplette Konstrukt durch einen Einzelbit-Setz
Befehl, wodurch dann auch wieder alles Paletti ist. Das ist dann von
Haus aus atomar.
Das ist dann wohl auch einer der Grund, warum dieses Problem in der
Praxis nicht öfter auftritt.
Also:
korrekt schreiben
Optimizer aufdrehen
Vielen Dank für die Kommentare.
>> PIND = 1<<PD5;
@Andreas: Ja stimmt, das habe ich auch im Datenblatt gelesen: "However,
writing a logic one to a bit in the PINx Register, will result in a
toggle in the corresponding bit in the Data Register.". Kann ganz
nützlich sein.
>> diese Zeile ist sinnlos.
@Spess53: Ihr habt natürlich Recht. Lustigerweise trat das Problem nur
mit "PORTD |= (0<<PD5);" auf. Bei "PORTD |= (1<<PD5);" nicht. Wie gesagt
habe ich den Code aufs nötige reduziert um das Phänomoen zu
reproduzieren. Bei "PORTD |= (1<<PD5);" tritt nehme ich an durch die
Logik kein 'write' mehr auf. Wenn das Outputbit 5 die ganze Zeit den
Wert 1 hat resultiert die Operation in "1 | 1" und bricht vor dem Prüfen
des zweiten Operanden ab (Fall schon erfüllt, keine Modifikation nötig).
Oder so ähnlich.
Eigentlich hatte ich ein funktionierendes Programm und wollte nur die
Interruptfrequenz prüfen (durch toggeln eines Pins in der ISR). Dann war
ich irritiert als das Ergebnis (scheinbar) so instabil war. Jetzt ist
mir klar, dass ich mir und meinem Programm damit ein Bein gestellt habe.
Gruß
Jonas
Hab mich verzettelt. "@Spess53: Ihr habt natürlich Recht." sollte
"@Spess53 und Karl Heinz: Ihr habt natürlich Recht." werden. Keine
Hoheitsanrede... ;-)
Jonas P. schrieb:> reproduzieren. Bei "PORTD |= (1<<PD5);" tritt nehme ich an durch die> Logik kein 'write' mehr auf. Wenn das Outputbit 5 die ganze Zeit den> Wert 1 hat resultiert die Operation in "1 | 1" und bricht vor dem Prüfen> des zweiten Operanden ab (Fall schon erfüllt, keine Modifikation nötig).
Das wiedrrum kann nicht sein.
PORTD ist eine volatile Einheit. Und als solche muss der Compiler jeden
Zugriff darauf auch durchführen.
> Oder so ähnlich.
Es ist schon so, dass dich hier der Optimizer rettet, indem er diesen
Zugriff durch die Wahl der Assembler Operation atomar macht.
Was der allerdings macht bei
PORTD |= (1<<PB0) | (1<<PB1);
weiß ich jetzt auch nicht.
Trotzdem danke für das Problem. Das es hier ein potentielles Problem
gibt war mir bisher auch nicht bewusst.
Karl heinz Buchegger schrieb:
> Das wiedrrum kann nicht sein.> PORTD ist eine volatile Einheit. Und als solche muss der Compiler jeden> Zugriff darauf auch durchführen.
Verstehe.
> Es ist schon so, dass dich hier der Optimizer rettet, indem er diesen> Zugriff durch die Wahl der Assembler Operation atomar macht.
Ja, kann ich bestätigen. Hab es verifiziert.
>> Was der allerdings macht bei>>> PORTD |= (1<<PB0) | (1<<PB1);>> weiß ich jetzt auch nicht.
Aus
1
PORTD|=(1<<0);
wird
1
sbi 0x0b, 0
Aus
1
PORTD|=(1<<0)|(1<<1);
wird
1
in r24, 0x0b
2
ori r24, 0x03
3
out 0x0b, r24
Aus
1
PORTD|=(0<<0);
wird
1
in r24, 0x0b
2
out 0x0b, r24
(-Os und -O3 machen in allen Fällen kein Unterschied.)
>> Trotzdem danke für das Problem. Das es hier ein potentielles Problem> gibt war mir bisher auch nicht bewusst.
Hey, bitte gerne. Ich danke auch. :-)