Mein erster Versuch mit einem endlichen Automaten

Hallo zusammen,

mein mehr als unsauber erstelltes Programm (aber funktionierend :wink: meines Monitorlifts soll abgelöst werden von einer, so hoffe ich, durchdachten "State Machine", aka endlicher Automat.
Ich habe mir ein paar Gedanken gemacht, und hoffe die GrundzĂŒge verstanden zu haben.
Aufgrund diverser Informationen glaube ich, dass mein Monitorlift mit folgenden ZustÀnden komplett beschrieben ist:
Wartend, fÀhrt hoch, ist oben, fÀhrt runter, ist unten.
Viele meiner benutzten Variablen sind, denke ich, selbsterklÀrend, dank aussagekrÀftiger Namen.
Was die Endschalter abgeht, so habe ich derer 3. Einen "Vor"-Endschalter, bei dessen erreichen die Motordrehzahl deutlich reduziert werden soll, der eigentliche Endschalter, und ein Not-Endschalter, der, wenn alles richtig implementiert ist, entweder via ISR den Motor stoppt, oder brutal dem Arduino die 5V wegnimmt, und somit auch das SSR fĂŒr den Motor-Trafo, mal sehen.
Anbei mein Quelltest, der im Moment nur aus den Ein/AusgÀngen und viel Pseudo-code besteht.
Dies soll erstmal das "ArbeitsgerĂŒst" werden, und wenn dann keine groben Fehler mehr enthalten sind (in der allgemeinen Struktur), wird StĂŒck fĂŒr StĂŒck richtig programmiert.
Ich bin völlig unerfahren, was endliche Automaten angeht, daher bitte ich euch mal zu gucken, ob die grobe Richtung stimmt.
(Da die digitalen Pins zu wenig sind, habe ich direkt fĂŒr alle Anzeige-LED's analoge Pins genommen)

Besten Dank und Gruß aus dem Bergischen
Michael

//PSEUDOCODE MonitorLift

const int Notendschalter      = 2;
const int Taster_Auf          = 4;
const int Taster_Ab           = 5;
const int Vorendschalter      = 6;
const int Endschalter         = 7;
const int Step_Pin            = 8;            //---------- GrĂŒne Ader ----------
const int Dir_Pin             = 9;            //---------- Blaue Ader ----------
const int SSR_230V            = 10;
const int SSR_48V             = 11;

const int Vorendschalter_LED  = A1;
const int Endschalter_LED     = A2;
const int Referenz_LED        = A3;
const int Pos_oben_LED        = A4;
const int Pos_unten_LED       = A5;

(Da die digitalen Pins zu wenig sind, habe ich direkt fĂŒr alle Anzeige-LED's analoge Pins genommen)

bool Vorendschalter_Status   = false;
bool Endschalter_Status      = false;
bool Referenz_Status         = false;
bool Taster_Auf_Status       = false;
bool Taster_Ab_Status        = false;

const int    eeAddress       = 0;           //---------- Spricheradresse im EEprom ----------
unsigned int Position        = 0;           //---------- Speicher fĂŒr die aktuelle Position ----------
unsigned int Position_Oben   = 55000;       //---------- Hier kommt die obere Position hinein ---------- 

void Referenzfahrt()
{
  //prĂŒfe Zustand (&nicht)Notendschalter (Öffner), (&)Vorendschalter da ist (Schließer) und (&nicht)Endschalter (Öffner) 
  // schnelle oder Schleichfahrt, je nach Vorendschalter bis Endschalter
  // setze Referenz_Status auf true
}

enum {Wartend, Faehrt_hoch, Faehrt_runter, Ist_oben, Ist_unten} Monitorlift = Wartend;

void Warten()
{
  //Taster_Auf_Status = digitalRead(Taster_Auf);
  //Taster_Ab_Status = digitalRead(Taster_Ab);
  //Wenn "Taster_Auf, dann Àndere Status Monitorlift = Faehrt_hoch;
  //Wenn "Taster_Ab, dann Àndere Status Monitorlift = Faehrt_runter;    
}

void Hochfahren()
{
  //PrĂŒfe ob bereits oben, wenn ja, Ă€ndere Monitorlift = Ist_oben und Routine verlassen
  // PrĂŒfe Wert in EEPROM = 0
  // PrĂŒfe ob Referenzfahrt erledigt ist, wenn nicht, Referenzfahrt 
  // SSR_230V einschalten
  // SSR_48V einschalten, 5 sec. warten
  // fahre zu Position_Oben
  //Ändere Status auf Monitorlift = Ist_oben;
}

void Oben()
  {
  //LED obere Pos an
  // schreibe Wert in EEPROM
  //Ändere Status auf Monitorlift = Wartend;
  }

void Runterfahren()
{
  //PrĂŒfe ob bereits unten, wenn ja, Ă€ndere Monitorlift = Ist_unten und Routine verlassen
  //PrĂŒfe of Position in EEProm > 0 ist
  // PrĂŒfe ob Referenfahrt erledigt wenn nicht, Referenzfahrt machen
  // fahre zu Position_unten
  //Ändere Status auf Monitorlift = Ist_unen;
}

void Unten()
{
  //LED untere Position an
  // schreibe Wert in EEPROM
  // SSR_230V ausschalten
  // SSR_48V ausschalten
  // Ändere Status auf: Monitorlift = Wartend;
}

void setup()
{
pinMode(Notendschalter  , INPUT);
pinMode(Vorendschalter  , INPUT);
pinMode(Endschalter     , INPUT);
pinMode(Step_Pin        , INPUT);
pinMode(Dir_Pin         , INPUT);
pinMode(Step_Pin          , OUTPUT);
pinMode(Dir_Pin           , OUTPUT);
pinMode(SSR_230V          , OUTPUT);
pinMode(SSR_48V           , OUTPUT);
pinMode(Vorendschalter_LED, OUTPUT);
pinMode(Endschalter_LED   , OUTPUT);
pinMode(Referenz_LED      , OUTPUT);
}

void loop()
{
  switch (Monitorlift)
  {
  case Wartend:
    Warten();        
  break; 

  case Faehrt_hoch:
    Hochfahren();        
  break; 

  case Faehrt_runter:
    Runterfahren();        
  break;

  case Ist_oben:
    Oben();        
  break; 
  case Ist_unten:
    Unten();        
  break; 
  }     //---------- Ende Case ----------
}       //---------- Ende Loop ----------

Hallo gummiq

Ich kommt was zum "Schlaulesen" :grinning:

Die finde ich, hat in Hochfahren nix zu suchen.
Das ist ein eigenes Ding im Automaten, wenn nicht gar ein eigener Automat.

Frei nach dem Motto:
Eine Funktion tut nur eine Sache.

Ansonsten sieht das auf den ersten Blick ganz gut aus.

VerbesserungsvorschlĂ€ge, könnte ich machen. Die es vielleicht hĂŒbscher machen, aber keine funktionale Änderung erwirken.

z.B.

ersetzen durch

constexpr byte Notendschalter      {2};

und natĂŒrlich auch die anderen .

Oder: Du hast da 3 AufzÀhlungen, welche eigentlich das gleiche aufzÀhlen

  1. die Funktionsnamen
  2. die enum AufzÀhlung
  3. im switch/case nochmal die gleichen Schritte aufgereiht

Die drei kann man zu einem zusammenfassen.
WĂŒrde die Sache deutlich verschlanken.

Ach ja, die EEPROM Adresse......

Sehr schön so weit, nur daß "Wartend" verzichtbar ist - der Lift wartet entweder oben oder unten.
Das ist aber nur die halbe Miete. Jetzt kommt dazu die Beschreibung, am besten als Zustandsdiagramm, welche Ereignisse in welchem Zustand den Übergang in einen anderen Zustand initialisieren, und was dabei noch zu tun ist. Anhand dieses vollstĂ€ndigen Diagramms kann man dann den entsprechenden Code erzeugen bzw. ĂŒberprĂŒfen.

ach das schaut doch schon gut aus.
Wartend wurde eh schon angesprochen. Wartend ist eigentlich eh im Ist_oben und Ist_uten.
Die Statusvariable Monitorlift wĂŒrde ich an deiner Stelle so wie alle anderen Variablen klein schreiben - monitorlift.
Apropos Kleinschreibung - auch Funktionen sollten mit kleinem Buchstaben beginnen.

Wenn ich das richtig sehe hast du nur einen echten Endschalter, dafĂŒr aber einen weiteren Schalter "Vorendposition". Das deutet aber auch darauf hin, dass du einen weiteren Status "langsamfahrt" hast.

Dein "notendschalter" deuted darauf hin, dass du (zumindest in den Bewegungen) sofort beenden willst. Auch das deutet auf einen weiteren Status das du einen "nothalt" benötigst.
Auch aus diesem nothalt willst du mit einem Trigger wieder rauskommen wollen (glaube ich).

STRG-T kannst auch mal in der IDE drĂŒcken. Macht das Ganze schöner.

Ja - und ich bin auch pro Diagramm. Zeichne ein Diagram und dann siehst du auch schön die ÜbergĂ€nge. Wenn du sonst nichts zum zeichnen hast, nimm Graphviz.
Hier hab ich zwei Beispiele fĂŒr dich: Simple Graphics with Graphviz (rothschopf.net)

Erstmal danke fĂŒr die Antworten.
@paulpaulson
Den Link werde ich durchackern... :wink:

@combie
In der ENUM-Definition muss ich ja schon was angeben.
Und im Case frage ich ja genau diese ZustÀnde ab.
Klar, die dazugehörige Programmierung könnte ich auch direkt im jeweiligen Case-Zweig machen, aber fĂŒr meine Augen ist es so ĂŒbersichtlicher.
Ggf könnte ich aus dem ENUM eine Klasse machen, die einfacher "zÀhlbar" ist, mit 0 usw.

constexpr byte Notendschalter      {2};

Ich könnnte wohl auch mit #define arbeiten.
Die von Dir vorgeschlagene Notation ist mir, als Pascal/Delphi-Mensch gÀnzlich unbekannt.
Birgt sie Vorteile?

Jepp. die EEPROM-Adresse...
Da der Lift max 1-2mal am Tag fÀhrt, reicht der Arduino interne EEPROM völlig.
Schreiben, bzw vorher löschen, kostet "Lebenszeit", Lesen noch nicht mal.

Referenzfahrt...
Es kann vorkommen, dass alles abgeschaltet ist (Ich schalte ĂŒber ein SchĂŒtz meine komplette Bastelecke ab, incl. des Arduino, usw)
Wenn ich nun einschalte, darf der Arduino KEINESFALLS einfach eine Referenzfahrt machen, denn der Monitor KÖNNTE aus dem erlaubten Bereich herausgeschwenkt sein (Monitor fĂ€hrt in eine Kiste hinter der Werkbank), dafĂŒr habe ich noch keine Überwachung, ein Lichtvorhang-Sensor ist mir etwas zu teuer, muss mir da noch was einfallen lassen...
Und wenn die Frau einschaltet, weil Sie da mal Strom fĂŒr den Staubsauger braucht oder so.
Neeee, so bestimme ICH, mittels der Auf und Ab-Taster, WANN eine Referenzfahrt durchgefĂŒhrt wird, deshalb auch das Speichern der Position im dauerhaften Speicher.

Status "Wartend"
So habe ich es einigen Beispielen entnommen, als EINEN, genau definierten Zustand, den ein System haben sollte (da oft einfach "Idle").
Die ÜbergĂ€nge sind, aus meiner Sicht, sauber definiert, oder sind sie nicht?
Mittlerweile sind weitere Antworten eingetrudelt...

erster Versuch:

Graphviz Online (dreampuf.github.io)

1 Like

@DrDiettrich
Ich habe mal den "Diagramm-Generotro" bemĂŒht:
https://bit.ly/463K999

@noiasca
Besten Dank :+1:

Wenn ich mir das so angucke, könnte ich eher uf die ZustÀnde "Ist_oben & Ist_unten" verzichten, oder?

Das ist eine philosophische Betrachtung: machst einen Moore Automaten oder einen Mealy...

Endlicher Automat – Wikipedia

Der Moore passt imho schon bei dir.

Hab schon gesehen... da liegen die Gelehrten im Disput, scheint ein wenig eine Glaubensfrage zu sein.

Genau wie die Nomenklatur von Variablen :slight_smile:
FĂŒr MEINE Programme, und ich schreibe NUR solche, gehe ich meinen Weg, aber ich denke, auch mit Großbuschtaben am Anfank ist es gut zu erkennen, da ich ja nur winzige Programme schreibe.

Was den "Nothalt" angeht, den lasse ich fĂŒr den Moment aussen vor, da ich da eher geneigt bin, brutal die Spannug abzuschalten

Nomenklatur ist keine Glaubensfrage.
Es gibt so was wie best practices.
Und wenn du DEIN Programm öffentlich zur Diskussion stellst, dann soll man sich an sowas halten.
:wink:

Auf die ENUM-Definition könnte man verzichten.

Auf das switch/case Konstrukt könnte man verzichten.

Das ist modernes C++.
Damit ist der Ausdruck wirklich konstant.
Es spart pro Zeile zwei byte
Das const, was du verwendest heißt/bedeutet nur "read only", was nicht ausschließt, dass es von "hinten links" heimlich/versehentlich verĂ€ndert wird.
Ist mit den Klammern "typesicher"

constexpr byte Notendschalter      {2.44}; // wirft einen Error

Mein Problem ist da die hÀndisch festgelegte Adresse.
Das kann man evtl. besser dem Kompiler ĂŒberlassen. Denn der kann sich nicht irren. Der ist unmenschlich.

Dein Wille ist dein Himmelreich.
Meiner Überzeugung nach ist das normale Hochfahren was anderes, als eine Referenzfahrt.
Wenn du das in einen Topf werfen möchtest, gerne.

Ja, dass Wiki-Bild vom "EA vom Typ Transduktor: Moore-Modell" spricht mich an, ich kann es intuitiv lesen.
Ich war eh fast immer der Typ "wilder" Programmierer. Zu Pascal Zeiten hatte ich ein Programm, dasss aus meinem Quelltext ein Struktogramm erzeugt hat, somit hat in der Technikerschule mein Struktogramm immer zu 100% zu meinem Quelltext gepasst :joy:

Hmmmm
Muss ich mich nochmal schlau machen.
Ich warf auch verwundert, dass ich (im 21 Jahrhundert) explizit eine Adresse angeben muss, habe es aber in diversen Beispielen so gesehen.

Klar ist das regulÀre Hochfahren( Obere Endlage anfahren) was anderes als eine Referenzfahrt, aber wenn ich ein Ziel angebe, mus ich mich drauf verlassen können, dass der Startpunkt richtig ist. Es handelt sich zwar um ein Closed-Loop Stepper, aber eben ohne Absolut-Wert Geber.

Ich verstehe die Bedingungen, glaube ich, aber bei deinem logischen Schluss kann ich nicht mithalten.

Aus Beispielen lernt man nur begrenzt.
Man unterliegt den Grenzen des Autors.
Man lernt auch seine/ihre Fehler und IrrtĂŒmer gleich mit.

Erst kĂŒrzlich erweitert: [Tutorial] Umgang mit dem AVR EEPROM

Weis nicht was fĂŒr Schalter du nutzt, fĂŒr Nothalt wurde ich nur Motor abschalten und das der Steuerung "sagen" mit zweitem Schalter, ja nach dem was fĂŒr Lift das ist darf man aus dem Notendschalter nur Manuel rausfahren den es muss ein Grund geben warum der zu Hoch, Tief gefahren ist.

Nur so am Rande alle AufzĂŒge mĂŒssen vom SachverstĂ€ndigen abgenommen werden, auch wen das nur ein Sackaufzug in was hat 2m Förderhöhe in einer MĂŒhle, und das nicht nur in DE.

enum {Wartend, Faehrt_hoch, Faehrt_runter, Ist_oben, Ist_unten} Monitorlift = Warten;

Das dĂŒrfte aber nicht funktionieren....

Danke.
Hatte Warten auf Wartend gĂ€ndert, aber in dieser Zeile noch nicht korrigiert, wĂŒrde also eh Alarm geben.

Hallo Fony, danke fĂŒr Deine Anmerkung.
Der "Lift" befördert lediglich den Monitor hinter meiner Werkbank auf und ab, damit ich, bei grĂ¶ĂŸeren Hardwarebastelleien, nicht den Monitor versehentlich runterschubse.