Archiv verlassen und diese Seite im Standarddesign anzeigen : Mit goto aus ISR
Grüezi
In meiner Timer0-Overflow ISR (OVF0) wird der Zustand eines Pins abgefragt. Je nach Zustand dieses Pins sollte der Programmcode an einem anderen Ort weitergeführt werden. Da dieser Timer-Overflow an jeder beliebigen Stelle im Programm auftreten kann, kann ich ja nicht einfach eine Variable setzen, mit Return aus der ISR raus und anschliessend an den richtigen Ort springen.
Die erste Idee ist natürlich, den ISR per Goto zu verlassen. Aber irgendwie habe ich ein schlechtes Gefühl dabei. Einerseits werden bei Eintritt des ISR ja die Register 0-11, 16-31 auf den Stack gepusht und ohne ein sauberes Verlassen nicht mehr entfernt. Durch Nosave sollte ich dieses Problem umgehen können, aber wie finde ich die überschriebenen Register heraus, welche ich dann manuell absichern muss? Und was gibt es sonst noch zu beachten, wenn ich einen ISR mit Nosave aufrufe?
Oder wäre es in dem Fall einfacher, in der ISR irgendwie den PC zu manipulieren, dass nach dem RETI dann an unterschiedliche Orte gesprungen wird?
Besten Dank für eure Inputs bereits im Voraus
Hallo!
Ich bin ein ASMan mit k.A. über BASCOM, der aber BASIC kennt. Beim Interrupt wird die Adresse (PC) auf dem Stapel abgelegt und nach deren beendigung (RETI) wird genau an die Stelle, wo Programm unterbrochen wurde, zurück gesprungen. In der Praxis um irgendwas aus ISR dem ganzen Programmn "mitzuteilen" setzt man in ISR ein s.g. Flag (Hilfsvariable) die in ganzem Programm jederzeit geprüft und daran reagiert werden kann.
Genau das schreibt er ja oben. Wenn er aber ein Programm mit den Befehlen
A
B
C
D
hat, nach A springt er in die ISR. Er will aber, dass dann z. B. C ausgeführt wird. Beim normalen Rücksprung müsste erjetzt vor jedem Befehl den Zustand des Flags (Beziehungsweise den Rückgabewert) prüfen, ob Befehl A, B, C oder D ausgeführt werden soll.
Wieviele Möglichkeiten gibt es denn? Wenn es sich im Rahmen hält, würde ich alle Befehle per IF einklammern und das Flag vorher prüfen.
Besserwessi
26.06.2013, 21:00
Der Sprung per Goto aus der ISR heraus ist an sich keine so gute Idee. Man weiss nicht wie die Hardware gerade eingestellt ist, und auch wie viele Daten ggf. auf dem Stack sind, weiss man auch nicht. Wenn man Pech hat wird auch gerade ein Integer Zahl oder ähnliches mit mehr als 1 Byte Länge verändert - das gibt dann ggf. Datenmüll weil alte und neue Daten gemischt werden. Die richtigere Lösung wäre die Struktur des Programms so zu ändern das der Sprung nicht mehr nötig ist. Ggf. einfach in der ISR ein Flag setzen und dann im Hauptprogramm das Flag an den Stellen kontrollieren wo es passt.
Wenn es unbedingt sein muss, dass man aus der ISR verzweigt, müsste man schon fast so etwas wie einen Rest machen, also die Hardware ggf. neu initialilisieren, den Stackpointer neu auf den passenden Wert setzen. Auch im Hauptprogramm müsste man an vielen Stellen Interrupts sperren, z.B. immer wenn man eine Variable mit mehr 1 Byte verändert oder in 16 Bit HW Register (z.B. 16 Bit PWM Werte) schreibt.
peterfido
26.06.2013, 22:09
Das lässt sich bestimmt auch elegant lösen. Jedoch fehlen für einen guten Lösungsvorschlag mehr Details. Warum soll sofort eine andere Sub aufgerufen werden, was sind das überhaupt für zwei Subs? Compilierfähiger Code?
Trotz Allem: Sowas habe ich noch nie gebraucht.
Hey,
wie schon mehrfach gesagt, ist ein Goto zum Abbruch einer ISR sehr schlecht und kann zu Fehlern führen.
Eventuell kannst du ja eine If-Abfrage mit einem Return einbauen, sprich wenn ein bestimmtes Ereignis während eines Interrupts ausgelöst wird springt der Controller aus der ISR raus.
Damit wird sie "sauber" abgebrochen.
Wie schon gesagt, einfach über ein Flag etc. machen, aber NIEMALS vorzeitig aus einer ISR rausspringen. Diese muss immer mit reti beendet werden!
mfg
Vielen Dank für alle eure Antworten. Ich versuche es mal mit einer Flag in der ISR und werde an verschiedenen Orten in der Routine, in welcher der Timer-Overflow vorkommen kann, dieses Flag abfragen. Noch nicht ganz optimal elegant denke ich, aber sollte klappen. Ansonsten melde ich mich gerne nochmal konkreter :)
Irgendwas funktioniert noch nicht ganz so, wie es sollte. Das Problem hat vereinfacht folgende Struktur.
Die Hauptschleife ruft eine Sub auf, welche unter Umständen lange dauern kann. Falls alles läuft wie geplant, beendet die innerhalb einer gewissen Zeit und alles geht weiter wie gewohnt.
Sollte es aber länger dauern als erlaubt, dann wird der Timer0 Overflow aufgerufen. Dieser unterscheidet den Zustand des Pins und springt dann an unterschiedliche Sprungadressen.
Mit Flags wäre die einzige mir in den Sinn kommende Möglichkeit, dass ich in der Routine1 immer wieder ein bestimmtes Flag polle und nur dieses in der ISR setze. Aber das finde ich nicht ganz so elegant. Wie löst man ein solches Problem typischerweise?
'Mainloop
Do
Label1:
{anweisungen}
Label2:
{anweisungen}
{...}
Gosub Routine1
{...}
Loop
Routine1:
For I = 0 To 100
Start Timer0
{ganz Viele Anweisungen}
Next I
Stop Timer0
Return
Isr_ovf0:
If Pinb.1 = 0 Then
Goto Label1
Else
Goto Label2
End If
Return
HeXPloreR
27.06.2013, 20:04
Hallo,
mal abgesehen davon dass das hier nur der halbe Code ist...startest Du den Timer0 in der For-Next Schleife 100mal neu... um ihn dann einfach nach dem Next anzuhalten?? Hast Du irgendeine Zeit berechnet- die vergehen darf/soll?
Also, ich denke normalerweise würde man für Dein Problem einen Interrupt abstellen...und das ist völlig unabhängig von irgendeinem Timer. Und das ganze dann in der Hauptschleife. Sobal Interrupt auslöst geht es in die ISR und mit Return wieder raus...woher der return (z.B. if Bedingung) kommt ist völlig egal. In der ISR setzt man die entsprechende Variable A B C oder D = 1 (=Flag; um mal im obigen Beispiel zu bleiben) - und im Hauptprogramm kann man dann auf diese Variablen testen und in neuen Programmteile verzweigen.
Vielleicht, wenn es kein ultra geheimes Projekt ist, kannst Du ja nochmal besser beschreiben was Du eigentlich genau machen möchtest?
RoboHolIC
27.06.2013, 22:34
Die Hauptschleife ruft eine Sub auf, welche unter Umständen lange dauern kann. Falls alles läuft wie geplant, beendet die innerhalb einer gewissen Zeit und alles geht weiter wie gewohnt.
Sollte es aber länger dauern als erlaubt, dann wird der Timer0 Overflow aufgerufen.
Das ist der klassische Fall einer Timeout-Sicherung: Warten auf ein äusseres Signal, das zu spät oder vielleicht auch nie kommt; der Timeout leitet das Programm weg vom operativen Ziel hin zu einer Störbehandlungsfunktion.
Der Timeout wird wie eine Eieruhr immer dann frisch "aufgezogen", wenn eine potentiell erfolglose Wartephase beginnt. Das Warten auf das operative Signal und die Prüfung auf Timeout werden ständig nacheinander ausgeführt. Innerhalb dieser "aktives-warten"-Schleife wird dann je nach Ereignis in die Prozessfunktion oder die Störbehandlung verzweigt.
Analog zu einer festen Timeoutzeit kann auch ein externes Störmeldungssignal diese Verzweigung auslösen; der Mechanismus bleibt gleich.
Hallo,
mal abgesehen davon dass das hier nur der halbe Code ist...startest Du den Timer0 in der For-Next Schleife 100mal neu... um ihn dann einfach nach dem Next anzuhalten?? Hast Du irgendeine Zeit berechnet- die vergehen darf/soll?
Also, ich denke normalerweise würde man für Dein Problem einen Interrupt abstellen...und das ist völlig unabhängig von irgendeinem Timer. Und das ganze dann in der Hauptschleife. Sobal Interrupt auslöst geht es in die ISR und mit Return wieder raus...woher der return (z.B. if Bedingung) kommt ist völlig egal. In der ISR setzt man die entsprechende Variable A B C oder D = 1 (=Flag; um mal im obigen Beispiel zu bleiben) - und im Hauptprogramm kann man dann auf diese Variablen testen und in neuen Programmteile verzweigen.
Vielleicht, wenn es kein ultra geheimes Projekt ist, kannst Du ja nochmal besser beschreiben was Du eigentlich genau machen möchtest?
Das Projekt ist nicht ultrageheim, es geht um die Emulation eines One-Wire Bauteils, das es nicht mehr gibt. Aber die 600 Zeilen finde ich unübersichtlich, deshalb fasse ich das wesentliche zusammen.
Wenn ich in der Leseroutine bin (=vom Server kommen Bits), dann habe ich bei jeder ankommenden Flanke die folgenden zwei Möglichkeiten:
- Es kommt ein nächstes Bit, bis das Byte voll ist
- Das Lesen soll beendet werden und der Master sendet ein Reset.
Ich weiss aber vorher nicht, was genau kommt, deshalb nehme ich an, es soll ein nächstes Bit ankommen. Dese Operation wird in 60 us abgehandelt. Falls das nach den 60 us aber noch nicht fertig ist, dann weiss ich, dass es ein Reset sein soll und muss in die Resetroutine springen.
Deshalb starte ich bei jedem Schleifendurchgang der Timer aufs Neue und initialisiere mit 255-60. Falls alles läuft wie gewünscht, dann kommt das nächste Bit, Timer wird wieder auf den Wert 255-60 resettet. Wenn die 8 Bits von einem Byte durch sind, dann stoppe ich den Timer und arbeite weiter. In diesem Fall wird nie ein Interrupt auftreten.
Wenn jetzt aber ein Timer0-Overflow auftritt, dann hat der Master einen Reset gesendet. Ich muss also die Leseroutine verlassen und ins Hauptprogramm zurück, wobei je nach Pegelstand der Signalleitung unterschiedliche Orte angesprungen werden müssen. Das bedeutet, ich muss aus dem ISR an zwei verschiedene Orte gelangen können.
Wenn ich in der ISR nur ein Flag setze, ist das sicher die beste Lösung. Aber wo frage ich denn das Flag ab? Der Overflow kann irgendwo in der "Lese ein Bit"-Routine vorkommen, also müsste ich alle paar Zeilen wieder auf das Flag prüfen? Oder gibt es da einen schöneren Weg?
Das ist der klassische Fall einer Timeout-Sicherung: Warten auf ein äusseres Signal, das zu spät oder vielleicht auch nie kommt; der Timeout leitet das Programm weg vom operativen Ziel hin zu einer Störbehandlungsfunktion.
Der Timeout wird wie eine Eieruhr immer dann frisch "aufgezogen", wenn eine potentiell erfolglose Wartephase beginnt. Das Warten auf das operative Signal und die Prüfung auf Timeout werden ständig nacheinander ausgeführt. Innerhalb dieser "aktives-warten"-Schleife wird dann je nach Ereignis in die Prozessfunktion oder die Störbehandlung verzweigt.
Genau das ist die Idee dahinter.
HeXPloreR
28.06.2013, 15:56
hmm, also meine erste Idee wäre nun zu versuchen die ISR zum Hauptprogarmm (ohne GOSUB aufruf) umzubauen und daraus dann die zwei jeweiligen GoSubs anzuspringen.
Sonst kann man natürlich auch aus der ISR eine neue GoSub anspringen, allerdings muss man da wieder per Return raus, und landet dann wieder in der ISR... man kann hier jetzt über ein (Globales-)Flag prüfen ob das relevante Ereigniss schon passiert ist, wenn ja => return ins Hauptprogramm.
Ich denke das sagt auch der Text "der klassischen Timeout-Sicherung" aus.
Ich habe etwas ähnliches mit einer RC-Fernsteuerung gemacht, allerdings bricht das Programm bisher niemals ab (z.B. kein counter, kein Timer), sondern wartet auf einen richtigen Wert von der Fernsteuerung, sind drei do-loop Schleifen in einer SUB verpackt, die nacheinander durchgeprüft werden. Ist Wert gegeben geht's mit "exit do" ais der Schleife raus, in die nächste Schleife usw... nach dem letzten loop, wird das Flag gestzt, dann der Return.
Ob Dir das nun hilft kann ich nicht beurteilen.
EDIT: Wir reden ja die ganze Zeit von GOSUB...bei einer Sub oder Funktion benötigt man das Return nicht. Wäre das dann nicht besser für Dich geeignet?
peterfido
28.06.2013, 17:24
Für eine gute Idee fehlen mir immer noch Details. Die Subs sind mir noch zu geheimnisvoll. Wenn diese umfangreich sind, ist die Wahrscheinlichkeit hoch, dass da mindestens eine Schleife vorhanden ist, wo das Flag abgefragt werden kann.
Für eine gute Idee fehlen mir immer noch Details. Die Subs sind mir noch zu geheimnisvoll. Wenn diese umfangreich sind, ist die Wahrscheinlichkeit hoch, dass da mindestens eine Schleife vorhanden ist, wo das Flag abgefragt werden kann.
Die eine Sub sieht so aus:
Sub1:
For I = 0 To 7
Lread1:
Enable Interrupts
Config Powermode = Idle 'PCINT0 ist aktiv, bei Flanke wird er geweckt und je nach auf-/absteigender Flanke Edge gesetzt
Disable Interrupts
If Edge = 1 Then
Goto Lread1
End If
Timer0 = 256 - 60
Start Timer0
Rotate Inputbyte, Right
Waitus 15
Inputbyte.7 = PINB.0
Next I
Stop Timer0
Return
Ich kann hier einfach so 3-4 Mal die Abfrage des Flags streuen, aber gibt es nicht eine efizientere Methode als das polling des Flags?
RoboHolIC
28.06.2013, 19:43
hier einfach so 3-4 Mal die Abfrage des Flags streuen, aber gibt es nicht eine efizientere Methode
Das, was bereits geschrieben wurde, ist doch effizient. Die Abfrage auf das Flag zu "streuen" ist nicht hilfreich.
Was macht wohl der Protokollautomat in dem OneWire-Chip? Der kann auch nur Zeitabstände zwischen zwei Flanken messen (oder vergleichen mit analogem Zeitglied) und bewerten, ob das ein gültiges Bit bzw. Signal war oder nicht; im letzteren Fall ist das ein Grund zum Reset der Kommunikation.
Ebenso solltest du die Kommunikation aufbauen: Bit für Bit senden und empfangen und immer parallel dazu auf Zeitüberschreitung überwachen. Also nicht irgendwie einstreuen sondern genau jede erwartete Zustandsänderung gegen unerwartete Verzögerungen etc. absichern.
Wenn OneWire was ähliches wie das Clock Stretching durch den Empfänger bei I2C kennen sollte, muss auch der Sendevorgang entsprechend überwacht werden.
peterfido
29.06.2013, 08:44
Sub1:
For I = 0 To 7
Lread1:
Enable Interrupts
Config Powermode = Idle 'PCINT0 ist aktiv, bei Flanke wird er geweckt und je nach auf-/absteigender Flanke Edge gesetzt
Disable Interrupts
If Flagbyte <> 1 Then '1 für Sub1, 2 für Sub2, ... 255 für Reset
Return
End If
If Edge = 1 Then
Goto Lread1
End If
Timer0 = 196
Start Timer0
Rotate Inputbyte, Right
Waitus 15
Inputbyte.7 = PINB.0
Next I
Stop Timer0
Return
Wobei auch ich den Empfang komplett in einer Timer_ISR erledigen würde. Ähnlich dem DCF Empfang.
Danke für alle eure Antworten. Bis jetzt siehts erfolgversprechend aus, morgen wird systematisch durchgetestet. Aber ich bin optimistisch :pray:
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.