Tutorial · Principiante · 20 min di lettura

Costruisci un robot seguilinea: dai sensori allo sterzo con PID

Come funziona un robot seguilinea: sensori a riflessione, errore di posizione ponderato e un ciclo PID che sterza dolcemente un telaio a trazione differenziale.

Un robot seguilinea è il classico primo robot autonomo: legge una linea sotto i suoi sensori e sterza per restarci sopra. Sotto, è un piccolo problema di controllo—trasformare le letture dei sensori in una correzione di sterzo—che lo rende un modo perfetto per imparare il controllo in retroazione. Segui il tutorial nel Simulatore di robot seguilinea.

Rileva la linea

Un sensore a riflessione proietta luce infrarossa verso il basso e misura quanta ne torna. Una linea scura riflette meno della superficie chiara intorno, quindi ogni sensore segnala se si trova sulla linea. Una barra di più sensori sul fronte del robot dice non solo se sei sulla linea, ma dove si trova rispetto al centro.

Cinque sensori sono un buon punto di partenza. Tre funzionano a bassa velocità; i robot da competizione ne usano spesso otto per una risoluzione più fine.

Cinque sensori di linea in fila, ciascuno etichettato con un peso da meno due a sinistra, zero al centro e più due a destra, con sotto una lettura di uno o zero. I due sensori con peso zero e più uno leggono uno e sono evidenziati; gli altri leggono zero. La formula della media ponderata calcola la somma dei pesi moltiplicati per le letture divisa per la somma delle letture, dando zero più uno diviso due uguale a più zero virgola cinque. Un avvertimento dice che tutti zeri significa linea persa, che è un caso diverso e non un errore pari a zero.
Cinque letture on/off si riducono a un unico numero con segno su cui il ciclo di sterzo può agire. Il segno indica la direzione, la grandezza quanto sei fuori centro. Scarica SVG

Trasforma le letture in un unico errore

Invece di reagire ai singoli sensori, combinale in un’unica posizione ponderata. Assegna a ogni sensore un valore di posizione (negativo a sinistra, zero al centro, positivo a destra) e calcola la media pesata in base alla forza con cui ciascuno vede la linea:

// positions: {-2, -1, 0, 1, 2}; readings: reflectance per sensor
float weightedPosition = sum(position[i] * reading[i]) / sum(reading[i]);

Il risultato è il tuo errore: zero significa centro, negativo significa che la linea è a sinistra, positivo significa che è a destra. Un errore continuo e regolare è esattamente ciò di cui un controllore ha bisogno.

Sterza con il PID

Passa questo errore a un controllore PID. La correzione accelera una ruota e rallenta l’altra, riportando il robot verso la linea:

float correction = kp * error + ki * integral + kd * derivative;
setMotorSpeeds(baseSpeed - correction, baseSpeed + correction);

Il guadagno proporzionale gestisce gran parte dello sterzo; quello derivativo attenua l’oscillazione sulle curve; un piccolo termine integrale elimina la deriva costante. Se sei alle prime armi nella scelta dei guadagni, segui prima come regolare un controllore PID.

Correggi i guasti comuni

  • Oscillazione intorno alla linea: il guadagno proporzionale è troppo alto o quello derivativo troppo basso. Riduci prima la velocità, poi regola di nuovo. Sull’hardware reale, un delay() lungo nel ciclo aggiunge tempo morto che vanifica il termine derivativo.
  • Perdita della linea nelle svolte brusche: il robot supera la frequenza di aggiornamento dei sensori. Rallenta, allarga la barra oppure riutilizza l’ultima direzione nota della linea, così il robot sterza indietro invece di uscire dritto dalla linea.
  • Deriva verso un lato: calibra i sensori. Falli scorrere sulla linea e sullo sfondo, registra il minimo e il massimo di ciascuno e normalizza le letture in tempo reale rispetto a quell’intervallo.

L’intero ciclo, in un unico posto

I frammenti precedenti diventano un robot funzionante solo quando temporizzazione, calibrazione e caso di perdita della linea vengono gestiti insieme:

const int N = 5;
const int PIN[N] = {A0, A1, A2, A3, A4};
const int POS[N] = {-2000, -1000, 0, 1000, 2000};   // weights, in the error's units

int minv[N], maxv[N];
long lastError = 0;
float integral = 0;
unsigned long lastMicros = 0;

const float KP = 0.10f, KI = 0.0f, KD = 1.6f;
const int   BASE = 140;                              // out of 255

void loop() {
  // 1. Real elapsed time — never assume the loop period.
  const unsigned long now = micros();
  const float dt = (now - lastMicros) * 1e-6f;
  if (dt < 0.002f) return;                           // cap the rate at 500 Hz
  lastMicros = now;

  // 2. Position from the weighted average.
  long weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    const int v = normalized(analogRead(PIN[i]), i);
    if (v > 100) { weighted += (long)v * POS[i]; total += v; }
  }

  // 3. Line lost: hold the last error, do NOT zero it.
  const long error = (total == 0) ? lastError : (weighted / total);

  // 4. PID, with derivative on measurement and conditional integration.
  const float derivative = -(float)(error - lastError) / dt;
  const float candidate = integral + error * dt;
  const float raw = KP * error + KI * candidate + KD * derivative;
  const float correction = constrain(raw, -BASE, BASE);
  if (raw == correction) integral = candidate;

  lastError = error;

  // 5. Differential steering.
  setMotors(BASE - correction, BASE + correction);
}

Cinque elementi in questo codice separano un robot che segue una linea da uno che quasi ci riesce.

dt reale. Sia I sia D dividono per il tempo trascorso. Un ciclo la cui durata cambia man mano che aggiungi funzionalità—e cambierà—calcola entrambi contro una menzogna se assumi un periodo costante.

La soglia v > 100. Senza di essa, cinque canali che leggono ciascuno un piccolo valore di fondo trascinano la media ponderata verso il centro, indipendentemente da dove si trovi la linea. Il sintomo è un robot che segue bene una linea netta e male una linea sbiadita.

Mantenere l’ultimo errore in caso di perdita della linea. Zero significa «centrato», quindi un robot che mette zero procede dritto a piena velocità—via dalla linea, che quasi certamente stava curvando. Mantenere l’ultimo errore lo fa sterzare nella direzione in cui stava già sterzando.

Derivativo sulla misura. Differenziare l’errore significa che un cambio del valore di riferimento produce un enorme picco. Qui il valore di riferimento è sempre zero, quindi la differenza è piccola, ma vale la pena acquisire questa abitudine.

Integrazione condizionale. Quando la correzione satura, continuare ad accumulare l’integrale significa solo immagazzinare un overshoot da restituire in seguito.

Un ordine di regolazione che funziona

Non regolare tutti e tre contemporaneamente. Imposta KI = 0 e KD = 0, poi:

Passaggio Modifica Osserva Fermati quando
1 Alzare KP da un valore piccolo Il robot inizia a rispondere alla linea segue una curva dolce, oscillando un po’
2 Continuare ad alzare KP L’oscillazione cresce oscilla stabilmente— annota questo valore come Ku
3 Impostare KP a circa metà di Ku La risposta è rapida, con overshoot —
4 Alzare KD L’oscillazione si smorza le dritte sono pulite; un ulteriore KD lo rende nervoso
5 Solo se segue costantemente fuori centro, aggiungere un po’ di KI Lo scostamento si riduce si riduce— KI serve raramente su un seguilinea
6 Alzare la velocità BASE Tutto peggiora insieme regola di nuovo KP e KD alla nuova velocità

Il passaggio 6 è quello che le persone dimenticano: i guadagni dipendono dalla velocità. Una regolazione perfetta a un duty di 140 oscillerà a 220, perché la stessa correzione di sterzo produce ora una variazione maggiore della direzione per unità di tempo. Aspettati di dover regolare di nuovo ogni volta che cambi la velocità in modo significativo.

L’integrale è davvero raramente utile qui. Il valore di riferimento di un seguilinea è zero e le perturbazioni sono transitorie, quindi di solito non esiste uno scostamento costante che KI possa eliminare. Se vedi uno scostamento persistente, sospetta prima una regolazione disuguale dei motori o un sensore non calibrato, poi passa al guadagno integrale.

La frequenza del ciclo limita tutto in silenzio

Frequenza del ciclo Intervallo Distanza percorsa a 0.4 m/s
20 Hz 50 ms 20 mm tra una decisione e l’altra
50 Hz 20 ms 8 mm
200 Hz 5 ms 2 mm
500 Hz 2 ms 0.8 mm

Un robot che decide ogni 20 mm non può seguire strettamente una curva, indipendentemente dai guadagni, e nessun termine derivativo recupera informazioni che non sono mai state campionate.

I colpevoli abituali sono facili da eliminare: un delay() ovunque nel ciclo, Serial.print di più di pochi valori a 9600 baud e la lettura di sensori che non usi. Rimuoverli porta di solito una prima versione da 50 Hz a diverse centinaia, e l’inseguimento migliora senza toccare un solo guadagno. Misura la frequenza prima di regolare, non dopo.

Risoluzione dei problemi

Sintomo Causa probabile Soluzione
Oscilla sulle dritte KP troppo alto o KD troppo basso Dimezza KP, poi alza KD
Oscilla sempre più velocemente KP superiore al guadagno ultimo Riduci finché non si stabilizza
Taglia gli angoli Troppo veloce per l’anticipo del sensore Rallenta oppure sposta in avanti la schiera
Deriva lontano dalla linea quando la perde Errore azzerato in caso di perdita della linea Mantieni l’ultimo errore
Segue bene in una direzione e male nell’altra Array non in piano oppure motori non regolati Livella la schiera; regola ogni motore
Deriva verso un lato Sensori non calibrati oppure motori diversi Calibra ogni canale, poi regola
Funziona sul banco, vaga sul pavimento Altezza di marcia cambiata 3–8 mm, supporto rigido, ricalibra
Funzionava ieri, oggi è lento Tensione della batteria I guadagni sono proprietà del sistema—regola di nuovo o chiudi un ciclo di velocità
Vibra solo a bassa velocità Sotto la banda morta dei motori Misura il duty di avvio di ogni motore e compensalo
KD non produce alcuna reazione Un delay() nel ciclo Il tempo morto vanifica completamente l’azione derivativa

Provalo tu

Nel Simulatore di robot seguilinea puoi disegnare un tracciato, cambiare il numero di sensori e la velocità e regolare i guadagni mentre osservi l’errore e il tempo del giro. Tutto ciò che impari sull’errore ponderato e sul ciclo di sterzo si trasferisce direttamente a un robot Arduino.

Esplora il grafo

Fa parte di queste costruzioni

Progetti e percorsi di apprendimento che includono questo tutorial.

Per approfondire

Fonti