Tutorial · Intermedio · 14 min di lettura
Giunzioni nei labirinti lineari con un array IR
Riconosci giunzioni a T, incroci, vicoli ciechi e arrivo con una matrice IR, risolvendo l'ambiguità che una sola lettura non può eliminare.
Un robot seguilinea ha un solo compito: mantenere la linea al centro. Un robot per un labirinto tracciato ne ha due, ed è il secondo a mettere in difficoltà i principianti. Deve continuare a seguire la linea, ma deve anche notare i momenti in cui la linea offre una scelta e decidere di che tipo di scelta si tratta.
Questo strato decisionale si trova sopra l’anello PID, non al suo interno. Il controllore non deve mai sapere che cos’è un labirinto. Il controllore sterza verso una linea; lo strato delle giunzioni osserva le stesse letture dei sensori alla ricerca dei modelli che significano «qualcosa è cambiato» e subentra quando ne trova uno.
Sei possibilità per una giunzione
Ogni giunzione in un labirinto tracciato con nastro adesivo si riduce a uno di sei casi. Con un array di riflettanza a 8 canali che legge 1 per il nastro nero e 0 per il pannello bianco, ciascun caso produce una firma:
- Svolta solo a sinistra — i sensori esterni di sinistra leggono il nero mentre il lato destro resta bianco.
- Svolta solo a destra — il simmetrico.
- Giunzione a T — il nastro attraversa tutta la larghezza e davanti non c’è nulla.
- Incrocio — il nastro attraversa tutta la larghezza e la linea continua davanti.
- Vicolo cieco — ogni sensore legge il bianco; la linea si è semplicemente interrotta.
- Arrivo — un blocco pieno, largo e lungo, che resta nero mentre ci entri.
La lettura da sola è ambigua
Osserva la giunzione a T, l’incrocio e il segnale di arrivo. Tutti e tre riportano 11111111. Un ramo a sinistra da cui la linea continua anche in avanti produce la stessa lettura di una semplice svolta a sinistra.
Questa è la parte da assimilare: il modello istantaneo dei sensori non può classificare una giunzione. Un robot che decide quando i sensori esterni diventano neri si rivolterà con sicurezza a sinistra in un incrozio che avrebbe dovuto attraversare dritto.
La soluzione è il movimento, non altri sensori. Quando l’array riporta per la prima volta un ampio modello nero:
- Registra quale lato o quali lati sono diventati neri: sono i rami disponibili.
- Continua ad avanzare per circa una lunghezza del robot.
- Leggi di nuovo. Se i sensori centrali vedono ancora nastro, anche dritto era un’opzione. Se tutto è bianco, era una giunzione a T. Se è ancora tutto nero, sei entrato nel blocco d’arrivo.
Solo ora sai quale tipo di giunzione hai superato e solo ora lo strato del labirinto può scegliere.
Junction classify(Sensors& s) {
Branches at = s.readWideBranches(); // left / right seen on arrival
driveForward(ONE_ROBOT_LENGTH); // commit past the intersection
Reading after = s.read();
if (after.allBlack()) return FINISH; // still on tape = solid block
at.straight = after.centreOnLine(); // line continued through
return at.toJunction();
}
Il classificatore guida il robot. È insolito per una funzione di sensore, ma è proprio il punto: il tipo di giunzione è una proprietà di una breve traiettoria, non di un singolo campione.
Scelta di un ramo
Una volta classificata la giunzione, lo strato del labirinto sceglie un ramo secondo una regola fissa. La regola della mano sinistra, scegliere sempre il ramo disponibile più a sinistra e tornare indietro in un vicolo cieco, è la scelta standard perché non richiede stato, esaurisce tutti i percorsi in un labirinto senza anelli ed è banalmente verificabile a mano.
Qui la coerenza conta più dell’astuzia. Un robot che preferisce sempre sinistra visiterà prima o poi ogni ramo raggiungibile e il registro delle sue svolte è ciò che lo strumento di semplificazione del percorso comprimerà in seguito nel percorso ottimale. Osserva lo stesso comportamento di esplorazione e ottimizzazione nel Simulatore di risolutore di labirinto: percepisce le pareti anziché il nastro, ma lo strato decisionale sopra la percezione è identico.
L’array deve essere più largo della linea
Il rilevamento delle giunzioni pone al sensore una domanda specifica: i canali più esterni sono diventati neri mentre il centro è ancora sulla linea? Un appena più largo del nastro non può rispondere fisicamente.
left branch 8-channel array, ~60 mm wide
────────────────┐ [ 1 1 1 0 0 0 0 0 ] outer left black -> a branch exists
│
════════════════╪═══════ [ 0 0 0 1 1 0 0 0 ] centre still on the line
│
| Larghezza nastro | Larghezza minima utile dell’array | Motivo |
|---|---|---|
| 19 mm (standard) | ~60 mm | I sensori esterni devono superare il nastro di un’intera spaziatura tra sensori |
| 25 mm | ~75 mm | |
| 12 mm | ~40 mm |
La regola pratica è tre volte la larghezza del nastro. Con un array più stretto, il robot non può distinguere «la linea ha curvato bruscamente» da «la linea si è ramificata», che è la decisione centrale su cui si fonda l’intero progetto. Un array a 8 canali con spaziatura di 8 mm misura circa 60 mm, motivo per cui è la scelta standard per nastro da 19 mm.
Anti-rimbalzo di una giunzione
L’array attraversa una giunzione in diverse iterazioni dell’anello e ciascuna produce una lettura nuova. Un classificatore che si attiva sul primo ampio modello si attiverà spesso due o tre volte per una sola giunzione, riempiendo il percorso registrato con caratteri che non corrispondono a vere svolte.
const uint8_t CONFIRM_SAMPLES = 3; // consecutive readings that must agree
const unsigned long JUNCTION_DEADTIME_MS = 300;
uint8_t agreeCount = 0;
uint8_t lastPattern = 0;
unsigned long lastJunctionAt = 0;
bool junctionDetected(uint8_t pattern) {
// Ignore everything for a short while after committing to one.
if (millis() - lastJunctionAt < JUNCTION_DEADTIME_MS) return false;
if (pattern == lastPattern) agreeCount++;
else { lastPattern = pattern; agreeCount = 1; }
if (agreeCount >= CONFIRM_SAMPLES && isWide(pattern)) {
lastJunctionAt = millis();
agreeCount = 0;
return true;
}
return false;
}
Dimensionare il tempo morto è un vero compromesso e vale la pena farlo con calcoli numerici anziché a impressionse:
deadtime < (minimum spacing between junctions) / speed
A 0.2 m/s con giunzioni distanti almeno 15 cm hai 750 ms di margine, quindi 300 ms sono sicuri. Guida a 0.5 m/s in un labirinto con spaziatura di 10 cm e il margine scende a 200 ms: è il motivo per cui l’esplorazione procede lentamente. Questo vincolo, non l’algoritmo, determina la velocità di esplorazione.
La svolta stessa
Classificare la giunzione è metà del lavoro; eseguire la svolta senza perdere la linea è l’altra metà, e una svolta ad angolo fisso è il metodo sbagliato.
| Metodo | Come termina | Affidabilità |
|---|---|---|
| Svolta per un tempo fisso | Dopo N millisecondi | Scarsa: varia con la tensione della batteria e la superficie |
| Svolta di un angolo fisso usando gli encoder | Dopo N conteggi | Migliore, ma gioco e slittamento si accumulano |
| Svolta finché la linea non viene riacquisita | Quando i sensori centrali vedono il nastro | Migliore — autocorrettiva, termina sulla linea |
void turnUntilLine(int direction) {
pivot(direction);
delay(120); // clear the junction's own tape first
while (!centreOnLine()) { // then rotate until the new line appears
if (millis() - startedAt > TURN_TIMEOUT_MS) { stopMotors(); fault(); return; }
}
stopMotors();
}
Il delay(120) è importante. Subito dopo aver iniziato la svolta, i sensori centrali si trovano ancora sul nastro della giunzione, quindi un ingenuo «ruota finché non vedi la linea» si ferma immediatamente e il robot non gira affatto. Allontanati prima dalla giunzione, poi cerca.
Anche il timeout è importante: senza di esso, un robot che supera il ramo, perché viaggiava troppo in fretta oppure il ramo è stato rilevato in modo errato, gira all’infinito.
La classificazione completa, con risoluzione della continuazione dritta
Combina anti-rimbalzo, superamento e svolta:
Junction classifyAndTurn() {
const Branches at = readWideBranches(); // left and/or right, on arrival
driveForward(ONE_ROBOT_LENGTH); // commit past the intersection
const Reading after = read();
if (after.allBlack()) return FINISH; // still on tape = the solid block
Junction j;
j.left = at.left;
j.right = at.right;
j.straight = after.centreOnLine(); // resolved only by moving
if (!j.left && !j.right && !j.straight) return DEAD_END;
return j;
}
Il classificatore guida il robot, cosa insolita per una funzione di sensore e proprio il punto centrale: il tipo di giunzione è una proprietà di una breve traiettoria, non di un singolo campione. Tre dei sei casi, T, incrocio e arrivo, sono indistinguibili all’istante in cui arrivi.
Provalo senza un labirinto
Non ti serve un labirinto per verificare questo strato, e costruirne uno prima che il rilevamento funzioni spreca spesso una serata. Disponi una giunzione di ogni tipo su un pannello e fai percorrere al robot ciascuna giunzione in successione, stampando la classificazione:
| Pezzo di prova | Risultato atteso | Guasto comune |
|---|---|---|
| Linea retta semplice | Nessuna giunzione | Si attiva su una curva leggera: soglia esterna troppo sensibile |
| Curva lieve, raggio 15 cm | Nessuna giunzione | Come sopra |
| Solo ramo a sinistra | LEFT |
Rilevato come T: tempo morto troppo breve, si è attivato due volte |
| Solo ramo a destra | RIGHT |
Simmetrico del caso precedente |
| Giunzione a T | T |
Rilevata come LEFT: non è stato superato il tratto per risolvere la continuazione dritta |
| Incrocio | CROSS |
Rilevato come T: distanza di superamento troppo breve |
| Vicolo cieco | DEAD_END |
Rilevato come perdita di linea e il robot è continuato |
| Blocco pieno di arrivo | FINISH |
Rilevato come CROSS: il blocco è più lungo di una giunzione |
Il test della curva lieve intercetta più bug di tutti gli altri, eppure nessuno pensa di eseguirlo. Una curva stretta mette temporaneamente più sensori esterni sul nastro, producendo esattamente l’aspetto di un ramo. Se il robot inventa giunzioni sulle curve, richiedi che i sensori esterni restino neri per una distanza minima, non soltanto per un numero minimo di campioni: una curva li libera di nuovo rapidamente, un ramo vero no.
Risoluzione dei problemi
| Sintomo | Causa probabile | Correzione |
|---|---|---|
| Giunzioni rilevate sulle curve | Soglia esterna troppo sensibile | Richiedi che il modello persista per una distanza minima |
| Una giunzione registrata due volte | Nessun tempo morto | 200–300 ms dopo aver impegnato il robot in una giunzione |
| T segnalata come svolta a sinistra | Non è stato superato il tratto per risolvere la continuazione dritta | Il classificatore deve muoversi prima di decidere |
| Incrocio segnalato come T | Distanza di superamento troppo breve | Una lunghezza completa del robot oltre l’incrocio |
| Arrivo segnalato come incrocio | Il blocco è più lungo di una giunzione | Continua a guidare; resta nero, mentre un incrocio no |
| Giunzioni completamente mancate | Troppo veloce, oppure array troppo stretto | Rallenta l’esplorazione; array ≥ 3× la larghezza del nastro |
| Le svolte superano la nuova linea | Svolta a tempo o angolo fisso | Svolta finché i sensori centrali non riacquistano la linea |
| Il robot gira all’infinito dopo una giunzione | Nessun timeout di svolta | Aggiungine uno e segnala l’errore in modo pulito |
| Non gira mai | Sta leggendo il nastro della giunzione come nuova linea | Allontanati dalla giunzione prima di cercare |
| Funziona su un pannello e non su un altro | Taratura o luce ambiente | Ritarala a ogni accensione; usa la sottrazione con emettitore spento |
Ottieni l’affidabilità sul nastro reale
Tre fattori compromettono il rilevamento delle giunzioni sull’hardware molto più spesso dell’algoritmo:
- Deriva della taratura. Esegui la scansione di taratura dell’array sia sul nastro nero sia sul pannello bianco a ogni accensione e ripetila quando cambi stanza. La luce infrarossaambientale del sole e delle lampade fluorescenti modifica le letture.
- Altezza del sensore. Tra 5–8 mm dal pavimento. Troppo in alto il contrasto collassa; troppo in basso un pannello leggermente deformato fa graffiare l’array.
- Qualità del nastro. Usa nastro da 18–20 mm e realizza giunzioni fisicamente pulite. Un angolo sfilacciato viene letto come ramo intermittente e nessuna quantità di codice corregge un nastro che ti mente.
Prima rendi stabile il semplice inseguimento di linea su un ovale: regolalo nel Simulatore di robot seguilinea e poi sul nastro reale. Un labirinto nasconderà problemi meccanici e dei sensori dietro ciò che sembra un bug logico.
Esplora il grafo
Fa parte di queste costruzioni
Progetti e percorsi di apprendimento che includono questo tutorial.
Per approfondire