Forum: Mikrocontroller und Digitale Elektronik [AVR Assember] Interrupt ohne reti beenden


von Sascha T. (ernie1973)


Lesenswert?

Hallo,

ich habe aktuell eine Ablaufsteuerung, bei der viele verschiedene
Aktionen durchgeführt werden. Während der Ablaufsteuerung muss immer
eine bestimmte Taste gedrückt sein und ab einem bestimmten Punkt darf
eine Zeit X nicht überschritten werden.

Sobald die Taste losgelassen wird oder die Zeit überschritten wird muss
der Ablauf sofort unterbrochen werden und es soll eine Fehlermeldung
kommen. Danach wird das Programm von vorne gestartet.

Die Überwachung der Abschaltkritierien erolfgt regelmäßig über Timer
Interrupts. Selbstverständlich kann ich mir dort einen Merker setzen und
diesen im Hauptprgramm abfragen. Allerdings muss ich die diese Abfrage
dann im Hauptprogramm an sehr viellen Stellen einfügen, damit es zu
keiner großen Verzögerung der Abschaltung kommt.

Aus diesem Grunde würde ich gerne direkt im Interrupt abschalten die
Fehlermeldung ausgeben und danach das Programm wieder von vorne starten.
Ich lese zwar öfters, dass es nicht sauber oder nicht der Sinn einer
Interruptroutine ist, aber spricht theoretisch wirklich was dagegen?

Wenn ich in der Interruptroutine mein Abschaltkriterium erkenne, dann
mein SREG wieder zurückschreibe und den Interrupt durch cli zurücksetze
bin ich doch wieder sauber und kann die Fehlermeldung ausgeben und mein
Programm neu starten. Das aktuelle reti müßte dann doch gelöscht sein
oder springt der mir beim nächsten reti an die alte Stelle?

Vielen Dank,

Ernie

von A. N. (netbandit)


Lesenswert?

Du kannst es doch einfach so machen, dass sobald er das Kriterium 
erkennt, der Stack so verändert wird, dass reti nicht die 
Rücksprungadresse holt sondern eine von der gewünschte Adresse (z.b. die 
Programmstartadresse).

von Johannes M. (johnny-m)


Lesenswert?

Sascha T. wrote:
> Wenn ich in der Interruptroutine mein Abschaltkriterium erkenne, dann
> mein SREG wieder zurückschreibe und den Interrupt durch cli zurücksetze
> bin ich doch wieder sauber
Nö, bist Du nicht. Was ist mit dem Stack (Programmzähler)?

Ich weiß nicht, ob ich zu 100% verstehe, was Du machen willst, aber wenn 
Du bei Auftreten eines speziellen Interrupt möchtest, dass nach der 
Rückkehr aus dem Handler die Interrupts nicht automatisch wieder 
freigegeben werden, dann mach den Rücksprung mit einem ret anstelle 
des reti. Der einzige Untschied des reti gegenüber einem ret ist 
schließlich, dass das reti das I-Bit im SREG wieder setzt.

Wenn Du allerdings einfach aus dem Handler das Programm quasi als 
"Warmstart" von Anfang an neu starten willst (also ein Reset), dann 
kannst Du natürlich zur Adresse 0 jumpen und anschließend bei der 
Stack-Neuinitialisierung wird eh alles, was vorher da war, platt 
gemacht. Das setzt natürlich voraus, dass Dein Controller keinen 
Hardware-Stack hat und dass der Stack am Programmanfang tatsächlich 
explizit initialisiert wird (was bei neueren AVRs eigentlich nicht mehr 
unbedingt eforderlich ist, da der Reset-Wert des Stack Pointers schon 
entsprechend gesetzt ist).

Von einer direkten Stackmanipulation ist abzuraten.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Um es kurz zu machen (und ohne das Programm gesehen zu haben):
Dein Programmierstil ist falsch :-o

Du solltest in deinem Programm zyklisch die Schritte
1
- Eingabe 
2
    (nur hier werden Eingänge in das Prozessabbild eingelesen)
3
- Verarbeitung 
4
    (Verknüpfen der eingelesenen Eingänge mit dem aktuellen Zustand)
5
- Ausgabe
6
    (nur hier werden die Ausgänge des Prozessabbilds geschrieben)
durchlaufen. Die Durchlaufzeit muß so kurz sein, dass deine Definition 
von "Echtzeit" erfüllt ist.
In einer etwaigen Interruptroutine werden nur Flags gesetzt, die im 
Eingabeteil auf dein Prozessabbild eingelesen werden.

Auf diese Art funktionieren zigtausende SPSen (und alle meine Programme) 
problemlos.

von Winfried (Gast)


Lesenswert?

Für Sonder-/Ausnahmebehandlung kann man so ein Design durchaus machen. 
Ich sehe erstmal nichts, was dagegen spricht, insofern du das Programm 
dann vollständig neu startest und alle Hardware nicht in gefährliche 
Zustände reinfahren kann.

von Sascha T. (ernie1973)


Lesenswert?

Vielen Dank für die ersten Hinweise.

Im Grunde will ich halt sicherstellen, dass z.B. alle 100µs überprüft 
wird, ob die Taste noch gedrückt ist und nicht losgelassen wurde. Diese 
regelmäßige Überprüfung ist ein Sicherheitskriterium und ist halt nur 
mit einem Interrupt sauber zu lösen.

Während des Programmablaufs werden halt viele Routinen durchlaufen und 
ich müßte in jeder Routine eine Abfrage reinbringen und ob in dem Timer 
Interrupt ein Merker für das Loslassen der Taste gesetzt wurde. So wäre 
es vielleicht "sauber" programmiert.

Wenn ich aber den Programmablauf direkt im Interrupt abbreche, kann ich 
mir diese Abfragen ja sparen und ich finde diesen Ablauf vom Sinn her 
"sauberer". Der Interrupt soll ja den normalen Programmablauf 
unterbrechen :o)

Die Frage ist halt, was ich alles berücksichtigen müßte, damit der 
Interrupt sauber beendet wird.

von Peter D. (peda)


Lesenswert?

Du kannst Dir nen Longjump in Assembler proggen:
1
.dseg
2
longjump_addr:  .db 2
3
.cseg
4
5
longjump_dst:
6
        cli
7
        in      r16, spl
8
        sts     longjump_addr, r16
9
        in      r16, sph
10
        sts     longjump_addr+1, r16
11
        sei
12
; ...
13
14
15
; sprung zu longjump_dst:
16
        cli
17
        lds     r16, longjump_addr
18
        out     spl, r16
19
        lds     r16, longjump_addr+1
20
        out     sph, r16
21
        rjmp    longjump_dst


Peter

von Hannes Lux (Gast)


Lesenswert?

> Im Grunde will ich halt sicherstellen, dass z.B. alle 100µs überprüft
> wird, ob die Taste noch gedrückt ist und nicht losgelassen wurde. Diese
> regelmäßige Überprüfung ist ein Sicherheitskriterium und ist halt nur
> mit einem Interrupt sauber zu lösen.

Dann zieh' halt mit der Taste Reset gegen einen PullDown-R nach Vcc... 
;-)

...

von Peter D. (peda)


Lesenswert?

Sascha T. wrote:
> Im Grunde will ich halt sicherstellen, dass z.B. alle 100µs überprüft
> wird, ob die Taste noch gedrückt ist und nicht losgelassen wurde.

Kein Mensch kann nur 100µs lang ne Taste betätigen.
Bei menschlicher Betätigung sind 100ms vollkommen ausreichend.


> Diese
> regelmäßige Überprüfung ist ein Sicherheitskriterium und ist halt nur
> mit einem Interrupt sauber zu lösen.

100µs sind gut geeignet, alle möglichen Störimpulse fälschlich als 
Loslassen zu erkennen.


> Während des Programmablaufs werden halt viele Routinen durchlaufen und
> ich müßte in jeder Routine eine Abfrage reinbringen und ob in dem Timer
> Interrupt ein Merker für das Loslassen der Taste gesetzt wurde. So wäre
> es vielleicht "sauber" programmiert.

Sauber (d.h. Fehlersicher) wäre es, wenn Du die Taste entprellst (4 
Abfragen etwa alle 10ms sind gleich).


Peter

von Sascha T. (Gast)


Lesenswert?

Hatte mich sowieso verschriebn. Aktuell prüfe ich die Taste alle 100ms. 
Entprellung steht sowieso schon hardwareseitig.

Aber es geht hier doch um was anderes. Normalerweise habe ich auch immer 
strikt zyklisch programmiert und alle Interruptroutinen sauber beendet.

In diesem Fall habe ich aber mal umgedacht und zwar aus folgenden 
Gründen:

Der Tastendruck und das Halten der Taste ist für einen bestimmten 
Programmteil die Voraussetzung und höchste Priorität. Einen Reset direkt 
kann ich nicht durchführen, da ich ja eine Fehlermeldung erzeugen muss. 
Während diese Programmablauf werden z.B. UART und SPI 
Kommunikationsroutinen aufgerufen. Diese Routinen rufen ja wieder 
Abfrageroutinen auf z.B. ob etwas empfangen wurde. Diese Abfrageroutinen 
laufen natürlich Schleifen durch und werden z.B. abgebrochen wenn 
mehrere Sekunden lang nichts empfangen wurde.

Wenn ich jetzt "normal" programmiere, dann frage ich in der Schleife 
einen Merker ab, ob bei dem Timer Interrupt ein Loslassen der Taste 
festgestellt wurde. Falls ja breche ich ab und springe zu der Routine 
darüber. Dort muss ich natürlich auch wieder abfragen, ob der Merker 
gesetzt wurde und dann diese Routine verlassen. Wenn ich dann im 
Hauptprogramm bin, dann muss ich natürlich auch wieder gucken ob der 
Merker gesetzt wurde und dann springe ich endlich in meine 
Fehlerroutine.

Ist ja so weit auch kein Problem. Allerdings muss ich dann diese 
Abfragen überall einfügen und die Gefahr besteht, dass ich irgendwo mal 
eine vergesse und somit doch mal eine Verzögerung bis zum Erkennen des 
Loslassen habe.

Somit ist die Beendigung des Programmablaufs direkt im Interrupt 
natürlich eine sichere Sache. Ich habe das Programm ja aktuell so laufen 
und es funktioniert auch einwandfrei. Aber ich wollte halt mal 
theoretisch nachvollziehen, ob diese Art der Interruptbeendigung zu 
Problemen führen könnte.

von Uwe (Gast)


Lesenswert?

Hi!
Ja das geht, habe ich selbst schon gemacht.
Aber du musst auch den Resetstatus der Register und E/A's bedenken wenn 
du dir keine Fehler einbauen willst.
Eine andere Möglichkeit wäre noch den µC vom Wachhund schnappen zu 
lassen.
Das ist dann, glaube ich jedenfalls, ein sauberes Reset.

von Sascha T. (Gast)


Lesenswert?

Die Register und E/A werden ja im Reset wieder definiert. Aber was ist 
dem Stack. Habe ich da was zu befürchten?

von Uwe (Gast)


Lesenswert?

Hi!
>Aber was ist dem Stack. Habe ich da was zu befürchten?
Hä? Initialisierst du den am Anfang nicht? Das ist doch Standard.
Da muss man eher aufpassen dass alle I-Flags gelöscht werden.
(meine nicht das I-Flag im SREG)

Viel Erfolg, Uwe

von Klugscheisser (Gast)


Lesenswert?

@ Sascha

Ich meine Deine Gründe zu verstehen und die Lösungsansätze die hier 
genannt wurden, sind sicherlich sinnvoll.
Trotzdem kräuselt sich da was bei mir und ich habe mir überlegt ob es 
nicht doch eine "saubere" Möglichkeit gibt.

Wenn Du den Ablauf in mehrere Teilschritte unterteilen kannst 
(vermutlich rufst Du ohnehin verschiedene Funktionen auf und hast nicht 
eine Riesenfunktion) dann wäre eine Lösung, das Du eine State-Machine 
daraus machst und vor dem Wechsel in den jeweiligen Folgezustand ein 
Flag abfragst, das in dem fraglichen Interrupt gesetzt werden kann.
Auf diese Weise wärst Du sicher, regelmäßig die Abfrage zu machen, hast 
aber trotzdem nur eine Stelle im Code wo diese geschieht und vermeidest 
dieses return-from-int ohne RETI.
Genaugenommen hat diese Methode allerdings doch einen Nachteil. Wie fein 
die möglichen Abbruchstellen im Ablauf verteilt sind (das ist ein 
bischen ungeschickt ausgedrückt) hängt davon ab, wieviele Zustände Du 
der State-Machine gibtst. Je mehr desto feiner.

Naja. Vielleicht hilft Dir das was.

von Johannes M. (johnny-m)


Lesenswert?

Uwe wrote:
> Hä? Initialisierst du den am Anfang nicht? Das ist doch Standard.
Hab ich oben schon mal erwähnt: Bei den neueren AVRs muss man den Stack 
Pointer nicht mehr zwangsläufig von Hand initialisieren, da der 
Reset-Wert schon auf RAMEND voreingestellt ist...

von Sascha T. (Gast)


Lesenswert?

Vielen Dank für die weiteren Hinweise.

@Klugscheisser: Ich verstehe jetzt nicht ganz was Du meinst. Ist diese 
State Machine denn nicht dasselbe wie ein Merker, den ich mir in der 
Interruptroutine setzte und dann bei jeder Unterroutine abfrage?

@Uwe: Du hast ja recht, natürlich initialisiere ich den Stack zu Beginn. 
Also dürfte ja kein Risiko bestehen, wenn ich im Interrupt die 
Fehlerausgabe mache und einen Reset durchführe. Welche Interrupt Flags 
meinst Du denn noch?

von Klugscheisser (Gast)


Lesenswert?

@ Sascha

>Ich verstehe jetzt nicht ganz was Du meinst. Ist diese
>State Machine denn nicht dasselbe wie ein Merker, den ich mir in der
>Interruptroutine setzte und dann bei jeder Unterroutine abfrage?
Möglicherweise verstehe ich Dich jetzt nicht.
Ein Merker ist grundsätzlich etwas anderes als eine State-Machine.

Ich konstruiere mal ein Beispiel:

Gegeben sei etwa folgernder Pseudcode, den es gilt unterbrechbar zu 
machen.
In diesen würdest Du dann (was Du ja vermeiden möchtest den Test auf das 
Flag einfügen:
1
a();
2
flag_test();
3
b();
4
flag_test();
5
c();
6
flag_test();
7
d();
8
flag_test();
9
e();
10
flag_test();
11
f();
12
flag_test();
13
g();
14
flag_test();
15
h();
16
flag_test();
17
i();
18
flag_test();

Daraus würde ich nun die State-Machine machen:
1
do {
2
   switch (state) {
3
      case 1: a(); state = 2; break;
4
      case 2: b(); state = 3; break;
5
      case 3: c(); state = 4; break;
6
      case 4: d(); state = 5; break;
7
      case 5: e(); state = 6; break;
8
      case 6: f(); state = 7; break;
9
      case 7: g(); state = 8; break;
10
      case 8: h(); state = 9; break;
11
      case 9: i(); state = 10; break;
12
      default: break;
13
   }
14
   if (test_flag ()) state = 10; // "Ausnahmezustand"
15
} while (state != 10);    // 10 ist der Endzustand

Das kann man noch varieren, aber so ungefähr.

Klar?

von Sascha T. (Gast)


Lesenswert?

Sorry, aber da ich in Assembler programmiere kann ich dir auf Anhieb 
nicht folgen. Deshalb kenne ich wohl auch keine State Machine :(

von Klugscheisser (Gast)


Lesenswert?

State-Machines sind ein Konzept. Hat nichts mit der Programmiersprache 
zu tun. Soll heissen: Wenn Du Asm kannst dann begreifst Du auch das.

Ein wenig gurgeln sollte die grundlegende Idee klarmachen.

(More-Mealy vielleicht noch vom Studium bekannt?)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sascha T. wrote:
> Hallo,
>
> Sobald die Taste losgelassen wird oder die Zeit überschritten wird muss
> der Ablauf sofort unterbrochen werden und es soll eine Fehlermeldung
> kommen. Danach wird das Programm von vorne gestartet.
>
> Die Überwachung der Abschaltkritierien erolfgt regelmäßig über Timer
> Interrupts. Selbstverständlich kann ich mir dort einen Merker setzen und
> diesen im Hauptprgramm abfragen. Allerdings muss ich die diese Abfrage
> dann im Hauptprogramm an sehr viellen Stellen einfügen, damit es zu
> keiner großen Verzögerung der Abschaltung kommt.
>
> Aus diesem Grunde würde ich gerne direkt im Interrupt abschalten die
> Fehlermeldung ausgeben und danach das Programm wieder von vorne starten.
> Ich lese zwar öfters, dass es nicht sauber oder nicht der Sinn einer
> Interruptroutine ist, aber spricht theoretisch wirklich was dagegen?

Was spricht dagegen, das in der ISR zu machen?

Wenn die Bedingung ni der ISR erfüllt ist, wird die Fehlermeldung 
ausgegeben (wie immer das auszusehen hat) und der µC macht per WatchDog 
nen RESET.

Wenn die Fehler absolute Priorität hat, dann ist's nun mal so. Ich würd 
mir da keinen Kopp um irgendwelche Dogmen machen (eine ISR darf nicht 
"lange" dauern, etc.)

Nach der Meldung erfolgt der RESET per WatchDog, weil er im Gegensatz zu 
einem direkten Sprung zum Reset-Vektor die Hardware zurücksetzt (Timer, 
UART, SPI, ...)

Und dann begint das Spiel von neuem. Wozu also die Applikation 
verfrickeln mit Abfragen?

Weitere Möglichkeit könnte ein Software-Interrupt sein, aber dafür weiß 
man zu wenig über deine Anwendung.

Johann

von Sascha T. (Gast)


Lesenswert?

Stimmt, dass ist eigentlich eine sehr gute Idee. Sobald in der 
Interruptroutine erkenne, dass die Taste losgelassen wurde gebe ich kurz 
die Fehlermeldung aus (3 Bytes über SPI) und dann lasse ich den Wachhund 
los.

Mit einem Softwarinterrupt habe ich mich bisher nocht nicht beschäftigt. 
Hört sich aber auch gut interessant an.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> Deshalb kenne ich wohl auch keine State Machine :(
Ich hatte den Verdacht schon recht früh:
>> Dein Programmierstil ist falsch :-o

Sieh dir mal das an:
1
:
2
:
3
    rcall  SIOInit         ;
4
    rcall  ADInit          ;
5
    rcall  InterruptInit   ;
6
    sei                    ; 
7
forever:                   ; Endlosschleife, ein Durchlauf dauert max. 300us
8
    rcall  SioEinlesen     ;
9
    rcall  ADWandlung      ;
10
    rcall  InputsEinlesen  ;
11
    rcall  DrehzahlEinlesen;
12
    rcall  Greifer         ;
13
    rcall  Ventil          ; 
14
    rcall   Ausgabe        ;
15
    rcall  SIOAusgabe      ;
16
    rcall  Fehler          ;
17
    rjmp  forever          ; eternal loop

Und hier exemplarisch die Funktion zum Zeicheneinlesen
1
SioEinlesen:
2
    sbis  UCSRA,RXC    ; Zeichen da?
3
    ret                ; nein: sofort Ende
4
                       ;
5
    in  temp,UDR       ; sonst: Daten holen
6
    sbrc  temp,6       ; Zahlen nicht wandeln
7
    andi  temp,$DF     ; Umwandlung kleine->grosse Buchstaben
8
                       ; a..f: $61..$66,  A..F: $41..$46
9
    cpi   temp,'T'     ; 'T' angekommen? Tabellenanfang
10
    brne  SE000        ; nein: weiter
11
    clr   RxCnt        ; Zaehler zuruecksetzen
12
    ret                ;
13
14
SE000:  cpi   temp,'Z' ; 'Z' angekommen? 
15
    brne  SE00a        ; nein: weiter
16
    ret                ; ja: Ende
17
:
18
:
Es wird nicht gewartet, bis ein Zeichen da ist. Sondern wenn kein 
Zeichen da ist, bin ich sofort fertig und es geht weiter in der 
Haupt-Schleife. In keiner der Unterprogramme wird auf irgendwas 
gewartet. Es wird erst geprüft, ob sich etwas geändert hat (z.B. ein 
Eingang, Timer oder ein anderer Zustand) und dann darauf reagiert.

@ Johann L.
> Nach der Meldung erfolgt der RESET per WatchDog, weil er im Gegensatz
> zu einem direkten Sprung zum Reset-Vektor die Hardware zurücksetzt
> (Timer, UART, SPI, ...)
> Und dann begint das Spiel von neuem.
Na toll, hoffentlich passiert das nicht beim Landeanflug....  :-/
Game Over *Insert Coin*

von Sascha T. (Gast)


Lesenswert?

Naja von Automaten habe ich schonmal was gehört, auch wenn es auch schon 
lange her ist. State-Machine war mir bisher allerdings kein Begriff.

Aber vielen Dank Lothar. Mit der Assembler Routine ist das Prinzip 
direkt bei mir angekommen. Das trifft halt meine Synapsen schneller als 
die C Routinen, ist ja auch "Hardwarenäher" :o)

Ich werde mir mein Programm morgen nochmal dahingehend anschauen und 
denke, dass ich es dann auch sehr gut ohne Interrupt Abbruch hinbekomme.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Lothar Miller wrote:

> @ Johann L.
>> Nach der Meldung erfolgt der RESET per WatchDog, weil er im Gegensatz
>> zu einem direkten Sprung zum Reset-Vektor die Hardware zurücksetzt
>> (Timer, UART, SPI, ...)
>> Und dann begint das Spiel von neuem.
> Na toll, hoffentlich passiert das nicht beim Landeanflug....  :-/
> Game Over *Insert Coin*

Klar. Ein AKW schaltet man auch nicht einfach aus. Ich vermute mal, daß 
die Anwendung kein AKW oder Airbus steuert. Und wenn für die Anwendung 
ein RESET ok ist, kommt das als "Design Pattern" auch in Betracht. (Ich 
selbst würd es zugegebenermassen ohne Reset machen).

Zumal 3 Bytes per SPI auszugeben ist doch kein Drama, also warum nicht 
einfach ausgeben? Ausser, wenn die Übtrtragungsrate 1 Bit/Minute ist. 
Und selbst dann geht das über des SPI-IRQ (falls HW-SPI).

Johann

von Karl H. (kbuchegg)


Lesenswert?

Johann L. wrote:
> Und wenn für die Anwendung
> ein RESET ok ist, kommt das als "Design Pattern" auch in Betracht. (Ich
> selbst würd es zugegebenermassen ohne Reset machen).

Ich würds mal so formulieren:
Da das Kind nun mal in den Brunnen gefallen ist, ist es vernünftig das 
Beste draus zu machen und einen Schwimmreifen hinterher zu werfen.
Aber für die Zukunft merken dass ein Gitter am Brunnen das Ganze hätte 
verhindern können.

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.