https://www.mikrocontroller.net/articles/Statemachine Ich arbeite schon seit ewigen Zeiten mit solchen Zustandsmaschinen. Es begann mit Apple II und C unter CPM (Softcard), dann PC mit DOS und heute mit diversen Mikrocontrollern in C++ (Arduino) und auch auf dem PC in der Idle-Schleife von Grafikprogrammen (Linux-GTK). Für meine Anwendungen musste ich dabei ein paar spezielle Randbedingungen berücksichtigen, damit meine Zustandsmaschinen (C++) auf Mikrocontrollern und auf dem PC einsetzbar sind. Vielleicht hilft es dem einen oder anderen, wenn er sich auch daran versuchen möchte: Einsatz von Timern Meine Zustandsmaschinen sind meistens Klassen (hin und wieder auch einfache Folgen von Funktionen). Die Klassen haben eine Funktion run(), die ich zyklisch aufrufe. Wobei ich feststellen musste, dass es beim Einsatz von Timern unterschiedliche Probleme gab. Timer sind oft direkte Interrupt-Handler, so dass die Zustandsmaschinen dann auch im Interrupt laufen. Hier müssen Prioritäten beachtet werden, damit die üblichen Schnittstellen mit ihren Interrupt-Routinen sicher funktionieren (ein Schlüsselerlebnis vor längerer Zeit: Bei einem Arduino-DUE mit Ethernet-Shield konnte das Netzwerk nicht aus der Timer-Routine bedient werden). Sicher kennen die meisten von euch das Problem mit Timern in einer Grafik-Umgebung. Direkte Zugriffe sind nicht möglich (Umweg: Run-on-UI-Thread) und geeignete Dispatcher-Timer wackeln wie die Lämmerschwänze. Software-Timer Es ist relativ einfach, sich einen Software-Timer zu basteln, indem vorhandene Zähler ausgewertet werden. Bei Arduino liefert die Funktion micros() die Zeit ab Start in Mikrosekunden, bei Linux reicht eine Zeitabfrage mit entsprechendem Parameter und bei Windows gibt es die Funktion QueryPerformanceCounter. Bei den PCs bekommt man die Zeit allerdings in Nanosekunden. Um gleiches Zeitverhalten beim PC und bei den Mikrocontrollern zu bekommen, implementiert man zunächst auch eine Funktion micros() auf dem PC, die auf Mikrosekunden umrechnet. Zeitdifferenzen berechnet man dann einfach über unsigned-Variablen, die auch bei Zählerüberläufen die korrekte Zeitdistanz liefern. Einbindung in loop() Die Funktion loop() bei Arduino ruft geradezu nach dem Einsatz von Zustandsmaschinen. Sie wird meist alle paar Mikrosekunden aufgerufen (unter 10us beim ESP32, bis zu 100us bei einigen anderen Modellen). Beim Einsatz mancher RTOS wird loop() allerdings eher stiefmütterlich bedient. Eine Frechheit, wenn dann noch die Funktion micros() aus einem Millisekundenzähler liest. Abhilfe bringt meist eine while(1)-Schleife (in loop(), echt doof so etwas) und ein direkter Zugriff auf die Zähler des Mikrocontrollers bzw. das Aufsetzen eines eigenen Mikrosekundenzählers. Beim PC ist die Basis sowieso eine while(run == true)-Schleife in main() oder die Idle-Schleife in der Grafik (da habe ich allerdings nur Erfahrung mit Linux-GTK). Multi-Tasking Wenn man mehrere Software-Timer mit angehängten Zustandsmaschinen implementiert, dann führt das zu einem kooperativen Multi-Task-System. Die Echtzeitfähigkeit bestimmt man selbst durch Vorgaben bei den Aufrufzyklen und für die maximale Dauer eines einzelnen Zustandes. Bei richtiger Dimensionierung ist ein solches Multi-Tasking jedem RTOS haushoch überlegen. Keine Verluste durch den Scheduler, keine Semaphorensteuerung erforderlich, etc. Ich setze bei einigen Arduinos mehrere Zustandsmaschinen gleichzeitig ein, mit Zykluszeiten von 50 Mikrosekunden bis zu mehreren Millisekunden. Es macht Spaß In dem oben genannten Artikel ist deutlich geworden, wie übersichtlich Zustandsmaschinen im Vergleich zu den meist üblichen Ablaufprozeduren sind. Da macht sogar die Dokumentation Freude. Und die Programme sind auch grundsätzlich einfacher und übersichtlicher, zeitliche Abläufe sind trivial und über Zählvariablen hoch genau steuerbar. Ich bin in der langen Zeit meiner Programmiertätigkeiten (C/C++, C#, PHP, JavaScript, ...) noch nicht in Versuchung gekommen, meine Zustandsmaschinen durch ein (noch so tolles) RTOS zu ersetzen. Bei sehr schnellen und umfangreichen Zustandsmaschinen ist es allerdings wichtig, die Zustände nicht über case xyz anzufahren, sondern über umgehängte Verzweigungsvektoren (in C und C++ ziemlich einfach, Stichwort CallBack).
Aber nur, wenn man die Hintergründe nicht durchschaut und Grundlagen ignoriert.
Eine schöne Liste :-) Ich kann das nur unterschreiben: FSMs kann man sehr sauber und übersichtlich aufbauen. Unserer Erfahrung nach gehören sie zu den am wenigsten fehleranfälligen Programmierprinzipien. Dazu kommt, dass man durch entsprechende Graphen-Programme die Programmierung in ein Grafikprogramm verlagern kann und zusätzlich direkt eine sehr gute Beschreibung/Kommentierung des Programms erhält. Man "sieht" quasi sofort, was das Programm macht. Tatsächlich waren hier die einzigen größeren Programmentwicklungen, die auf Anhieb(!) fehlerfrei liefen, FSM-Programme (bspw. eine Revolversteuerung einer CNC-Maschine). Nutzt Du auch grafische Editoren?
Robert schrieb: > Timer sind oft direkte Interrupt-Handler, so dass die Zustandsmaschinen > dann auch im Interrupt laufen. Hier müssen Prioritäten beachtet werden, Deshalb setzt man im Timer auch nur ein Flag, das dann in der zyklisch aufgerufenen FSM abgefragt wird. Oder man zählt einen Zähler hoch (dein Softwaretimer) und vergleicht den in der zyklisch aufgerufenen FSM mit einem anderen Zählerwert oder besser: mit einem Delta zu einem vorher gespeicherten Zählerwert. > Die Funktion loop() bei Arduino ruft geradezu nach dem Einsatz von > Zustandsmaschinen. Sie wird meist alle paar Mikrosekunden aufgerufen Diese Funktion wird sofort dann wieder aufgerufen, wenn sie fertig ist: - https://docs.arduino.cc/language-reference/de/struktur/sketch/loop/ Wenn da ein rechenzeitverschwendendes delay_ms(5000) drin ist, dann dauert diese loop() mindestens 5 Sekunden. Sie kann also gar nicht nach 10µs wieder "aufgerufen" werden. BTW: auch ich liebe FSM und jede SPS läuft mit solchen Automaten. Nur weiß das der Schrauber, der diese SPS mit einem KOP programmiert, gar nicht. Er tut es einfach.
:
Bearbeitet durch Moderator
Chris D. schrieb: > Nutzt Du auch grafische Editoren? Nein, da habe ich mich nicht weiter mit befasst. Ich hatte mich mal intensiv mit UML-Diagrammen und entsprechend abgeleiteten Klassen beschäftigt, aber das war dann doch nicht so produktiv. Da war ich beim direkten Programmieren doch deutlich schneller.
Lothar M. schrieb: > Diese Funktion wird sofort dann wieder aufgerufen, wenn sie fertig > ist: > - https://docs.arduino.cc/language-reference/de/struktur/sketch/loop/ ... es sei denn, serialEventRun ist gesetzt. Dann wird zwischendurch auch noch serialEventRun() aufgerufen, bevor loop() wieder dran ist. So steht es bei Arduino in main():
1 | for (;;) { |
2 | loop(); |
3 | if (serialEventRun) serialEventRun(); |
4 | }
|
:
Bearbeitet durch User
Lothar M. schrieb: > Diese Funktion wird sofort dann wieder aufgerufen, wenn sie fertig > ist: Nein, das ist nur eine Beschreibung des prinzipiellen Verhaltens. Die Details kann man leicht erkennen, wenn man sich mal in der Software zum Aufruf von loop() durchhangelt. Zum Beispiel war es bei den ersten Versionen des Arduino DUE so, dass der UART-Transmit noch nicht über Interrupt abgewickelt wurde, sondern zu Fuß vor dem Aufruf von loop() bedient wurde. Weil ich mit meinen schnellen Zustandsmaschinen sehr vom Timing der loop() abhängig bin, habe ich ein Testprogramm entwickelt, mit dem ich die Zeiten innerhalb und außerhalb von loop() messe. So wähle ich dann die besser geeigneten Boards aus. Manche brauchen auch noch etwas Rechenzeit außerhalb von loop(). Damit meine ich nicht die im Hintergrund laufenden Schnittstellen. > Wenn da ein rechenzeitverschwendendes delay_ms(5000) drin ist Das ist genau der Punkt. Solche Aufrufe sind in einem kooperativen Multi-Tasking nicht erlaubt und bei getakteten Zustandsmaschinen auch unnötig.
Rainer W. schrieb: > So steht es bei Arduino in main() Danke für den Hinweis. Oft setze ich auch nicht nur Original-Arduinos ein, sondern mehr oder weniger kompatible Boards mit Arduino-Toolchain. Da kann man noch ganz andere Dinge erleben.
Chris D. schrieb: > Ich kann das nur unterschreiben: FSMs kann man sehr sauber und > übersichtlich aufbauen. Unserer Erfahrung nach gehören sie zu den am > wenigsten fehleranfälligen Programmierprinzipien. Zweifellos. Aber eine FSM ist nicht die Lösung für jedes Problem. Und so habe ich vor allem den letzten Absatz des TOs verstanden.
Robert schrieb: >> Wenn da ein rechenzeitverschwendendes delay_ms(5000) drin ist > > Das ist genau der Punkt. Solche Aufrufe sind in einem kooperativen > Multi-Tasking nicht erlaubt und bei getakteten Zustandsmaschinen auch > unnötig. Das ist nichts Neues unter der Sonne und wurde schon ausführlich im Artikel Multitasking beschrieben. Es ist erstaunlich, wie alten Hasen wie du sich noch über derartige Grundlagen noch freuen können.
Ich bin auch ein absoluter fan von FSMs! Ich hab das inzwischen weiter getrieben. Meine FSMs kommunizieren ausschließlich über Messages. D.h. es gibt keine calls in die FSMs. Will eine FSM einer anderen etwas mitteilen (ADC-Wert liegt vor, UART-Puffer hat Zeichen, ...) dann schickt sie eine Message an einen Messagehandler mit der Adresse des Empfängers (ist ein enum der aber aus einem Namen wie "ADC", "TCP", "SDCARD" ...) entsteht. Diese Message kommt in eine inbox (queue), die die empfangende FSM, wenn sie in der Taskliste wieder dran ist, abfrägt. Ist eine Message da, wird die abgearbeitet, liegt nix vor ist die FSM auch schon wieder fertig. Es kann also sein, dass eine FSM mehrere Messages anstehen hat, aber nicht genug Zeit hatte. Dadurch werden die FSMs entkoppelt und blockieren sich nicht gegenseitig. Weitergesponnen ist ein Taskmanager dazugekommen. Der verwaltet die Liste der FSMs/Tasks. Ein Task kann sich dort selbst als schlafend eintragen und wird erst dann wieder aufgeweckt, wenn eine Message für ihn da ist. Die Messages haben Prioritäten. Das kann man als Messagestack innerhalb einer FSM "missbrauchen". Wenn sich eine Aufgabe in mehrere Unteraufgaben aufteilen lässt, dann schiebt die FSM sich selbst messages mit einer höheren Priorität in die Messagequeue. Damit das funktioniert, gibt es eine Standard-Priorität an die man sich strikt halten muss. Nochmal weitergesponnen kann der Taskmanager Tasks für x µs schlafen legen (warten auf UART-Timeout) oder erst zu einem festgelegten Zeitpunkt (chron-mässig) aufgerufen werden und der Task wird erst nach Ablauf der Zeit aufgerufen. So lässt sich das Timing von z.B. LCDs einhalten. Hängt man zwischen Quelle (z.B. Messwerte) und Ziel (z.B. TCP) einen queue-Task, dann kann der queue-Task die Länge der Messageliste des Ziels (TCP) beobachten und, wenns irgendwie hängt, auf eine SD-Card zwischenspeichern. Die Messages müssen aber allokiert werden (das wird einigen Puristen nicht gefallen), damit sie effektiv verwaltet werden können. Das Schöne an den Messages ist, dass die Verknüpfungen der Tasks auch aus einer Konfiguration kommen können und sogar zur Laufzeit verändert werden können. Also nix projektspezifisch (im eingeschränkten Rahmen) neucompilieren. Dadurch kann natürlich toter code im µC/PC rumliegen, DLL geht halt nicht auf dem µC. Die FSMs sind so aufgebaut (falls nötig), dass es auch mehrere Instanzen davon geben kann. Also z.B. "ADC.1", "ADC.2". Der code wird dadurch nicht dupliziert, lediglich die struct die eine Instanz der FSM benötigt.
Robert schrieb: > Weil ich mit meinen schnellen Zustandsmaschinen sehr vom Timing der > loop() abhängig bin Das hört sich irgendwie eigenartig an. Natürlich läuft eine FSM im theoretischen Idealfall in der Zeit 0 durch. In der echten Welt dauert die Bearbeitung aber länger und deshalb kommt der Begriff "Zykluszeit" ins Spiel, den auch der erwähnte Schrauber von seiner SPS kennt. Und er weiß: wenn irgendwas in der seinem Programm (seiner "Schrittkette", seiner "Merkerkette", seiner FSM) zu viel Rechenzeit beötigt, dann muss es so aufgeteilt werden, dass diese Rechenarbeit auf mehrere Zyklusdurchläufe aufgeteilt wird. Also muss diese Rechnarbeit wiederum mit einer FSM abgehandelt werden (auch ein Zähler der dann z.B. die Durchläufe zählt, ist eine FSM). Udo S. schrieb: > Aber eine FSM ist nicht die Lösung für jedes Problem. Aber viele Probleme wären gar nicht aufgetreten, wenn man das mit den FSM verstanden hätte. Nick schrieb: > Meine FSMs kommunizieren ausschließlich über Messages. Das ist dann lediglich die logische Weiterentwicklung der Kommunikation über einzelne Bits (aka "Flags").
:
Bearbeitet durch Moderator
> grafische Editoren? Alles Spaß und Spiel, bis jemand ein Auge verliert. Nämlich wenn man mal über so was stolpert CCITT alias ITU-T SDL/GR: https://de.wikipedia.org/wiki/Specification_and_Description_Language
Falk B. schrieb: > Es ist erstaunlich, wie alten Hasen wie du sich noch über derartige > Grundlagen noch freuen können. Geteilte Freude ist doppelte Freude. https://i.pinimg.com/736x/a8/98/70/a898702aa79c59b3ad33d7b2e9578578.jpg
Moin, Man kann auch einfach sagen: So eine FSM ist mindestens so wichtig wie Zinkoxid. https://www.youtube.com/watch?v=uRdZg-ph_U4 scnr, WK
:
Bearbeitet durch User
Lothar M. schrieb: > Das ist dann lediglich die logische Weiterentwicklung der Kommunikation > über einzelne Bits (aka "Flags"). Mit dem Vorteil, dass man sich lediglich in der FSM die den INT empfängt Sorgen um atomare Zugriffe machen muss. Der weitere Transport ist dann INT-sicher und auch für beliebig große Strukturen möglich. Zusätzlich geht keine Zustandsänderung verloren, falls die beobachtende FSM zu langsam war.
Robert schrieb: > Multi-Tasking > > Wenn man mehrere Software-Timer mit angehängten Zustandsmaschinen > implementiert, dann führt das zu einem kooperativen Multi-Task-System. > Die Echtzeitfähigkeit bestimmt man selbst durch Vorgaben bei den > Aufrufzyklen und für die maximale Dauer eines einzelnen Zustandes. Bei > richtiger Dimensionierung ist ein solches Multi-Tasking jedem RTOS > haushoch überlegen. Keine Verluste durch den Scheduler, keine > Semaphorensteuerung erforderlich, etc. Ich setze bei einigen Arduinos > mehrere Zustandsmaschinen gleichzeitig ein, mit Zykluszeiten von 50 > Mikrosekunden bis zu mehreren Millisekunden. > > Es macht Spaß Deine Ausführungen sind auf der einen Seite so allgemein, teils trivial, auf der anderen Seite so schräg, dass es kaum zu unterscheiden ist, ob Du etwas geniales entdeckt oder nur wenig Erfahrung mit RTOS hast. Die 3 RTOS-Ansätze "Kooperierend", "Unterbrechend" und z.B. bei Autosar "basic Task" (ohne Warten oder Kooperation) sind z.B. so unterschiedlich, dass ein Vergleich nur Sinn macht, wenn Du ihn präzisierst.
Udo S. schrieb: > Chris D. schrieb: >> Ich kann das nur unterschreiben: FSMs kann man sehr sauber und >> übersichtlich aufbauen. Unserer Erfahrung nach gehören sie zu den am >> wenigsten fehleranfälligen Programmierprinzipien. > > Zweifellos. > Aber eine FSM ist nicht die Lösung für jedes Problem. Natürlich nicht. Niemand möchte bspw. mit 1000 Knoten hantieren :-) Aber WENN sie eine brauchbare Lösung ist, dann ist die Entwicklung immer sehr sauber und "rock stable" möglich. Im Studium haben wir mal einen einfachen grafischen Editor/Compiler für FSMs als simple SPS geschrieben. Diese bestanden im Wesentlichen aus einem Eprom mit einem winzigen RAM (für den Status), Timer und Goodies wie 8-Bit-Schieberegister usw. Damit konnte man ganz ohne Controller schon richtig viel machen.
Bin ich denn der einzige, der sich über den Betreff wundert? "Toller Artikel und." Was "und."? Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack doch sehr nach LLM-erzeugt. Zumal der Account des TO erst wenige Wochen alt ist.
Johannes F. schrieb: > Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack > doch sehr nach LLM-erzeugt. Die Vermutung hatte ich auch schon.
Wegen der State Machine mit
1 | switch (state) |
2 | case ... |
Ich verwende diese Art ebenfalls sehr gerne und häufig. Jedoch habe ich meist noch diese Erweiterung:
1 | uint32_t stateT = 0; // Schrittzeit |
2 | uint32_t stateOld = 0; // Zuvoriger Schritt |
3 | uint32_t stateArr[10]; // Array mit Historie der letzten Schritte |
4 | uint32_t stateArrPos = 0; // Zeiger in Array |
5 | |
6 | stateT += ZyklusZeitMs; // Wert der Dauer der Schrittzeit in ms |
7 | // (ZyklusZeitMs ist die Anzahl ms, die seit dem letzten Aufruf der Funktion vergangen sind.)
|
8 | |
9 | if (Rücksetz Bedingung) { |
10 | state = 0; // Sicheres Rücksetzen um Schrittkette neu zu starten |
11 | }
|
12 | |
13 | switch (state) |
14 | case 0: // Schritt Grundstellung |
15 | |
16 | case ... |
17 | if (stateT >= 1000) { |
18 | // Schritt aktiv für 1000ms, mache was...
|
19 | }
|
20 | ...
|
21 | |
22 | // Erkennung Schrittwechsel
|
23 | // Aufzeichnung Schritt Historie in Array
|
24 | // Rücksetzen Schritt Dauer
|
25 | if (state != stateOld) { |
26 | stateOld = state; |
27 | stateArrPos = (stateArrPos + 1) % 10; // Erst Zeiger erhöhen |
28 | stateArr[stateArrPos] = state; // Dann Wert speichern in Array |
29 | stateT = 0; // Lösche Schrittzeit |
30 | }
|
Damit kann ich sehr einfach auf einen zeitlichen Ablauf reagieren ohne extra Zeitmerker zu bilden/verwalten und mit dem Array weis ich welche states zuvor alles durchlaufen wurden, also was die Historie ist um in diesen State zu kommen. Sehr gerne mache ich das in der Programmierung von Maschinensteuerungen um im nachhinein bei Störungen sehen zu können was genau alles passiert ist. Denn der Kunde sagt ohnehin immer "hab nix gemacht".
:
Bearbeitet durch User
Markus M. schrieb: > stateT += ZyklusZeitMs; > // (ZyklusZeitMs ist die Anzahl ms, die seit dem letzten Aufruf der > Funktion vergangen sind.) Wo wird dann ZyklusZeitMs überall manipuliert? Oder ist das eine Konstante und du rufst diese FSM über einen Timer auf? Markus M. schrieb: > stateArrPos = (stateArrPos + 1) % 10; // Erst Zeiger erhöhen Warum 10? Ich hätte da 8 oder 16 oder sonst eine Zahl mit *2^x* genommen, denn das ist das Zahlensystem, in dem sich ein binärer Rechner bewegt. Es ist ihm egal, dass du mit 10 Fingern mit dem Zehnersystem besser zurecht kommst. Du musst nur 1x denken, er muss den Job ständig tun. Als Tipp: sieh dir einfach mal den Maschinencode an, der bei
1 | stateArrPos = (stateArrPos + 1) % 10; |
und bei
1 | stateArrPos = (stateArrPos + 1) % 16; |
erzeugt wird und rechne aus, wieviel Prozent mehr Rechenleistung dafür nötig ist. Es ist mir klar, dass das in diesem speziellen Fall nur wenige Maschinenzyklen sind. Aber wenn sowas in jeder zweiten Programmzeile "passiert"...
:
Bearbeitet durch Moderator
Markus M. schrieb: > Jedoch habe ich meist noch diese Erweiterung: Das regt mich an, auch noch ein wenig aus der Schule zu plaudern: Abgesehen davon, dass ich statt switch/case über einen Funktionszeiger verzweige, lege ich auch mehrere Verzweigungsmöglichkeiten an. Also z.B. "Nächster Zustand" und "Übernächster Zustand" und werte diese in den Zuständen aus. Darüber kann man dann in einen bestimmten Zustand verzweigen und den dazu anregen, anschließend in einen anderen aktuell bestimmten Zustand zu verzweigen. Also Zustände anders zu nutzen, als in der ursprünglichen Programmierung vorgesehen war. Werden mehrere solcher Verzweigungsoptionen geschickt verwaltet, so erhält man Abläufe, die sich zur Laufzeit beliebig manipulieren lassen. Auch nachladbare Tabellen für spezielle Abläufe sind möglich. Als weitere Möglichkeit habe auch ich (ähnlich wie Du) einen manipulierbaren Zähler (Priorität) vor dem Aufruf eines Zustandes. Den nutze ich allerdings meistens, um beim Verlassen eines Zustandes die Zeit bis zum Aufruf des Folgezustandes zu setzen. Dadurch kann man Zeitabläufe optimieren und zur Laufzeit an sich ändernde Bedingungen anpassen. Für Debug-Zwecke habe ich einen extra Zustand, der bei jedem Zustandswechsel aufgerufen wird. Allerdings brauche ich den inzwischen nur noch sehr selten. Der Nachteil beim Einsatz von Funktionszeigern ist allerdings, dass man nun keine erkennbaren Bezeichner (Nummern, case ...) mehr hat. Ich muss also zusätzlich einen Bezeichner für die Zustände einführen, wenn ich Abfolgen sichtbar machen will. Es freut mich, dass hier auch viele Zustandsmaschinenanwender sind. Und vielleicht könnt ihr mir zustimmen, dass in diesem Verfahren enorme Möglichkeiten stecken.
Die ZyklusZeitMs berechne ich im OB1 gleich am Anfang mit Hilfe des Moduls TIME_TCK() in einer Siemens 1500er Steuerung. Wenn man andere Systeme hat, oder direkt einen µC, so muss man sich das selbst einfallen lassen wie man das ausrechnet. Wenn man RTOS oder andere OS verwendet, ist es etwas aufwändiger, da man das für jeden Task separat errechnen muss. Das gute daran ist, man muss es sich nur einmalig einfallen lassen und kann diese "ZyklusZeitMs" sehr einfach in allen Programmteilen des Tasks weiter verwenden. Ob 10, 8, 16, oder 1000 ist eigentlich völlig egal, man hat in einer Steuerung meist eine überschaubare Anzahl von diesen Aufzeichnungen, und der Modulo wird auch nur bei Schrittwechsel berechnet, also auch nicht in jedem Durchlauf des Loops. Der Modulo Operator macht das in jedem Fall korrekt und sorgt in zusammenhand mit dem "unsigned" Datentyp für einen korrekten Bereich innerhalb vom Array. In meinem Fall kommt es darauf an wie groß mein Bildschirm ist, dass ich dieses Array ordentlich in der HMI darstellen kann, für Diagnose über Fernwartung ist das ungemein wichtig (Kunde schickt mir z.B. ein Foto davon). Bei einfachen Schrittketten mache ich das Array kleiner, bei komplexen Schrittketten hatte ich auch schon mal 50 Arraypunkte. Man kann das Array auch 2-Dimensional ausführen (oder Array mit Record), damit kann man noch eine weitere Zahl für einen speziellen Zustand speichern. Ich hatte bei dem Fall mit 50 Array Punkte dann ein zweites Array mit nur 10, für die Darstellung in der HMI und die 50 konnte ich dann nur online direkt in der SPS Steuerung anschauen. Ich wollte hier mal die nächste Ausbaustufe der Statemachine zeigen, wie man sich das Leben deutlich vereinfachen kann um Fehler zu finden. Ich nutze diese Techniken schon über 20 Jahre und kann damit sehr schnell Fehler finden und beheben, denn sehr oft finden Fehler nicht am aktuellen Schritt statt, sondern schon viel früher wurde z.B. in den falschen Schritt gesprungen, was dann einige Schritte später zu dem Fehler führt.
Falk B. schrieb: > Johannes F. schrieb: >> Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack >> doch sehr nach LLM-erzeugt. > > Die Vermutung hatte ich auch schon. Kann ich aber guten Gewissens entkräften. Alles gut.
Rainer W. schrieb: > for (;;) { > loop(); > if (serialEventRun) serialEventRun(); > } Wichtig ist dass man immer alles aus der Arduino Welt heraus betrachtet. Was gibt es bitte sonst?
Chris D. schrieb: >>> Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack >>> doch sehr nach LLM-erzeugt. >> >> Die Vermutung hatte ich auch schon. > > Kann ich aber guten Gewissens entkräften. Alles gut. Wie? Bist du Hellseher oder LLM Plagiatsjäger?
Falk B. schrieb: > Chris D. schrieb: >>>> Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack >>>> doch sehr nach LLM-erzeugt. >>> >>> Die Vermutung hatte ich auch schon. >> >> Kann ich aber guten Gewissens entkräften. Alles gut. > > Wie? Bist du Hellseher oder LLM Plagiatsjäger? Ich bin Moderator und sehe damit einiges mehr als Ihr Sterblichen ;-)
Robert schrieb: > Der Nachteil beim Einsatz von Funktionszeigern ist allerdings, dass man > nun keine erkennbaren Bezeichner (Nummern, case ...) mehr hat. Stimmt nicht. Einfach eine Tabelle über ein enum adressieren, schon hat man menschenlesbare Zustandsnamen. > Ich muss > also zusätzlich einen Bezeichner für die Zustände einführen, wenn ich > Abfolgen sichtbar machen will. Ja und? Wo liegt das Problem? Die Methode ist praktisch nicht langsamer, wohl aber deutlich besser lesbar. Und das ist eines der Hauptkriterien guter Software.
Hannes J. schrieb: > Alles Spaß und Spiel, bis jemand ein Auge verliert. Nämlich wenn man mal > über so was stolpert CCITT alias ITU-T SDL/GR: Oh, damit hast Du die Büchse der Pandora geöffnet! Ursprünglich wurde SDL nur für sie Spezifikation von Kommunikationsprotokollen erschaffen, aber nicht direkt für die Codegenerierung. Diese wurde erst später von den Toolherstellern (z.B. Telelogic) und einigen Anwendern (z.B. Hagenuk Telecom) dazugebastelt. Und gerade dort gibt es auch Unmengen unterschiedlicher Codegeneratoren und Integrationsmodelle. Telelogic hatte z.B. den C Basic für Simulation, C Advanced für "große" Zielsysteme und C Micro für kleine Systeme wie z.B. Microcontroller. Und je nach Codegenerator konnte man dann den Code ohne Betriebssystem (bare integration), alle SDL-Prozesse in einem Betriebsystemprozess (minimum integration) oder pro SDL-Prozess ein Betriebssystemprozess (maximum integration) laufen lassen. Damals(tm) verbrachte ich viel Zeit damit, mich mit diesen Integrationen und der entsprechenden Fehlersuche zu beschäftigen. Und wenn man womöglich "eigenen C-Code" aufrufen wollte, konnte man das nicht einfach in einem Anweisungskästchen, sondern musste das über sog. ADT (abstract data types) realisieren, für die man externe Methoden definieren konnte. Daran merkte man sehr deutlich, dass SDL eigentlich nie für die Codegenerierung gedacht war. Und unfassbar teuer, fehlerhaft und ressourcenfressend war der Scheiß von Telelogic auch noch. Neben den Telelogic-Kram gab es auch noch den SDL-Codegenerator der Berliner Humboldt-Universität, der sogar Open Source und recht durchdacht war. Allerdings war das mehr oder weniger eine "one man show" eines fachlich sehr guten Doktoranden (Ralf Schröder). Ich lernte ihn mal im Rahmen eines Kundenprojektes kennen, in dem er genau diesen SDL-Codegenerator betreute und natürlich alles sehr genau wusste und erklären konnte. Schließlich hatte er das Teil über viele Jahre entwickelt. Zu der Zeit war er aber natürlich kein HU-Mitarbeiter mehr, und die öffentliche Version verwaiste dann. Ach, und es gibt noch das dänische Cinderella SDL und Cinderella SITE (Codegenerator). (Mit-)Entwickelt wurde das durch fjord-e-design, einer Ausgründung der FH Flensburg, die es für deren eigene TETRA-Protokollstacks und -tester einsetzte. Und da schließt sich auch schon wieder der SDL-Kreis: dieser TETRA-Protokollstack wurde in auch in den obigen Projekt eingesetzt, und WIMRE basiert Cinderella SITE wiederum auf dem HU-Codegenerator... Angeblich soll das Produkt sogar noch leben: https://www.cinderella.dk/
:
Bearbeitet durch User
Hallo, in dem ganz oben zitierten Artikel fehlt noch der Link auf Finite State Machines in Forth von J.V. Noble https://galileo.phys.virginia.edu/classes/551.jvn.fall01/fsm.html
Bruno V. schrieb: > Deine Ausführungen sind auf der einen Seite so allgemein, teils trivial, > auf der anderen Seite so schräg, dass es kaum zu unterscheiden ist, ob > Du etwas geniales entdeckt oder nur wenig Erfahrung mit RTOS hast. +1 für die LLM-These. Chris D. schrieb: > Ich bin Moderator und sehe damit einiges mehr als Ihr Sterblichen ;-) Hmm, wie schließt du denn aus IP-Adresse u.a. Server-Logdaten darauf, ob ein LLM/generative KI/Chatbot/wie auch immer man es nennen will den Text der Postings des TO erzeugt hat? Es kann ja auch ein Mensch sich hinter dem Account verbergen, der aber seine Beitragstexte KI-generieren lässt, aus welchem Grund auch immer.
Robert schrieb: > Als weitere Möglichkeit habe auch ich (ähnlich wie Du) einen > manipulierbaren Zähler (Priorität) vor dem Aufruf eines Zustandes. Den > nutze ich allerdings meistens, um beim Verlassen eines Zustandes die > Zeit bis zum Aufruf des Folgezustandes zu setzen. Dadurch kann man > Zeitabläufe optimieren und zur Laufzeit an sich ändernde Bedingungen > anpassen. Es scheint, dass Du nicht eine sogenannten SPS-Loop nutzt, also wo x parallel State-Machines zyklisch durchgerattert werden. Sondern etwas, wo Du einen State durchläufst und dann verschieden lange Pausen machst. > Für Debug-Zwecke habe ich einen extra Zustand, der bei jedem > Zustandswechsel aufgerufen wird. Das deutet ebenso darauf hin. Bei Markus ist das (wie üblich) im Vor- oder Abspann. > Der Nachteil beim Einsatz von Funktionszeigern ist allerdings, dass man > nun keine erkennbaren Bezeichner (Nummern, case ...) mehr hat. Ich muss > also zusätzlich einen Bezeichner für die Zustände einführen, wenn ich > Abfolgen sichtbar machen will. Funktionszeiger zeigen auf Funktionen. Deren Namen sind die Bezeichner. Was meinst Du mit "zusätzlich"? > Es freut mich, dass hier auch viele Zustandsmaschinenanwender sind. Und > vielleicht könnt ihr mir zustimmen, dass in diesem Verfahren enorme > Möglichkeiten stecken. Im Hinblick auf Deine vagen Timer-Ausführunge hier und im Ausgangspost halte ich die Aussage für so allgemein wie > Es freut mich, dass hier auch viele [strukturiert programmieren]. Und > vielleicht könnt ihr mir zustimmen, dass in [strukturierter Programmierung] > enorme Möglichkeiten stecken.
Auf sachliche Diskussionen lasse ich mich gerne ein. Allerdings habe ich leider meistens zu wenig Zeit, um mich intensiv mit Beiträgen in Foren zu befassen. Deshalb muss ich oftmals etwas oberflächlich bleiben. Auf persönliche Anmache (wie die Unterstellung der Nutzung eines LLM oder anderweitiger unsachlicher Äußerungen) reagiere ich in der Regel nicht. Meine ersten Zustandsmaschinen (vor Jahrzehnten) liefen tatsächlich im Timer-Interrupt. Das war eine sehr schöne Möglichkeit, unter DOS ein Multitasking zu gestalten. Die Zeitdistanzen waren damals ganzzahlige Vielfache des 55ms Zeittaktes. Damit habe ich früher auch ähnliche Abläufe aufgebaut, wie sie bei den SPSen üblich waren (EVA-Prinzip). Damals hatten die PCs eine parallele Schnittstelle (Centronics/Drucker), über die Peripherieankopplungen erfolgen konnten. Eine grundsätzliche Änderung habe ich vorgenommen, als ich vermehrt Mikrocontroller auf Basis der Arduino-Umgebung programmierte. Ich habe mich dabei anstelle der Verwendung von Timern auf die Abfrage von micros() in der loop()-Funktion eingelassen. Dabei wurde die minimale Taktzeit von mir willkürlich auf 50 Mikrosekunden festgelegt. Wenn mir der Aufruf von micros() zu lang erschien (z.B. beim Nano BLE 33), habe ich ihn durch einen direkten Zugriff auf die Hardware ersetzt (je nach CPU oder OS auf den Systemtakt oder auf bereits laufende Timer). Also in loop() werte ich konkrete Zeiten (Zeitdifferenzen) aus und verzweige von da über eine Zeigervariable in den aktuellen Zustand. Jeder Zustand hat einen Eingangszähler (virtuell, physikalisch ist da nur einer in der run()-Funktion). Hat der einen Wert größer Null, wird er nur dekrementiert und die Ausführung des Zustandes über return() abgebrochen. Auf diese Weise entscheide ich in einem beliebigen Zustand durch Setzen des Zählers, wann ich den nächsten Zustand aufrufe (also immer über ganzzahlige Vielfache des Aufruftaktes). So kann sich auch ein wiederholt aufgerufener (wartender) Zustand selbst verlangsamen, um die CPU-Last zu verringern. Wenn man den Ablauf einer Zustandsmaschine zur Laufzeit sichtbar machen will, dann kann man z.B. jedem Zustand die Möglichkeit geben, sich irgendwo (im Speicher oder an einer Schnittstelle) zu markieren (evt. mit Zeitstempel). Die Adresse des Zustandes ist für die Identifikation nicht geeignet, weil sie sich mit jeder Programmänderung ebenfalls ändert. Das ist bei switch/case natürlich nicht der Fall, der case-Wert ist eine eindeutige Zustandsidentifikation.
Robert schrieb: > Auf sachliche Diskussionen lasse ich mich gerne ein. Schön. Sollte man auch voraussetzen können, wenn jemand einen Thread in einem Forum aufmacht. Wozu sonst das ganze? > Allerdings habe ich leider meistens zu wenig Zeit, um mich intensiv mit > Beiträgen in Foren zu befassen. Deshalb muss ich oftmals etwas > oberflächlich bleiben. Ah, alles klar. Um inkonsistentes KI-Blabla ins Forum zu kopieren, reicht deine Zeit gerade noch aus, aber um auf Antworten darauf einzugehen, nicht mehr. Robert schrieb: > Damals hatten die PCs eine parallele Schnittstelle (Centronics/Drucker), > über die Peripherieankopplungen erfolgen konnten. Ach? XD. Robert schrieb: > Wenn man den Ablauf einer Zustandsmaschine zur Laufzeit sichtbar machen > will, dann kann man z.B. jedem Zustand die Möglichkeit geben, sich > irgendwo (im Speicher oder an einer Schnittstelle) zu markieren (evt. > mit Zeitstempel). Die Adresse des Zustandes ist für die Identifikation > nicht geeignet, weil sie sich mit jeder Programmänderung ebenfalls > ändert. Das ist bei switch/case natürlich nicht der Fall, der case-Wert > ist eine eindeutige Zustandsidentifikation. Und hier wieder Feststellungen trivialer Fakten, unpassend hochtrabend ausformuliert, wie die restlichen Absätze. Dass diese Texte nicht von einem LLM stammen, kannst du meiner Oma erzählen, und selbst die würde es nicht glauben.
Georg M. schrieb: > Next time, please at least include a few spelling mistakes. Es ist schon grotesk, dass gut formulierte Beiträge von manchen hier als LLM-Ergebnisse unterstellt werden. Was kommt denn da noch? Aber vielen Dank für den Tipp!
Johannes F. schrieb: > Nix für ungut, aber der Eröffnungsbeitrag riecht für meinen Geschmack > doch sehr nach LLM-erzeugt. Ich wollte es nicht sagen, aber: ja
Robert schrieb: > Es ist schon grotesk, dass gut formulierte Beiträge von manchen hier als > LLM-Ergebnisse unterstellt werden. Was kommt denn da noch? Die meisten hier nutzen Statemachines. Im Embeddedbereich allerdings eher mit Zykluszeiten von ~1ms und alles schnellere eventgetrieben (aka Interrupts). Warum? Weil jegliche Benutzerinteraktion im Bereich von mehreren ms schnell genug ist (Tastenentprellung per ms-Tick ist immer ausreichend) und weil es meist nur wenige Tasks gibt, die schnellere Zykluszeiten brauchen. Motorsteuerung wäre sowas. Kommunikation auch, aber da: Interrupts. Wenn Du dann sowas schreibst: Robert schrieb: > anstelle der Verwendung von Timern auf die Abfrage von > micros() in der loop()-Funktion eingelassen. Dabei wurde die minimale > Taktzeit von mir willkürlich auf 50 Mikrosekunden festgelegt. Wenn mir > der Aufruf von micros() zu lang erschien (z.B. beim Nano BLE 33), habe > ich ihn durch einen direkten Zugriff auf die Hardware ersetzt (je nach > CPU oder OS auf den Systemtakt oder auf bereits laufende Timer). Also in > loop() werte ich konkrete Zeiten (Zeitdifferenzen) aus und verzweige von > da über eine Zeigervariable in den aktuellen Zustand. ist das *schräg*: Entweder, Du missbrauchst das für einen Poor-Mans-Scheduler oder Deine Anwendungen sind *schräg*=ungewöhnlich (Spinnmaschine mit 20 unabhängigen Motorsteuerungen) oder Deine Statemachines haben mit den üblichen nur wenig gemeinsam. Da Du das auch nicht auflösen möchtest (kein Beispiel, kein Kontext, keine Erklärung, kein Eingehen auf die Antworten) ist Dein Beitrag einer KI nicht unähnlich. Viel Muster-Erfahrung aber ohne Bezug zur Realität.
Und sowas hier: Robert schrieb: > Das regt mich an, auch noch ein wenig aus der Schule zu plaudern: Die Wendung "aus der Schule plaudern" ist mir unbekannt und zudem in diesem Kontext unpassend. Einzige naheliegende Erklärung: LLM-Halluzination. Robert schrieb: > Es freut mich, dass hier auch viele Zustandsmaschinenanwender sind. Und > vielleicht könnt ihr mir zustimmen, dass in diesem Verfahren enorme > Möglichkeiten stecken. Ein typischer künstlich-euphorischer Schluss-Absatz, wie man ihn von Chatbots wie GPT gewohnt ist.
Bruno V. schrieb: > (Tastenentprellung per ms-Tick ist immer > ausreichend) auch IR-Code Bits einsammeln alle 64µs z.B. und dann zählt man halt mit ob die Entprellung bearbeitet werden muß.
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.