Idee: Im Forum taucht immer wieder der Rat auf: „Das kann man mit einem Endlichen Automaten lösen.“ Hier ein Beispiel, wie einer entsteht.
Zielgruppe: Anfänger, die noch nie einen Endlichen Automaten programmiert haben, oder jene, die es getan haben, aber nicht wussten.
Motivation: Mit einem Endlichen Automaten kann ich Sketche realisieren, an die ich mich vorher nicht getraut hätte.
Aufgabenstellung: Ein Kohlekran auf einer Eisenbahnanlage soll Kohle von einem Kohlehaufen in einen Eisenbahnwagen verladen.
Material: Neben einem Arduino, ich verwende einen UNO, drei LEDs mit je einem Vorwiderstand und ein Schrittmotor, wobei die Ansteuerung des Schrittmotors nebensächlich ist.
Quellen: Viele nette Menschen, die ihr Wissen im Internet zur Verfügung stellen so wie in diesem Forum.
Tabelle für die Schrittmotoransteuerung (Link leider nicht mehr aktuell)
1. Schritt: Konzept der Ablaufsteuerung
Vor meinem geistigen Auge befindet sich ein Kohlehaufen und ein zu beladener Wagen. Die Anfangsposition sei beim Kohlehaufen, die Schaufel geöffnet.
- Kran dreht zum Taster für den Nullpunkt (links von Kohlehaufen)
- Kran dreht auf Startposition beim Kohlehaufen
- am Kohlehaufen, Schaufel runter
- am Kohlehaufen, Schaufel schließen
- am Kohlehaufen, Schaufel rauf
- Kran dreht zum Wagen
- am Wagen, Schaufel runter
- am Wagen, Schaufel öffnen
- am Wagen, Schaufel rauf
- Kran dreht zum Kohlehaufen
Die Punkte 1 und 2 werden nur am Anfang durchlaufen, die Punkte 3 bis 10 wiederholt.
2. Schritt: Konzept testen
Um zu sehen, ob die Logik stimmt, nutzen wir den seriellen Monitor der IDE.
enum ZUSTAENDE {SucheNull, Anfangsposition, KSrunter, KSschliessen, KSrauf, WKdrehen, WSrunter, WSoeffnen, WSrauf, KKdrehen};
byte zustand = SucheNull;
void setup() {
Serial.begin(9600);
Serial.println("Programmstart");
}
void loop() {
switch (zustand) {
case SucheNull:
if (MilliVerzoegerung(1000)) {
Serial.println("1. Kran dreht zum Taster fuer den Nullpunkt (links von Kohlehaufen)");
zustand = Anfangsposition;
}
break;
case Anfangsposition:
if (MilliVerzoegerung(1000)) {
Serial.println("2. Kran dreht auf Startposition beim Kohlehaufen");
zustand = KSrunter;
}
break;
case KSrunter:
if (MilliVerzoegerung(2000)) {
Serial.println("3. am Kohlehaufen, Schaufel runter");
zustand = KSschliessen;
}
break;
case KSschliessen:
if (MilliVerzoegerung(2000)) {
Serial.println("4. am Kohlehaufen, Schaufel schliessen");
zustand = KSrauf;
}
break;
case KSrauf:
if (MilliVerzoegerung(2000)) {
Serial.println("5. am Kohlehaufen, Schaufel rauf");
zustand = WKdrehen;
}
break;
case WKdrehen:
if (MilliVerzoegerung(2000)) {
Serial.println("6. Kran dreht zum Wagen");
zustand = WSrunter;
}
break;
case WSrunter:
if (MilliVerzoegerung(2000)) {
Serial.println("7. am Wagen, Schaufel runter");
zustand = WSoeffnen;
}
break;
case WSoeffnen:
if (MilliVerzoegerung(2000)) {
Serial.println("8. am Wagen, Schaufel oeffnen");
zustand = WSrauf;
}
break;
case WSrauf:
if (MilliVerzoegerung(2000)) {
Serial.println("9. am Wagen, Schaufel rauf");
zustand = KKdrehen;
}
break;
case KKdrehen:
if (MilliVerzoegerung(2000)) {
Serial.println("10. Kran dreht zum Kohlehaufen");
zustand = KSrunter;
}
break;
}
}
bool MilliVerzoegerung(unsigned long millisIntervall) { // Während die Zeit läuft, darf diese Funktion wegen der static-Variablen nicht ein zweites Mal aufgerufen werden!
static unsigned long intervall = 0;
static unsigned long prevMillis;
unsigned long aktMillis = millis();
if (intervall == 0) {
intervall = millisIntervall;
prevMillis = aktMillis;
}
if (aktMillis - prevMillis >= intervall) {
intervall = 0;
return true;
}
return false;
}
Die Funktion MilliVerzoegerung() wartet die angegebenen Millisekunden, bis sie den Zustand true zurückgibt, wodurch die Befehle innerhalb der Bedingung ausgeführt werden. Anders als delay() ist diese Funktion nicht blockierend, was den wesentlichen Unterschied ausmacht. Allerdings darf diese Funktion wegen der static-Variablen nicht ein zweites Mal aufgerufen werden, während eine Zeit läuft!
Damit ist der Endliche Automat fertig.