Hallo,
Ich versuche mit einer Fernbedienung den externen Interrupt zu testen
und sehe offensichtlich wieder mal den Wald vor lauter Bäume nicht.
Ich bekomme zwar die UART Ausgabe, aber die bits toggeln nicht!!!
Es werden hier sicher einige Leser bereit sein, den Code zu lesen, zu
analysieren, was er macht; zu überlegen was er machen soll und auf
welche Weise und Dir zu sagen inwiefern beides voneinander abweicht.
Wenn Du darauf Wert legst, dann ignoriere meinen Beitrag.
Ich bin eher für Hilfe zur Selbsthilfe. Das ist aber meistens nicht
erwünscht, deswegen biete ich es hier erstmal nur an, ohne weiter Kritik
zu üben oder Hinweise zu geben, wie Du das Problem selbst lösen kannst.
Das ist ganz klar der aufwendigere Weg für Dich. Vielleicht auch
manchmal nicht ganz angenehm für das Selbstbewußtsein.
Falls Du das doch möchtest, teile uns (mir) das hier bitte mit.
Bitflüsterer schrieb:
> Ich bin eher für Hilfe zur Selbsthilfe.
Ich versuche eigentlich immer den Dingen selbst auf den Grund zu gehen,
aber manchmal ist man einfach blind, wenn man immer nur im eigenen Saft
schmort.
Also, jede Art von Hilfe ist willkommen!
Lad den Code in den Simulator
Setze einen Breakpoint an den Anfang des Interrupt Handlers, also hier
1
int0_handler:
2
rjmp IRbit
und steppe den Code erst mal soweit durch, bis du in der Hauptschleife
angelangt bist. Dann klickst du in der I/O View (keine Ahnung wie das im
neueren Atmel Studio heißt, ich verwende lieber das alte AVR Studio) den
Pin an, an dem der Interrupt hängt. Der Breakpoint müsste ansprechen
(gegebenenfalls 'Single Step' drücken) und von dort kannst du dann mit
weiteren Single Steps verfolgen was passiert.
Bruno M. schrieb:> ldi temp, LOW(RAMEND)> out SPL, temp> ldi temp, HIGH(RAMEND)> out SPH, temp
Beim Lesen von 16 Bit "Registern" (wie zb dem Ergebnis vom ADC): erst
Low, dann High
Beim Schreiben genau umgekehrt: erst High, dann Low
Bruno M. schrieb:> Das habe ich schon x mal gemacht. Es passiert genau das was beabsichtigt> ist, aber leider funktioniert es in der Realität nicht.
Wenn deine Ausgabe anspringt, dann wird die ISR auch angesprungen.
Was noch sein könnte: Achte mal darauf, ob dein Prozessor resettet.
Bruno M. schrieb:> Bitflüsterer schrieb:>>> Ich bin eher für Hilfe zur Selbsthilfe.>> Ich versuche eigentlich immer den Dingen selbst auf den Grund zu gehen,> aber manchmal ist man einfach blind, wenn man immer nur im eigenen Saft> schmort.>> Also, jede Art von Hilfe ist willkommen!
Gut.
Betriebsblind sind wir alle mal. Nicht weiter schlimm. Aber wie gehen
wir damit um?
Ich versuche es mal: Dieser Teilsatz "... zu analysieren, was er macht;
zu überlegen was er machen soll und auf welche Weise ..." sollte Dir
Anregungen geben.
Aber ich will das noch etwas detaillierter ausführen:
Ein wesentlicher Punkt bei der Softwareentwicklung ist eine genau
Beschreibung der Umgebung. Was für ein Puls kommt am Interrupt-Eingang
an? Von welcher Quelle (Schaltkreis, Taster etc.)? In welchem Rythmus?
Schaltung? Wie testest Du?
Ein weiterer wesentlicher Punkt ist die Beschreibung der Funktion einer
SW im voraus - ein Entwurf. Als Text, Pseudocode, Diagramm etc.
Wenn man ein Feature (z.B. Interrupts von externen Quellen) erst wenig
benutzt hat, lohnt sich ein schrittweiser Aufbau, der SW. Wenn man an
einen toten Punkt kommt, dann ein Rückbau. Z.B. könntest Du ersteinmal
einfach einen Interrupt schreiben, der eine LED toggelt. Nur um zu
sehen, ob das überhaupt wie gewünscht geht.
Ein weiterer Punkt sind Variablen- und Funktionsnamen (resp. Labels).
Idealerweise ersparen gut formulierte Variablen- und Funktionsnamen den
Kommentar. In Deinem Fall sind z.B. "IR_bit", "IO_" und "IR_tst"
schlecht gewählt. Ich weiss nicht und kann nur raten, was genau diese
Bits anzeigen oder bewirken. Ähnlich geht es mir mit dem Funktionsnamen
"weiter".
Funktionsnamen enthalten am besten ein Verb und mindestens das Subjekt;
das Objekt würde auch noch helfen. Ein Variablenname sind am besten so
formuliert, dass sie eine Zustand beschreibe. Z.B. "XXXIntIstBehandelt"
oder "ADCIstInitialisiert" oder "EingangsDatenVorhanden" oder ähnliches.
Eine ganz wesentliche Tätigkeit bei der Software-Entwicklung ist das
"debuggen". Im eigentlichen Sinn, der Vergleich, von dem was geschehen
soll (was man aus dem Entwurf abliest) und dem was tatsächlich
geschieht. Praktisch stößt wird man dabei mit Beschränkungen
konfrontiert. Etwa wenn man kein Oszilloskop hat, oder keinen Debugger.
Aber man kann das umgehen, indem man etwa eine oder mehrere LEDs
anschliesst oder den UART zu Ausgabe benutzt.
Ein anderes Problem ist, dass man unter Umständen garnicht sicher ist,
dass die UART-Ausgabe funktioniert oder die LED-Ausgabe. Dann kommt der
oben erwähnte stufenweise Aufbau (bzw. der Rückbau) zum Tragen. Ich kann
das noch näher ausführen, falls Du es möchtest.
Ich hoffe das hilft Dir weiter. Mein Ansatz ist sehr analytisch. Auch
nicht jedermanns Fall. Falls es nicht Deiner ist, ignoriere diesen
Beitrag.
Gruß
Bitflüsterer
P.S. In der Zwischenzeit hat Karl Heinz geantwortet und Du.
Meinem Ansatz folgende ergeben sich folgende Fragen: Was genau soll
geschehen? (Siehe Entwurf). Es ist oftmals ein Problem, dass man meint,
der Code würde genau das realisieren, was man im Kopf entworfen hat, es
aber garnicht der Fall ist. Wenn man den Entwurf auf Papier hat, dann
ist es einfacher, die Tücke zu umschiffen, dass man gedanklich
Voraussetzungen macht, die man nicht mehr hinterfragt. Im Ergebnis hat
man tausendmal über die fehlerhafte Stelle geguckt ohne zu merken, dass
sie es ist.
Setzt man aber voraus, dass Dein Code perfekt ist dann bleibt noch die
Frage ob es die Schaltung nicht ist. Das wiederrum führt zu dem Punkt
des Rückbaus. Geht die serielle Kommunikation? Ändert sich die Ausgabe,
wenn Du im Code das auslösende Ereignis "hart" herbeiführst? Gehen die
Interrupts? Löst das physische Ereignis auch eine Änderung des Ablaufs
der SW aus?
Karl Heinz schrieb:> Bruno M. schrieb:>>> ldi temp, LOW(RAMEND)>> out SPL, temp>> ldi temp, HIGH(RAMEND)>> out SPH, temp>> Beim Lesen von 16 Bit "Registern" (wie zb dem Ergebnis vom ADC): erst> Low, dann High> Beim Schreiben genau umgekehrt: erst High, dann Low
Die Regel muss man nicht für den Stackpionter beachten, sie gilt nur für
Registerpaare, die mit dem Shadowregister arbeiten.
Bruno M. schrieb:> Danke für den Hinweis. Am Problem hat das aber nichts geändert.>> Z. B. eine typische UART Ausgabe:>> 1 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 0 0 0 1 1 0 0 1 1 1 1 1 1 1 0 1 0Bruno M. schrieb:> Ich bekomme zwar die UART Ausgabe, aber die bits toggeln nicht!!!
Toggled doch, wo ist das Problem?
adenin schrieb:
> Toggled doch, wo ist das Problem?
Da ich bei jedem Interrupt den Wert toggle, stimmen nur die ersten 5,
aber der 6. nicht mehr.
Zusätzlich ist mir jetzt noch aufgefallen, daß das Muster immer gleich
ist!
Bruno M. schrieb:> Das habe ich schon x mal gemacht. Es passiert genau das was beabsichtigt> ist
Nicht wirklich.
> aber leider funktioniert es in der Realität nicht.
Es funktioniert auch in der Simulation nicht. Jedenfalls dann nicht,
wenn man denn INT0-Pin hinreichend häufig toggelt.
Das Ganze ist ein sehr schönes Beispiel dafür, wie man eine
Synchronisierung zwischen ISR und main KEINESFALLS bauen sollte. So, wie
der Code dort steht, stellt er eigentlich nur eins sicher:
Daß irgendwann mal entweder '0' oder '1' über die UART ausgegeben wird,
wenn der externe Interrupt mindestens einmal ausgelöst wurde.
uwe schrieb:
> Mit welcher Frequenz arbeitet den die Fernbedienung!? Was soll die> Software denn machen wenn die Fernbedienung scheller als der UART ist!?
Ich vermute, das ist die Lösung des Rätsels. Fernbedienung ca 38 kHz,
UART 9600 Baud.
Danke für den Hinweis!
c-hater schrieb:
> Das Ganze ist ein sehr schönes Beispiel dafür, wie man eine> Synchronisierung zwischen ISR und main KEINESFALLS bauen sollte.
Etwas mehr Aufklärung wäre sehr hilfreich und willkommen.
Was, genau, hängt eigentlich an deinem Interrupt Pin?
Woran machst du fest, dass aus der Fernsteuerung genau 1 Puls beim
einmaligen Drücken einer Taste rauskommt?
Weiteres Problem in der Software: dir können Zeichen verloren gehen,
weil du einfach ohne Ansehen des Arbeitsstatuses der UART Zeichen ins
UDR schreibst.
Kommen die Interrupts schneller als die UART ausliefern kann, dann
verlierst du Zeichen.
Mach dir halt mal in die ISR eine Error-LED rein, die dann eingeschaltet
wird, wenn ein ISR AUfruf erfolgt, noch ehe die Abarbeitung des
vorhergehenden Interrupts beendet ist (erkennbar am Inhalt von IR_tst.
Ist IR_tst auf 0, wenn die ISR angesprungen wird, dann ist die UART
Sache noch nicht durchgelaufen.
Karl Heinz schrieb:> Was, genau, hängt eigentlich an deinem Interrupt Pin?>> Woran machst du fest, dass aus der Fernsteuerung genau 1 Puls beim> einmaligen Drücken einer Taste rauskommt?
@ Bruno
Ein kleiner Bezug auf meinen Beitrag oben als Beleg, dass solche
Vorgehensweise Vorteile haben kann:
> Ein wesentlicher Punkt bei der Softwareentwicklung ist eine genau> Beschreibung der Umgebung. Was für ein Puls kommt am Interrupt-Eingang> an? Von welcher Quelle (Schaltkreis, Taster etc.)? In welchem Rythmus?> Schaltung? Wie testest Du?
Gruß
Bitflüsterer
An meinem Pin hängt ein IR-Empfänger.
Natürlich kommen bei einmal drücken erheblich mehr Impulse. Jeder Impuls
löst aber einen Interrupt aus. Lt. UART Ausgabe wären es 34. Ich weiß
jetzt allerdings nicht mehr, was die Ausgabe Routine überhaupt macht,
wenn sie von den Interrupts überholt wird.
Ich muß mich korrigieren:
>Fernbedienung ca 38 kHz,>UART 9600 Baud.
Das stimmt so nicht! 38 kHz ist die Trägerfrequenz der FB. Die Ausgabe
des Empfängers ist wesentlich langsamer.
Ich sehe schon, ich muß das alles noch mal überdenken.
Bruno M. schrieb:> 38 kHz ist die Trägerfrequenz der FB. Die Ausgabe> des Empfängers ist wesentlich langsamer.
Zumindest zeitweise kommt ganz offensichtlich der 38kHz-Träger durch.
Das war, was ich dir mit dem Hinweis sagen wollte, daß es auch in der
Simulation nicht klappt, wenn man nur häufig genug am Int-Pin "wackelt".
> Ich sehe schon, ich muß das alles noch mal überdenken.
Besser ist das.
Bruno M. schrieb:> Nachtrag für alle die an diesem Thread Interesse hatten:>> Nach Änderung der Baudrate auf 38400 sieht die UART Ausgabe so aus:>> 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0> 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0>> ein Beweis für die Vermutung, daß die Ausgabe den Interrupts nicht> folgen konnte.
Genau. Und wenn du korrekt synchronisiert hättest, wäre das schon bei
der Synchronisation aufgefallen. Dann hätte nämlich die ISR auf main
warten müssen, was sie nicht tun kann (weil sie dann bis in alle
Ewigkeit warten würde).
Was sie aber tun kann, ist, die Fehlersituation (Echtzeitbedingung nicht
gegeben) zu erkennen und dann eine sinnvolle Fehlerbehandlung zu
starten. Bei der Testanwendung wäre das natürlich eine entsprechende
Ausgabe auf das Debug-Terminal gewesen.
Und schon hätte der Controller selber dir gesagt, wo die Säge klemmt...
Dein Programm krankt, je nach Betrachtungsweise, an drei Sachen oder an
einer:
Variante A)
1) Der eigentliche Meßwert wird an der falsche Stelle gewonnen, nämlich
in main. Er sollte aber in der ISR gewonenn werden, denn dort ist
(zeitlich) die erste Chance, den aktuellen Pegelstatus nach einem durch
Interrupt signalisierten Pegelwechsel zu gewinnen. Ja, das ist auch
nicht perfekt, weil auch hier immer noch hochfrequentes "Prellen" stören
könnte, aber allemal besser, als erst viele Takte später in main
nachzuschauen.
2) Die Synchronisation schützt wegen der falschen Stelle der Gewinnung
nicht den Meßwert, sondern bestenfalls sich selber, hier also das
Faktum, daß ein Interrupt aufgetreten ist.
3) Die Synchronisation ist nur eine halbe Synchronisation. Es fehlt
effektiv die Rückmeldung "Datum verarbeitet", bzw. eigentlich: deren
Prüfung. Damit ist also nur noch die Information über das erstmalige
Auftreten eines Interrupts wirklich geschützt.
Variante B)
Die Aufgabenteilung zwischen ISR und main ist hier grundsätzlich
unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam
und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt
nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin
nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur
viel effizienter implementieren...
Hallo c-hater,
danke für den umfangreichen Kommentar.
Einiges ist mir dabei allerdings nicht klar!
> Genau. Und wenn du korrekt synchronisiert hättest, wäre das schon bei> der Synchronisation aufgefallen. Dann hätte nämlich die ISR auf main> warten müssen, was sie nicht tun kann (weil sie dann bis in alle> Ewigkeit warten würde).
das wäre auch nicht im Sinne des Erfinders gewesen, da ich ja kein
Interruptsignal auslassen darf. Dabei ist mir allerdings nicht ganz
klar, was du mit main bezeichnest, ich nehme aber an die Loop Schleife.
> Was sie aber tun kann, ist, die Fehlersituation (Echtzeitbedingung nicht> gegeben) zu erkennen und dann eine sinnvolle Fehlerbehandlung zu> starten. Bei der Testanwendung wäre das natürlich eine entsprechende> Ausgabe auf das Debug-Terminal gewesen.
Das ist sicherlich richtig! Da ich aber ein anderes Programm mit
gleichen Einstellungen laufen habe (von microcontroller.net) habe ich
überhaupt nicht damit gerechnet, daß es an dieser Stelle haken könnte.
Die Arbeitsweise dieses Programms ist allerdings ganz anders. Im Moment
weiß ich auch nicht, wie diese Fehlerbehandlung zu realisieren ist.
> Variante A)>> 1) Der eigentliche Meßwert wird an der falsche Stelle gewonnen, nämlich> in main. Er sollte aber in der ISR gewonenn werden, denn dort ist> (zeitlich) die erste Chance, den aktuellen Pegelstatus nach einem durch> Interrupt signalisierten Pegelwechsel zu gewinnen. Ja, das ist auch> nicht perfekt, weil auch hier immer noch hochfrequentes "Prellen" stören> könnte, aber allemal besser, als erst viele Takte später in main> nachzuschauen.
Da ich wie gesagt nicht sicher bin was du mit main bezeichnest, kann ich
nur vermuten.
Sicherlich hätte ich den Inhalt von IRbit: sofort in der ISR erledigen
können, die Aufteilung ist hier sicherlich Unsinn.
> 2) Die Synchronisation schützt wegen der falschen Stelle der Gewinnung> nicht den Meßwert, sondern bestenfalls sich selber, hier also das> Faktum, daß ein Interrupt aufgetreten ist.
Verstehe ich nicht.
> 3) Die Synchronisation ist nur eine halbe Synchronisation. Es fehlt> effektiv die Rückmeldung "Datum verarbeitet", bzw. eigentlich: deren> Prüfung. Damit ist also nur noch die Information über das erstmalige> Auftreten eines Interrupts wirklich geschützt.
Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung
aller Interrupts erste Priorität.
> Variante B)>> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam> und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt> nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin> nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur> viel effizienter implementieren...
????
Gruß Bruno
Bruno M. schrieb:> ????
OK, heute und morgen habe ich keine Zeit dafür, das ausführlich zu
beantworten, aber am WE werde ich was dazu schreiben, was allgemein die
Probleme der Synchronisation zwischen ISRs und zwischen ISRs und main
erklärt. Guck' einfach Sonntagabend nochmal in diesen Thread rein.
Aber soviel vorab: Mit "main" meine ich das, was auch ohne jeden
Interrupt geht, also die Code-Ebene, die nach dem Reset eines µC
standardmäßig abläuft.
Die (oft genug völlig unpassende) Bezeichnung "main" stammt daher, daß
ich mich leider viel zu oft auch mit Scheißdreck wie C abgeben muß und
dort wird dieser Codebereich halt üblicherweise so genannt, weil's der
verfickte Macroassembler, den die C-Typen für eine Programmiersprache
halten, halt vor einem halben Menschenleben irgendwann mal so vorgekaut
hat und alle wahren Gläubigen das bis heute exakt so nachbeten.
Sogar ich... Aber es ist wirklich echt verrückt, da gebe ich dir absolut
Recht.
Hallo "einzig wahrer Gläubiger" aka c-hater. Bei dem Sch..ssdreck würde
halt der GCC die Drecksarbeit machen und man könnte sich auf's Problem
konzentrieren. Ich kann übrigens diverse Kisten in ASM dirigieren und
weiß deshalb das die "Drecksarbeiter" ihre Arbeit fast immer gut genug
machen. Und wenn nicht, kann man ja nachhelfen, muß sich aber nie fragen
ob SP -= 2; erst low oder erst High Byte braucht.
Aber schon lustig zuzusehen/mitzulesen ;-)
Inzwischen habe ich ich eine Fehlerroutine eingebaut.
Da ich aber nicht nur die Interrupts erfassen will, sondern auch die
dazwischliegenden Zeiten, funktioniert das so nicht mehr. Ich werde die
Werte daher im Flash zwischenspeichern und dann erst ausgeben.
c-hater schrieb:> OK, heute und morgen habe ich keine Zeit dafür, das ausführlich zu> beantworten, aber am WE werde ich was dazu schreiben, was allgemein die> Probleme der Synchronisation zwischen ISRs und zwischen ISRs und main> erklärt. Guck' einfach Sonntagabend nochmal in diesen Thread rein.
Also hier die Lieferung:
Bruno M. schrieb:> das wäre auch nicht im Sinne des Erfinders gewesen, da ich ja kein> Interruptsignal auslassen darf.
Das geht nur, wenn die Datensenke mindestens so schnell ist wie die
Datenquelle. Man kann allerdings u.U. ein wenig tricksen, um kurzzeitige
Bursts verarbeiten zu können, bei denen diese Bedingung nicht über die
gesamte Laufzeit gegeben ist, sondern nur im Mittel über einen
bestimmten Zeitabschnitt.
> Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung> aller Interrupts erste Priorität.
Das ist ein grober Denkfehler. Was nützt ein erfaßter Interrupt, der
nicht verarbeitet werden konnte? Genauso viel wie ein nicht erfaßter
Interrupt, nämlich garnix. Der einzige Unterschied ist, daß die
Information an einer anderen Stelle der Verarbeitungskette verloren
geht, weg ist sie aber so und so gleichermaßen. Genau das hast du ja
ganz praktisch und hautnah selber erlebt!
>> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*>> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam>> und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt>> nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin>> nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur>> viel effizienter implementieren...>> ????
Na, das sollte doch klar sein. Stelle dir einfach die Frage, was main
hier eigentlich tut (ich bleibe einfach mal bei diesem oft unpassenden
Ausdruck). Es holt ein (möglicherweise bereits veraltetes) Datum,
checkt, ob eine Ausgabe aktuell möglich ist, wartet ggf. bis das der
Fall ist und macht dann die Ausgabe. Bis auf das Warten kann das alles
auch die ISR selber erledigen.
Anmerkung: Bei allen folgenden Beispielcodes vereinfache ich im Detail
etwas, um das Wesentliche darin deutlicher hervortreten zu lassen. Weder
Retten/Wiederherstellen von Registern und Flags ist dort enthalten noch
wird darauf Rücksicht genommen, ob eine IO-Adresse eventuell memory
mapped ist und damit nicht per in/out ansprechbar oder ob sie eventuell
nicht für sbic/sbis/sbi/cbi erreichbar ist. Auch werden alle Daten in
Registern gehalten, nicht wie normalerweise meist nötig/sinnvoll im RAM.
Realer Code müsste das natürlich alles korrekt bzw. sinnvoll
implementieren.
isr_ext:
ldi tmp,1 ;status togglen
eor status,tmp
sbis UCSRA,UDRE ;ausgabekanal prüfen
rjmp fail_realtime ;echtzeitfehler, ausgabekanal noch belegt
mov tmp,status ;ausgabezeichen berechnen
subi tmp,-'0'
out UDR,tmp ;und ausgeben
reti
ext_fail_realtime:
sbis UCSRA,UDRE ;ausgabekanal prüfen
rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert
ldi tmp,'f' ;und ausgeben
out UDR,tmp
reti
Damit hast du schonmal die Synchronisation mit main komplett eingespart,
kriegst mit Sicherheit entweder korrekte Daten oder aber eine
Fehlermeldung, wenn der Interrupt häufiger auftritt, als du die Daten
wegschreiben kannst.
Diese Lösung hat aber noch weiteres Verbesserungspotential. Zum einen
blockiert die ISR hier nämlich im Fehlerfall bis zu dessen Bereinigung
das gesamte System und zum anderen sorgt sie auch nicht dafür, daß der
Schaden, der aus der Fehlersituation resultiert, möglichst gering
bleibt.
An diesen beiden Sachen kann man noch arbeiten:
ext_fail_realtime:
cbi GICR,INT0 ;den eigenen interupt verbieten
sei ;Interrupts global erlauben
ext_fail_poll:
sbic GIFR,INTF0 ;interruptflag nunmehr pollen
rjmp ext_fail_noint ;noch keiner wieder aufgetreten
sbi GIFR,INTF0 ;interruptflag zurücksetzen
eor status,tmp ;status korrekt nachführen
ext_fail_noint:
sbis UCSRA,UDRE ;ausgabekanal prüfen
rjmp fail_poll ;warten, bis sich daran etwas ändert
ldi tmp,'f' ;fehlermeldung ausgeben
out UDR,tmp
cli ;alle interrupts kurzzeitig sperren
sbi GICR,INT0 ;eigenen interrupt wieder freigeben
reti ;rückkehr zur normalsituation
Hiermit ist nun (soweit das überhaupt möglich ist) sichergestellt, daß
während des Bestehens der Fehlersituation der Rest des Systems
weiterlaufen kann (soweit er in ISRs implementiert ist und daß die
Variable mit der Nutzinformation auch während der Fehlersituation
korrekt weiter behandelt wird, so daß ab Ende der Fehlersituation wieder
korrekte Daten kommen, ansonsten würden hier nämlich mit 50%iger
Wahrscheinlichkeit "invertierte" Daten geliefert. Zusätzlich ist
sichergestellt, daß ein zu häufiger ISR-Aufruf nicht in der Katastrophe
des Stacküberlaufs endet, deswegen sperrt die ISR den eigenen Interrupt
für die nichtexklusive Phase der Fehlerbehandlung.
Übrigens: Es wäre im konkreten Fall genausogut möglich, auf den
Interrupt ganz zu verzichten, und alles in main abzuhandeln, genauso,
wie es hier nur im Fehlerfall passiert, per Polling der beiden
Interruptflags. Das wäre im konkreten Fall sogar die deutlich
effizientere Lösung. Aber es soll hier ja um Interrupts und deren
Synchronisation gehen.
Soweit erstmal zu deinem konkreten Fall, jetzt die Sache etwas
allgemeiner. Als erstes: Synchronisation funktioniert in einem
Interruptsystem völlig anders als in einem üblichen Multitasking-System,
die dort gebrauchten Designpattern sind für ein Interruptsystem fast
vollständig unbrauchbar, denn sie basieren größtenteils auf Warten.
Genau das ist aber für eine normale ISR gerade nicht möglich, denn
während sie exklusiv läuft, läuft keinerlei andere Software, die
irgendwas an dem Systemstatus ändern könnte.
Was in Multitasking-Systemen nur gelegentlich beim Zugriff auf multiple
geteilte Resourcen bei gleichzeitig inkorrekter Programmierung passiert,
der gefürchtete Deadlock, wäre in Interruptsystemen also der absolute
Normalfall. Nur wenn ein Ereignis ohne Mitarbeit eines anderen
Codepfades eintreten kann (also ein reines Hardwareereignis ist), oder
in der ISR die Interrupts wieder freigegeben werden, führt Warten in
einer ISR nicht zwingend zum Deadlock. In der verbesserten ISR oben
werden gleich beide Sachverhalte benutzt. Das kann sinnvoll sein, muß es
aber nicht unbedingt.
So weit, so schlecht. Wie synchronisiert man nun, wenn konkurrierender
Code unmöglich warten kann? Die einzige Möglichkeit dafür ist, zu
verhindern, daß konkurrierender Code überhaupt laufen kann, während man
auf eine geteilte Resource zugreift. Das ist einfach im exklusiven Teil
einer ISR, denn hier ist das von vornherein sichergestellt. An allen
anderen Stellen hilft nur, die Zugriffsroutine auf die geteilte Resource
in einen cli/sei Block zu verpacken. Oder in Kurzfassung:
Der einzige verfügbare Synchronisierungsmechanismus in einem
Interruptsystem ist das Verbieten der Interrupts.
Das hört sich irgendwie witzig an, ist aber die pure Wahrheit.
Neben diesem wesentlichen Unterschied zwischen "normalem" Multitasking
und Interruptprogrammierung gibt es aber auch viele Gemeinsamkeiten.
Z.B. ist es auch bei dem Designpattern normalen Multitaskings, welches
der Interruptsperre bei Interruptprogrammierung am nächsten kommt, den
sog. "critical sections" wichtig, diese immer möglichst kurz zu halten.
Auch steckt in beiden Fällen der Schlüssel für ein effizentes Programm
darin, die Abhängigkeiten zwischen den Tasks möglichst zu minimieren.
Genau das war oben auch die allererste Optimierung: Bei genauerer
Betrachtung des Problems war festzustellen, daß es mit nur einem Task
lösbar ist. Besser kann man die Abhängigkeiten garnicht verringern als
dadurch, sie gleich ganz eleminieren.
Um nun zu zeigen, wie man synchronisiert, führen ich mal eine weitere
Forderung an dein Programm ein: Es soll zusätzlich zu der in der isr_ext
realisierten Funktionalität auch von anderer Stelle irgendwelche
Ausgaben auf die UART gemacht werden können, konkret: die Ausgabe eines
Timerticks in Form eines 'T'. Aus der Anzahl der Ts zwischen den Nullen
und Einsen der isr_ext könnte man dann also die Dauer des jeweiligen
status der ISR ermitteln, das wäre doch ein nettes Feature, nicht wahr?
Also die ISR bleibt wie oben gezeigt, und Timer0 läuft in einer
sinnvollen Betriebsart mit einer bezüglich der Anwendung sinnvollen
Überlaufrate. Dann könnte die Timer-ISR so aussehen:
isr_tov0:
sbis UCSRA,UDRE ;ausgabekanal prüfen
rjmp tov0_fail_realtime ;echtzeitfehler, ausgabekanal noch belegt
ldi tmp,'T' ;ansonsten 'T'
out UDR,tmp ;ausgeben
reti
tov_fail_realtime:
cbi TIMSK,TOIE0 ;den eigenen interupt verbieten
sei ;Interrupts global erlauben
tov_fail_poll:
sbis UCSRA,UDRE ;ausgabekanal prüfen
rjmp fail_poll ;warten, bis sich daran etwas ändert
ldi tmp,'f' ;fehlermeldung ausgeben
out UDR,tmp
cli ;alle interrupts kurzzeitig sperren
sbi TIMSK,TOIE0 ;eigenen interrupt wieder freigeben
reti ;rückkehr zur normalsituation
Auch hier wird die Fehlersituation geprüft, der Ausgabekanal ist ja der
gleiche, und in eine Fehlerbehandlungsroutine gesprungen, aber bevor wir
uns mit der näher befassen, noch ein Hinweis: auch in dieser ISR wird
des mit "tmp" benannte Register verwendet. Geht das einfach so? Was den
exklusiven Teil der beiden ISRs betrifft: Ja, das geht so, denn sie
sperren sich gegenseitig und die Kohärenz des Inhalts von "tmp" muß nur
innerhalb des jeweiligen exklusiven Blocks gewährleistet sein. Es geht
aber nicht, soweit es die Fehlerbehandlung betrifft, denn die läuft
nicht exklusiv. Zumindest insofern wären also die ext_fail_realtime
schonmal umzubauen.
Umgebaut werden müssen beide *_fail_realtime aber auch noch aus einem
anderen Grund. Beide greifen nämlich auf nichtatomare Art auf die UART
zu. Zuerst wird gefragt, ob sie frei ist und dann wird bei positivem
Ergebnis etwas darauf ausgegeben. Nun kann aber jede der beiden ISRs die
andere zu jeder Zeit unterbrechen, wenn diese nicht mehr exklusiv läuft,
was sie ja in der Fehlerbehandlung nicht mehr tut. Erfolgt diese
Unterbrechung nun genau zwischen der Frage und der Ausgabe, passiert
Scheiße. (Könnte passieren, wenn's die UART-Hardware nicht abfangen
würde, aber wir tun wir hier mal so, als täte sie es nicht). Dann ergibt
sich die Notwendigkeit der Synchronisation dieser ZUgriffe.
Da bei beiden ISRs im Fehlerfall bezüglich der Ausgabe exakt das gleiche
passiert, würde man diesen Teil vielleicht sinnvollerweise in eine
gemeinsam genutzte Subroutine auslagern (oder besser ein entsprechendes
Macro), die dann etwa so aussähen könnte:
uartout_sync:
cli
sbis UCSRA,UDRE
rjmp uart_free
sei
ret
uart_free:
ldi tmp,'f'
out UDR,tmp
ret
Auch hier wird wieder das tmp-Register verwendet, aber wieder nur in
einem exklusiven Codeblock. Geht also. Genutzt würde die Routine dann so
aus den beiden Fehlerbehandlungen heraus:
tov_fail_realtime:
sei ;Interrupts global erlauben
cbi TIMSK,TOIE0 ;den eigenen interupt verbieten
tov_fail_poll:
rcall uartout_sync
brie tov_fail_poll
sbi TIMSK,TOIE0 ;eigenen interrupt wieder freigeben
reti ;rückkehr zur normalsituation
ext_fail_realtime:
cbi GICR,INT0 ;den eigenen interupt verbieten
sei ;Interrupts global erlauben
push tmp2 ;zusätzliches Hilfsregister
ldi tmp2,1 ;retten und initialisieren
ext_fail_poll:
sbic GIFR,INTF0 ;interruptflag nunmehr pollen
rjmp ext_fail_noint ;noch keiner wieder aufgetreten
sbi GIFR,INTF0 ;interruptflag zurücksetzen
eor status,tmp2 ;status korrekt nachführen
ext_fail_noint:
rcall uartout_sync
brie ext_fail_poll
pop tmp2 ;hilfsregister wiederherstellen
sbi GICR,INT0 ;eigenen interrupt wieder freigeben
reti ;rückkehr zur normalsituation
Damit ist auch diese Aufgabe gelöst, ohne das main irgendwas dazu tun
muß.
Herzlichen Dank für den umfangreichen Beitrag. Du kannst dir sicherlich
vorstellen, daß ich das erst noch verarbeiten muß. Sollten dabei Fragen
auftauchen, melde ich mich noch mal.
> Das geht nur, wenn die Datensenke mindestens so schnell ist wie die> Datenquelle.
Da ich meine oberste Priorität "keinen Interrupt auslassen" so nicht
erfüllen kann, habe ich das Prinzip geändert und speichere ein
komplettes Infrarot Signal im SRAM und gebe es erst dann komplett aus.
So, hier bin ich wieder.
>> Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung>> aller Interrupts erste Priorität.> Das ist ein grober Denkfehler.
Das ist kein Denkfehler, da beim Lesen eines IR-Codes das Ergebnis
sinnlos wird, wenn zwischendrin was fehlt.
>>> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*>>> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam>>> und kompliziert.
Ich hatte diese Lösung aus zwei Gründen gewählt. Erstens habe ich mal
gelernt, daß man die ISR möglichst schnell wieder verlassen sollte um
den Interrupt nicht zu blockieren und zweitens weil der von mir hier
eingestellte Code nur ein Ausschnitt ist. Das Original ist
umfangreicher, da auch noch die Zeitabstände zwischen den Interrupts
gemessen und ausgegeben werden.
> sbis UCSRA,UDRE ;ausgabekanal prüfen> rjmp fail_realtime ;echtzeitfehler, ausgabekanal noch belegt
das sollte sicherlich rjmp ext_fail_realtime heißen.
> mov tmp,status ;ausgabezeichen berechnen> subi tmp,-'0'> out UDR,tmp ;und ausgeben
einfach und elegant gelöst, da fragt man sich warum man selbst nicht
darauf kommt.
>> ext_fail_realtime:> sbis UCSRA,UDRE ;ausgabekanal prüfen> rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert>> ldi tmp,'f' ;und ausgeben> out UDR,tmp> reti
auch das werde ich mir sicherlich merken.
Zum Rest komme ich erst morgen.
Bruno M. schrieb:> einfach und elegant gelöst, da fragt man sich warum man selbst nicht> darauf kommt.
Solange es sich nur um Zahlen 0-9 handelt...
Bruno M. schrieb:>> ext_fail_realtime:>> sbis UCSRA,UDRE ;ausgabekanal prüfen>> rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert>>>> ldi tmp,'f' ;und ausgeben>> out UDR,tmp>> reti>> auch das werde ich mir sicherlich merken.
Als sichere Möglichkeit, ewig in ISR zu bleiben ?
Der nächste Teil hat mir schon erheblich größere Kopfschmerzen bereitet.
Einige Dinge verstehe ich auch nach wie vor nicht.
> ext_fail_poll:> sbic GIFR,INTF0 ;interruptflag nunmehr pollen> rjmp ext_fail_noint ;noch keiner wieder aufgetreten
Warum sbic und nicht sbis? Ist die Flag nicht clear nach Start der ISR?
Erst ein neuer Interrupt setzt es doch auf 1, wobei mir der Begriff
pollen nicht geläufig ist.
> sbi GIFR,INTF0 ;interruptflag zurücksetzen> eor status,tmp ;status korrekt nachführen
hier fehlt vor eor wohl das ldi tmp, 1
> ext_fail_noint:> sbis UCSRA,UDRE ;ausgabekanal prüfen> rjmp fail_poll ;warten, bis sich daran etwas ändert>> ldi tmp,'f' ;fehlermeldung ausgeben> out UDR,tmp> cli ;alle interrupts kurzzeitig sperren
warum muß ich hier sperren?
> sbi GICR,INT0 ;eigenen interrupt wieder freigeben
muß ich nicht durch sei erneut setzen?