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
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.
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
intx=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
staticintstate=0;
5
staticuint32_ttimerVal=0;
6
7
switch(state)
8
{
9
case0:
10
SetRelaisOn();
11
state++;
12
break;
13
14
case1:
15
timerVal++;
16
if(timerVal>=THREE_MIN)
17
{
18
timerVal=0;
19
state++;
20
}
21
break;
22
23
case2:
24
intx=GetBrightness();
25
if(x>=BRIGHTNESSLIMIT)
26
state=3;
27
else
28
state=4;
29
break;
30
31
case3:// Do something
32
...
33
state=0;
34
break;
35
36
case4:// 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.
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.
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.
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
staticuint32_ttimerVal=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
intx=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.
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.
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?
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
typedefenum
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).
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.
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.
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
caseAUSGESCHALTET:
3
if(PINB&TASTER/* Einschaltbedingung */){
4
state=EINSCHALTEND;
5
}
6
break;
7
8
caseEINSCHALTEND:
9
PORTD|=SIGNAL;/* Einschaltaktion */
10
state=EINGESCHALTET;
11
break;
12
13
caseEINGESCHALTET:
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
caseAUSGESCHALTET:
3
if(PINB&TASTER/* Einschaltbedingung */){
4
state=EINGESCHALTET;
5
PORTD|=SIGNAL;
6
}
7
break;
8
9
caseEINGESCHALTET:
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.
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.
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.