Forum: Mikrocontroller und Digitale Elektronik Aus Interrupt springen ohne an "Auslösepunkt" zu gelangen


von Timo B. (milka)


Lesenswert?

Hi,

habe nichts zu dem Thema gefunden, da mir kein passendes Stichwort dazu 
eingefallen ist.

Und zwar hab ich folgendes Problem. Überall wird ja immer gesagt das man 
seine  Interrupts möglichst klein halten soll, deshalb hab ich mir 
überlegt ob man nicht aus der Interruptroutine springen und diese 
gleichzeitig beendet aber nicht zu der Stelle an der sie ausgelöst wurde 
zurückspringt.
Das soll so was werden wie ein Notausschalter. Der so aussieht:
In einem Timer Interrupt wird abgefragt ob ein Schalter gedrück ist, 
wenn er gedrückt ist spring das Programm in eine Schleife in der es 
etwas anderes arbeitet, bis ein anderer Schalter gedrückt wird.
Das ganze sollte in C sein (CodeVision).
Wahrscheinlich gibt es eine ganz einfache Lösung auf ich einfach nicht 
komm...
Hoffe ihr habt verstanden was ich mein.

mfg milka

von Tobias P. (hubertus)


Lesenswert?

In C geht das nicht so simpel, ausser du benützt den inline-Assembler.
Man könnte das z.B. so lösen, dass in der Interrupt-Routine als erste 
Anweisung der Stack etwas manipuliert wird, sprich die Rücksprungadresse 
(der uP rettet ja den PC auf den Stack, bevor er in die Int Routine 
springt) ändert. Also etwa so:

(Hauptprogramm)

InterruptRoutine:
     pop Temporäres_Register
     mov Temporäres_Register,neue_Adresse
     push Temporäres_Register
     reti

In 8051 Assembler.
Das Programm macht folgendes: Sobald der Interrupt aufgerufen wird, wird 
der letzte Wert, der auf den Stack 'gepusht' wurde, zurückgeholt. Ggf. 
musst du noch nachschauen, was für Werte dein uP evtl. sonst noch auf 
den Stack rettet.
Danach wird das Register, wo die alte Rücksprungadresse drin war, 
überschrieben (wir brauchen die Adresse ja nicht mehr, trotzdem muss sie 
aus dem Stack raus, da der ja sonst überläuft). Das Register kann jetzt 
wieder auf den Stack 'gepusht' werden, und mit reti springt der uP aus 
der Interruptroutine und das Interrupt pending-Flag wird gelöscht. Als 
Rücksprungadresse verwendet er den wert, den du zuvor auf den Stack 
gelegt hast. Möglicher Lösungsansatz, sicher nicht elegant, funktioniert 
aber.
Und nun eine Gegenfrage: WOZU soll das dienen? Du kannst ja in deiner 
Interruptroutine auch nur ein Flag setzen, das dann im Mainloop geprüft 
wird, sodass dann das Programm ggf. an einer anderen Stelle fortfährt.

von Michael U. (amiga)


Lesenswert?

Hallo,

naja, ohne jetzt über Grundsätzliches zu diskutieren...

In ASM waren solche Tricks frücher durchaus üblich und eigentlich auch 
kein Problem, wenn man wusste, was man tat.

Beim AVR: am Ende der IRQ mit 2x POP die dann nutzlose Rücksprungadresse 
vom Stack holen, das neue Ziel mit PUSH auf den Stack und RET.

2 Randbedingungen: das Register, welches zum pushen der 
Rücksprungadresse genutzt wird, hat in der angesprungenen Routine 
ungültigen Inhalt.
Durch RET statt RETI wird der IRQ nicht wieder freigegeben, das muß die 
Routine erledigen, nachdem sie für definierte Zustände zur Fortsetzung 
gesorgt hat.

Ist für bestimmte Anwendungen eine durchaus sparsame Lösung.
Hauptnachteil: man muß solche Tricks auf das Verhalten des konkreten 
Prozessortyps zuschneiden, kein Schwein kann sowas hinterher ohne gute 
Kommentare lesen (auch man selbst nach 3 Monaten nicht...).

Hauptvorteil: man kann zu jeder Zeit geziehlt eine solche Aktion 
auslösen, ohne x Abfragen einbauen zu müssen, solange der IRQ 
freigegeben ist.

Warum hat der AVR keinen NMI??? ;-((

In C? Ich behaupte mal, keine Chance ohne ASM-Hilfe.

Gruß aus Berlin
Michael

von Tobias P. (hubertus)


Lesenswert?

@Michael:
In C könnte es höchstens gehen, wenn man irgendwie auf den SP zugreifen 
kann. Evtl geht das irgendwie, auch ohne ASM-Hilfe. Aber ich stimme dir 
zu, ich würde sowas auch eher in Assembler schreiben. Jedenfalls den 
Teil, wo die Adressen vertauscht werden. Dazu gibts in C ja den Inline 
Assembler.

@Timo:
Ich weiss nicht, ob du den Inline Assembler kennst. Wenn nicht: Im 
C-Quellcode kannst du schreiben

asm
{
    ; normaler Assemblercode
};

Falls es dein Compiler unterstützt. Wäre aber schade wenn nicht ;)

von Matthias L. (Gast)


Lesenswert?

>n einem Timer Interrupt wird abgefragt ob ein Schalter gedrück ist,
>wenn er gedrückt ist spring das Programm in eine Schleife in der es
>etwas anderes arbeitet, bis ein anderer Schalter gedrückt wird.

Offensichtlich geht es um sowas wie eine (Maschinen)steuerung.

Das bedeutet, um es sauber zu programmieren, hast du ja eh eine 
(technologischen) Ablauf im Form einer Schrittkette, bzw. eines 
Zustandsgraaphen.

Wenn du jetzt einen zusätzlichen Schritt einführst, desssen erreichen 
nur durch die Interruptroutine realisiert wird, hast du das sauber 
gelöst.
In diesem Fault-Step kannst du solange verharren bis der andere Taster 
betätigt wird.
Dann musst du (aus technologischen) Gründen eh (meist) wieder von vorn 
anfangen...

Etwa so:
1
ISR ( Timer )
2
{
3
 if (Notaus betätigt) anlagenzustand=c_fehler;
4
}
5
...
6
main
7
{
8
  switch (anlagenzustand)
9
  {
10
  case c_allesgut1:
11
    ...
12
  case c_allesgut2:
13
    ...
14
  case c_allesgut3:
15
    ...
16
  case c_fehler:
17
    ... // fehlerauswertung
18
    if (anderer TAster gedrückt und Notaus losgelassen) anlagenzustand=...
19
  }
20
}

von Marc (Gast)


Lesenswert?

Hallo Timo,

Prinzipiell geht das schon, so wie Du es beschreibst.  In den meisten 
Prozessoren wird die Ruecksprung Adresse auf dem Stack abgelegt. Du 
kannst dann vor Verlassen des Interrupts die gewuenschte Zieladresse auf 
den Stack legen und ab gehts dahin.

Die dortige Funktion sollte allerdings alle Register und Prozessor-Stati 
retten, damit sie bei Verlassen (was Dich an den orginalen Punkt 
zurueckbringt) keine Datenverluste verursacht.  Je nach Compiler kann 
man das manchmal mit dem Zusatzwort "monitor" oder "interrupt" 
erreichen.

Besonders empfehlenswert ist dieses Vorgehen aber nicht, denn Deine 
Hauptfunktion kann ja JEDERZEIT unterbrochen werden - selbst im 
unguenstigsten Moment.  Dies kann zu sehr unvorhergesehenen Effekten 
fuehren.  So bleibt vielleicht ein LCD nur zur Haelfte aktualisiert, 
oder ein PWM-Dimmer dreht ploetzlich 100% auf, oder ein kurzer Pieps aus 
dem Lautsprecher wird zur nervenden Sirene.

Der normale Weg waere, im Interrupt ein Flag zu setzen "sonder-funktion 
gewuenscht".  Im normalen Programm wird das Flag dann an den Punkten 
abgefragt, an denen ein problemloser Uebergang in die Sonderfunktion 
moeglich ist (zB zu Beginn der Hauptschleife).

Bei komplexeren Systemen ist ein Multitasking Betriebssystem die beste 
Loesung.  Ein Taskswitch ist im Endeffekt genau das was Du beschreiben 
hast.  Darueber hinaus stellt Dir ein Betriebsystem aber eine Reihe von 
Werkzeugen zur Verfuegung, mit denen Du den Kontextwechsel kontrolliert 
ausfuehren lassen kannst.

Gruss,
Marc

von Fred S. (Gast)


Lesenswert?

Hallo Timo,

als Grund für Deinen Wunsch führst Du an, dass man die 
Interrupt-Service-Routine möglichst kurz halten sollte. Bei einem echten 
Not-Aus-Schalter wäre mir das egal. Ansonsten weiß ich nicht, wie 
zeitaufwendig Dein main() Programm ist. Kann Dein main() Programm nicht 
einfach den Schalter-Zustand abfragen? Oder würde dann auf den Notfall 
nicht schnell genug reagiert? Etwa so:

#include <stdbool.h>
#include <avr\interrupt.h> // oder wie es bei Dir heißt

volatile uin8_t Schalter_Zustand=0;

ISR (Schalter_interrupt_vektor) {
   ++Schalter_Zustand;
}

int main(void){
  switch (Schalter_Zustand) {
    case (0): {
         // tu was, wenn Schalter inaktiv
    }
    break;
    default: {
     // warte auf 2. Schalter
    }
  };
  return 1;
}

Ginge natürlich auch mit while(), aber so hast Du die Möglichkeit, noch 
weiter Zustände abzuarbeiten. Wie gesagt, das hängt alles davon ab, wie 
häufig main() die switch() Bedingung überprüft.

Gruß

Fred


Oooops, da habe ich zu lange getippt, Matthias hat das ähnlich 
geschrieben.

von Timo B. (milka)


Lesenswert?

Hallo,

vielen Dank für alle Antworten und vor allem so schnell!
Am Anfang habe ich immer ein Flag gesetzt und diese abgefragt aber das 
schien mir nicht sehr gut für einen Notaus Schalter, weil in der Main 
Schleife etliche Delays und so was sind. Außerdem sollte man das Lcd 
noch ansteuern können während die Maschine angehalten ist, also kann ich 
das Programm nicht in dem Interrupt Schleifen lassen, was aber für einen 
Notaus Schalter auch eine Lösung wäre.
Die Idee mit der Änderung der Rücksprungaddresse finde ich sehr gut, ich 
werde mich mal ausgiebig damit befassen.

Mein Ursprüngliches Problem mit dem ich dann über viele Umwege auf 
dieses andere Problem gestossen bin war, dass ich eine Abfüllmaschine 
durch Tastendruck zum anhalten bringen wollte. Am Anfang machte ich 
diese durch setzen eines Flags. Nun funktionierte alles sehr gut blos ab 
und zu "resetete" der uC neu oder das LCD zeigte mist oder der uC sprach 
auf nichts mehr an so, dass ich nur noch durch eine Stromunterbrechung 
neustarten konnte. Also das Programm funktioniert zeitlich gesehen 
einwandfrei und es wird auch keine Spannung induziert (hatte zuvor ne 
Freilaufdiode vergessen^^). Manchmal kann ich die Maschine sehr oft 
hintereinander anhalten ohne das so etwas passiert, machmal aber auch 
passiert das ein paar mal hintereinander. Eine Wackelkontakt vermute ich 
nicht. Was könnte es aber dann sein? Was vermutet ihr?

von Falk B. (falk)


Lesenswert?

@ Timo Benesch (milka)

>Die Idee mit der Änderung der Rücksprungaddresse finde ich sehr gut, ich
>werde mich mal ausgiebig damit befassen.

Das denke ich nicht. Solche Taschenspielertricks sind selten notwendig 
und sinnvoll. Mach lieber eine solide State Machine etc. Und Delays 
haben in einem normen Programm mit (Timer) Interrupts sowieso nix 
verloren!

>passiert das ein paar mal hintereinander. Eine Wackelkontakt vermute ich
>nicht. Was könnte es aber dann sein? Was vermutet ihr?

Prellen des Tasters.

Entprellung

MFG
Falk

von Matthias L. (Gast)


Lesenswert?

> Was könnte es aber dann sein? Was vermutet ihr?

Spaghetti-Code. Offensichtlich hast du Wollknäul-Programmierung 
betrieben:

>Main Schleife etliche Delays
=> Hälts du dort etwas das prg an, a la delay(xx) ??

>Lcd noch ansteuern können während die Maschine angehalten
=> Da tut sich delay schlecht..

>Änderung der Rücksprungaddresse
=>Ist ganz großer Murks!

>Abfüllmaschine
=> Zustandsautomat

>Nun funktionierte alles sehr gut blos ab
>und zu "resetete" der uC neu ode
=> Also funktioniert es eben NICHT sehr gut...

Du hast nur eine Chance: Du musst aufhöre, nach Trial&Error zu 
programmieren und dir VORHER ein Konzept erstellen.
-> was soll der reihe nach gemacht werden, um die Maschine arbeiten zu 
lassen
-> wie soll das visualisiert werden?

ich mache bei sowas IMMER zwei UNABHÄNGIGE programme (diese können ja 
der reihe nach in main aufgerufen werden)
Das eine Programm steuert die Maschine. Dieses in NUR mit 
Zustandsgrafen, also mit Schrittketten realisiert. ALs Transitionen 
könne ja zB Taster dienen.
Das zweite Programmteil (Visu) tut jetzt nur den Schrittkettenvariablen 
(also das, was in switch steht) entsprechende Bildchen zuweisen und das 
darstellen. Falls gebraucht, kann die Visu auch Parameter anzeigen und 
ändern...


Sonst wird das nichts..

Beitrag "Re: Programm bzw. Ablaeufe steuern"

von Timo B. (milka)


Lesenswert?

@ Falk Brunner (falk)
Warum benutzt man keine Delays wenn man gleichzeitig Timer benutzt? Weil 
sonst  die Zeit verlängert wird durch den Interrupt? Und was ist eine 
Solid State Machine?
Ich glaube nicht, dass es am Prellen liegt da bei dem einem Taster ja 
ein Flag gesetzt wird also beim prellen dann halt öfters und mit dem 
anderen Taster wird es gelöscht. Wieso sollte es daran liegen?

mfg
milka

von Matthias L. (Gast)


Lesenswert?

>Wieso sollte es daran liegen?

Weil das Setzen der Flags durch Interrupts geschieht, bei Dir.

Und das permanente Unterbrechen des Programmes zum Flagsetzen (beim 
Prellen) ist der Programmabarbeitung nicht wirklcih förderlich

von Fred S. (Gast)


Lesenswert?

Hallo Timo,
> ...Und was ist eine Solid State Machine?
Die gibt's nicht. Gemeint war wohl eine solide State Machine, zu 
Deutsch "Zustandmaschine", oder "endlicher Automat". Hier eine schöne 
englische Definition: 
http://www.nist.gov/dads/HTML/finiteStateMachine.html
Hier im Forum gibt es auch eine gute Zusammenfassung - finde ich nur 
gerade nicht.

Gruß

Fred

von Matthias L. (Gast)


Lesenswert?

>Hier im Forum gibt es auch eine gute Zusammenfassung

Beitrag "Programm bzw. Ablaeufe steuern"

von Timo B. (milka)


Lesenswert?

Ok da muss ich doch noch einiges klarstellen (mein schönes Programm 
verteidigen^^). Das Programm mag zwar nicht perfekt sein aber es 
funktioniert!
Der Fehler tritt nur manchmal auf liegt vieleicht doch an einem 
Wackler..
Mit dem Prellen hat es wirklich nichts zu tun da die Erkennung in einem 
Timer Interupt steck, vieleicht hab ich das nicht erwähnt, sorry. Und 
zum Delay wenn ich was abfülle dann wird bei mir das Ventil geöffnet 
dann folgt ein Delay und dann geht es weiter, also d.h. ja nicht direkt 
unterbrechung, Interrupts können ja noch ausgelöst werden.
Naja jedenfalls Danke an alle, ich werde mir so einiges nochmal genauer 
anschauen müssen.

Mfg
Milka

von Matthias L. (Gast)


Lesenswert?

>ann folgt ein Delay und dann geht es weiter,

Das ist schon murks. Dafür fügt man einen extra Zustand ein, der nach x 
(Milli)sekunden verlassen wird!
Siehe meinen Link

von Falk B. (falk)


Lesenswert?

@ Timo Benesch (milka)

>verteidigen^^). Das Programm mag zwar nicht perfekt sein aber es
>funktioniert!

Mag sein, aber das tut Windwos auch seit über 20 Jahren . . . ;-)

>Der Fehler tritt nur manchmal auf liegt vieleicht doch an einem
>Wackler..

Möglich, nach deinen beschreibungen aber unwahrscheinlich.

Deine Beschreibung ist nicht viel wert, man müsste den Quelltext sehen.

MFG
Falk

von Smith. Mr. Smith. (Gast)


Lesenswert?

Der AVR hat zwar keinen NMI, einen Reset hat er aber schon. Warum nicht 
den für Notaus benutzen? Deine Firmware im AVR setzt vielleicht eh schon 
nach einem Reset alles in den Start- bzw. Auszustand.

Oder: Warum nicht den Watchdog aktivieren? Notaus löst Interrupt aus, 
dieser erledigt alles Notwenige und geht in eine Endlosschleife, womit 
der Watchdog auslöst -> Reset. Nebenbei bringt der Watchdog auch das 
mehr an Funktionssicherheit.

Alternativ kann der Prozessor auch beim Hochfahren erkennen ob der WDT 
ausgelöst hat und entsprechend darauf reagieren.

Für einen Notaus halte ich so eine Vorgehensweise durchaus angebracht, 
weil sehr sicher.

Die verbogene Rücksprungadresse im Interrupt hätte die Folge, dass der 
Fordergrundprozess keine Unterprogramme mehr aufrufen darf, da sonst der 
Stackinhalt "korrupt" wird.

Wenn, dann ein longjmp aus dem Interrupt und man gelangt sauber zum 
Aufruf von setjump (mit einem Parameter!). Etwas unkonventionell, aber 
der OP wollte es ja so. longjmp darf aber nicht direkt verwendet werden, 
weil dieser am Ende die Interrupts nicht wieder einschaltet. Evtl. kann 
man das auch vorher machen.

von Timo B. (milka)


Lesenswert?

> Deine Beschreibung ist nicht viel wert, man müsste den Quelltext sehen.

Naja das will ich euch nun nicht antun. Ich muss schon zugeben es ist 
sehr viel "Wollknäul-Programmierung" dabei. Weil das ist mein erstes 
größeres Projekt und da fällt mir immer noch was ein was man dazu machen 
kann und so wirds immer komplexer und Fehleranfälliger. Ich brauch das 
Teil eigentlich nur noch morgen und der Fehler ist schon lang nicht mehr 
aufgetreten. Und wenns doch passiert einfach mal schütteln oder sowas^^

Mfg
Milka

von Falk B. (falk)


Lesenswert?

@ Timo Benesch (milka)

>Naja das will ich euch nun nicht antun. Ich muss schon zugeben es ist
>sehr viel "Wollknäul-Programmierung" dabei.

Aha, wir nähern uns langsam der Wahrheit . . . ;-)

MfG
Falk

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
Noch kein Account? Hier anmelden.