Hallo, ich frage mich wie das Unterbrechen eines Befehls durch eine ISR abläuft. Wenn ich folgenden Befehle benutze um den Pin2 des Ports B zu setzen: (I) PORTB |= _BV(PB2); // 2 Takte Und ich habe eine ISR die am gleichen Port B aber einen anderen Pin (z.B.: PB3) setzen möchte, dann kann es doch passieren, das der Befehl (I) durch ein Befehl in der ISR nicht erkannt wird, da die ISR eine höhere Priorität hat und der Befehl(I) 2 Takte benötigt? Ich habe mir das mal so dargestellt, wenn ich die Reihenfolge der von der CPU ausgeführten Schreib- und Leseanweisungen betrachte: [ PORTB |= _BV(PB2) <=> laden und setzen <=> LDI R1,x + MOV R2,R1 ] Also es wird erst geladen: LDI R1, x Dann tritt die ISR auf... Die ISR verdrängt erstmal den MOV Befehl für eine Zeitdauer und in dieser Zeit wird das Register R1 überschrieben. Nachdem die ISR fertig ist, wird der MOV Befehl ausgeführt. Damit wurde der LDI Befehl nicht erkannt! Oder? Gruß speedo
avr-tutorial -> Kapitel: Interrupts -> Besonderheiten des Interrupthandlers wer lesen kann ist klar im vorteil
"erkannt" ist hier der falsche Ausdruck. Aber du hast das Problem im Kern richtig erkannt. Wenns geht in der ISR ein Flag setzen und Arbeit in Mainloop machen.
Stefan P. wrote: > Nachdem die ISR fertig ist, wird der MOV Befehl ausgeführt. > Damit wurde der LDI Befehl nicht erkannt! Oder? Hallo Namensvetter ;-) Da man in der Regel vor/nach der Bearbeitung eines IRQ alle Register pusht/popt (sichert/wiederherstellt) geht dabei kein Befehl "verloren". Der MOV macht dann genau das was er auch anfangs machen sollte.
Stefan P. wrote: > Stefan P. wrote: > >> Nachdem die ISR fertig ist, wird der MOV Befehl ausgeführt. >> Damit wurde der LDI Befehl nicht erkannt! Oder? > > Hallo Namensvetter ;-) > Da man in der Regel vor/nach der Bearbeitung eines IRQ alle Register > pusht/popt (sichert/wiederherstellt) geht dabei kein Befehl "verloren". > Der MOV macht dann genau das was er auch anfangs machen sollte. Ja, aber das ist das Problem, welches der Threadsteller meint: Hauptroutine: Variable = PortB; Variable |= 0x00010000; <- Jetzt schlägt der Interrupt zu, der ein Flag setzt. TMP = PortB; TMP |= 0x10000000; PortB = TMP; <- Zurück ins normale Programm PortB = Variable; Und Schwupps - ist das Flag, das in der Interruptroutine gesetzt werden sollte, verloren. (Die hier eingesetzte Sprache ist übelster Pseudocode)
Nur so am Rande: Das konkrete Beispiel
1 | PORTB |= _BV(PB2); |
wird vom GCC in einen einzelnen sbi-Befehl übersetzt, der nicht unterbrochen werden kann. Ähnliches gilt für
1 | PORTB &= ~_BV(PB2); |
Na und??? Der ASM-Programmierer sichert in der ISR das SREG in einem Exklusivregister oder notfalls auch auf dem Stack.. Er nutzt in der ISR auch Exklusiv-Register oder sichert die benutzten Register auf dem Stack. Der Hochsprachenbenutzer überlässt dies dem Compiler, der weiß schon, was er tut.... Wo also ist das Problem? Mit setzt ein Flag ist übrigens nicht ein Flag des SREG gemeint, sondern eine globale Boolsche Variable im Verantwortungsbereich des Programmierers.
Sinusgeek wrote: > > Wo also ist das Problem? Vielleicht verstehe ich es nicht. Aber es gibt Situationen, die funktionieren ohne eine mutual exclusion nicht... > Der Hochsprachenbenutzer überlässt dies dem Compiler, der weiß schon, > was er tut.... Oh man, der ist gut. Deshalb sind Race Conditions ja auch überhaupt keine Problem! lach
Noch vergessen: Es gibt allerdings Fälle, in denen Unterbrechungen durch Interrupt vermieden werden müssen. Z.B. beim EEP-Schreiben zwischen Aufheben des Schreibschutzes und Schreibbefehl, aber auch bei RAM- oder Portzugriffen, wenn auch die ISR auf dieselben Adressen zugreifen kann. Diskussionen darüber wirst Du in diesem Forum vermutlich unter dem Suchbegriff "atomar" finden können.
Sinusgeek wrote: > Es gibt allerdings Fälle, in denen Unterbrechungen durch Interrupt > vermieden werden müssen. Z.B. beim EEP-Schreiben zwischen Aufheben des > Schreibschutzes und Schreibbefehl, aber auch bei RAM- oder > Portzugriffen, wenn auch die ISR auf dieselben Adressen zugreifen kann. Exakt um einen solchen Port-Zugriff geht es doch, oder?
Ach, verdammt. Ich habe die Frage falsch verstanden. Sorry. Aber das mit dem "Der PortB hat am Ende einen falschen Wert" liegt nicht daran, dass ein Befehl übersehen wurde ;-)
> Exakt um einen solchen Port-Zugriff geht es doch, oder?
Das wird doch aber nur dann zum Problem, wenn dieser Port (bzw. diese
Speicherzelle(n)) von Hauptprogramm und ISR bedient werden. Dann sind
im Hauptprogramm die zusammengehörenden (ASM-)Befehle durch kurzzeitige
Sperrung der Interruptfreigabe atomar (ununterbrechbar) zu machen.
Sinusgeek wrote: >> Exakt um einen solchen Port-Zugriff geht es doch, oder? > > Das wird doch aber nur dann zum Problem, wenn dieser Port (bzw. diese > Speicherzelle(n)) von Hauptprogramm und ISR bedient werden. Dann sind > im Hauptprogramm die zusammengehörenden (ASM-)Befehle durch kurzzeitige > Sperrung der Interruptfreigabe atomar (ununterbrechbar) zu machen. Jo, genau das meinte ich. Siehe Mutual Exclusion (kurz: MutEx ;-) )
Vielen Dank für die reichhaltige Beteiligung :) Ich meine natürlich nicht das ein Befehl ausgelassen wird, sondern das der Wert der in R1 geladen wird nicht registriert wird, da in der ISR, R1 wieder überschrieben wird und erst nach der Beendigung der ISR der MOV Befehl ausgeführt wird. "sbi"-Befehl setzt sich aus einem "load" und einem "set" Befehl zusammen. Zusammenfassend: Eine versuchte Änderung an einem Port "PORTB |= _BV(PB2); // 2 Takte" kann durch eine ISR überschrieben werden sodass die ursprüngliche Änderung nicht erkannt wird. Beheben durch: cli(); PORTB |= _BV(PB2); sei(); oder einer globalen Variable die eine Änderung schützt. Frage nebenbei: Haben die 2 Takte für den Befehl jetzt irgendeine Auswirkung? :)
Stefan P. wrote:
> einer globalen Variable die eine Änderung schützt.
Nein. Dann passiert exakt das Gleiche.
Was soll die ISR machen, wenn der Port durch die globale Variable
geschützt ist?
Beim anständigen Threading kannst du einfach mal kurz schlafen gehen und
danach wieder überprüfen, den Luxus hast du in einer ISR aber nicht.
> Zusammenfassend: > Eine versuchte Änderung an einem Port "PORTB |= _BV(PB2); // 2 Takte" > kann durch eine ISR überschrieben werden sodass die ursprüngliche > Änderung nicht erkannt wird. Nein... PortB liegt in dem I/O-Bereich (die unteren 32 I/O-Adressen), für den es Bit-Befehle gibt (SBI, CBI, SBIS, CBIS). Diese Befehle sind atomar, sie lassen sich nicht unterbrechen. Ein cleverer ASM-Programmierer oder Compiler nutzt diese Befehle. Anders sieht es aus, wenn eine der oberen 32 I/O-Adressen angesprochen wird. Da muss dann programmiert werden: in r16,gimsk ori r16,1<<int0 out gimsk,r16 Das sind also 3 unabhängige ASM-Befehle, wo ein Interrupt dazwischenfunken kann. Da es aber kaum vorkommt, dass eine ISR auch auf gimsk zugreift, braucht das im Normalfall nicht mit CLI/SEI atomar gemacht werden. Wird aber vom Hauptprogramm und der ISR auf dieselbe Adresse zugegriffen, wobei das Hauptprogramm mehrere (ASM- bzw. Maschinensprache-)Befehle braucht, dann müssen diese mit CLI und SEI ununterbrechbar zusammengefasst werden. Es schadet also einem Hochsprachenbenutzer nicht, etwas ASM zu verstehen, denn der Controller kann keine Hochsprache, er arbeitet in MC, was 1 zu 1 in ASM notierbar ist.
Also so:
1 | cli ;Interrupt aus |
2 | in TEMP1, PORTD ;2 Takte |
3 | ori TEMP1, 1<<PD7 ;Pin 7 setzen, 1 Takt |
4 | out PORTD, TEMP1 ;2 Takte |
5 | sei ;Interrupt ein |
bei:
1 | sbi PORTD, PD7 ;Pin 7 setzen, 2 Takte |
muss der Int nicht disabled werden, da es ein Befehl ist, der zwar 2 Takte benötigt, aber nicht unterbrochen wird. Und woher weiss ich, welche Variante der Compiler umsetzt? Und ob er da CLI automatisch berücksichtigt? Ich weiss schon, warum ich ASM programmiere...
>> PortB liegt in dem I/O-Bereich (die unteren 32 I/O-Adressen), für den es >> Bit-Befehle gibt (SBI, CBI, SBIS, CBIS). Diese Befehle sind atomar, sie >> lassen sich nicht unterbrechen. Ein cleverer ASM-Programmierer oder >> Compiler nutzt diese Befehle. Wie machst Du
1 | in TEMP1, PORTD ;2 Takte |
2 | eor TEMP1, 0b11001100 ;Port Pins toggeln |
3 | out PORTD, TEMP1 ;2 Takte |
mit SBI und CBI? Also immer schauen, was effektiver ist.
Sven wrote: > Und woher weiss ich, welche Variante der Compiler umsetzt? Und ob er da > CLI automatisch berücksichtigt? Da du dank Kapselung auch nie weißt, wo du deinen Code noch einsetzt und du nie alle Eigenheiten des Compilers kennst, lautet Sicherheit die Maxime. Außer in den 2 % Code, von denen Echtzeitfähigkeiten gefordert werden.
> Ich weiss schon, warum ich ASM programmiere... Ich auch... ;-) Übrigens: Im ersten Beitrag dieses Threads steht: LDI r1,x LDI kenne ich nur von AVRs (andere ASM-Dialekte nennen das MOV), daher gehe ich davon aus, dass Du AVR-ASM meinst. R1 iat aber nicht mit LDI ansprechbar, das geht nur mit R16 bis R31. ;-(
Vielen Dank euch Allen. Jetzt habe ich es verstanden. Was man so alles berücksichtigen muss :)
> Wie machst Du > in TEMP1, PORTD ;2 Takte > eor TEMP1, 0b11001100 ;Port Pins toggeln > out PORTD, TEMP1 ;2 Takte > mit SBI und CBI? Also immer schauen, was effektiver ist. Gar nicht, denn EOR geht nicht mit einer Konstante!!! Außerdem kommt es auf den Controller an, bei den neuen AVRs mache ich es dann so: ldi r16,0b11001100 out pind,r16 Sollte die ISR auch r16 nutzen, dann hat der Programmierer an anderer Stelle etwas Grundlegendes falsch gemacht. Der eigentliche Portzugriff ist aber atomar und braucht nicht gekapselt werden.
Hallo, ganz richtig ist, daß man bei Zugriffen auf Ports oder Daten, die auch durch ISRs benutzt werden, eine Gleichzeitigkeit vermeiden muss. In gcc z.B. so: char tmp = SREG; // sichere I-Bit cli(); unteilbar_teil1; unteilbar_teil2; SREG = tmp; // restauriere I-Bit Eine Kontrolle der Assemblerbefehle, die der GCC erzeugt, halte ich für Quatsch, dann bitte direkt ASM kodieren. Schliesslich weiss man nie, ob eine andere -ox das genauso macht. Gruss, Michael
>> Gar nicht, denn EOR geht nicht mit einer Konstante!!! Oh, sorry, da stand vorher andi. ;-) Außerdem kommt es auf den Controller an, bei den neuen AVRs mache ich es dann so: ldi r16,0b11001100 out pind,r16 Häh? Das ist aber nicht das gleiche. Wenn Du nur einige Pins ändern willst, musst Du trotzdem vorher den Port einlesen.
Dochdoch, das ist auch eine AVR-Spezialität, Bit-Setzen im PIN-Register toggelt das entsprechende Ausgangspin Ahoi, Martin
Achso, hab das pind für einen Schreibfehler gehalten. ;-) Ist ja witzig, welche AVRs können das denn, die Xmegas?
> Achso, hab das pind für einen Schreibfehler gehalten. ;-) Ist ja witzig, > welche AVRs können das denn, die Xmegas? Neee, sooooo neu müssen sie nun auch wieder nicht sein. Tiny13, Tiny24/44/84, Tiny25/45/85, Mega48/88/168 und Mega644 z.B. können das auch schon. Einfach mal die entsprechenden Datenblätter ansehen.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.