Tutorial · Principiante · 22 min
Algoritmo di evitamento ostacoli: rileva, scandisci, decidi, gira
Scopri il ciclo di un robot che evita ostacoli: soglia di arresto, scansione con servomotore e macchina a stati che gira verso lo spazio libero senza bloccarsi.
Quando un robot può leggere una distanza affidabile e cercare la direzione libera, rimane un pezzo: la logica che decide che cosa fare. È il cervello di un robot che evita ostacoli—un breve ciclo che avanza, nota che qualcosa è troppo vicino, guarda intorno e gira verso lo spazio libero. È più semplice di quanto sembri e farlo bene dipende soprattutto dal non bloccarsi.
Reattivo, non pianificato
Questo robot non ha una mappa. Reagisce a ciò che ha davanti in quel momento: rileva, decide, agisce e ripete. Questa è la navigazione reattiva, il primo approccio corretto—economico, robusto e sufficiente a esplorare una stanza affollata senza colpire nulla. Non è la stessa cosa della pianificazione del percorso, in cui il robot usa una mappa per scegliere una rotta in anticipo (il territorio di A* su una griglia di occupazione). L’evitamento reattivo è la base; la pianificazione viene dopo.
La soglia di arresto
Tutto dipende da un numero: quanto vicino è troppo vicino? Scegli la distanza alla quale il robot si ferma e rivaluta. Se è troppo piccola, urta gli ostacoli prima di poter reagire; se è troppo grande, si ferma davanti a tutto e avanza timidamente. Il valore corretto dipende dalla velocità: un robot più veloce deve reagire prima, perché percorre più terreno prima di potersi fermare. Per un piccolo robot che avanza con moderazione, 15–25 cm è un punto di partenza sensato.
La macchina a stati
Un sensore di distanza evita la maggior parte degli ostacoli; un interruttore a urto intercetta quelli che non vede, e ogni robot destinato a funzionare senza supervisione vuole entrambi.
Il modo più pulito di esprimere il comportamento è una macchina a stati finiti: pochi stati con nome e regole chiare per passare da uno all’altro. Sostituisce un intrico di if annidati con qualcosa che puoi leggere e correggere. (Per il modello generale, vedi macchine a stati finiti per il comportamento del robot.)
enum State { DRIVE, SCAN, TURN, REVERSE };
State state = DRIVE;
const int STOP_CM = 20;
void loop() {
switch (state) {
case DRIVE:
driveForward();
if (medianCm() < STOP_CM) { stopMotors(); state = SCAN; }
break;
case SCAN: {
int a = bestAngle(); // servo sweep from the scan tutorial
long open = distanceAt(a);
if (open < STOP_CM) state = REVERSE; // nowhere to go
else { turnToward(a); state = TURN; }
break;
}
case TURN:
if (turnComplete()) state = DRIVE; // opening cleared, resume
break;
case REVERSE:
backUp(400); // ms
state = SCAN; // then look again
break;
}
}
Scelta della direzione di rotazione
La scansione ha già fatto la parte difficile: restituisce la direzione più libera. Gira da quel lato. La chiamata turnToward(angle) trasforma «libero a sinistra» in una rotazione a sinistra e «libero a destra» in una rotazione a destra, usando la trazione differenziale: una ruota avanti e l’altra indietro per ruotare più o meno sul posto.
Evita oscillazioni e vicoli ciechi
Due modalità di guasto intrappolano i robot ingenui, e entrambe hanno correzioni semplici:
- Oscillazione fra sinistra e destra in un angolo in cui entrambe le direzioni sembrano simili. Aggiungi una durata minima della rotazione, così il robot mantiene l’impegno nella svolta invece di riconsiderare a ogni ciclo.
- Incastro in un vicolo cieco, dove ogni direzione è bloccata. A questo serve lo stato REVERSE: indietreggia, poi esegui una nuova scansione. Un piccolo contatore di blocco—indietreggia con più forza se hai scandito più volte senza progressi—rompe le trappole peggiori.
Regolazione: velocità e distanza di reazione
Due parametri decidono se il robot sembra fluido o agitato: velocità di marcia e soglia di arresto. Se aumenti la velocità, devi aumentare anche la soglia per mantenere sicura la distanza di frenatura — v·t per la latenza e v²/2a per la frenata — ed è per questo che raddoppiare la velocità aumenta la soglia di più del doppio. Il modo più rapido per trovare una buona coppia è modificarla dove un errore non costa nulla — nel simulatore di evitamento ostacoli — e solo dopo trasferire i valori all’hardware.
Isteresi: due soglie, non una
La macchina a stati precedente usa un solo STOP_CM, ed è la prima cosa da correggere. Un robot
che si aggira vicino a una soglia passa da DRIVE a SCAN molte volte al secondo, perché variazioni
minime della misura invertono il confronto. Si ferma a scatti invece di mantenere un comportamento deciso.
const int STOP_CM = 20; // stop when closer than this
const int RESUME_CM = 30; // do not drive again until clearer than this
La distanza fra le due è isteresi e costringe il comportamento a essere deciso. Lo stesso modello risolve quasi ogni oscillazione basata su soglie nel codice di un robot, non solo questa: un rilevatore di bordi, un avviso di batteria scarica, un avvicinamento alla stazione di ricarica.
Una regola utile per dimensionare la distanza: deve superare il rumore della misura più lo spostamento del robot durante un ciclo. Per un robot a 0.3 m/s con un ciclo del sensore di 60 ms, sono 18 mm di spostamento più un paio di centimetri di dispersione degli ultrasuoni: una distanza di 10 cm è generosa, mentre una di 3 cm può ancora vibrare.
Mantieni l’impegno nella svolta
La seconda oscillazione si verifica a livello decisionale: il robot gira a sinistra, la scansione mostra ora qualcosa di diverso, gira a destra e oscilla sul posto. Due meccanismi lo risolvono:
unsigned long turnStartedAt = 0;
const unsigned long MIN_TURN_MS = 250; // commit for at least this long
case TURN:
if (millis() - turnStartedAt < MIN_TURN_MS) break; // no re-deciding yet
if (medianCm() > RESUME_CM) { stopTurn(); state = DRIVE; }
break;
Una durata minima della rotazione impedisce al robot di rivalutare prima che la svolta abbia cambiato qualcosa, e il confronto usa la soglia di ripresa, non quella di arresto.
Rileva che il robot è bloccato
REVERSE gestisce un vicolo cieco davanti. Non gestisce il caso più difficile: un robot che
tecnicamente si muove, scandisce e gira senza arrivare da nessuna parte — un angolo concavo o una sedia
attorno alla quale continua a girare.
uint8_t scanCount = 0;
unsigned long lastProgressAt = 0;
// Called whenever the robot successfully drives forward for a while.
void notedProgress() { scanCount = 0; lastProgressAt = millis(); }
void escalate() {
scanCount++;
if (scanCount >= 3) {
backUp(800); // back further
pivot(random(120, 240)); // and turn a large, RANDOM amount
scanCount = 0;
} else if (millis() - lastProgressAt > 15000) {
backUp(1200);
pivot(180); // full about-turn
lastProgressAt = millis();
}
}
Vale la pena nominare due aspetti progettuali.
Escalation. La prima risposta a un ostacolo è una piccola svolta; la terza risposta alla stessa situazione è una svolta grande. Ripetere una risposta fissa è ciò che intrappola un robot in un angolo.
Casualità. Un robot deterministico in una trappola simmetrica sceglie sempre la stessa opzione e ripete il ciclo per sempre. Una componente casuale nella svolta di fuga rompe la simmetria; è il motivo per cui i primi robot aspiratori usavano rimbalzi casuali anziché una regola elaborata, una soluzione realmente robusta anche contro trappole impreviste.
Leggi gli interruttori a urto in ogni stato
La macchina a stati così scritta controlla soltanto la distanza, mentre ogni sensore di distanza ha superfici che non vede. Un robot senza rilevamento del contatto spinge una tenda finché i motori non si bloccano.
Il contatto deve essere gestito al di sopra della macchina a stati, non dentro uno stato:
void loop() {
updateBumpers(); // debounced, from the bump-switch tutorial
// Contact overrides whatever the robot thought it was doing.
if (bumpLeft || bumpRight) {
stopMotors();
backUp(300);
pivot(bumpLeft ? +60 : -60); // turn AWAY from the side that hit
state = SCAN;
return;
}
switch (state) { /* ... as before ... */ }
}
Osserva il segno: gira dall’altra parte rispetto all’interruttore che è scattato. Ecco perché due interruttori si guadagnano il loro posto rispetto a uno: un singolo interruttore centrale comunica che hai colpito qualcosa, ma lascia la direzione della svolta al caso.
Velocità e soglia, in forma di tabella
La soglia di arresto non è una costante: è una funzione della velocità, perché la distanza di frenatura cresce con il quadrato della velocità. Per un robot con 80 ms di latenza fra sensazione e decisione, frenata a 1.0 m/s², raggio di 90 mm e margine di 50 mm:
| Velocità | Termine di latenza | Termine di frenatura | Soglia totale |
|---|---|---|---|
| 0.15 m/s | 12 mm | 11 mm | 16 cm |
| 0.30 m/s | 24 mm | 45 mm | 21 cm |
| 0.45 m/s | 36 mm | 101 mm | 28 cm |
| 0.60 m/s | 48 mm | 180 mm | 37 cm |
| 0.90 m/s | 72 mm | 405 mm | 62 cm |
Raddoppiare la velocità da 0.3 a 0.6 m/s non raddoppia la soglia, ma la aumenta del 76%; a 0.9 m/s il robot deve reagire a oltre mezzo metro di distanza. Ecco perché un robot che evita gli ostacoli in modo attendibile a passo d’uomo comincia a sbattere contro le cose quando aumenti la velocità e perché la correzione è una soglia dipendente dalla velocità, non un unico valore fisso elevato che rende timido il robot ovunque.
int stopThresholdCm(float speedMps) {
const float latency = speedMps * 0.08f; // 80 ms
const float braking = (speedMps * speedMps) / 2.0f; // a = 1.0 m/s^2
return (int)((latency + braking + 0.09f + 0.05f) * 100.0f);
}
Stampare lo stato è lo strumento di diagnostica
La singola riga più utile che puoi aggiungere:
if (state != lastReportedState) {
Serial.print(millis()); Serial.print(F(" -> "));
Serial.println(STATE_NAMES[state]);
lastReportedState = state;
}
Stampare a ogni cambiamento, anziché a ogni ciclo, mantiene leggibile l’uscita e trasforma un
robot che «a volte si comporta in modo strano» in una trascrizione che puoi analizzare. Quasi ogni bug di
questo comportamento è visibile come una sequenza di stati priva di senso: un TURN che non finisce mai,
uno SCAN che scatta quaranta volte al secondo, un REVERSE che vi rientra immediatamente.
Se non riesci a rispondere alla domanda «che cosa sta facendo il robot adesso?» guardando una sola variabile, la macchina a stati non sta svolgendo il suo compito.
Risoluzione dei problemi
| Sintomo | Causa probabile | Correzione |
|---|---|---|
| Si ferma a scatti davanti agli ostacoli | Una sola soglia, senza isteresi | Fermati a 20 cm e riprendi a 30 cm |
| Oscilla fra sinistra e destra sul posto | Riconsidera la decisione durante la svolta | Durata minima della rotazione |
| Gira per sempre nello stesso angolo | Fuga deterministica | Aumenta progressivamente l’impegno e aggiungi una componente casuale |
| Spinge tende e cuscini | Il suono viene assorbito: fisica | Interruttori a urto, gestiti al di sopra della macchina a stati |
| Indietreggia contro gli oggetti | Nessun sensore posteriore | Un interruttore a urto posteriore, oppure limita il tempo di retromarcia |
| Evita bene lentamente, ma colpisce ad alta velocità | Soglia fissa e frenatura quadratica | Soglia dipendente dalla velocità |
| Si blocca a intervalli casuali | Letture bloccanti del sensore | Ping e scansione non bloccanti |
| Inverte la marcia subito dopo averla invertita | REVERSE è stato rientrato prima che la scansione si aggiornasse |
Forza una nuova scansione dopo la retromarcia |
| Gira verso il muro appena colpito | Gira verso il paraurti che è scattato | Gira dall’altra parte |
| Non riesci a capire che cosa sta facendo | Non stampa lo stato | Stampa al cambio di stato |
Esplora il grafo
Fa parte di queste costruzioni
Progetti e percorsi di apprendimento che includono questo tutorial.
Per approfondire