Tutorial · Principiante · 17 min di lettura
Macchine a stati finiti per il comportamento del robot
Progetta per il robot una macchina a stati finiti con transizioni, timeout, ripristino, aggiornamenti non bloccanti e regole di sicurezza verificabili.
I programmi dei robot spesso iniziano come un ciclo pieno di istruzioni if: segui la linea a meno che un ostacolo non sia vicino, gira finché il percorso non si libera, fermati quando la batteria è scarica e in qualche modo riprenditi quando un sensore scade. Man mano che i comportamenti crescono, la stessa condizione può attivare comandi contraddittori.
Una macchina a stati finiti (FSM) rende esplicito il comportamento attivo. In ogni istante il robot si trova in uno stato; eventi e guardie determinano quando passa a un altro. Il modello è abbastanza piccolo per un Arduino e strutturato quanto basta per essere verificato.
Separare stato, evento e azione
Uno stato descrive ciò che il robot fa nel tempo: FOLLOW_LINE, AVOID, SEARCH o FAULT. Un evento è qualcosa che viene osservato: un ostacolo entra nella zona di arresto, la linea viene persa, scade un timeout oppure l’operatore preme Ferma. Un’azione è l’uscita: imposta le velocità delle ruote, azzera un integratore o genera un avviso.
Evita di creare stati per fatti istantanei. OBSTACLE_DETECTED è in genere un evento o una guardia; AVOIDING_OBSTACLE è uno stato perché persiste mentre il robot esegue un comportamento.
Inizia da una tabella delle transizioni prima di scrivere il codice:
| Stato corrente | Guardia o evento | Stato successivo | Azione di ingresso |
|---|---|---|---|
| Inattivo | Avvio richiesto e sensori validi | Segui | Reimposta il controllore |
| Segui | Ostacolo entro la soglia di arresto | Evita | Ferma la trazione in avanti |
| Segui | Linea assente per 200 ms | Cerca | Memorizza l’ultimo lato |
| Evita | Percorso libero per 500 ms | Segui | Reimposta il controllore |
| Qualsiasi stato attivo | Timeout del sensore | Guasto | Frena i motori |
| Guasto | Reset dell’operatore e stato di salute valido | Inattivo | Cancella il guasto |
La tabella rende evidenti i casi mancanti e rende visibile la priorità della sicurezza.
Mantieni il ciclo di aggiornamento non bloccante
Uno stato deve eseguire un piccolo aggiornamento e restituire il controllo. Un delay() di due secondi dentro AVOID impedisce al programma di vedere una richiesta di arresto o un guasto del sensore per quei due secondi.
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;
}
}
Ogni aggiornamento legge la fotografia dei sensori già acquisita e scrive un unico comando coerente agli attuatori. In questo modo stati diversi non leggono valori che cambiano in momenti diversi durante lo stesso ciclo.
Colloca le transizioni ai limiti chiari
Scegli una sola regola di proprietà. Un semplice progetto embedded permette a ogni stato di richiedere le proprie transizioni in uscita, mentre un controllo di sicurezza di livello superiore può sovrascrivere qualsiasi stato attivo.
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);
}
Restituisci subito il controllo dopo una transizione, così lo stato precedente non può comandare anch’esso gli attuatori. All’ingresso in uno stato di controllo, azzera la cronologia che non deve trapassare da un comportamento all’altro, come il termine integrale di un PID o un campione derivativo.
Usa con parsimonia le azioni di uscita. La maggior parte della pulizia può avvenire nella funzione centralizzata transitionTo() o nel percorso di ingresso dello stato successivo. Funzioni separate onEntry, onUpdate e onExit diventano utili solo quando sono davvero necessarie a diversi stati.
Usa timeout e isteresi come guardie
Gli eventi fisici sono rumorosi. Se AVOID esce nell’istante in cui la distanza supera la stessa soglia che ne ha provocato l’ingresso, il rumore di misura può causare rapide oscillazioni tra stati. Usa due soglie oppure un tempo di persistenza: entra sotto i 250 mm, ma esci solo dopo che la distanza è rimasta sopra i 350 mm per 500 ms.
Ogni stato che prevede progresso richiede un timeout. Se il robot entra in SEARCH ma non trova mai la linea, non deve continuare a girare per sempre. Passa a FAULT o a un ripristino limitato dopo un intervallo verificato.
Basati i timer sul tempo trascorso dall’ingresso o dall’ultimo progresso. Usa una sottrazione come now - enteredAt >= timeout, che rimane corretta al rollover dei timer senza segno purché i tipi siano coerenti.
Dai priorità alle transizioni di sicurezza
Definisci che cosa può interrompere tutto: arresto di emergenza, dati non validi di un sensore critico, guasto del driver del motore o timeout del watchdog. Valuta queste condizioni prima delle transizioni specifiche del comportamento e rendi esplicita l’uscita sicura.
Lo stato FAULT dovrebbe rimanere attivo quando il ripristino automatico potrebbe riavviare un movimento pericoloso. Richiedere un reset dell’operatore dopo che la causa è svanita è spesso più sicuro che tornare direttamente a FOLLOW.
Non permettere a due macchine a stati indipendenti di scrivere gli stessi comandi dei motori senza una regola di arbitrato. Se sia la navigazione sia la gestione della batteria influenzano il movimento, un unico proprietario deve selezionare il comando finale e applicare i limiti di sicurezza.
Verifica le transizioni senza azionare i motori
Sposta le decisioni in funzioni che accettano una fotografia dei sensori e un orario, poi restituiscono uno stato successivo o un comando. Fornisci una sequenza di fotografie sintetiche:
- Linea valida e percorso libero mantengono lo stato
FOLLOW. - Un unico campione rumoroso e ravvicinato non causa la transizione se è richiesta la persistenza.
- Un ostacolo vicino persistente fa entrare in
AVOIDe comanda prima l’arresto. - Un percorso libero mantenuto per l’intervallo di uscita riporta a
FOLLOW. - Un timeout del sensore raggiunge
FAULTda ogni stato attivo.
Registra le variazioni di stato come old → new, l’evento scatenante e la marca temporale. I registri continui a ogni ciclo nascondono le transizioni nel rumore; quelli delle transizioni spiegano perché il comportamento è cambiato.
Sai quando una FSM non è più il modello più semplice
Una FSM funziona bene quando il robot ha un numero modesto di modalità mutuamente esclusive. Diventa difficile quando gli stati si moltiplicano per rappresentare combinazioni come «navigare mentre si monitora la batteria mentre si ripete la localizzazione». Il numero di transizioni può crescere più rapidamente dei comportamenti stessi.
Gli alberi di comportamento compongono azioni riutilizzabili con una struttura di sequenza, fallback e ripristino: è il motivo per cui i sistemi di navigazione come Nav2 li usano per attività complesse. Non introdurre un framework di alberi di comportamento per quattro stati. Passa a usarlo quando la tabella delle transizioni diventa davvero difficile da verificare o quando i comportamenti richiedono un riutilizzo gerarchico.
Diagnostica i comuni errori di progettazione
- Il robot ignora l’arresto mentre gira: uno stato contiene un ciclo bloccante o un ritardo.
- I motori ricevono due comandi per ciclo: il codice di transizione continua a essere eseguito dopo aver cambiato stato.
- La macchina oscilla: guardie rumorose senza isteresi o persistenza.
- Il ripristino non termina mai: lo stato non ha una condizione di progresso né un timeout.
- Un guasto riavvia subito il movimento: i guasti di sicurezza non rimangono attivi oppure le condizioni di reset sono troppo deboli.
- Le verifiche richiedono l’intero robot: decisioni e accesso all’hardware sono mescolati invece di usare fotografie dei sensori e uscite di comando.
Il Simulatore di evitamento degli ostacoli fornisce un’utile sequenza di comportamenti da modellare: avanzare, deviare da una minaccia, arrivare oppure fermarsi quando non rimane alcun movimento sicuro. Il Simulatore di risoluzione del labirinto mostra un’altra separazione tra rilevamento, aggiornamento della mappa, pianificazione e movimento di una cella. Il mini robot sumo è il test più severo della regola di sicurezza bloccata descritta sopra: il riflesso in prossimità del bordo deve anticipare ogni altro stato, da qualunque stato si provenga, altrimenti il robot esce dal cerchio.
Esplora il grafo
Fa parte di queste costruzioni
Progetti e percorsi di apprendimento che includono questo tutorial.
- ProgettoCostruisci un robot di prelievo e posa che sa quando ha mancato
- ProgettoCostruisci un robot ROS 2 che vede e raggiunge un marcatore
- ProgettoCostruisci un'auto robot col telefono via Bluetooth o Wi-Fi
- ProgettoMini sumo robot: presa, cuneo e rilevamento del bordo
- PercorsoGrasping e organi terminali: raccoglilo davvero
- PercorsoNavigazione reattiva: insegna a un robot a evitare gli ostacoli
Per approfondire