Tutorial · Beginner · 17 min lezen

Toestandsmachines voor het gedrag van een robot

Ontwerp een toestandsmachine voor robotgedrag: expliciete overgangen, timeouts, hersteltoestanden, niet-blokkerende updates en toetsbare veiligheidsregels.

Robotprogramma’s beginnen vaak als een lus vol if-statements: volg de lijn tenzij er een obstakel dichtbij is, draai tot het pad vrij is, stop als de accu bijna leeg is, en herstel op de een of andere manier als een sensor zijn tijd overschrijdt. Naarmate het gedrag groeit, kan dezelfde voorwaarde tegenstrijdige commando’s uitlokken.

Een toestandsmachine (FSM) maakt het actieve gedrag expliciet. Op elk moment bevindt de robot zich in één toestand; gebeurtenissen en voorwaarden bepalen wanneer hij naar een andere overgaat. Het patroon is klein genoeg voor een Arduino en gestructureerd genoeg om te toetsen.

Scheid toestand, gebeurtenis en actie

Een toestand beschrijft wat de robot over de tijd doet: FOLLOW_LINE, AVOID, SEARCH of FAULT. Een gebeurtenis is iets waargenomens: een obstakel komt binnen de stopzone, de lijn valt weg, een timeout verloopt, of de operator drukt op stop. Een actie is de uitvoer: wielsnelheden instellen, een integrator resetten, of een alarm geven.

Maak geen toestanden voor momentane feiten. OBSTACLE_DETECTED is meestal een gebeurtenis of voorwaarde; AVOIDING_OBSTACLE is een toestand omdat die aanhoudt terwijl de robot een gedrag uitvoert.

Begin met een overgangstabel vóór de code:

Huidige toestand Voorwaarde of gebeurtenis Volgende toestand Actie bij binnenkomst
Idle Start aangevraagd en sensoren geldig Follow Regelaar resetten
Follow Obstakel binnen de stopdrempel Avoid Voorwaartse aandrijving stoppen
Follow Lijn ontbreekt al 200 ms Search Laatste gekozen zijde onthouden
Avoid Pad is 500 ms vrij Follow Regelaar resetten
Elke actieve toestand Sensortimeout Fault Motoren stoppen
Fault Operatorreset en systeem gezond Idle Fout wissen

De tabel laat ontbrekende gevallen zien en maakt de veiligheidsprioriteit zichtbaar.

Een toestandsdiagram met vier kaders in een cyclus: DRIVE, STOP, SCAN en TURN. Pijlen verbinden ze in die volgorde, elk met het label van de voorwaarde die de overgang veroorzaakt: obstakel dichter dan twintig centimeter van DRIVE naar STOP, gestopt van STOP naar SCAN, veegactie klaar van SCAN naar TURN, en richting bereikt van TURN terug naar DRIVE.
Eén toestand tegelijk actief, en elke overgang is een benoemde voorwaarde. Het alternatief is een nest van voorwaarden waarover niemand vragen kan beantwoorden. SVG downloaden

Houd de updatelus niet-blokkerend

Een toestand hoort een kleine update uit te voeren en terug te keren. Een delay() van twee seconden binnen AVOID zorgt ervoor dat het programma die twee seconden lang geen stopverzoek of sensorstoring ziet.

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;
  }
}

Elke update leest de al vastgelegde sensormomentopname en schrijft één samenhangend actuatorcommando. Zo voorkom je dat verschillende toestanden binnen één cyclus op verschillende tijdstippen veranderende waarden lezen.

Zet overgangen op duidelijke grenzen

Kies één eigendomsregel. Een eenvoudig embedded ontwerp laat elke toestand zijn uitgaande overgangen aanvragen, terwijl een veiligheidscontrole op het hoogste niveau elke actieve toestand kan overschrijven.

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);
}

Keer onmiddellijk terug na een overgang, zodat de oude toestand de actuatoren niet alsnog kan aansturen. Bij binnenkomst in een regeltoestand reset je historie die niet naar ander gedrag mag lekken, zoals een PID-integrator of een afgeleide-sample.

Gebruik uitstapacties met mate. De meeste opschoning kan plaatsvinden in de centrale functie transitionTo() of in het binnenkomstpad van de volgende toestand. Afzonderlijke functies onEntry, onUpdate en onExit worden pas nuttig wanneer meerdere toestanden ze werkelijk nodig hebben.

Gebruik timeouts en hysterese als voorwaarden

Fysieke gebeurtenissen zijn ruisachtig. Verlaat AVOID het moment dat de afstand boven dezelfde drempel komt die er hem binnen bracht, dan kan meetruis snelle wisselingen tussen toestanden veroorzaken. Gebruik twee drempels of een aanhoudtijd: ga bij minder dan 250 mm naar binnen, maar er pas uit nadat de afstand 500 ms boven 350 mm is gebleven.

Elke toestand die vooruitgang verwacht heeft een timeout nodig. Gaat de robot naar SEARCH maar vindt de lijn nooit, dan mag hij niet eindeloos blijven draaien. Ga na een beproefd interval over op FAULT of op een begrensd herstel.

Baseer timers op de tijd sinds binnenkomst, of op de tijd sinds de laatste vooruitgang. Gebruik aftrekken zoals now - enteredAt >= timeout, dat correct blijft over de grens van een unsigned timer zolang de typen consistent zijn.

Geef veiligheidsovergangen prioriteit

Definieer wat alles kan onderbreken: een noodstop, ongeldige gegevens van een kritieke sensor, een storing van de motordriver, of een watchdog-timeout. Beoordeel deze voorwaarden vóór de gedragsspecifieke overgangen en maak de veilige uitvoer expliciet.

Een FAULT-toestand moet blijven hangen als automatisch herstel gevaarlijke beweging op gang zou kunnen brengen. Een operatorreset vereisen nadat de oorzaak is weggevallen, is vaak veiliger dan meteen terug te springen naar FOLLOW.

Laat niet twee onafhankelijke toestandsmachines dezelfde motorcommando’s schrijven zonder een regel voor arbitrage. Als navigatie en batterijbeheer beide invloed hebben op de beweging, moet één eigenaar het eindcommando kiezen en de veiligheidsgrenzen toepassen.

Test overgangen zonder motoren te laten draaien

Verplaats beslissingen naar functies die een sensormomentopname en een tijd accepteren en vervolgens een volgende toestand of een commando teruggeven. Voer een reeks synthetische momentopnames in:

  1. Een geldige lijn en een vrij pad blijft in FOLLOW.
  2. Eén ruisachtige dichte meting triggert niets wanneer aanhoudtijd vereist is.
  3. Een aanhoudend dicht obstakel voert AVOID binnen, met eerst een stopcommando.
  4. Een vrij pad dat het uitstapinterval vasthoudt, keert terug naar FOLLOW.
  5. Een sensortimeout bereikt FAULT vanuit elke actieve toestand.

Log toestandswisselingen als old → new, de uitlokkende gebeurtenis en een tijdstempel. Doorlopende logs per lus verstoppen de overgangen in ruis; overgangslogs verklaren waarom het gedrag veranderde.

Weten wanneer een FSM niet meer het eenvoudigste model is

Een FSM werkt goed als de robot een bescheiden aantal onderling uitsluitende toestanden heeft. Hij wordt lastig zodra de toestanden zich vermenigvuldigen om combinaties af te beelden zoals “navigeren terwijl de accu wordt bewaakt terwijl de lokalisatie opnieuw wordt geprobeerd”. Het aantal overgangen kan sneller groeien dan het gedrag zelf.

Gedragsbomen stellen herbruikbare acties samen met een structuur van sequentie, terugval en herstel, en daarom gebruiken navigatiesystemen zoals Nav2 ze voor complexe taken. Introduceer geen gedragsboomframework voor vier toestanden. Stap over wanneer de overgangstabel werkelijk lastig te beoordelen wordt of wanneer gedrag hiërarchisch hergebruikt moet worden.

Diagnoseer veelvoorkomende ontwerpfouten

  • De robot negeert stop terwijl hij draait: een toestand bevat een blokkerende lus of een delay.
  • De motoren krijgen twee commando’s per cyclus: de overgangscode blijft draaien nadat de toestand is gewijzigd.
  • De machine ratelt: ruisachtige voorwaarden missen hysterese of aanhoudtijd.
  • Herstel eindigt nooit: de toestand heeft geen voorwaarde voor vooruitgang en geen timeout.
  • Een storing start de beweging meteen opnieuw: veiligheidsfouten zijn niet blijvend vastgezet, of de resetvoorwaarden zijn te zwak.
  • Toetsen vereist de hele robot: beslissingen en hardwaretoegang zijn door elkaar gegooid in plaats van sensormomentopnames en commandouitvoer te gebruiken.

De Obstacle Avoidance Simulator levert een nuttige gedragsreeks om te modelleren: cruisen, uitwijken om een dreiging heen, aankomen, of stoppen wanneer er geen veilige beweging overblijft. De Maze Solver Simulator laat een andere scheiding zien tussen waarnemen, kaart bijwerken, plannen en één vakje bewegen. De mini sumo robot is de scherpste toets van de regel hierboven dat veiligheidsfouten blijven hangen: de randreflex moet elke andere toestand voorafgaan, vanuit elke toestand, of de robot rijdt zichzelf de ring uit.

Verken de graaf

Onderdeel van deze bouwprojecten

Projecten en leerroutes waarin deze tutorial voorkomt.

Verder lezen

Bronnen