Hallo zusammen, für das laufende Projekt habe ich den größten Teil an Funktionen zusammengebaut und getestet. Gemäß den Hinweisen hier im Forum und der eigenen Logik nach läuft also in der main eine Schleife, in der fortlaufend die Hauptfunktion (mit starkt variierender Laufzeit) abgearbeitet wird, bei der auch keine größeren Unterbrechungen auftreten sollten. Am Anfang befindet sich eine Abfrage der Tastatur und dort wird eben auch auf die verschiedenen Tastenbetätigungen reagiert. Solange das dem Muster "ein Tastendruck - eine Funktionsauslösung" folgt, ist das auch völlig problemlos. Jetzt bin ich aber an dem Punkt gelandet, wo nach der Tastaturabfrage in eine Routine verzweigt werden muss, wo diverse weitere Tastenabfragen erfolgen müssen, bis die Funktion soweit mit Daten versorgt ist, die sie zwingend für die Ausführung benötigt. Läuft also nach dem Muster "auf Tastendruck warten - Anzeige aktualisieren - auf Tastendruck warten - Anzeige aktualisieren - ggf. verzweigen - auf Tastendruck warten - .... - Ausführung". Da der Mensch hier mit seiner Reaktionsgeschwindigkeit der limitierende Faktor ist, bleibt die Hauptfunktion für diese Zeit auf der Strecke - was nicht passieren darf. Man könnte das Dilemma meiner Vorstellung nach mit einer Statusmaschine lösen, damit ich nicht auf einen Tastendruck (respektive das Loslassen einer Taste) warten muss, aber in Anbetracht der zahlreich möglichen Verzweigungen erscheint mir das recht aufwendig und fehleranfällig. Notwendig wird wohl der Einbau mehrerer Verriegelungen, da ja z.B. die gleiche Taste in der Hauptschleife eine ganz andere Bedeutung als in der zweiten Stufe haben könnte. In einer früheren Version habe ich mir damit beholfen, die Hauptfunktion auch beim Warten auf Tastendrücke oder beim Warten auf das Loslassen einer Taste aufzurufen. Hat funktioniert, scheint mir aber nicht der Stein des Weisen zu sein. Wie packt man das richtig an? Danke für konstruktive Hinweise Thomas
Thomas P. schrieb: > - auf Tastendruck warten - Das Anti-Pattern schlechthin. Warten ist immer mies. Für "Warten" gibt es m.E. 2 Anwendungsfälle: -- Kurze Delays von wenigen Ticks um an lahme (externe) Hardware anzugleichen. -- Einfachste (Beispiel)programme.
@Thomas P. (topla) >eben auch auf die verschiedenen Tastenbetätigungen reagiert. Solange das >dem Muster "ein Tastendruck - eine Funktionsauslösung" folgt, ist das >auch völlig problemlos. OK. >>zwingend für die Ausführung benötigt. Läuft also nach dem Muster "auf >Tastendruck warten - Anzeige aktualisieren - auf Tastendruck warten - >Anzeige aktualisieren - ggf. verzweigen - auf Tastendruck warten - .... Falsch. Man wartet so nicht. Multitasking " Prozesse eines kooperativen Multitaskingsystems warten nicht (lies: niemals) auf das Eintreten von Ereignissen, sondern bearbeiten nur bereits eingetretene Ereignisse. Größere Aufgaben werden in kleine Teilaufgaben zerlegt, welche nur durch mehrfaches Aufrufen der Funktion abgearbeitet werden. Das erreicht man meist am besten mit einer State machine. Prozesse eines kooperativen Multitaskings haben eine garantierte, maximale Durchlaufzeit, welche möglichst klein ist." >Man könnte das Dilemma meiner Vorstellung nach mit einer Statusmaschine >lösen, damit ich nicht auf einen Tastendruck (respektive das Loslassen >einer Taste) warten muss, aber in Anbetracht der zahlreich möglichen >Verzweigungen erscheint mir das recht aufwendig und fehleranfällig. Nö. >Wie packt man das richtig an? Siehe oben. Statemachine + Multitasking.
Hmm, prinzipiell ist das einleuchtend und klar, aber wie ich mich (bzw. der µC) in dem ganzen Gewirr der verschiedensten möglichen Abfolgen von Tastatureingaben zurechtfinden soll, erschließt sich mir noch nicht so ganz. Ich krame wohl besser erstmal Papier und Bleistift hervor. Mal sehen, ob ich irgendwo ein Beispiel finde, irgendwie stehe ich Moment total auf dem Schlauch. Thomas
@ Thomas P. (topla) >Hmm, prinzipiell ist das einleuchtend und klar, aber wie ich mich (bzw. >der µC) in dem ganzen Gewirr der verschiedensten möglichen Abfolgen von >Tastatureingaben zurechtfinden soll, erschließt sich mir noch nicht so >ganz. Eben DARUM ist eine Statemachine so toll! Da kann man sehr leicht Übersicht gewinnen! Dein jetziger Code ist wohl eher Sphagettistyle!
Kann man jetzt nennen wie man will, funktioniert aber, indem eben immer, wenn auf ein Ereignis gewartet wird, die Hauptfunktion aufgerufen wird. Der Aufruf steckt in den beiden Funktionen "Warten auf keine Taste gedrückt" und "Warten auf Tastendruck" und ist damit in der rufenden Funktion nicht sichtbar. Dafür ist die Abfolge in den Strängen mit Tastatureingabe sehr übersichtlich. So doof finde ich diese Lösung nun auch nicht. Dass das so nicht die eleganteste Methode ist, weiß ich ja, sonst hätte ich nicht hier nach einer besseren Lösung gefragt. Thomas
Hallo Thomas, Thomas P. schrieb: > Dafür ist die Abfolge in den Strängen mit > Tastatureingabe sehr übersichtlich. So doof finde ich diese Lösung nun > auch nicht. > Dass das so nicht die eleganteste Methode ist, weiß ich ja, sonst hätte > ich nicht hier nach einer besseren Lösung gefragt. Es stimmt sicher, dass es für die meisten Menschen einfacher ist eine lineare Abfolge zu programmieren, als eine FSM mit all ihren Verzweigungen im Kopf zu behalten. Dafür gibt es aber einen Zwischenweg namens Protothreads: http://dunkels.com/adam/pt/ Damit kannst du lineare State Machines, im Stile eines Threads schreiben. Was du dafür brauchst steht auf Dunkels Seite. Es sind aber eigentlich nur ein paar Macros, mit denen dein Praeprozessor ein großes switch case Statement erzeugt. Damit kannst du immer wieder deine Tastenabfrage aus der Hauptschleife aufrufen und musst nicht das ganze Program blockieren. Max
@ Max (Gast) >Es stimmt sicher, dass es für die meisten Menschen einfacher ist eine >lineare Abfolge zu programmieren, als eine FSM mit all ihren >Verzweigungen im Kopf zu behalten. Nicht wirklich. Man muss bei einer Statemachine eben NICHT alles im Kopf haben, denn das steht IN der Statemachine! Die kann man GANZ einfach tippel tappel Tour Schritt für Schritt durchlaufen, das ist kein bisschen schwieriger als ein einfacher linearer Ablauf. >Dafür gibt es aber einen Zwischenweg namens Protothreads: >http://dunkels.com/adam/pt/ Och neee!!!
Falk B. schrieb: > das ist kein > bisschen schwieriger als ein einfacher linearer Ablauf. Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf funktioniert und er findet ihn "sehr übersichtlich". Es gibt nur das Problem, dass es den ganzen Controller blockiert. > >>Dafür gibt es aber einen Zwischenweg namens Protothreads: >>http://dunkels.com/adam/pt/ > > Och neee!!! An sich ist das nur ein anderer Formalismus für lineare FSM. Es gibt sogar ein Beispiel, welches auf Tastendrücke wartet, ganz so ähnlich wie die Aufgabe, bei der wir den TO unterstützen wollen: http://dunkels.com/adam/pt/examples.html#codelock
@ Max (Gast) >> das ist kein >> bisschen schwieriger als ein einfacher linearer Ablauf. >Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf >funktioniert und er findet ihn "sehr übersichtlich". Der OP ist bockig und verschlossen. Geht nicht, kann ich nicht, will ich nicht.
Falk B. schrieb: >>Naja, der TO scheint es aber so zu sehen. Sein linearer Ablauf >>funktioniert und er findet ihn "sehr übersichtlich". > > Der OP ist bockig und verschlossen. Geht nicht, kann ich nicht, will ich > nicht. Nana, nur weil ich Deine jahrelange Erfahrung nicht habe und nicht sofort vollkommen durchschaue, wie ich das in diesem Fall am Besten anfange? Meine state machine für den Empfang serieller Daten läuft problemlos; allerdings ist das hier ein anderes Kaliber. Und ja, der Ablauf funktioniert in der bisherigen Konstellation einwandfrei, da hängt auch nichts, da immer dann, wenn gewartet werden muss, die Hauptroutine läuft. Aber eben nicht durch den Aufruf in der main sondern durch Aufrufe von verschiedenen Stellen aus - das gefällt mir nicht. Und gefragt habe ich hier, weil ich das mal richtig hinstellen möchte und dabei auch noch was lernen möchte. Thomas
@Thomas P. (topla) >Und gefragt habe ich hier, weil ich das mal richtig hinstellen möchte >und dabei auch noch was lernen möchte. Na dann stell es auf einen Statemachine um und am Ende wirst du es nie mehr anders machen wollen ;-)
Jo, und da sind wir wieder beim Stand von 15:25. Wenn ich aus der Tastaturabfrage mit einem Wert einer bestimmten Taste komme, kann ich problemlos verzweigen (switch) und die Funktion abarbeiten. Komme ich in einen Zweig, der weitere Tastaturabfragen benötigt, gehe ich dort den ersten Schritt, setzte die state machine und verlasse die Routine wieder. Für den zweiten Durchlauf muss ich jetzt alle anderen Abfragen deaktivieren, da ich ja zwingend in den "angearbeiten" Programmzweig muss, egal, ob die aktuelle Taste auch in der ersten Stufe eines anderen Zweiges vorkommen kann. Und nein, ich weiß nicht, wie ich das einfach lösen kann. Thomas
@ Thomas P. (topla) >Wenn ich aus der Tastaturabfrage mit einem Wert einer bestimmten Taste >komme, kann ich problemlos verzweigen (switch) und die Funktion >abarbeiten. Das kann die Statemachine auch. > Komme ich in einen Zweig, der weitere Tastaturabfragen >benötigt, gehe ich dort den ersten Schritt, setzte die state machine Du macht einen Zustandsübergang. >und >verlasse die Routine wieder. Genau. > Für den zweiten Durchlauf muss ich jetzt >alle anderen Abfragen deaktivieren, Nein. > da ich ja zwingend in den >"angearbeiten" Programmzweig muss, egal, ob die aktuelle Taste auch in >der ersten Stufe eines anderen Zweiges vorkommen kann. Dort kommst du so oder so hin, denn es ist eine Statemachine! >Und nein, ich weiß nicht, wie ich das einfach lösen kann. Weil du das Konzept der Statemachine noch nicht wirklich verstanden hast. Hier ein Hinweis.
1 | switch(state) { |
2 | case WAIT_FOR_FIRST_KEY: |
3 | if (KEY==UP) { |
4 | mach was; |
5 | state = WAIT_FOR_SECOND_KEY; |
6 | } else if (KEY==DOWN) { |
7 | mach was anderes; |
8 | state = WAIT_FOR_THIRD_KEY; |
9 | }
|
10 | |
11 | ansonsten bleiben wir hier |
12 | break; |
13 | |
14 | case WAIT_FOR_SECOND_KEY: |
15 | if (KEY==UP) { |
16 | mach was; |
17 | state = WAIT_FOR_WHATEVER; |
18 | }
|
19 | break; |
20 | |
21 | case WAIT_FOR_WHATEVER: |
22 | if (KEY==UP) { |
23 | mach was; |
24 | state = WAIT_FOR_WHATEVER; |
25 | }
|
26 | break; |
27 | |
28 | |
29 | }
|
Verstehst du nun besser, wie eine FSM funktioniert? Man hangelt sich mit mehreren Zustandsübergängen an der Kette der Tastendrücke entlang. Dazwischen kann man immer wieder kleine Funktionen aufrufen, welche aber eher ein kurze, zumindestens endliche Laufzeit haben (keine Warteschleifen). In JEDEM Zustand kann man immer wieder verschieden reagieren. Man muss und darf NICHT auf irgendwas warten, sondern das auswerten, was schon vorhanden ist (Tastendruck, aktueller Zustand). Denk mal in Ruhe drüber nach.
Noch ein Tip. Der bisherige lineare Ablauf mit Aktionen und Warten auf Tasten kann recht einfach in eine FSM umgewandelt werden. Aufgabe 1 Warte auf Taste A Aufgabe 2 Warte auf Taste B Aufgabe 3 Warte auf Taste C All diese Einzelaktionen kommen in einen einzelnen State. Die FSM geht nur dann weiter, wenn die jeweilige Taste gedrückt wurde (wird jeweils von der Tastenauswertung geliefert). Wird sie NICHT gedrückt, geht die FSM zurück und es können andere FSMs aufgerufen werden. Solange die jeweilige Taste nicht gedrückt wird, ist die Abfrage in der FSM sehr kurz und es wird keine CPU-Zeit vertrödelt.
1 | switch (state) { |
2 | case mache_A1: |
3 | mache Aufgabe 1; |
4 | state = warte_taste_A; |
5 | break; |
6 | |
7 | case warte_taste_A; |
8 | if (taste == A) { |
9 | state = mache_A2; |
10 | }
|
11 | break; |
12 | |
13 | case mache_A2: |
14 | mache Aufgabe 2; |
15 | state = warte_taste_B; |
16 | break; |
17 | |
18 | case warte_taste_B; |
19 | if (taste == B) { |
20 | state = mache_A3; |
21 | }
|
22 | break; |
23 | |
24 | case mache_A3: |
25 | mache Aufgabe 3; |
26 | state = warte_taste_C; |
27 | break; |
28 | |
29 | case warte_taste_C; |
30 | if (taste == C) { |
31 | state = mache_A1; |
32 | }
|
33 | break; |
34 | }
|
Ich hoffe das bringt Verständnis.
Mal doch erstmal alle Deine Zustände auf ein Blatt Papier und schreibe an die Pfeile von Zustand zu Zustand was den Wechsel bewirken soll. Tastenabfrage kannst Du auch global erledigen und dann die Variable der Taste(n) in der FSM auswerten. Siehe z.B. Pedas Tastenentprellung.
Jo, danke, das ist nun klarer. Verstanden habe ich das Prinzip schon, aber ich habe am Anfang eine Stufe zu weit unten angesetzt. Wenn man den ersten Tastendruck in die SM mit einbezieht, ist das dann ganz einfach. Genau das, was die SM nach den ersten Tastendruck macht, meinte ich mit dem Ausdruck "Verriegeln" der anderen Tasten(codes). Dankeschön, thomas
So, ich buddle den ursprünglichen Thread wegen dem Kontext meiner Frage
noch einmal aus, weil ich nach erfolgreicher Umsetzung der SM-Ratschläge
bei einer Erweiterung auf einen Fall gestoßen bin, bei dem ich noch
einmal Hilfe benötige.
Mit Hilfe einer SM hangle ich mich durch mehrere Tastenbetätigungen zu
einem Punkt, an dem ein- bis dreistellige Zahlen eingegeben, korrigiert
und das ganze mit einer Taste abgeschlossen wird.
In der alten Version, die jetzt auf nicht blockierende Arbeitsweise
umzurüsten ist, werkelt hier eine Funktion der Art:
uint16_t get_int (uint16_t vorbel, char x, char y)
{
vorbel auf Position x;y ausgeben
do
{
Anzeige des eingegebenen/bearbeiteten Werts
Tastatur abfragen
Wert ändern
} while (Taste "enter")
return (aktueller Wert)
}
In dem Statusnetz wird diese Funktion an unterschiedlichen Stellen
benötigt, insgesamt 14 Mal mit unterschiedlichen Parametern. Würde diese
Funktion nur einmal benötigt, könnte man das in das vorhandene
Statusnetz integrieren.
Aber wie gehe ich vor, wenn das als Unterprogramm funktionieren soll?
Man müsste sich ja dann den Aufrufpunkt (Status) bzw. den auf den Aufruf
folgenden Status merken, also quasi einen eigeen Stack einrichten.
Hat da jemand vielleicht ein Beispiel? Irgendwie tue ich mich schwer, da
was Vernünftiges zusammenzubringen.
Thomas
Dir ist vermutlich soweit schon aufgegangen, dass das Du den gegenwärtigen Zustand einer Zustandsmaschine in einer Variablen speicherst. Nun ist also die Frage: Wenn ich statt einer nun zwei Zustandsmaschinen habe, wieviele Zustandsvariablen gibt es dann? Das ist wie bei den Autos. Wenn ich statt einem, plötzlich zwei Autos habe, wieviele Lagerplätze für die Winterreifen brauche ich nun? So schwer war das nicht, oder? Die Antwort ist: Doppelt so viele. Gut. Das selbe Spiel noch einmal, nun aber in Bezug auf die Variablen die innerhalb einer Zustandsvariable manipuliert werden. In Deiner Frage, Dein Textbuffer. Wenn ich erst eine Zustandsmaschine habe in der ein Textbuffer manipuliert wird, und dann eine zweite, die einen zweiten Text manipuliert damit sich die Inhalte nicht überschneiden oder stören, wieviele Textbuffer brauche ich dann? Lautet die Antwort: Zwei? Dann hast Du richtig geraten. OK. Nächster Schritt: Wenn ich eine Ansammlung von Variablen habe, die sinngemäß irgendwie zusammengehörig sind - in Deinem Fall einen Status und einen Textbuffer - wie kann ich die im Programm noch anders organisieren als eben zwei Variablen (den Zustand und den Buffer) zu haben? Das Zauberwort lautet: "struct". OK. Noch ein Schritt: Wenn ich eine Struktur habe, in der Zustand und Textbuffer vereint sind und ich brauch einen weiteren Zustandsautomaten, der unabhängig davon einen weiteren Textbuffer manipuliert, brauche ich dann eine zusätzliche Struktur mit der selben Bedeutung? Antwort: Ja. Voilá: Du hast es. Einfach wie das kleine Ein-Mal-Eins. OK. Bei 14 braucht man dann noch das große Ein-Mal-Eins.
Wobei meine Antwort eigentlich nicht passt, denn Deine Skizze hat ja keinen Zustand als Variable nötig. Ich bin irrigerweise davon ausgegangen, dass Du die Tastendrücke in einer Statemachine verarbeitest. Also reicht es, wenn Du jeweils eine Struktur aus einem Buffer und einem Index anlegst und einen Zeiger darauf jeweils beim Aufruf übergibst. So kannst Du die Buffer unterscheiden. Und der RÜckgabewert, sagt Deiner Statemchine ob sie nun in einen anderen Zustand wechseln soll oder nicht. (Weiß nicht, was in Deiner Skizze "aktueller_Wert" sein soll).
Hmm, erstmal Danke für die Antwort. Ist aber an der Frage vorbei, da habe ich mich wahrscheinlich nicht eindeutig genug ausgedrückt. Ich habe 14 verschiedene Aufrufstsellen für diese Funktion, die aber immer sequentiell aktiv sind. Ich benötige also das Umfeld dieser Funktion (Buffer) nur einmal. Mal als Beispiel mit zwei Aufrufpunkten: Benötigt wird der Aufruf der Funktion aus den Zuständen 45 und 88. Jetzt kann man die Zustände der Funktion (angenommen 3 mit 100, 101, und 102) hinter 45 und 88 anordnen: 45-100-101-102-46 und 88-103-104-105-89. Dabei hat man die Funktion quasi als Inline (und eben doppelt bzw. 14-fach. Das soll aber ein Unterprogramm (Funktion) bleiben: 45-100-101-102-46 und 88-100-101-102-89. Wie rettet man also nun die 46 und die 89 möglichst geschickt über den Funktionsaufruf? Denkbar wäre ja auch eine mehrstufige Schachtelung. Thomas
EinmalEinsIstEins schrieb: > (Weiß nicht, was in Deiner Skizze "aktueller_Wert" sein soll). Die Variable steht für das Ergebnis der Eingabe bzw. der Bearbeitung in der Funktion. Thomas
Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal. Du hast die Funktion als Zustandsmaschine implementiert. Du willst die Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren". Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal hinschreiben. Richtig? Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und erforderte andernfalls eben solche Tricks mit Stacks von Zuständen. Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack (nämlich den der Rückkehradresse).
Mal so als Skizze:
[c]
void Statemachine ()
static int Status = START;
switch Status:
case START:
Resultat = Funktion (x,y,z...
if (Resultat == ...)
Status = WEITER;
break;
.
.
.
case HASE_GESEHEN:
Resultat = Funktion (x,y,z...
if (Resultat == ...)
Status = GRILL_ANWERFEN;
break;
.
.
.
}
int Funktion (x,y,z...
int Status:
switch (Status)
wie gehabt
}
Wobei der Status innerhalb der "Funktion", wenn ich Dein Problem recht verstehe, und sie innerhalb eines Zustands der "großen Zustandsmaschine" mehrmals aufgerufen wird, ehe die ihren Zustand ändert, natürlich auch static sein muss. Das sind einfach nur zwei verschiedene Zustandsmaschinen. Die haben nicht nötigerweise was miteinander zu tun; heisst sie haben keine Variablen gemeinsam oder bewegen sich im selben "Zustandsraum".
EinmalEinsIstEins schrieb: > Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal. > > Du hast die Funktion als Zustandsmaschine implementiert. Du willst die > Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren". > Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal > hinschreiben. > Richtig? Ja, genau das ist das Problem. > Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere > Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und > erforderte andernfalls eben solche Tricks mit Stacks von Zuständen. > Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack > (nämlich den der Rückkehradresse). Weil die Funktion mehrfach die Tastatur abfragt, um eine bis zu dreistellige Zahl einzugeben bzw. einen Vorbelegungswert zu editieren bis die Funktion mit der Taste "enter" beendet wird. In der Zwischenzeit steht die Main, in der aber das Hauptprogramm zyklisch abgearbeitet werden muss. Auflösung über den Vorschlag von Falk mit einer SM, die eben nicht blockierend funktioniert. Thomas
Thomas P. schrieb: > EinmalEinsIstEins schrieb: >> Dann habe ich Dich wohl nicht verstanden und ich versuche es nochmal. >> >> Du hast die Funktion als Zustandsmaschine implementiert. Du willst die >> Zustände und deren Abfolge in die erste Zustandsmaschine "integrieren". >> Du willst aber die immer gleiche Folge von Zuständen nicht 14 mal >> hinschreiben. >> Richtig? > > Ja, genau das ist das Problem. > >> Da wäre die Frage, wozu Du die Funktion als ihre Zustände in die andere >> Zustandsmaschine integrieren willst. Das ist doch gar nicht nötig und >> erforderte andernfalls eben solche Tricks mit Stacks von Zuständen. >> Mit einem ganz normalen Funktionsaufruf hast Du doch schon einen Stack >> (nämlich den der Rückkehradresse). > > Weil die Funktion mehrfach die Tastatur abfragt, um eine bis zu > dreistellige Zahl einzugeben bzw. einen Vorbelegungswert zu editieren > bis die Funktion mit der Taste "enter" beendet wird. In der Zwischenzeit > steht die Main, in der aber das Hauptprogramm zyklisch abgearbeitet > werden muss. Auflösung über den Vorschlag von Falk mit einer SM, die > eben nicht blockierend funktioniert. > > Thomas Dann habe ich ja Deine Frage beantwortet. Siehe mein Beispiel. Oder gibt es da noch ein Detail, das Du erklärt haben möchtest? Wenn das mit dem Satzanfang mal versuche zu deuten, dann liegt das Missverständnis vielleicht darin, das Eingaben "innerhalb" der Zustandsmaschine geholt werden. Wenn man das aber macht, dann ist die Maschine tatsächlich "blockieren" bis der nächste Input kommt. Das ist aber nicht die übliche Vorgehensweise. Vielmehr ruft man die Zustandsmaschine auf und sie beendet sich wieder wenn sie den vorherigen Input verarbeitet hat. Dann gibt es neuen Input und wieder einen Aufruf der Funktion mit der Zustandsmaschine. Man wartet nicht in dem Code eines Zustandes auf Input und macht das verlassen der Funktion davon abhängig. (Nur falls man eben die Nebenläufigkeit nicht braucht).
Beim Aufruf der "Funktion" habe ich ja auch kein Problem, da ich weiß, dass ich diese Funktion benötige und nach dem Zustand 45 einfach 100 setzen. Der Rest in dieser Funktion läuft dann immer gleich ab: 101-102. Woher weiß ich jetzt aber die Rückkehradresse nach 102? In diesem Beispiel 46, im zweiten Beispiel 89. Da brauche ich doch so eine Art Stack, um das zu verwalten, könnte ja auch mehrstufig werden. Ich kann doch nicht der Erste sein, der auf dieses Problem läuft. Oder habe ich mir das aus Dämlichkeit selber organisiert? Thomas.
Ich erweitere mal meine obige Skizze, damit man das sehen kann:
1 | main () { |
2 | while(1) { |
3 | if (InputAvailable) { |
4 | Statemachine (Input); |
5 | }
|
6 | // Tue was anderes
|
7 | }
|
8 | }
|
9 | |
10 | void Statemachine (char Input) |
11 | |
12 | static int Status = START; |
13 | |
14 | switch Status: |
15 | case START: |
16 | Resultat = Funktion (Input); |
17 | if (Resultat == ...) |
18 | Status = WEITER; |
19 | break; |
20 | .
|
21 | .
|
22 | .
|
23 | |
24 | |
25 | case HASE_GESEHEN: |
26 | Resultat = Funktion (Input); |
27 | if (Resultat == ...) |
28 | Status = GRILL_ANWERFEN; |
29 | break; |
30 | .
|
31 | .
|
32 | .
|
33 | }
|
34 | |
35 | int Funktion (char Input); |
36 | int Status: |
37 | int ret_val = RUF_MICH_NOCHMAL_AUF; |
38 | |
39 | switch (Status) |
40 | case START: |
41 | if (Input == ... |
42 | Status = NOCH_KEIN_ENDE; |
43 | break; |
44 | case ...: |
45 | // wie gehabt
|
46 | return (ret_VAL); |
47 | }
|
Thomas P. schrieb: > [...] > Oder > habe ich mir das aus Dämlichkeit selber organisiert? Hm. Ich fürchte, so könnte man das formulieren. Guck mal meine letzte Skizze an.
Thomas P. schrieb: > [...] > Woher weiß ich jetzt aber die Rückkehradresse nach 102? In diesem > Beispiel 46, im zweiten Beispiel 89. Der Compiler erzeugt Code, so daß nach einem Funktionsende genau mit dem Befehl nach dem Funktionsaufruf fortgefahren wird.
1 | a(); // nach DIESEM Aufruf ... |
2 | z+=1; // geht es genau HIER weiter |
3 | .
|
4 | .
|
5 | .
|
6 | a(); // und nach DIESEM Aufruf ... |
7 | k = k + 7; // geht es genau hier weiter |
8 | |
9 | a () { |
10 | // mach irgendwas
|
11 | }
|
Das ist aber jetzt schon very basic, wenn ich das anmerken darf. Und mit Zustandsmaschinen ist das ganz genauso. Das ist einfach ganz normaler C-Code. Oder was ist das Problem?
Danke, ich schaue mir das Morgen an, jetzt mache ich mich erstmal auf die Matratze, der Tag war lang. Thomas
Das Problem ist, dass ich nach meinem Verständnis nach dem Umbau einer Funktion auf SM eben keinen klassischen Funktionsaufruf mit Parametern mehr habe. Im Beispiel ist der erste Schritt der Funktion die Ausgabe des übergebenen Wertes auf das LCD. Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck wird zwingend benötigt, um die Funktion zu verlassen. Um ein Blockieren der Hauptfunktion zu vermeiden, kann (soll) ich nicht auf den Tastendruck warten und muss die Funktion verlassen, um die weitere Abarbeitung in der Main zuzulassen. Wie komme ich jetzt nach dem Durchlauf der Main wieder an die Stelle der Funktion? Thomas
Thomas P. schrieb: > Das Problem ist, dass ich nach meinem Verständnis nach dem Umbau einer > Funktion auf SM eben keinen klassischen Funktionsaufruf mit Parametern > mehr habe. > Im Beispiel ist der erste Schritt der Funktion die Ausgabe des > übergebenen Wertes auf das LCD. > Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck wird zwingend > benötigt, um die Funktion zu verlassen. Um ein Blockieren der > Hauptfunktion zu vermeiden, kann (soll) ich nicht auf den Tastendruck > warten und muss die Funktion verlassen, um die weitere Abarbeitung in > der Main zuzulassen. Wie komme ich jetzt nach dem Durchlauf der Main > wieder an die Stelle der Funktion? > > Thomas Du hast doch den Zustand in einer static-Variable gespeichert. Das ist jedenfalls der übliche Weg, wenn man Nebenläufigkeit erreichen will. Der Switch fällt also genau in den case der darin steht. Solange Du den Zustand nicht änderst, trifft immer genau dieser case zu. Und das tut es immer wieder solange bis Du den Zustand änderst.
Thomas P. schrieb: > Aber wie gehe ich vor, wenn das als Unterprogramm funktionieren soll? > Man müsste sich ja dann den Aufrufpunkt(Status) bzw. den auf den Aufruf > folgenden Status merken, also quasi einen eigenen Stack einrichten. Denkbar waere, der Tastenabfrage eigene Zustaende im gleichen Zustandsautomaten zu spendieren und vor Aufruf den gewuenschten Zustand nach Tastenabfrage zu speichern. Die Zustaende der Tastenabfrage tanzen damit aus der Reihe, ist aber klar, da sie von verschiedenen Stellen gerufen werden koennen. Also Zustand1{ ... Zustand= Zustand2 } Zustand2{ ... FolgeZustand= Zustand3 Zustand= Tastenabfrage } Zustand3{ ... Zustand= Zustand1 } Tastenabfrage{ ... Zustand= FolgeZustand }
@ Thomas Ich möchte Dir raten den Beitrag von Tommi zu ignorieren. "Viele Köche verderben den Brei". Falls Du weiter mit mir reden oder jedenfalls der von mir eingeschlagenen Linie folgen willst helfe ich gerne, ansonsten bin ich raus. Tommis Vorschlag würde so gehen ist aber eigentlich Deine Idee (nur ohne das Wort Stack zu verwenden und indem ein ein-stufiger Stack verwendet wird) und komplizierter als überhaupt notwendig. Als wenn man ein neues Auto kauft, weil man den Tankdeckel nicht aufkriegt. Meine Linie hingegen macht irgendwelche Stacks, Tricks usw. völlig unnötig einfach nur indem Du Deine Sichtweise des Problems veränderst , dass was Du über das Problem denkst . Du hast, soweit ich das bis jetzt verstehe, ein Problem damit, zwei Zustandsmaschinen in ihrer Bedeutung zu trennen und daraus die Implementierung abzuleiten. Insbesondere ist unnötig, dass eine Sub-Zustandsmaschine irgendwas davon weiß, was abhängig von ihrem eigenen Verhalten oder Zustand, der Folgezustand in der übergeordneten Zustandsmaschine ist. Anders ausgedrückt muss die übergeordnete Maschine nicht von der Sub-Maschine mitgeteilt bekommen, was ihr eigener Folgezustand ist. Das kann sie allein entscheiden. Es muss eine Verabredung (eine Festlegung durch Dich) existieren, mit welchen Codes (Zahlen, Texten uswusf.) die Sub-Maschine der übergeordneten Maschine etwas mitteilt. Was nötig oder unnötig ist, ist aber ein elementares Thema in der Programmierung mit häufig bedeutenden Konsequenzen. Das zweite Verständnisproblem scheint verursacht zu haben, dass Du die Eingabe in die Zustände hineingeschrieben hast und nicht ausserhalb der Maschine abhandelst. Das hat aber die Folge das so die Zustandsmaschine blockierend ist und die Funktion in der sie implementiert ist, nicht mehr nebenläufig zu anderen Funktionen sein kann.
Thomas P. schrieb: > Das Problem ist, dass ich nach meinem Verständnis nach > dem Umbau einer Funktion auf SM eben keinen klassischen > Funktionsaufruf mit Parametern mehr habe. Ein klares und entschiedenes Jein. Rein formal wird man einen endlichen Automaten ("finite state machine") sicherlich als Funktion implementieren - ob mit oder ohne Parameter sei mal dahingestellt. Inhaltlich ist diese "C-Funktion" ein Objekt -- denn natürlich braucht unser endlicher Automat eine Zustandsvariable, die den aktuellen Zustand des Automaten von Funktionsaufruf zu Funktionsaufruf rettet. Gekapselte Variable plus Methode gleich Objekt. > Im Beispiel ist der erste Schritt der Funktion die Ausgabe > des übergebenen Wertes auf das LCD. > Der zweite Schritt fragt die Tastatur ab. Ein Tastendruck > wird zwingend benötigt, um die Funktion zu verlassen. Um > ein Blockieren der Hauptfunktion zu vermeiden, kann (soll) > ich nicht auf den Tastendruck warten und muss die Funktion > verlassen, um die weitere Abarbeitung in der Main zuzulassen. Richtig. Soweit klar. > Wie komme ich jetzt nach dem Durchlauf der Main wieder an die > Stelle der Funktion? Durch die Zustandsvariable. Die static-Variable innerhalb der Funktion behält ja ihren Wert auch außerhalb der Laufzeit der Funktion. Beim nächsten Aufruf steht dieser Wert also ohne weitere Maßnahmen zur Verfügung. Die einzelnen Zustände werden - wie von EinmalEinsIstEins oben vorgeschlagen - als große switch-case-Konstrukion ausprogrammiert. Die static-Zustandsvariable verwendest Du als Steuervariable im switch/case. Bei Aufruf der Funktion findest Du Dich sofort ohne weitere Maßnahme im "case"-Teil des aktuellen Zustandes wieder. Was ist daran unklar?
EinMalEinsIstEins schrieb: > Meine Linie hingegen macht irgendwelche Stacks, Tricks > usw. völlig unnötig einfach nur indem Du Deine Sichtweise > des Problems veränderst, dass was Du über das Problem denkst . Naja. Der Fairness halber möchte ich anmerken, dass endliche Automaten, die von Steuerungstechnikern im Halbschlaf programmiert werden können, für "normale" Programmierer i.d.R. recht ungewohnt sind. Das ist schon eine spezielle Denkweise. Gekoppelte Automaten (=Automatennetze) sind dann noch eine Stufe härter. > Insbesondere ist unnötig, dass eine Sub-Zustandsmaschine > irgendwas davon weiß, was abhängig von ihrem eigenen > Verhalten oder Zustand, der Folgezustand in der übergeordneten > Zustandsmaschine ist. Anders ausgedrückt muss die übergeordnete > Maschine nicht von der Sub-Maschine mitgeteilt bekommen, was ihr > eigener Folgezustand ist. Das kann sie allein entscheiden. Richtig. Der untergeordnete Automat muss vom übergeordneten Rechenzeit erhalten, d.h. die Funktion muss aufgerufen werden, und es muss "irgendwas" vom untergeordneten Automaten zurückgemeldet werden. Im einfachsten Falle ist das ein boolscher Funktionswert als Freigabe. > Es muss eine Verabredung (eine Festlegung durch Dich) existieren, > mit welchen Codes (Zahlen, Texten uswusf.) die Sub-Maschine der > übergeordneten Maschine etwas mitteilt. Genau. > Das zweite Verständnisproblem scheint verursacht zu haben, dass > Du die Eingabe in die Zustände hineingeschrieben hast und nicht > ausserhalb der Maschine abhandelst. Das hat aber die Folge das > so die Zustandsmaschine blockierend ist Das sehe ich noch nicht ganz. Wenn man erstmal prüft, ob überhaupt ein neues Zeichen vorhanden ist, dann passiert doch nix Schlimmes. Wenn keins da ist, wird abgebrochen, und wenn eins da ist, wird gearbeitet.
Zunächst mal ein Dankeschön für die schnelle Hilfe. Ich hoffe, ich habe das richtig gelesen und verstanden: Die Teile der Funktion sind nicht in die große SM zu inegrieren. Die Funktion für die Dateneingabe/-darstellung ist so zu modifizieren, dass - die eigentlichen funktionsinternen Variablen global definiert werden - eine SM in die Funktion gebaut wird, die bei mehrmaligem Aufruf das Ansprechen der verschiedenen Schritte steuert - Abschluss des letzten Funktionsschrittes durch Rückgabewert signalisieren Da werde ich mich mal ans Werk machen. Danke, Thomas
Ich habe solche "unter-Zustandsmaschinen" gelegentlich als eine Art Filter implementiert, also als Funktionen, die zuerst aufgerufen werden und dann zurückmelden, ob sie die Eingabe "verbraucht" haben oder nicht:
1 | void keypressed(char key) { |
2 | if(zahleneingabe(key)) return; |
3 | switch(state) { |
4 | ...
|
5 | }
|
6 | }
|
zahleneingabe() hat dann natürlich einen eigenen Zustand (der normalerweise auf IDLE steht, also nichts tun und false zurückliefern), plus weitere globale Variablen für die Anzahl einzugebender Stellen, aktuelle Stelle, aktueller Wert, Darstellungsformat (z.B. xx:xx oder xxx.x), ... Wenn die Haupt-Zustandsmaschine eine Zahleneingabe braucht, setzt sie sämtliche globalen Variablen der Zahleneingabe (incl. den Zustand) passend (dafür gibt es natürlich eine Funktion), und setzt dann ihren eigenen Zustand entsprechend. Die Haupt-Zustandsmaschine wird dann erst wieder erreicht, wenn die Zahleneingabe fertig ist (also z.B. wenn "OK" oder "Abbrechen" gedrückt wurde).
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.