Hallo zusammen Ich "baue" zur Zeit meine allererste State Machine (mit Code) und stehe vor vielen grundsätzlichen Fragen. Eine Frage beschäftigt mich ziemlich stark: Sollte die Anzahl der States (also Zustände) so klein wie möglich gehalten werden, oder sollten die eizelnen States besser so einfach wie möglich gehalten werden, dafür werden es aber einige States mehr? Um meine Frage zu veranschaulichen habe ich ein rein fiktives Beispiel im Bild (Dateianhang) skizziert: - Im ersten Fall schalte ich in State 7 ein Relais, warte bis drei Minuten vergangen sind, dann folgt eine Abfrage (Helligkeit) und aufgrund dessen wird der nächste State (entweder 8 oder 9) entschieden. - Im zweiten Fall schalte ich in State 7 NUR ein Relais. Dann gehe ich zu State 8 wo ich NUR warte bis 3 Minuten vorbei sind. Dann gehe ich zu State 9 wo die Abfrage (Helligkeit) durchgeführt wird, aufgrund dessen wird der nächste State (entweder 10 oder 11) entschieden. Was ist "sauberer", "klüger" oder entspricht eher dem Wesen einer State Machine? Ich hoffe, hier keine allzu doofe Frage gestellt zu haben... Grüsse Roman
:
Verschoben durch User
Du implementierst so viele Zustände wie Du brauchst. 3 Minuten Warten ist eindeutig ein separater Zustand. Irgendwas einschalten sind Dinge die bei Zustandsübergängen passieren, es verweilt nicht im Zustand "Einschalten", das ist überhaupt kein Zustand, das ist eine Aktion. Es verweilt im Zustand "Mit eingeschaltetem Relais 3 Minuten warten". Das ist ein Zustand.
:
Bearbeitet durch User
naja, der Zustand Einschalten hat durchaus Sinn. Zustand AUS .. einschalttrigger Zustand EINSCHALTEN .. überwachung, timer aktivieren (z.B. 1s) Zustand EINGESCHALTET (z.B. Rückmeldung durch messen von Spannung/Strom) .. wird die Rückmeldung EIN eher geliefert als der Alarmtimer aus Zustand Einschalten abläuft ist alles gut, Timer stoppen. .. sollte das Timeout im, Zustand EINSCHALTEN eintreffen ist das Einschalten gescheitert => Fehlerbehandlung Zum Beispiel Bremslichtüberwachung am PKW UML bietet eine übersichtliche statemachine an. je weniger Gimmicks intern verbaut sind desto logischer ist die UML SM lesbar. Zaubern man in einem state X trigger und Abfragen rein wird das zwar auf den ersten Blick kompakt, aber je mehr Zeug darin steckt desto schwerer wartbar. https://de.wikipedia.org/wiki/Zustandsdiagramm_%28UML%29
@Roman K. (rk_aus_s) >Sollte die Anzahl der States (also Zustände) so klein wie möglich >gehalten werden, Nein. > oder sollten die eizelnen States besser so einfach wie >möglich gehalten werden, dafür werden es aber einige States mehr? Jain. Es muss eher möglichst gut lesbar sein und in den States darf man nicht warten, bestenfalls wenige us, in bestimmten Fällen (sehr langsame FSM) vielleicht auch Millisekunden. >- Im ersten Fall schalte ich in State 7 ein Relais, warte bis drei >Minuten vergangen sind, dann folgt eine Abfrage (Helligkeit) und >aufgrund dessen wird der nächste State (entweder 8 oder 9) entschieden. FAIL! Siehe Multitasking! >- Im zweiten Fall schalte ich in State 7 NUR ein Relais. Dann gehe ich >zu State 8 wo ich NUR warte bis 3 Minuten vorbei sind. Ist so wie es gezeichnet ist immer noch falsch bzw. mehrdeutig. Der Zustand Warten ist an sich OK, WENN er immer wieder verlassen wird und dort nur ein Zähler läuft. Aber es darf mal sicher kein delay(3min) dort drinstehen! >State 9 wo die Abfrage (Helligkeit) durchgeführt wird, aufgrund dessen >wird der nächste State (entweder 10 oder 11) entschieden. Im Prinzip bist du mit der unteren Version in der richtigen Richtung unterwegs. Siehe auch Statemachine.
Roman K. schrieb: > Im ersten Fall schalte ich in State 7 ein Relais, warte bis drei > Minuten vergangen sind, Grundsätzlich werden Änderungen bei einer Statemachine nicht in einem Zustand durchgeführt, sondern beim Übergang von einem Zustand zum anderen. Das sollte schon deshalb logisch sein, weil ja das Einschalten des Relais den realen Zustand verändert, danach ist eben das Relais on. Wechsel zu einem anderen Zustand werden durch Ereignisse ausgelöst, z.B. Änderungen an einem Eingang, auch das Ablaufen eines Timers kann einen neuen Zustand erfordern. Der grosse Vorteil dabei: man macht eine Tabelle mit den Zuständen und den möglichen Ereignissen, für jede Kombination gibt es eine Funktion, die eine Aktion durchführt wie Relais on und danach den neuen Zustand bestimmt. Dadurch ist für jede mögliche Kombination von Zustand und Ereignis die Reaktion des Systems definiert, das ist mit anderer Softwarestruktur nur schwer erreichbar. Zumindest muss man sich über jede mögliche Kombination Gedanken machen, damit man in die Tabelle was eintragen kann. So entsteht sehr sichere Software. Georg
Falk B. schrieb: > Zustand Warten ist an sich OK, WENN er immer wieder verlassen wird und > dort nur ein Zähler läuft. Aber es darf mal sicher kein delay(3min) dort > drinstehen! Unsinn! Das hängt ganz von der Implementierung der delay() Funktion ab. Falls diese den zugehörigen Task zum Schlafen bringt, einen Timer startet, und den Task dann wenn der Timer abgelaufen ist, wieder zum Leben erweckt, ist da alles i.O.
Je mehr mögliche Zustände es gibt desto mehr unzulässige Zustände gibt es, die alle abgefangen werden sollten. Insofern ist eine minimale Anzahl besser.
Eric B. schrieb: > Falk B. schrieb: >> Zustand Warten ist an sich OK, WENN er immer wieder verlassen wird und >> dort nur ein Zähler läuft. Aber es darf mal sicher kein delay(3min) dort >> drinstehen! > > Unsinn! Das hängt ganz von der Implementierung der delay() Funktion ab. > Falls diese den zugehörigen Task zum Schlafen bringt, einen Timer > startet, und den Task dann wenn der Timer abgelaufen ist, wieder zum > Leben erweckt, ist da alles i.O. ganz richtig. Das Konzept der Abarbeitung der Statemachine und die Möglichkeiten des verwendeten Multitask- oder Multithread-Systems (z.B. eines RTOS) fließen auch mit in das Design der Statemachine mit ein. Wenn die Statemachine durch regelmäßige Aufrufe z.B. einer handle_statemachine()-Funktion aus einem Main-Loop heraus bearbeitet wird, brauche ich relativ kleinteilige States, bei denen ich jeweils prüfe, ob die Bedingung zum Übergang in einen der nächsten States erfüllt ist (3 Minuten Timer abgelaufen, dann xyz). Denn jedesmal wenn mein Handler aufgerufen wird, muss ich den ganzen Code für diesen State durchlaufen. Läuft die Statemachine dagegen als eigener Thread, der jederzeit ein delay(3min) aufrufen darf und das RTOS lässt dann in der Zeit andere Threads laufen, dann können die States auch "dicker" werden und ich muss nicht jede Kleinigkeit als eigenen State modellieren. Es müssen nur die Übergänge in andere States (inkl. Fehler-States) stimmen. Macht das ganze etwas übersichtlicher. Aber Multithreading hat dafür andere Tücken wie z.B. Deadlocks etc. die ich ausschließen muss.
In der Praxis erfordern die meisten state-machines eine zeitnahe Reaktion auf umweltbedingungen (bestes Beispiel: User Eingaben), dann ist ein delay egal welcher Form jenseits einiger Millisekunden keine Option mehr. Wenn meine Applikation so linear ist, dass ich viele states hab in denen einfach nur gewartet wird, dann braucht ich normal auch keine state-machine um das zu modellieren.
Max D. schrieb: > In der Praxis erfordern die meisten state-machines eine zeitnahe > Reaktion auf umweltbedingungen (bestes Beispiel: User Eingaben), dann > ist ein delay egal welcher Form jenseits einiger Millisekunden keine > Option mehr. > Wenn meine Applikation so linear ist, dass ich viele states hab in denen > einfach nur gewartet wird, dann braucht ich normal auch keine > state-machine um das zu modellieren. Was hat denn das Konzept State-Machine mit der Reaktionsgeschwindigkeit zu tun? > In der Praxis erfordern die meisten state-machines eine zeitnahe > Reaktion auf umweltbedingungen Halte ich für eine haltlose Behauptung.
Mit welcher Frequenz wird die FSM denn getaktet? Also wenn die z. B. 100 mal pro Sekunde aufgerufen wird und man will sie minimalistisch mit einer Zustandsvariablen realisieren, dann komme ich bei 3 min Wartezeit auf 18000 Zustände. Auf zwei Werte, z. B. 18000 und 0, muss man testen und die entsprechenden Aktionen ausführen, also das Relais einschalten bzw. auf Helligkeit prüfen und das Licht passend dimmen. Außer dem Drei-Minuten-Software-Timer muss die FSM für dieses einfache Problem keine weiteren Komponenten haben. Aktionen wie "Relais schalten" und "Helligkeit testen" Zustände zuzuordnen, ist natürlich sinnfrei. Das Konzept der Zustandsmaschine beruht ja gerade darauf, genau zwischen Zuständen, Zustandsübergängen und Aktionen zu unterscheiden.
LostInMusic schrieb: > Mit welcher Frequenz wird die FSM denn getaktet? Nicht. Eine SM taktet man nicht, die reagiert auf Events/Ereignisse. > Also wenn die z. B. 100 > mal pro Sekunde aufgerufen wird und man will sie minimalistisch mit > einer Zustandsvariablen realisieren, dann komme ich bei 3 min > Wartezeit auf 18000 Zustände. Nein, dann komm ich auf 1 Zustand, nämlich "Warte bis 3min abgelaufen sind", und ich implementiere mir einen Timer, der nach 3 Minuten genau dieses Event an die SM schickt. (z.B. mit einem Zähler der jede 10ms hochgezählt wird und bei 18000 das Event auslöst).
@Eric B: Ich fasse den von Dir erwähnten Timer halt auch als Zustandsmaschine auf. Sie reagiert auf den Clock-"Tick", den sie irgendwoher (evtl. Hardware) bekommen muss, und ihre Reaktion darauf ist das Herunterzählen der Timer-Variable - die, die innerhalb der 3 min dann 18000 Zustände annimmt. So what? Deine alternative Sichtweise, bei der Du den Timer als separates System ausklammerst, ist natürlich legitim und dann lautet die korrekte Antwort "Es ist eine Zustandsmaschine mit zwei Zuständen" (der Wechsel vom einen zum anderen erfolgt zum Zeitpunkt 3 min). Diese Darstellung finde ich aber für jemanden, der das Konzept Zustandsmaschine offensichtlich noch gar nicht verstanden hat, didaktisch wenig wertvoll.
@ Eric B. (beric) >> Zustand Warten ist an sich OK, WENN er immer wieder verlassen wird und >> dort nur ein Zähler läuft. Aber es darf mal sicher kein delay(3min) dort >> drinstehen! >Unsinn! Das hängt ganz von der Implementierung der delay() Funktion ab. >Falls diese den zugehörigen Task zum Schlafen bringt, einen Timer >startet, und den Task dann wenn der Timer abgelaufen ist, wieder zum >Leben erweckt, ist da alles i.O. Ja, wenn. Wollen wir mal wetten, daß der OP ebenso wie 99% der hier heute und auch in Zukunft Mitlesenden mal ganz sicher KEIN RTOS mit einem derartigen delay() haben? Sonder eher das klassisch blockierende?
@Eric B. (beric) >> Mit welcher Frequenz wird die FSM denn getaktet? >Nicht. Eine SM taktet man nicht, die reagiert auf Events/Ereignisse. Stimmt so allgmein auch nicht. Nicht alles läuft unter QT und ähnlichen Frameworks. Wir wollen doch mal eher bei den EINFACHEN, Grundlegenden Konzepten anfangen und die Neulinge nicht mit den allerneusten RTOS-Geschichten überfordern. Und da wird eine FSM getaktet, indem sie periodisch aufgerufen wird.
Falk B. schrieb: > Und da wird eine FSM getaktet, indem sie periodisch aufgerufen wird. Was soll man denn da aufrufen? Man kann als Event in eine FSM z.B. ein "Event_Timer_Tick" reingeben. Alle 10 ms z.B. Ein Zustand kann dann damit arbeiten, z.B. warten.
Hallo TO, schau dir doch mal die SDL an (https://en.wikipedia.org/wiki/Specification_and_Description_Language) Dort speziell das Kapitel "Behaviour" und versuche, deine Statemachine in SDL zu formulieren, das hilft dir vllt. beim systematischen Erarbeiten des Sachverhaltes. Für dein Vorhaben brauchst du die Symbole State, Input, Output, Decision, Start/Stop Timer, Plain Code Für mich persönlich war das Erlernen und Verstehen dieser Syntax extrem hilfreich zum Erstellen von sauberen Statemachines und zum Diskutieren bzw. Darstellen von Funktionalitäten eines Systems für andere. PS: Zu der Frage, ob Messen ein eigener Zustand ist: Wenn die Messung viel Zeit benötigt, dann ggf eigener Zustand. Bei Eintritt in den "Mess-Zustand" Messung starten Bei Ereignis "Messung abgeschlossen" Ergebnis abholen, Auswerten, Aktion, Folgezustand Eine andere Möglichkeit wäre es, die Messung immer im Hintergrund laufen zu lassen, sodass immer ein aktuelles Ergebnis im Zugriff ist, dann muss nicht gewartet werden. Aber es gibt auch noch viele andere Möglichkeiten, hängt letztlich von der Erfahrung ab, die geeignete Methode zu wählen.
PS: Das Starten eines Timers ist in SDL eine Aktion Das Ablaufen eines Timers ist in SDL ein Input (also Event) Deine Abfrage Hell/dunkel ist eine Decision. Abhängig von der Decision können unterschiedliche Funktionen, Outputs und Folgezustände kommen. Oben steht immer der aktuelle Zustand, unten die Folgezustände. Wenn jeder Strang Zustand->Input->Decision/Funktion...->Output->Folgezustand aufgeschrieben wurde, ist die Statemachine vollständig beschrieben. Zur Übersicht nimmt man meistens noch ein Statechart, in dem nur Zustände und Übergänge zu sehen sind.
Vielleicht mal nen Blick auf die Implementierung werfen. 1.Fall
1 | {
|
2 | SetRelaisOn(); |
3 | Wait3m(); |
4 | int x= GetBrightness(); |
5 | if (x >= Limit) |
6 | DoSomething(); |
7 | else
|
8 | DoSomethingElse(); |
9 | }
|
Irgendwie lässt sich das so ganz ohne Statemachine runterproggen 2.Fall
1 | //Called from 1ms interrupt
|
2 | TheStateCallback() |
3 | {
|
4 | static int state = 0; |
5 | static uint32_t timerVal = 0; |
6 | |
7 | switch (state) |
8 | {
|
9 | case 0: |
10 | SetRelaisOn(); |
11 | state++; |
12 | break; |
13 | |
14 | case 1: |
15 | timerVal++; |
16 | if(timerVal >= THREE_MIN) |
17 | {
|
18 | timerVal = 0; |
19 | state++; |
20 | }
|
21 | break; |
22 | |
23 | case 2: |
24 | int x= GetBrightness(); |
25 | if (x >= BRIGHTNESSLIMIT) |
26 | state = 3; |
27 | else
|
28 | state = 4; |
29 | break; |
30 | |
31 | case 3: // Do something |
32 | ...
|
33 | state= 0; |
34 | break; |
35 | |
36 | case 4: // Do something else |
37 | ...
|
38 | state= 0; |
39 | break; |
40 | }
|
41 | |
42 | }
|
Das ist so der asynchrone Klassiker (für'n AVR). Man kann sich jetzt noch streiten, ob man die Wartefunktionen um nen Callback, einen AfterwaitState, oder sonstewas erweitert (dann kann man auch parametriert an unterschiedlichen Stellen warten, ggf. ne Cancelfunktion einbauen, etc). Auch kann man für den Statewert nen Enum bauen, ecetera, ecetera. Man kann sich auch darüber streiten, ob State 3 und 4 so überhaupt gebraucht werden (die Anweisungen können auch direkt in die Entscheidung von State 2). Im Grunde genommen geht's doch nur darum, die Funktionsteile so zu gestalten, wie sie optimal wiederverwendet werden können. (und man sollte daran denken, das Relais auch wieder auszuschalten)
Kleiner Tipp, um den Code besser lesbar zu machen: Benutze enum statt Zahlen. Dann steht dort anstelle der 2 CHECKBRIGHTNESS und wenn man mal mittendrin etwas einfügt, muss man nicht alle alle folgenden Zahlen ändern (bzw. eine chaotische Nummerierung hinnehmen)..
Stefanus F. schrieb: > Kleiner Tipp, um den Code besser lesbar zu machen: > Benutze enum statt Zahlen. Vor allem würde ich auf "state++" verzichten. Zustände eines endlichen Automaten sind im allgemeinen Fall nicht geordnet; auch ist der Folgezustand nur in sehr einfachen Fällen gerade der Zustand mit der nächsthöheren Nummer.
Max D. schrieb: > In der Praxis erfordern die meisten state-machines eine zeitnahe > Reaktion auf umweltbedingungen Es ist noch viel schlimmer: state machines sind schon vom Grundkonzept her völlig "zeitlos". Sie reagieren ausschließlich auf Ereignisse. Allerdings kann so ein Ereignis auch der Ablauf einer Zeitspanne sein. Natürlich müßte dazu die state machine bei einer Transition eben diesen Ablauf starten. Sie kann aber nicht selber den Ablauf dieser Zeitspanne sicherstellen. Denn dann ist sie keine state machine mehr, weil sie vor Ablauf der Zeitspanne auf andere Ereignisse nicht reagieren kann. D.h.: delay()'n stuff ist in formal korrekten statemachines schlicht nicht erlaubt. Wer was anderes behaupted, hat das Konzept einer state machine schlicht nicht wirklich begriffen. Und nein: selbst die eventuell vorhandene Tatsache, dass in so einer illegalen Wartezeit sowieso keine andere state transition erlaubt wäre, ändert am Sachverhalt rein garnichts. So ein Konstrukt ist und bleibt PFUSCH und ist eben keine state machine.
https://martin-thoma.com/how-to-draw-a-finite-state-machine/ https://spin.atomicobject.com/2013/01/30/making-diagrams-with-graphviz/ Mach das mit graphviz, dann kannst du schnell etwas ändern und es sieht alles schön sauber aus.
Egon N. schrieb: > Mach das mit graphviz, dann kannst du schnell etwas ändern und es sieht > alles schön sauber aus. Hab ich auch schon probiert, sehr schnell kommt man aber an den Punkt an dem es die States und die Übergänge anders anordnet als es der Übersicht halber besser wäre, besonders schlimm wird es wenn man die Übergänge auch noch mit Text dekorieren will um Auslöser und Aktionen zu notieren, dann kämpft man einen halben Tag verzweifelt mit den Layoutoptionen und dann gibt man schließlich auf und zeichnet es sauber von Hand unter der Verwendung von Dia (sehr empfehlenswert) oder vergleichbaren Hilfsmitteln. Es gibt bestimmt viele Anwendungen bei denen Graphwiz gute Dienste leistet, das saubere Dokumentieren einer halbwegs komplexen Statemachine gehört aber definitiv nicht dazum da stößt es schnell an seine Grenzen.
:
Bearbeitet durch User
Auch wenn die Frage "womit zeichnen?" eher ein Nebenaspekt der Frage des TO ist: Ich verwende gerne yed für sowas: https://www.yworks.com/products/yed Das macht es sehr leicht Blöcke zu verschieben und dennoch die Verbindungslinien zwischen ihnen in ansehnlicher Weise beizubehalten.
:
Bearbeitet durch User
Roman K. schrieb: > Sollte die Anzahl der States (also Zustände) so klein wie möglich > gehalten werden, oder sollten die eizelnen States besser so einfach wie > möglich gehalten werden, dafür werden es aber einige States mehr? Ein Automat (State Machine) kennt keine Zeit. Das heißt, dass du für alles, was messbar Zeit dauert, einen Zustand definieren musst. Messbar ist in dem Fall davon abhängig, mit welchem Takt du den Automaten laufen lässt (also z.B. wie oft du handle_states() aufrufst, z.B. alle 10 ms). In deinem Beispiel hast du also einen Startzustand, der auf eine Eingabe wartet, um loszulegen. Während des Umschaltens schaltest du das Relais ein (das dauert keine Zeit) und gehst in den Wartezustand. Der wartet nur die 3 Minuten. Wenn die abgelaufen sind, misst du die Helligkeit (das dauert ebenfalls keine Zeit) und gehst direkt in den Folgezustand, der wieder auf irgendwas wartet. Wenn die Messung messbar Zeit dauert, dann ist das ein eigener Zustand. Roman K. schrieb: > Ich hoffe, hier keine allzu doofe Frage gestellt zu haben... Nö, die ist mal wirklich interessant. :-)
>Man kann sich auch darüber streiten, ob State 3 und 4 so überhaupt >gebraucht werden (die Anweisungen können auch direkt in die Entscheidung >von State 2). Ja. Beide Varianten sind übrigens nicht äquivalent, denn bei derjenigen mit State 3 und 4 wird die Folgeaktion ("DoSomething"/"DoSomethingElse") erst einen Zyklus später ausgelöst, d. h. in Deinem Beispiel 1 ms später. Das mag oft egal sein, aber in anderen Fällen kann eine solche Verzögerung durchaus problematisch sein. Die States 3 und 4 sind letztlich redundant und man sollte besser darauf verzichten. Man kann sogar ohne Schwierigkeiten auf die "state"-Variable selbst verzichten (aber nicht auf "timerVal"):
1 | static uint32_t timerVal = 0; |
2 | |
3 | // Called once at power up
|
4 | Init() |
5 | {
|
6 | timerVal = 3*60*1000; // start timer (3 min) |
7 | SetRelaisOn(); |
8 | }
|
9 | |
10 | // Called periodically from 1ms interrupt
|
11 | Run() |
12 | {
|
13 | if (timerVal>=0) // if timer active... |
14 | {
|
15 | timerVal--; |
16 | }
|
17 | |
18 | if (timerVal==0) // if timer expired... |
19 | {
|
20 | int x= GetBrightness(); |
21 | if (x >= BRIGHTNESSLIMIT) |
22 | DoSomething; |
23 | else
|
24 | DoSomethingElse; |
25 | }
|
26 | }
|
Das fundamentale Problem, das hier eigentlich vorliegt, ist die vom TO gewählte Aufgabenstellung: Die ist schlicht zu einfach, um daran das Zustandsmaschinen-Know-How gut lernen zu können. Ein etwas komplizierteres System mit z. B. zwei Tasten und zwei oder drei Timern wäre da meiner Meinung nach viel besser geeignet.
LostInMusic schrieb: > if (timerVal>=0) // if timer active... > { > timerVal--; > } Das geht in die Hose sobald timerVal gleich Null ist. P.S. Und selbst wenn nicht - so macht man das ganz einfach nicht.
:
Bearbeitet durch User
Liebe Leute, zu Anfangs sah das doch recht einfach aus! Aber wenn man State-Machines (so nenne ich die) in realistischen Umgebungen implementieren will, dann geht es doch um ganz andere Dinge. Schnittstelle...wer sagt was...wer darf überhaupt was sagen...was mach ich mit mir selbst...wer macht wann, was , mit wem...was ist, wenn sonst noch was passiert... Und ich persönlich mache das nicht! mit State-M. Auf Controllerebe...eine gehörige Herausforderung. Und wenn auch alle heulen, dass mache ich entweder mit einem durchdachten Code oder ich lass' es sein (bin übrigens forthler" Gruß Rainer
guest...Rainer schrieb: > Und ich persönlich mache das nicht! mit State-M. Provokation: Ich behaupte, dass nahezu jedes funktionierende µC Programm eine StateMachine ist. Und das hat nichts damit zu tun, ob der Programmierer das Wort je gehört hat, oder beabsichtigt hat einen solchen Automaten zu bauen.
guest...Rainer schrieb: > Und ich persönlich mache das nicht! mit State-M. Klar -- warum einfach, wenn's auch umständlich geht :) > Auf Controllerebe...eine gehörige Herausforderung. > Und wenn auch alle heulen, dass mache ich entweder > mit einem durchdachten Code oder ich lass' es sein Diese Option (seinlassen) hat man nicht immer. Endliche Automaten ("state machines") sind nicht kompliziert -- sie werden nur von den "richtigen" Informatikern systematisch geringgeachtet. Übliche Programmiersprachen haben keine Ausdrucksmittel dafür, und Steuerungsprogrammierung gilt als Job für debile Elektriker. Das sollte einen aber dennoch nicht davon abhalten, sich damit zu befassen.
Arduino Fanboy D. schrieb: > Ich behaupte, dass nahezu jedes funktionierende µC Programm eine > StateMachine ist Ich mach einen "full-agree!" Es wird mal wieder mit einem "Mode"-Wort herumgeschmissen. Und ich möchte gern von allen denen Ardunio-Nutzern- oder wie sie heißen - hören, wo sie in ihren Libs eine StateMachine sehen können. Natürlich müssen sie das auch nicht, aber dennoch(F.-K.-Wächter) Gruß Rainer
guest...Rainer schrieb: > Es wird mal wieder mit einem "Mode"-Wort > herumgeschmissen. Quark. Du musst schon zwischen dem schwachsinnigen Ausdruck und dem dahinterstehenden Konzept unterscheiden. Und das Konzept der endlichen Automaten ist super, denn es ist viel einfacher als das ganze Chomsky-Grammatik- Gewürge, das die Informatiker immer zelebrieren, und überdies vor allem in den Fällen nützlich, in denen die normale "sequenzielle" Denkweise versagt. > Und ich möchte gern von allen denen Ardunio-Nutzern- > oder wie sie heißen - hören, wo sie in ihren Libs eine > StateMachine sehen können. Das ist nicht fair, denn es gibt auch genügend studierte Programmierer, die die Tragweite des Konzeptes nicht erfassen.
Die passenden Schluesselwoerter heissen : State - Event - Action In jedem "State" wartet man auf einen oder mehrere "Events", und wenn so ein "Event" ausgeloest wird, wird die passenden "Action" ausgefuehrt, und ein neuer "State" wird erlangt. Sinnvollerweise ruft man die Zustandsmaschine waehrend der Zeit, wo nichts anderes laeuft auf, und faellt im Wesentlichen ohne einen Delay durch. Der "State" sollte eine Zahl sein, denn die kann man dann per UART debuggen, dh sehen, in welchem Zustand die Zustandsmaschine steckt. Dann benoetigt man nur noch einen Pin-puls zu Beginn der Zustandsmaschine, um mit dem Oszilloskop zu sehen, ob der Controller in der Zustandsmaschine steht, und wie oft sie dran kommt. Und falls noetig, kann man den Controller am Ende der Zustandmaschine noch in einen Sleep werfen, nachdem die "Hardware-Events" als Aufwachkriterium definiert wurden.
guest...Rainer schrieb: > Und ich möchte gern von allen denen Ardunio-Nutzern- > oder wie sie heißen - hören, wo sie in ihren Libs eine StateMachine > sehen können. Ich kann dir lebhaft bestätigen, dass viele Arduinouser dauernd und bewusst mit endlichen Automaten hantieren. Und von daher auch einen solchen Automaten erkennen, wenn sie ihnen über den Weg läuft. Es ist DAS maßgebliche "Design Pattern" für Ablaufsteuerungen.
Possetitjel schrieb: > Quark. > Du musst schon zwischen dem schwachsinnigen Ausdruck > und dem dahinterstehenden Konzept unterscheiden. Quark ist "das" Schlüsselwort, für mich...anstatt sich mit Schwachköpfen auseinander zu setzen, muß man die Kompetenz des Progs gnadenlos inFrage stellen. "Das Leben ist nicht wirklich schwer" #Schopenhauer# Gruß Rainer
guest...Rainer schrieb: > Possetitjel schrieb: >> Quark. >> Du musst schon zwischen dem schwachsinnigen Ausdruck >> und dem dahinterstehenden Konzept unterscheiden. > > Quark ist "das" Schlüsselwort, für mich...anstatt sich > mit Schwachköpfen auseinander zu setzen, [...] Wer nicht will, der hat schon. Ich habe nur versucht, Dir zu verdeutlichen, dass es einen Unterschied gibt zwischen dem idiotischen Wort "state machine" und dem ganz und gar nicht idiotischen Konzept. Wenn Dich das nicht interessiert -- auch gut. Schlaf gut.
Proletus protectus Troll schrieb: > Die passenden Schluesselwoerter heissen : > State - Event - Action Interessant. Welche Programmiersprache ist das? SCNR
Possetitjel schrieb: > Das ist nicht fair, denn es gibt auch genügend studierte > Programmierer, die die Tragweite des Konzeptes nicht > erfassen. ...so weit wollte ich eigentlich nicht gehen.... Rainer
Proletus protectus Troll schrieb: > Der "State" sollte eine Zahl sein, denn die kann man dann per UART > debuggen, dh sehen, in welchem Zustand die Zustandsmaschine steckt. Das gute alte enum... Wieso sollter man per UART debuggen? GDB gibt es doch genauso.
An den TO: erstmal auch von mir das Kompliment, die Frage ist sinnvoll, gut gestellt, Deine gezeichntete Statemachine sehr gut. A) Die Nutzung von Zahlen (statt enums) ist in diesem Stadium richtig! Eine Sinnvolle Benennung der States ist erst möglich, wenn man ungefähr weiss, was man will. Solange eine aussagekräftige Definition eines States fehlt, ist eine Zahl besser als ein fast passender enum. B) beide vorgehen haben Vor- und Nachteile. Wenn Du irgendwo anders entscheidungen anhand des Zustand triffst, sind viele States unübersichtlicher. Wenn Du mit zyklischen Aufrufen (SPS-Loop) arbeitest, brauchst Du bei weniger States entweder Zusatzsstates (bool state_changed;) oder musst die Eingangsaktion als Ausgangsaktion des vorherigen States implementieren. Letzteres wird dann Problematisch, wenn von mehreren oder einem "weit entfernten" State hierhin gesprungen wird. Fazit: Programmiere erstmal so, wie es Dir sinnvoll erscheint und sammle damit Erfahrungen. Du machst das schon sehr gut. Die Unterschiede ergeben sich (wie schon vor mir erwähnt) aus dem Umfeld (Programmiersprache, SPS-Loop oder Task, Übergeordnete Prozesse wie Not-Aus oder Abbruch, die vorher oder woanders zentral behandelt werden, ....) Auf meiner Arbeit verwenden wir übrigens zwar getrennte States für Einsprung und Dauer, springen dafür aber (falls zeitlich sinnvoll) per "fallthrough" in den nächsten State. Und ja, am Ende haben wir auch sinnvoll benamte enums.
Arduino Fanboy D. schrieb: > Provokation: > Ich behaupte, dass nahezu jedes funktionierende µC Programm eine > StateMachine ist. Und das hat nichts damit zu tun, ob der Programmierer > das Wort je gehört hat, oder beabsichtigt hat einen solchen Automaten zu > bauen. Jau. Zumindest Teile vom Programm. Die state machine hat in erster Linie Zustände zu erfassen und darauf zu reagieren. Streng genommen gehört nicht mal die Fehlerbehandlung vorrangig dazu. Natürlich macht man das, weil man weiß, dass ein Taster klemmen kann oder ein Sensor defekt sein kann. Und um die ursprünglich Frage zu beantworten. So viele states wie nötig, so wenig wie möglich. Das macht die Fehlerbehandlung einfacher.
:
Bearbeitet durch User
Spätestens wenn man Multi-Tasking benötigt, werden Zustandsautomaten plötzlich sehr hilfreich. > Der "State" sollte eine Zahl sein, denn die kann man dann per UART > debuggen, dh sehen, in welchem Zustand die Zustandsmaschine steckt. Enums SIND Zahlen (im Maschinencode) und lassen sich daher problemlos auf einem Debugging Terminal ausgeben. Wenn du möchtest, kannst du jedem enum Wert eine konkrete Zahl zuweisen, ansonsten nummeriert der Compiler sie selbst durch. Egal ob enums oder integer: Irgendwo musst du Dir eine Tabelle hinlegen, die das ganze in menschen-lesbare Form übersetzt. Wenn der µC genug Speicher hat, kann er das gleich selbst erledigen. > Wieso sollter man per UART debuggen? "Früher" ging es nicht anders, in dieser Zeit hatte ich das Programmieren gelernt. Heute scheitert das Debuggen in der IDE oft daran, dass man das Programm nicht anhalten kann/darf oder dass man aus Sicherheitsgründen keinen Debugger an die produktive Maschine anschließen darf. Das gilt insbesondere für meinen Job (Java Backend Entwicklung im E-Commerce Umfeld). Für mich sind Logfiles daher immer der erste Ansatz, Debugger kommen nur auf dem Schreibtisch während der Entwicklung zum Einsatz. Ich habe zu 99% sogar überhaupt keinen Zugang zu den produktiven Maschinen. Ich muss fast jedes Problem ausschließlich anhand von Logfiles erkennen und dann eine Simulationsmethode erarbeiten, die das Problem reproduzierbar macht. Erst dann kommt die Entwicklungsumgebung (eventuell mit Debugger) zum Einsatz. Wenn ich beruflich µC programmieren würde, wäre die Situation häufig nicht anders - denke ich jedenfalls.
Proletus protectus Troll schrieb: > Der "State" sollte eine Zahl sein Der State kann auch ein Funktionszeiger sein, auch auf diese Weise kann man das was das äußere switch() macht implementieren, jeder State wäre dann eine separate Funktion.
Weshalb man mit UART Debuggen soll ? Weil bei den ueblichen Prozessen der Kontext kaputt ist wenn man per Breakpoint anhaelt. Bedeutet der Prozess laeuft, das Programm laeuft, man hat aber keine Ahnung weshalb es nicht geht. Dann benoetigt man das Wissen ueber den Zustand des Programmes, den Zustand der Zustandsmaschine. Logfile .. ein UART ist von der Komplexitaet viel einfacher wie ein Logfile.
Proletus protectus Troll schrieb: > Logfile .. ein UART ist von der Komplexitaet > viel einfacher wie ein Logfile. Ein UART ist ein Logfile. Nur, dass die Daten nicht in eine Datei geschrieben werden, sondern auf ein Kabel.
> Der State kann auch ein Funktionszeiger sein, auch auf diese Weise kann > man das was das äußere switch() macht implementieren, jeder State > wäre dann eine separate Funktion. Interessanter Ansatz, darüber muss ich mal nachdenken, bzw. es ausprobieren.
> Logfile .. ein UART ist von der Komplexitaet viel > einfacher wie ein Logfile. Das ist für mich prinzipiell das Selbe. Ob der Computer sein Protokoll auf einem Speichermedium, Netzlaufwerk oder in serieller Form bereit Stellt, spielt nur eine untergeordnete Rolle. Hauptsache man kann es irgendwie irgendwo abspeichern und untersuchen.
Stefanus F. schrieb: > Interessanter Ansatz, darüber muss ich mal nachdenken, bzw. es > ausprobieren. Aus meiner bescheidenen Sicht gibt es mehrere gesunde Möglichkeiten. --- Switch/case - Enum Einfach und übersichtlich. Kann aber recht unangenehm werden, wenn der Compiler keine Sprungleisten erstellen kann. z.B. bei lückenhafter Abfolge, oder sehr vielen Fällen. Dann wird intern sowas wie eine if Kaskade erstellt. If Kaskade Naja, nicht sehr Wartungsfreundlich. Die vorderen Fälle werden schnell erreicht, für die hinteren sind N Vergleiche nötig. Funktionspointer Recht fix, aber Speicher hungrig, wenn Pointer Arrays aufgebaut werden. Gut wartbar, auch bei bei großen Statemachines Computed Goto (in der eingeschränkten Version, ohne Distanzberechnungen) In etwa wie Funktionspointer, nur dass man auf den Funktionsoverhead verzichten kann OOP, Pointer auf State Objekte (C++) Wohl etwas hungriger.. Hat aber den Vorteil, den globalen Namensraum nicht so zudonnern zu müssen. Es lassen sich für jeden State Einstiegs und Ausstiegs Methoden schreiben und natürlich automatisch aufrufen. Schön bei sehr großen und komplexen Statemachines --- Welche Methode jetzt gerade die beste, oder schönste, ist sollte man aus meiner bescheidenen Sicht dem konkreten Problem überlassen. Eine Geschmacksfrage.... Und damit reichlich Potential für Grabenkriege.
Arduino Fanboy D. schrieb: > Funktionspointer > Recht fix, aber Speicher hungrig. Warum soll das speicherhungrig sein? Die Variable die sich den State merkt hat dann halt die Breite eines ganzen Maschinenwortes statt nur ein Byte und der Stack wird eine Ebene tiefer. Wir sprechen also über Speichermehrverbrauch den man an ein oder zwei Händen abzählen kann, ist es das was Du meinst?
:
Bearbeitet durch User
Bernd K. schrieb: > ist es das was Du meinst? Hatte ich noch editiert.... Weil nicht exakt genug formuliert. Ich meinte eigentlich die Schrittkette, als Sonderform des endlichen Automaten. Sie bedarf eines Arrays, oder einer Liste von Pointern. Außerdem sind die Beurteilungen, die ich da verwendet habe "weich". Eher von der konkreten Anwendung abhängig, als dass sie Muss Bedingungen sind.
Stefanus F. schrieb: > Egal ob enums oder integer: Irgendwo musst du Dir eine Tabelle hinlegen, > die das ganze in menschen-lesbare Form übersetzt. Wenn der µC genug > Speicher hat, kann er das gleich selbst erledigen. Wenn's in der Anwendung komplizierter wird: - Stati, die den Anfang eines Zweiges oder einen wiederverwertbaren Einstiegspunkt bilden, benenne ich über nen Enum. - Stati, die nur durch den Anspruch der Asynchronität entstehen, benenne ich üblicherweise nicht. Ein Enum mit 50 Elementen macht weder beim Lesen, noch beim Schreiben besonders Spaß, insbesondere, wenn die Bezeichner immer diffiziler, sperriger und dadurch länger werden. Beispiel: Beim asynchronen Lesen eines AD-Wertes aus der StateMachine könnte man sich im Enum ein StartGetADVal und ein PollGetADVal vorstellen? Ich würde ein GetADVal präferieren und im switch tatsächlich auch ein "case GetADVal+1:" benutzen. Man kann nichts pollen, wo nichts gestartet ist. Insofern sollte man sich auch die Benennung sparen. (Ansonsten pollt der Kollege und wundert sich.) Das setzt halt den Gebrauch von Nummernzirkeln im Enum voraus. In der state-Variable ist das sowieso üblich. Interessant dabei:
1 | typedef enum |
2 | {
|
3 | Start = 0, |
4 | Something = 10, |
5 | SomethingElse = 20 |
6 | } State_t |
Nach dem Beispiel des TO entspricht dies eher der 1. Zeichnung (man muss es nur asynchron ausführen). Die zeigt zwar nicht den typischen Anspruch der Asynchronität, komprimiert aber den Ablauf auf's Wesentliche. Man sieht, was untrennbar zusammengehört und was für ein Reusing vorbereitet werden muss. Da kann man keinen Pfeil zu "Warte3Min" ziehen.
Horst S. schrieb: > Interessant dabei:typedef enum > { > Start = 0, > Something = 10, > SomethingElse = 20 > } State_t Mit solchen Lücken, macht man sich fix die Switch/Case Optimierungen kaputt. Es degeneriert zu einer if Kaskade.
Horst S. schrieb: > Beispiel: Beim asynchronen Lesen eines AD-Wertes aus der StateMachine > könnte man sich im Enum ein StartGetADVal und ein PollGetADVal > vorstellen? "StartGetADVal" dauert keine messbare Zeit, sollte also kein eigener Zustand sein. Nur "PollGetADVal" (bzw. "WaitForADVal") dauert Zeit. > Ich würde ein GetADVal präferieren und im switch tatsächlich > auch ein "case GetADVal+1:" benutzen. Das ist extrem schlecht. Du bist damit nicht nur inkonsistent, sondern stellst späteren Entwicklungen zusätzlich auch noch eine Falle.
@ Arduino Fanboy D. (ufuf) >Mit solchen Lücken, macht man sich fix die Switch/Case Optimierungen >kaputt. Es degeneriert zu einer if Kaskade. Mach dir mal nicht zuviele Gedanken über derartige Details. Ersten haben die allerwenigsten State machines ultimative Geschwindigkeitsanforderungen, da spielen ein paar Takte mehr oder weniger keine Rolle. Und zweitens sind altuelle Compiler, auch der avr gcc unter der Arduino-Haube schlau genug, um das gescheit zu optimieren. Manuelle Codierung von States hat selten Sinn, nimm ein Enum und gut, der Compiler macht da schon was gescheites draus.
Das Bild im Startposting zeigt für mich keinen Zustandasautomaten, sondern ein Flussdiagramm. Wozu brauche ich da Zustände? Das ist einfach nur ein Ablauf, der da beschrieben wird. Falk B. schrieb: > @Eric B. (beric) > >>> Mit welcher Frequenz wird die FSM denn getaktet? > >>Nicht. Eine SM taktet man nicht, die reagiert auf Events/Ereignisse. > > Stimmt so allgmein auch nicht. Nicht alles läuft unter QT und ähnlichen > Frameworks. Was hat denn das mit Qt zu tun? > Wir wollen doch mal eher bei den EINFACHEN, Grundlegenden > Konzepten anfangen und die Neulinge nicht mit den allerneusten > RTOS-Geschichten überfordern. Das ist das EINFACHE Konzept: Es gibt Zustände, es gibt Ereignisse, die Zustandsübergänge auslösen, und es gibt Aktionen, die bei diesen Zustandsübergängen ausgeführt werden. Irgendeine Taktung ist zunächst mal nicht Teil einer Statemachine. > Und da wird eine FSM getaktet, indem sie periodisch aufgerufen wird. Man macht vielleicht ein Polling, um zu schauen, ob eines der Ereignisse eingetreten ist, aber das ist nicht Teil der Statemachine. Die kommt erst dran, wenn tatsächlich irgendwas passiert ist. Arne schrieb: > PS: Zu der Frage, ob Messen ein eigener Zustand ist: > Wenn die Messung viel Zeit benötigt, dann ggf eigener Zustand. Ob nun viel Zeit oder nicht, ist eigentlich egal. Es gibt, wie schon festgestellt, drei Dinge: Ereignisse, Zustände und Aktionen. Ein Zustand ist dabei etwas, wohin die Statemachine wechseln kann, worin sie verweilen kann und aus dem sie wieder austreten kann. Wenn das "etwas", von dem wir sprechen, das kann, ist es ein Zustand. Ob die nun 3 Taktzyklen oder 3 Minuten darin verweilt, spielt keine Rolle. Wie schon mehrfach erwähnt wurde: Eine Statemachine kennt keine Zeit und damit natürlich auch keine Taktung.
Arduino Fanboy D. schrieb: > Mit solchen Lücken, macht man sich fix die Switch/Case Optimierungen > kaputt. Es degeneriert zu einer if Kaskade. Zeig mir doch mal bitte 'nen Beispiel dafür.
Rolf M. schrieb: > Wozu brauche ich da Zustände? Das ist einfach > nur ein Ablauf, der da beschrieben wird. Man benutzt Automaten, um Abläufe zu beschreiben... Rolf M. schrieb: >> Wenn die Messung viel Zeit benötigt, dann ggf eigener Zustand. > Ob nun viel Zeit oder nicht, ist eigentlich egal. [...] Ob die nun 3 > Taktzyklen oder 3 Minuten darin verweilt, spielt keine Rolle. Ein eigener Zustand gehört da sinnvollerweise nur hin, wenn die Messung messbar viel Zeit benötigt. Wie lange das ist, hängt von der Taktung des Automaten ab (so er denn getaktet ist). Beispiel: Wenn ich zwischen "Command-Byte in Register schreiben" und "Statusregister meldet Busy" noch messbar Zeit vergeht, weil die Hardware zu langsam reagiert, dann muss ich da einen zusätzlichen Zustand einfügen, um Race Conditions zu vermeiden.
Horst S. schrieb: > Zeig mir doch mal bitte 'nen Beispiel dafür. Schwierig... Da doch sehr Prozessor/Compiler und Optimierungsstufen abhängig. Der AVR-GCC kennt da den Schalter -case-values-threshold > The smallest number of different values for which > it is best to use a jump-table instead of a tree of > conditional branches. http://ww1.microchip.com/downloads/en/AppNotes/doc8453.pdf Sagt: > for a “switchcase”, the compiler usually > generates lookup tables with index and > jump to the correct place directly. Gewöhnlich macht er das, wenn man ihm die Chance gibt. Aber wenn man ihm genügend Stöcke zwischen die Beine wirft, kollidiert das mit z.B. -Os. Diese Lücken sind halt solche Stöcke. Und dann wundert man sich, wenn sich bei einer eher marginalen Code Änderung, das Laufzeitverhalten doch recht massiv ändert. Gerade in einer ISR, will man das nicht unbedingt.
S. R. schrieb: > Rolf M. schrieb: >> Wozu brauche ich da Zustände? Das ist einfach >> nur ein Ablauf, der da beschrieben wird. > > Man benutzt Automaten, um Abläufe zu beschreiben... Ein Zustandsautomat ist etwas mehr als nur ein Ablauf. Was ich meine ist, dass man hier keinen Automat braucht. Man kann diese Aktionen auch einfach eine nach der anderen so als Code hinschreiben wie in dem Bildchen. Ich muss mir gar keinen Zustand merken. Das ergibt sich von selbst aus dem sequentiellen Ablauf des Programms. > Rolf M. schrieb: >>> Wenn die Messung viel Zeit benötigt, dann ggf eigener Zustand. >> Ob nun viel Zeit oder nicht, ist eigentlich egal. [...] Ob die nun 3 >> Taktzyklen oder 3 Minuten darin verweilt, spielt keine Rolle. > > Ein eigener Zustand gehört da sinnvollerweise nur hin, wenn die Messung > messbar viel Zeit benötigt. Wie lange das ist, hängt von der Taktung > des Automaten ab (so er denn getaktet ist). Wie ich schrieb: Wenn es etwas ist, in das er erst eintreten, dann darin verweilen, dann wieder austreten muss, dann ist es ein Zustand. Vielleicht ist es das, was du mit "messbar viel Zeit" meinst. > Beispiel: Wenn ich zwischen "Command-Byte in Register schreiben" und > "Statusregister meldet Busy" noch messbar Zeit vergeht, weil die > Hardware zu langsam reagiert, dann muss ich da einen zusätzlichen > Zustand einfügen, um Race Conditions zu vermeiden. "Command-Byte in Register schreiben" ist eine Aktion, "Statusregister meldet Busy" ist ein Ereignis. Dazwischen ist natürlich ein Zustand. Ich denke aber, ich weiß, was du meinst. Dabei geht es aber nicht darum, zu entscheiden, um was für ein Element eines Zustandsautomaten es sich handelt, sondern ob es überhaupt Teil des Zustandsautomaten ist.
@ Rolf M. (rmagnus) >Ein Zustandsautomat ist etwas mehr als nur ein Ablauf. Was ich meine >ist, dass man hier keinen Automat braucht. Man kann diese Aktionen auch >einfach eine nach der anderen so als Code hinschreiben Wer lesen kann ist klar im Vorteil. "Um meine Frage zu veranschaulichen habe ich ein rein fiktives Beispiel im Bild (Dateianhang) skizziert:"
Rolf M. schrieb: > Ich muss mir gar keinen Zustand merken. Das ergibt sich von > selbst aus dem sequentiellen Ablauf des Programms. Ich unterscheide zwischen einem explizitem Zustand und einem impliziten. Explizite sind klar benannt. Egal, ob das jetzt Funktionsbezeicher, Enums oder Goto Lables sind. z.B. ein Ringbuffer Da ergibt sich der Zustand durch die beiden Zeiger (Leer, hat Inhalt, Voll)
Falk B. schrieb: > Wer lesen kann ist klar im Vorteil. Lesen alleine reicht nicht immer… > "Um meine Frage zu veranschaulichen habe ich ein rein fiktives Beispiel > im Bild (Dateianhang) skizziert:" Etwas, wofür man gar keine Statemachine braucht, ist aber ein eher weniger gutes Beispiel, um anhand diesem zu verstehen, wie man eine Statemachine am besten aufbaut.
Rolf M. schrieb: > Man kann diese Aktionen auch > einfach eine nach der anderen so als Code hinschreiben wie in dem > Bildchen. Über welches Bildchen sprechen wir jetzt? In dem Bild in Post 1 kommt unverkennbar eine Wartezeit von 3 Minuten vor. Wenn man Threads haben kann, ja dann implementiert man das locker flockig als separaten Thread der in den 3 Minuten schläft mit sleep(), das mach ich auch so weils einfacher hinzuschreiben und zu lesen ist, weil ein Thread letztendlich auch nur eine schöne Abstraktion für eine Statemachine ist. Zum Beispiel ein Thread der 99.999% der Zeit blockierend an einem Netzwerksocket wartet und nichts tut, da kollabiert die Statemachine zur einfachen while-Schleife und die ganze Magie und der State ist im Scheduler versteckt und schön wegabstrahiert. Aber wenn man keine Threads haben kann weil man keinen Scheduler hat oder weil man 100000 offene Netzwerkverbindungen haben könnte und 100000 schlafende Threads selbst auf großen Maschinen der helle Wahnsinn wären dann muss man sich was anderes überlegen, dann macht man es event-gesteuert mit einem einzigen Thread (oder so vielen wie man Kerne hat) und dann braucht man selbst für die simpelsten Abläufe eine Statemachine sobald der Ablauf auf irgendwas warten muss (zum Beispiel auf das nächste Byte einer Nachricht oder auf ein ACK oder auf das Ausbleiben desselben). In manchen Sprachen hat man Sprachkonstrukte oder Frameworks für solche Probleme wie Coroutinen oder Greenlets oder Futures oder Callbacks, etc. und unter der Haube eine Runtime mit Eventloop und Scheduler aber hier haben wir es mit nacktem C zu tun! Im Falle eines kleinen AVRs kann man keine Threads haben, sobald einem da die Interrupts ausgehen oder man noch einen zweiten Ablauf braucht weil irgendwo noch manchmal eine LED mit 2Hz blinken soll während der main Thread mit Warten blockiert ist oder ein paar Tasten abgefragt werden sollen kommt man um Statemachines nicht mehr herum. Wenn Du jetzt sagst "dann mach das Blinken und das Tastenabfragen halt in einem Timer-Interrupt" dann sag ich: Gratuliere! Jetzt hast Du doch zwei Statemachines gebaut, eine fürs Blinken und eine fürs Tastenabfragen weil Du nur 2 Threads hast (main und timer-IRQ) aber Du hast 3 Abläufe (der Ablauf aus dem Bild, das Blinken und das Tasten abfragen)". Um das explizite Hinschreiben der Statemachine zu vermeiden bräuchtest mindestens 2 Timer und main und irgendwann gehen Dir die Interrupts aus oder Du fängst an einen Scheduler für Threads zu implementieren (halte ich für unwahrscheinlich) oder Du baust Statemachines für die ganze asynchrone nebenläufige Warterei überall (der übliche Ansatz der in allen Tutorials ab dem zweiten Kapitel gelehrt wird).
:
Bearbeitet durch User
georg schrieb: > Grundsätzlich werden Änderungen bei einer Statemachine nicht in einem > Zustand durchgeführt, sondern beim Übergang von einem Zustand zum > anderen. Mit Verlaub. So ein Quatsch. Das kann man so machen, muss es aber nicht. In der Digitaltechnik werden die Ausgänge einer FSM einzig aus dem aktuellen Zustand gebildet. Es gibt das Konzept der Aktion am Übergang nicht so Recht. Dementsprechend ist auch deine Verallgemeinerung nicht sinnvoll. Weiter gibt es oft kein schwarz und weiß bei der Anzahl der states. Blödes Beispiel: man kann einen Zähler wunderbar als FSM darstellen, aber bei mehr als einigen wenigen Bits ist es sinnbefreit, weil unglaublich aufwändig. Meiner Erfahrung nach bleibt es besser nachvollziehbar, wenn man die FSM auf die Beschreibung der Zustände und deren Übergangsbedingungen beschränkt. Mögliche Darstellungen von timern, countern, komplexer kombinatorischer Übergangsbedingungen macht man mmn besser außerhalb.
Karl schrieb: > In der Digitaltechnik werden die Ausgänge einer FSM einzig aus dem > aktuellen Zustand gebildet Es gibt das Konzept der Aktion am Übergang > nicht so Recht. Dann hast Du an der Stelle zwei Zustände: "Eingeschaltet" und "Ausgeschaltet". Aber immer noch keinen Zustand "Einschalten". Und genau darum ging es eigentlich, nicht um das Implementierungsdetail wie diese Änderung des Zustands ihren Weg zur Änderung des Ausgangspins findet.
:
Bearbeitet durch User
Karl schrieb: > Mit Verlaub. So ein Quatsch. Das kann man so machen, muss es aber nicht. > In der Digitaltechnik werden die Ausgänge einer FSM einzig aus dem > aktuellen Zustand gebildet. Es gibt das Konzept der Aktion am Übergang > nicht so Recht. Dementsprechend ist auch deine Verallgemeinerung nicht > sinnvoll. Wieso Quatsch ? Du sagst doch selber: > aus dem aktuellen Zustand gebildet. Beispiel: Ein Transistor als Schalter ist entweder ON oder OFF, Übergang selbst wird von der SM nicht registriert, deswegen aktueller Zustand. Und selbst wenn man auf einen Sinus reagiert, irgendwo muss es eine Triggerschwelle geben, ab da ist es auch entweder ON oder OFF.
Karl schrieb: > In der Digitaltechnik werden die Ausgänge einer FSM einzig aus dem > aktuellen Zustand gebildet Es gibt das Konzept der Aktion am Übergang > nicht so Recht. Ob da bei jedem Übergang ein Fetzen Code ausgeführt werden muss weil man die Information des neuen Zustands nicht anders zu dem Ausgangspins hin transportiert bekommt oder ob alles direkt verdrahtet ist ist ein Implementierungsdetail. Wir gehen davon aus daß der TE es in Software implementieren will. Deshalb sieht das Standardmodell einer Statemachine die Übergänge vor wo man bei Bedarf solche Codefetzen einhängen kann (aber nicht muß) die den neuen Zustand bei Bedarf irgendwie an die Außenwelt tragen und/oder dort was auslösen.
:
Bearbeitet durch User
Bernd K. schrieb: > Deshalb sieht das Standardmodell einer Statemachine > die Übergänge vor wo man bei Bedarf solche Codefetzen > einhängen kann (aber nicht muß) die den neuen Zustand > bei Bedarf irgendwie an die Außenwelt tragen und/oder > dort was auslösen. Auch wenn das jetzt "voll Achziger" ist: - "Statemachines" sind einfach "endliche Automaten". - Die Automatentheorie kennt zwei Grundformen endlicher Automaten, nämlich den Moore-Automaten, bei dem die Ausgangssignale NUR aus den Zuständen gebildet werden, und den Mealy-Automaten, der die Ausgangssignale aus (altem) Zustand und Eingangssignal bildet. Alter Zustand und (aktuelles) Eingangssignal bilden gerade eine Kante ("Transition"). Es gibt also beide Varianten.
Karl schrieb: > In der Digitaltechnik werden die Ausgänge einer FSM einzig aus dem > aktuellen Zustand gebildet. Es gibt das Konzept der Aktion am Übergang > nicht so Recht. Es gibt den Unterschied zwischen Moore-Automaten (die Ausgänge werden aus dem aktuellen Zustand gebildet) und Mealy-Automaten (die Ausgänge werden aus dem aktuellen Zustand und den Eingängen gebildet). Ein Moore-Automat benötigt immer zusätzliche Zustände, weil du immer eine Zustandskette "ausgeschaltet - einschaltend - eingeschaltet" bauen musst:
1 | switch(moore_state) { |
2 | case AUSGESCHALTET: |
3 | if(PINB & TASTER /* Einschaltbedingung */) { |
4 | state = EINSCHALTEND; |
5 | }
|
6 | break; |
7 | |
8 | case EINSCHALTEND: |
9 | PORTD |= SIGNAL; /* Einschaltaktion */ |
10 | state = EINGESCHALTET; |
11 | break; |
12 | |
13 | case EINGESCHALTET: |
14 | /* fertig */
|
15 | break; |
16 | }
|
Mit einem Mealy-Automaten ziehst du die Zustandsänderung (sofern sie nicht messbar Zeit benötigt) in den Zustand mit rein und sparst dir so Zustände:
1 | switch(mealy_state) { |
2 | case AUSGESCHALTET: |
3 | if(PINB & TASTER /* Einschaltbedingung */) { |
4 | state = EINGESCHALTET; |
5 | PORTD |= SIGNAL; |
6 | }
|
7 | break; |
8 | |
9 | case EINGESCHALTET: |
10 | /* fertig */
|
11 | break; |
12 | }
|
Man kann beide Ansätze fahren, aber ich finde Mealy meistens übersichtlicher, weil er unnötige kurze Zwischenzustände einspart. Außerdem ist er einen FSM-Takt schneller. In einem FPGA lässt sich ein Moore-Automat manchmal effizienter implementieren, weil man die gesamte Logik in Tabellenform codieren kann, aber in einem Controller spielt das keine Rolle (man schreibt nicht alle Register in jedem Zustand).
Arduino Fanboy D. schrieb: > Ich meinte eigentlich die Schrittkette, als Sonderform des endlichen > Automaten. Sie bedarf eines Arrays, oder einer Liste von Pointern. Nein. Kein Array, keine Liste. Der State IST ein Funktionspointer, und jede Funktion manipuliert ihn ggf. mit der Adresse des nachsten States. So wie das enun auch nirgendwo explizit im Speicher liegt, aber jeder Wert mal irgendwo zugewiesen wird. Bei Steuerungen meist nicht sinnvoll, aber häufig bei Menüs, Protokollen oder Interrupts.
Achim S. schrieb: > Nein. Mir scheint, hast du das Prinzip eines endlichen Automaten verstanden. Aber der Funktionsbaustein Schrittkette ist dir offensichtlich noch nicht unter gekommen.
Arduino Fanboy D. schrieb: > Aber der Funktionsbaustein Schrittkette ist dir offensichtlich noch > nicht unter gekommen. Doch, aber das ist mit enums genauso, während dann die statemachine mit FP bei deiner Aufzählung fehlt.
Arduino Fanboy D. schrieb: > OOP, Pointer auf State Objekte (C++) > Wohl etwas hungriger.. > Hat aber den Vorteil, den globalen Namensraum nicht so zudonnern zu > müssen. Es lassen sich für jeden State Einstiegs und Ausstiegs Methoden > schreiben und natürlich automatisch aufrufen. > Schön bei sehr großen und komplexen Statemachines Die moderne Variante davon ist ein Summentype der alle States abbildet.
1 | using sm = variant<State0, State1, State2, ...>; |
Achim S. schrieb: > Doch, aber das ist mit enums genauso, Nöö.. schritt++ > error: no match for 'operator++' Achim S. schrieb: > während dann die statemachine mit > FP bei deiner Aufzählung fehlt. Funktionspointer habe ich doch extra mit aufgeführt... Warum siehst du das nicht?
Arduino Fanboy D. schrieb: >> Doch, aber das ist mit enums genauso, > Nöö.. > schritt++ >> error: no match for 'operator++' Hä? verstehe ich nicht. Arduino Fanboy D. schrieb: > Funktionspointer habe ich doch extra mit aufgeführt... OK, dann meintest Du hier also beides, wobei sich Deine Ausführungen zum Speicher nur auf die Schrittkette bezogen. Arduino Fanboy D. schrieb: > Funktionspointer > Recht fix, aber Speicher hungrig, wenn Pointer Arrays aufgebaut werden. > Gut wartbar, auch bei bei großen Statemachines Dann ist ja gut, dass wir das herausgearbeitet haben, zumal die echte Statemachine mit FP anscheinend selbst alte Hasen hier nicht kennen. Also nichts für ungut.
Achim S. schrieb: > Hä? verstehe ich nicht. Eine Schrittkette, mit in enums definierten Schritten, macht meines Erachtens nach keinen Sinn. Denn dann verliert man einen der größten Vorteile einer solchen Schrittkette. Und man muss die Schrittbezeichner doppelt mit führen. Eigentlich muss kein Schritt, was über seine Nachfolger wissen. Man sagt dem KettenModul einfach next(), oder was ähnliches, und es geht einen Schritt weiter. Das geht nicht, wenn die Schritte in enums definiert werden. Der Vorteil, wenn ein Schritt nichts über seinen Nachfolger weiß, ist, dass man Schritte in einer Kette mehrfach verwenden kann. Und bei Umsortierungen/Anpassungen nicht unbedingt in den Schritten selber rum fummeln muss. Heizen Pause Heizen Pause Kühlen Pause Abfüllen Achim S. schrieb: > OK, dann meintest Du hier also beides, wobei sich Deine Ausführungen zum > Speicher nur auf die Schrittkette bezogen. Ja, so ist es.
Arduino Fanboy D. schrieb: > Man sagt dem KettenModul einfach next(), oder was ähnliches, und es geht > einen Schritt weiter. > Das geht nicht, wenn die Schritte in enums definiert werden. Nein, es ist identisch, ob eine Schrittkette enums oder FP enthält. Vielleicht meinst du verkettete Listen, die gehen bei beiden nicht bzw wären Unsinn.
Achim S. schrieb: > ann ist ja gut, dass wir das herausgearbeitet haben, zumal die echte > Statemachine mit FP anscheinend selbst alte Hasen hier nicht kennen. Die sind etwas verpönt, weil aus ner ISR heraus pro forma alle Register auf'm Stack gesichert werden. Und letztendlich wirst Du auch mit Funktionspointern zur Diagnose nach außen wieder eine (numerische) Abstraktion einfügen, weil die Adressen selbst recht dynamisch und auch mies zu lesen sind. Das hat seinen Reiz, aber nicht nur Vorteile.
Horst S. schrieb: > Die sind etwas verpönt, weil aus ner ISR heraus pro forma alle Register > auf'm Stack gesichert werden. > Und letztendlich wirst Du auch mit Funktionspointern zur Diagnose nach > außen wieder eine (numerische) Abstraktion einfügen, weil die Adressen > selbst recht dynamisch und auch mies zu lesen sind. > > Das hat seinen Reiz, aber nicht nur Vorteile. Völlig d'accor. In ISRs nur, wenn "sowieso" Funktionsaufrufe erfolgen würden und mies zu lesen sehe ich auch so, daher Achim S. schrieb: > Bei Steuerungen meist nicht sinnvoll Bei Menüs entfällt dieser Nachteil offensichtlich, im Gegenteil ;-) Bei Steuerungsaufgaben kommt hinzu, dass in den meisten Fällen ein funktionslokaler Kontext vorhanden ist (Zustand von Sensoren/Notaus/Timer, Plausibiltäten, Redundanzprüfungen, ...) der entweder künstlich in den State-Kontext mit eingearbeitet oder in jeder Funktion parallel ausgewertet werden muss. Beispiel: bool Cancel=Cancel_A() || Cancel_B();
F. F. schrieb: > NichtWichtig schrieb: >> Zustand EINSCHALTEN > > Irgendwie hast du das Thema nicht verstanden. Du hast es nicht kapiert. Denk nochmal drüber nach, vielleicht verstehst Du es dann ja auch.
Achim S. schrieb: > Nein, es ist identisch, ob eine Schrittkette enums oder FP enthält. Erstens: Mit Enum Values in einem Array benötigt man eine weitere Auflösungsstufe um das, um es ausführen zu können. Zweitens: Hat man für jeden Schritt 2 Bezeichner. Einmal im Enum, und einmal den Funktions/Klassen Bezeichner. Doppelt ist immer böse. Drittens: Bei Verwendung eines Enum muss jeder Schritt um seinen Nachfolger wissen. Viertens: Codeduplikate sind böse/fehlerträchtig, wartungsunfreundlich. Einen dieser Punkte kannst du evtl. eliminieren. Man kann sich, in Grenzen, sogar aussuchen welchen.... Aber nicht alle. -------- Achim S. schrieb: > Nein, es ist identisch, ob eine Schrittkette enums oder FP enthält. So langsam hätte ich gerne mal gesehen, was du meinst... Ein einfaches "Nein" reicht mir nicht, um zu verstehen, was du meinst.
Arduino Fanboy D. schrieb: > So langsam hätte ich gerne mal gesehen, was du meinst...
1 | enum States={ S1, S2, .... Sn, Sende}; |
2 | |
3 | enum States Schritte[]={S1, S3, S1, S2, S1, ....}; |
Ist gleichwertig zur Implementierung mit FP. Und eine declaration braucht die FP-Version meist auch. Wo siehst Du denn Unterschiede und wieso?
Achim S. schrieb: > Ist gleichwertig zur Implementierung mit FP. Bei Funktionspointern ist klar, wie man sie aufrufen muss, wenn sie in einem Array stecken und man darüber weg iteriert. Bei deinem Beispiel sehe ich das nicht. Das (für mich) interessanteste hast du also weg gelassen...
Arduino Fanboy D. schrieb: > Bei Funktionspointern ist klar, wie man sie aufrufen muss, wenn sie in > einem Array stecken und man darüber weg iteriert. Es gibt verschiedene Arten des Aufrufs. Und zu jeder gibt es ein enum-analogon. Z.b: mit i als Laufindex übers Array: FPliste[i](...); Statemachine(EnumListe[i]);
Achim S. schrieb: > FPliste[i](...); Das ist klar! (glaube ich) Achim S. schrieb: > Statemachine(EnumListe[i]); Aber hier fehlt der letzte Schliff... Da sehe ich nicht wie es zur Ausführung des konkreten Codes kommt. Die Enums sind ja nur benannte Zahlen. Keine Pointer, oder so.. Da fehlt mir noch eine Stufe der "Umschlüsselung". Oder: Willst du das dann in ein Switch/Case Konstrukt einbetten? Dann hast du trotz des Arrays evtl. alle Geschwindigkeitsvorteile verschenkt. Und dazu noch den ganzen Code (für alle Stati) in einer großen Wurst. Wie du siehst, ich sehe es (noch) nicht, was du meinst!
Arduino Fanboy D. schrieb: > Willst du das dann in ein Switch/Case Konstrukt einbetten? > > Dann hast du trotz des Arrays evtl. alle Geschwindigkeitsvorteile > verschenkt. > Und dazu noch den ganzen Code (für alle Stati) in einer großen Wurst. Natürlich mit enum im Switch Case. Und natürlich hat beides seine Vor und Nachteile, das wurde doch schon alles aufgeführt. Ein Switch hat z.b.den Vorteil, einzelne individuelle Dinge rauszuziehen und dass es nicht in dutzende Minifunktionen zerfällt.
Achim S. schrieb: > Natürlich mit enum im Switch Case. Ah, ja... Und ich dachte schon, ich hätte da einen wesentlichen Trick übersehen. Danke, dafür, dass wir darüber geredet haben.
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.