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
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).
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.
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.
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.
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.
> 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...
;-)
...
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
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.
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.
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
@ 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.
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...
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?
@ 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
case1:a();state=2;break;
4
case2:b();state=3;break;
5
case3:c();state=4;break;
6
case4:d();state=5;break;
7
case5:e();state=6;break;
8
case6:f();state=7;break;
9
case7:g();state=8;break;
10
case8:h();state=9;break;
11
case9: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?
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?)
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
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.
> 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
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*
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.
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
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.