Tutorial · Principiante · 20 min

Leggere un array di sensori IR per seguire una linea

Trasforma una fila di sensori a riflessione IR in un errore di sterzata: scegli sensori analogici o digitali, calibrali e calcola la posizione con media pesata.

Un robot seguilinea è tanto efficace quanto il dato su cui governa. Questo dato proviene da una fila di sensori a riflessione infrarossa puntati verso il pavimento: la linea ha un colore, lo sfondo un altro e i sensori indicano quanta luce IR viene riflessa. Questa guida trasforma quella serie grezza di letture in un’unica posizione della linea uniforme, cioè l’errore su cui agisce il controllore.

In che modo un sensore a riflessione vede la linea

Ogni sensore abbina un LED IR a un fototransistore. Il LED illumina verso il basso e il fototransistore misura la riflessione. Una linea nera opaca riflette poca luce IR, dando una lettura bassa; lo sfondo più chiaro ne riflette di più, dando una lettura alta. Se fai scorrere la matrice su un percorso nero su bianco, ogni sensore passa da un valore «sulla linea» a uno «fuori dalla linea».

Due fattori fanno cambiare questi valori grezzi da una misurazione all’altra: l’altezza del sensore rispetto alla superficie e la luce ambiente. Per questo non codifichi mai soglie fisse, ma esegui una calibrazione. Gli stessi sensori puntati verso il bordo di un anello anziché verso una linea diventano un rilevatore di bordo, in cui una lettura mancata fa perdere al robot la partita.

Sensori analogici o digitali

  • I sensori analogici restituiscono un valore continuo, ad esempio 0–1023 su un ADC di Arduino. Puoi determinare a che distanza si trova la linea da ciascun sensore, il che ti permette di calcolare una posizione uniforme e di governare in modo proporzionale.
  • I sensori digitali indicano soltanto on/off dopo una soglia interna. Sono più semplici da collegare, ma perdi l’informazione intermedia e quindi lo sterzo è meno fine.

Per il controllo proporzionale dell’inseguimento, quello che regoli con un controllore PID, usa le letture analogiche. È l’approccio modellato dal simulatore del robot seguilinea.

Calibra prima

Prima di una sessione, fai scorrere ogni sensore sia sulla linea sia sullo sfondo e registra per ciascun sensore il minimo e il massimo. Quindi normalizza ogni lettura in tempo reale su 0–1000 rispetto al suo intervallo:

// Per-sensor calibration captured during a sweep.
int minv[N], maxv[N];

int normalized(int raw, int i) {
  long span = maxv[i] - minv[i];
  if (span <= 0) return 0;                       // sensor never saw contrast
  long v = (long)(raw - minv[i]) * 1000 / span;  // 0 (background) … 1000 (line)
  return constrain(v, 0, 1000);
}

La calibrazione per singolo sensore compensa la luminosità non uniforme dei LED e le piccole differenze di altezza lungo la barra, così che un sensore non introduca di nascosto un bias nell’intera stima.

Dalle letture a un’unica posizione della linea

Ora riduci la matrice a un’unica posizione mediante una media pesata per riflettanza. Assegna a ogni sensore una posizione fissa, ad esempio …−2, −1, 0, +1, +2… lungo la barra, e ponderala in base alla lettura normalizzata. Il risultato è la posizione della linea rispetto al centro della barra, positiva su un lato e negativa sull’altro, pari a zero quando la linea si trova esattamente al centro.

Grafico a barre di cinque sensori a riflessione distribuiti sulla barra del robot. La linea si trova a destra del centro, quindi i sensori sul lato destro restituiscono i valori più alti. Una linea tratteggiata indica il centro della barra, corrispondente a errore zero, e una linea continua indica la posizione pesata per riflettanza a più 0.69, l'errore di sterzata calcolato.
La media pesata delle letture dei sensori fornisce una posizione della linea uniforme: qui la linea si trova a destra del centro, quindi l'errore è positivo e il controllore riporta la sterzata verso zero. Scarica SVG
// Sensor positions across the bar, e.g. { -2000,-1000,0,1000,2000 }.
long readLinePosition() {
  long weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    int v = normalized(analogRead(pins[i]), i);
    weighted += (long)v * position[i];
    total    += v;
  }
  if (total == 0) return lastPosition;  // line lost — reuse last direction
  lastPosition = weighted / total;
  return lastPosition;                  // 0 = centered, sign = which side
}

Due dettagli rendono questa soluzione robusta:

  • Dividi per la somma delle letture, non per il numero di sensori, così sia una linea tenue sia una linea marcata corrispondono alla stessa scala di posizione.
  • Quando tutti i sensori perdono la linea (total == 0), riutilizza l’ultima posizione nota anziché restituire zero. In questo modo il robot continua a girare verso il punto in cui si trovava la linea, invece di allontanarsi dritto da una curva brusca.

Quanti sensori servono?

Cinque è un buon punto di partenza: sono abbastanza per vedere direzione e grandezza del guasto, mantenendo al contempo una matematica semplice da verificare. Tre bastano a bassa velocità; i robot da competizione ne usano otto o più per una risoluzione più fine alle alte velocità. Più sensori permettono soprattutto una stima più uniforme della posizione nelle curve strette.

La routine di calibrazione completa

«Calibra prima» è facile da dire e facile da eseguire male. Ecco l’intera routine, inclusa la protezione che impedisce a un canale morto di rovinare la stima:

const int N = 8;
const int PIN[N] = {A0, A1, A2, A3, A4, A5, A6, A7};
int minv[N], maxv[N];

void calibrate(unsigned long durationMs) {
  for (int i = 0; i < N; i++) { minv[i] = 1023; maxv[i] = 0; }

  const unsigned long start = millis();
  while (millis() - start < durationMs) {
    for (int i = 0; i < N; i++) {
      const int v = analogRead(PIN[i]);
      if (v < minv[i]) minv[i] = v;
      if (v > maxv[i]) maxv[i] = v;
    }
  }

  // A channel that never saw contrast would turn noise into huge normalised
  // values and drag the whole estimate toward itself.
  for (int i = 0; i < N; i++) {
    if (maxv[i] - minv[i] < 50) {
      Serial.print(F("channel dead or never crossed the line: "));
      Serial.println(i);
    }
  }
}

Fai scorrere la matrice sulla linea per l’intera finestra di calibrazione. Un robot fermo registra la stessa lettura sia come minimo sia come massimo, producendo un intervallo nullo e un sensore che non contribuisce a nulla. La procedura standard consiste nel ruotare il robot sul posto sopra la linea per tre secondi; funziona perché ogni canale passa sul nastro.

Ricalibra all’inizio di ogni sessione, non una sola volta durante la realizzazione. La luce ambiente cambia, l’altezza da terra cambia quando si sposta il pacco batteria e il nastro accumula polvere. Questo è il motivo più comune per cui un robot seguilinea funziona su un tavolo e fallisce su un altro.

Eliminare l’influenza della luce ambiente

La luce solare contiene molta radiazione infrarossa a 940 nm e il fototransistore non riesce a distinguerla dalla riflessione del tuo emettitore. Un robot che funziona al chiuso e fallisce vicino a una finestra sta subendo esattamente questo effetto.

Se puoi attivare e disattivare gli emettitori della matrice, come consentono il pin LEDON o CTRL nella maggior parte dei dispositivi, la soluzione è completa e costa una lettura aggiuntiva per canale:

int ambientSubtracted(int i) {
  digitalWrite(LEDON, LOW);
  delayMicroseconds(200);              // let the phototransistor settle
  const int dark = analogRead(PIN[i]); // ambient only

  digitalWrite(LEDON, HIGH);
  delayMicroseconds(200);
  const int lit = analogRead(PIN[i]);  // ambient + our emitter

  return lit - dark;                   // just our emitter
}

Qualsiasi componente costante nell’ambiente si annulla esattamente. Il costo è il doppio del tempo di campionamento, una penalità reale su un robot seguilinea veloce, ma trasforma un sensore inaffidabile in uno affidabile: è la tecnica impiegata dalle matrici commerciali.

C’è anche un secondo vantaggio degno di nota: con gli emettitori spenti tra una lettura e l’altra, la loro corrente media diminuisce bruscamente. Gli emettitori di una matrice a 8 canali possono assorbire 100–200 mA in modo continuo, una quota significativa del bilancio energetico con un pacco piccolo.

Perdere la linea e ritrovarla

Il caso total == 0 descritto sopra merita più attenzione di quanta ne riceva di solito, perché è il luogo in cui un robot seguilinea si guasta più spesso in modo evidente.

Restituire zero è l’implementazione ovvia, ma è sbagliata: zero significa «perfettamente centrato», quindi il robot avanza dritto a tutta velocità, allontanandosi dalla linea appena persa, che era quasi certamente in curva.

Restituire l’ultima posizione fa sì che il robot continui a girare nella direzione in cui stava già girando, cioè verso il punto in cui la linea si è spostata. Questa sola modifica distingue un robot che si riprende dopo una curva brusca da uno che abbandona il percorso.

Una versione più completa aggiunge un timeout, così un robot che ha davvero perso la linea si ferma invece di descrivere una spirale:

long lastPosition = 0;
unsigned long lostSince = 0;

long readLinePosition() {
  long weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    const int v = normalized(analogRead(PIN[i]), i);
    if (v > 100) {                    // ignore channels clearly on the floor
      weighted += (long)v * POSITION[i];
      total    += v;
    }
  }

  if (total == 0) {
    if (lostSince == 0) lostSince = millis();
    if (millis() - lostSince > 1500) stopMotors();   // genuinely lost
    return lastPosition;              // keep turning the way we were
  }

  lostSince = 0;
  lastPosition = weighted / total;
  return lastPosition;
}

Anche la soglia v > 100 è importante. Senza di essa, otto canali che leggono ciascuno un piccolo valore di fondo non nullo trascinano la media pesata verso il centro della matrice, indipendentemente da dove si trovi realmente la linea. Il sintomo è un robot che segue bene una linea marcata e male una linea sbiadita.

L’altezza da terra determina il contrasto

I sensori a riflessione hanno un intervallo ottimale ristretto, e proprio questa è la variabile meccanica che più spesso si presenta come un problema software.

Altezza da terra Effetto sull’intervallo di calibrazione
1–3 mm Intervallo massimo, ma la matrice graffia su qualsiasi irregolarità del pavimento
3–8 mm La fascia utilizzabile. La maggior parte delle matrici indica circa 3 mm
8–15 mm L’intervallo si riduce nettamente; la normalizzazione inizia ad amplificare il rumore
> 15 mm Praticamente cieca sulla maggior parte delle superfici

Ne seguono due conseguenze. Montala rigidamente: una matrice su un supporto flessibile cambia la propria altezza quando il robot accelera, il che modifica le letture e viene interpretato dal loop di controllo come un movimento della linea. Inoltre, montala in piano, perché una matrice inclinata dà a ogni canale un’altezza effettiva diversa e quindi una calibrazione diversa; si tradurrà in un robot che segue bene in una direzione e male nell’altra.

Quanti sensori servono, in modo quantitativo

Canali Risoluzione di posizione su una linea di 19 mm Adatto a
3 Essenzialmente sinistra / centro / destra Comprendere il principio
5 ~4 regioni discrete, con valori uniformi intermedi Un primo robot seguilinea su un percorso dolce
8 ~7 regioni Percorsi veloci, curve strette, rilevamento delle intersezioni
12–16 Fine Robot da competizione ad alta velocità

Più del numero conta la larghezza della matrice rispetto alla linea. Per il semplice inseguimento, la matrice deve essere abbastanza larga da avere almeno un sensore sul pavimento a ciascun bordo della linea, quindi circa il doppio della larghezza del nastro.

Per il rilevamento delle intersezioni in un labirinto, il requisito è più rigoroso: il robot riconosce un ramo quando vede i sensori estremi diventare scuri mentre il centro è ancora sulla linea, quindi la matrice deve essere larga almeno tre volte la larghezza del nastro. Una matrice a 8 canali con passo di 8 mm misura circa 60 mm, l’altezza giusta per nastro da 19 mm.

Risoluzione dei problemi

Sintomo Causa probabile Soluzione
Funziona su una superficie e fallisce su un’altra Calibrazione obsoleta o assente Ricalibra all’inizio di ogni sessione
Un canale trascina la stima Emettitore morto: il suo intervallo è minimo Controlla maxv − minv di ogni canale e proteggi il calcolo
Il robot avanza dritto quando perde la linea Restituisce 0 alla perdita della linea Restituisci l’ultima posizione
Segue bene nastro marcato e vaga su nastro sbiadito Rumore del pavimento entra nella media Aggiungi la soglia v > 100
Fallisce vicino a una finestra Infrarosso ambiente Sottrai la lettura con emettitore spento oppure scherma la matrice
La posizione salta mentre la linea attraversa i sensori Nessuna normalizzazione per canale Calibra ogni canale sul proprio minimo e massimo
Le letture derivano durante una sessione lunga La tensione della batteria cala e gli emettitori si attenuano Controlla la tensione di alimentazione sotto carico
Tutti i canali restituiscono lo stesso valore Matrice troppo alta 3–8 mm, poi ricalibra
Segue bene in una direzione e male nell’altra Matrice non in piano Ogni canale ha un’altezza effettiva diversa

Come proseguire

Quel singolo valore di posizione è il termine di errore. Immettilo in un loop di controllo che accelera una ruota e rallenta l’altra: avrai un robot seguilinea. Impara a realizzare quel loop in Regola un controllore PID, poi prova l’intero sistema dall’inizio alla fine nel simulatore del robot seguilinea.

Esplora il grafo

Fa parte di queste costruzioni

Progetti e percorsi di apprendimento che includono questo tutorial.

Per approfondire

Fonti