Tutorial · Einsteiger · 17 Min. Lesezeit

Endliche Zustandsmaschinen für Roboterverhalten

Endliche Zustandsmaschine für Roboterverhalten: explizite Übergänge, Timeouts, Erholungszustände, nicht blockierende Updates und testbare Sicherheitsregeln.

Roboterprogramme beginnen oft als Schleife voller if-Anweisungen: Der Linie folgen, es sei denn, ein Hindernis ist nah, drehen, bis der Weg frei ist, stoppen, wenn der Akku niedrig ist – und irgendwie erholen, wenn ein Sensor per Timeout ausfällt. Mit wachsendem Verhalten kann dieselbe Bedingung widersprüchliche Befehle auslösen.

Eine endliche Zustandsmaschine (FSM) macht das aktive Verhalten explizit. Zu jedem Zeitpunkt ist der Roboter in einem Zustand; Ereignisse und Wächter bestimmen, wann er in einen anderen wechselt. Das Muster ist klein genug für einen Arduino und strukturiert genug zum Testen.

Zustand, Ereignis und Aktion trennen

Ein Zustand beschreibt, was der Roboter über Zeit tut: FOLLOW_LINE, AVOID, SEARCH oder FAULT. Ein Ereignis ist etwas Beobachtetes: Ein Hindernis betritt die Stoppzone, die Linie geht verloren, ein Timeout läuft ab oder der Bediener drückt Stopp. Eine Aktion ist die Ausgabe: Raddrehzahlen setzen, einen Integrator löschen oder einen Alarm auslösen.

Keine Zustände für momentane Fakten erzeugen. OBSTACLE_DETECTED ist meist ein Ereignis oder Wächter; AVOIDING_OBSTACLE ist ein Zustand, weil er andauert, während der Roboter ein Verhalten ausführt.

Mit einer Übergangstabelle vor Code anfangen:

Aktueller Zustand Wächter oder Ereignis Nächster Zustand Eintrittsaktion
Idle Start angefordert und Sensoren gültig Follow Regler zurücksetzen
Follow Hindernis innerhalb Stoppschwelle Avoid Vorwärtsfahrt stoppen
Follow Linie 200 ms vermisst Search Letzte Seite merken
Avoid Weg 500 ms frei Follow Regler zurücksetzen
Jeder aktive Zustand Sensor-Timeout Fault Motoren stoppen
Fault Bediener-Reset und Zustand gesund Idle Fehler löschen

Die Tabelle legt fehlende Fälle offen und macht Sicherheitspriorität sichtbar.

Ein Zustandsdiagramm mit vier Kästen im Kreis angeordnet: DRIVE, STOP, SCAN und TURN. Pfeile verbinden sie der Reihe nach, jeweils beschriftet mit der Bedingung, die den Übergang verursacht: Hindernis näher als zwanzig Zentimeter von DRIVE nach STOP, gestoppt von STOP nach SCAN, Schwenk fertig von SCAN nach TURN, und Kurs erreicht von TURN zurück nach DRIVE.
Ein aktiver Zustand zur Zeit, und jeder Übergang ist eine benannte Bedingung. Die Alternative ist ein Bedingungsnest, zu dem niemand Fragen beantworten kann. SVG herunterladen

Die Updateschleife nicht blockierend halten

Ein Zustand soll ein kleines Update tun und zurückkehren. Ein zwei Sekunden delay() in AVOID hindert das Programm zwei Sekunden lang daran, eine Stoppanforderung oder einen Sensorausfall zu sehen.

enum class State { Idle, Follow, Avoid, Search, Fault };

State state = State::Idle;
unsigned long enteredAt = 0;

void transitionTo(State next) {
  state = next;
  enteredAt = millis();
}

void loop() {
  Sensors s = readSensors();

  if (!s.healthy && state != State::Fault) transitionTo(State::Fault);

  switch (state) {
    case State::Idle:   updateIdle(s); break;
    case State::Follow: updateFollow(s); break;
    case State::Avoid:  updateAvoid(s); break;
    case State::Search: updateSearch(s); break;
    case State::Fault:  updateFault(s); break;
  }
}

Jedes Update liest den bereits erfassten Sensorschnappschuss und schreibt einen kohärenten Aktorbefehl. So lesen verschiedene Zustände nicht zu verschiedenen Zeiten eines Zyklus sich ändernde Werte.

Übergänge an klare Grenzen legen

Eine Zuständigkeitsregel wählen. Ein einfacher eingebetteter Entwurf lässt jeden Zustand seine abgehenden Übergänge anfordern, während eine übergeordnete Sicherheitsprüfung jeden aktiven Zustand überstimmen kann.

void updateFollow(const Sensors& s) {
  if (s.obstacleMm < stopThresholdMm) {
    setWheels(0, 0);
    transitionTo(State::Avoid);
    return;
  }

  if (s.lineMissingForMs > 200) {
    transitionTo(State::Search);
    return;
  }

  followLine(s.lineError);
}

Nach einem Übergang sofort zurückkehren, damit der alte Zustand den Aktoren nicht auch befiehlt. Beim Eintritt in einen Regelzustand Historie zurücksetzen, die nicht über Verhalten hinweg sickern darf – etwa ein PID-Integral oder D-Abtastwert.

Sparsam mit Austrittsaktionen. Der meiste Aufräumaufwand passt in die zentrale transitionTo()-Funktion oder den Eintrittspfad des nächsten Zustands. Separate onEntry-, onUpdate- und onExit-Funktionen lohnen erst, wenn mehrere Zustände sie wirklich brauchen.

Timeouts und Hysterese als Wächter nutzen

Physische Ereignisse sind verrauscht. Verlässt AVOID in dem Moment, wo die Entfernung dieselbe Schwelle überschreitet, die hineinführte, kann Messrauschen schnelles Zustandsflattern verursachen. Zwei Schwellen oder eine Beharrungszeit verwenden: unter 250 mm hinein, aber erst heraus, wenn die Entfernung 500 ms über 350 mm bleibt.

Jeder Zustand, der Fortschritt erwartet, braucht ein Timeout. Betritt der Roboter SEARCH, findet aber nie die Linie, soll er nicht ewig kreiseln. Nach getestetem Intervall zu FAULT oder begrenzter Erholung wechseln.

Timer auf Zeit seit Eintritt oder Zeit seit letztem Fortschritt stützen. Subtraktion wie now - enteredAt >= timeout verwenden – über vorzeichenlosen Timerüberlauf hinweg korrekt, wenn Typen konsistent sind.

Sicherheitsübergängen Priorität geben

Definieren, was alles unterbrechen darf: Not-Halt, ungültige kritische Sensordaten, Motortreiberfehler oder Watchdog-Timeout. Diese Bedingungen vor verhaltensspezifischen Übergängen prüfen und die sichere Ausgabe explizit machen.

Ein FAULT-Zustand sollte einrasten, wenn automatische Erholung gefährliche Bewegung neu starten könnte. Einen Bediener-Reset zu verlangen, nachdem die Ursache behoben ist, ist oft sicherer, als direkt in FOLLOW zurückzuspringen.

Nicht zwei unabhängige Zustandsmaschinen ohne Schiedsregel dieselben Motorbefehle schreiben lassen. Beeinflussen Navigation wie Batteriemanagement beide Bewegung, soll ein Verantwortlicher den finalen Befehl wählen und Sicherheitsgrenzen anwenden.

Übergänge ohne laufende Motoren testen

Entscheidungen in Funktionen verlegen, die einen Sensorschnappschuss und Zeit nehmen und dann einen nächsten Zustand oder Befehl zurückgeben. Eine Folge synthetischer Schnappschüsse füttern:

  1. Gültige Linie und freier Weg bleibt in FOLLOW.
  2. Eine einzelne verrauschte Nahtmessung löst nicht aus, wenn Beharrung verlangt wird.
  3. Ein anhaltendes nahes Hindernis betritt AVOID und befiehlt zuerst Stopp.
  4. Ein gehaltener freier Weg über das Austrittsintervall kehrt zu FOLLOW zurück.
  5. Ein Sensortimeout erreicht FAULT aus jedem aktiven Zustand.

Zustandswechsel als alt → neu, auslösendes Ereignis und Zeitstempel loggen. Fortlaufende Pro-Schleife-Logs verstecken Übergänge im Rauschen; Übergangslogs erklären, warum sich Verhalten änderte.

Wissen, wann eine FSM aufhört, das einfachste Modell zu sein

Eine FSM arbeitet gut, wenn der Roboter eine überschaubare Zahl sich ausschließender Modi hat. Sie wird schwierig, wenn sich Zustände multiplizieren, um Kombinationen darzustellen wie „navigieren während Batterie überwachen während Lokalisierung wiederholen“. Die Übergangszahl kann schneller wachsen als die Verhalten selbst.

Behaviour Trees komponieren wiederverwendbare Aktionen mit Sequenz-, Fallback- und Erholungsstruktur – weshalb Navigationssysteme wie Nav2 sie für komplexe Aufgaben nutzen. Kein Behaviour-Tree-Framework für vier Zustände einführen. Wechseln, wenn die Übergangstabelle wirklich schwer prüfbar wird oder Verhalten hierarchische Wiederverwendung braucht.

Gängige Entwurfsfehler diagnostizieren

  • Der Roboter ignoriert Stopp beim Drehen: Ein Zustand enthält eine blockierende Schleife oder Delay.
  • Motoren erhalten zwei Befehle pro Zyklus: Übergangscode läuft nach Zustandswechsel weiter.
  • Die Maschine flattert: Verrauschte Wächter ohne Hysterese oder Beharrung.
  • Erholung endet nie: Der Zustand hat keine Fortschrittsbedingung oder Timeout.
  • Ein Fehler startet Bewegung instant neu: Sicherheitsfehler rasten nicht ein oder Resetbedingungen sind zu schwach.
  • Testen braucht den ganzen Roboter: Entscheidungen und Hardwarezugriff sind vermischt statt Sensorschnappschüsse und Befehlsausgaben zu nutzen.

Der Hindernisvermeidungssimulator liefert eine nützliche Verhaltensfolge zum Modellieren: Marschfahrt, um eine Bedrohung herumlenken, ankommen oder stoppen, wenn keine sichere Bewegung bleibt. Der Labyrinthlösersimulator zeigt eine weitere Trennung zwischen Wahrnehmung, Kartenupdate, Planung und Einzellzellenbewegung. Der Mini-Sumo-Roboter ist der schärfste Test der Einrast-Sicherheitsregel oben: Sein Kantenreflex muss jeden anderen Zustand verdrängen, aus jedem Zustand – sonst fährt sich der Roboter selbst aus dem Ring.

Den Graphen erkunden

Teil dieser Bauprojekte

Projekte und Lernpfade, die dieses Tutorial enthalten.

Weiterführende Literatur

Quellen