Guide · 17 min di lettura

Robot seguilinea con Arduino: sensori TCRT5000 e controllo PID

Robot seguilinea con Arduino: barra di sensori TCRT5000, posizione e calibrazione, codice if-else e PID, regolazione di Kp/Kd, driver, batteria e regole.

Un robot seguilinea è un veicolo autonomo che rileva una linea nera (o bianca) sul pavimento con sensori a infrarossi e regola di continuo la velocità delle sue due ruote per restarci sopra. È uno dei primi veri progetti di robotica che si realizzano con Arduino, perché riunisce in un unico progetto la lettura dei sensori, le decisioni e il controllo dei motori. I veicoli a guida automatica (AGV) che spostano materiali nelle fabbriche funzionano con la stessa idea, e il seguilinea è una delle categorie più affollate nelle gare di robotica scolastiche.

In questa guida imparerai come funziona il sensore TCRT5000, che differenza c'è tra le barre a 2, 3 e 5 sensori, a quale altezza e con quale distanza montare i sensori e come calibrare la soglia. Prima farai muovere il robot con un semplice sketch if-else, poi passerai a una versione con controllo PID che segue la linea senza zigzagare e senza uscire di pista in curva. Oltre al codice per Arduino Uno, ESP32 e Raspberry Pi Pico, troverai come scegliere il driver dei motori e la batteria, le regole tipiche delle gare e le soluzioni ai problemi più comuni.

Sensore a infrarossi a riflessione TCRT5000
TCRT5000: un LED a infrarossi blu e un fototransistor nero affiancati nello stesso contenitore; i moduli sensore di linea sono costruiti attorno a questo componente.

Come funziona un robot seguilinea?

Una superficie bianca riflette gran parte della luce infrarossa, mentre una superficie nera opaca ne assorbe quasi tutta. I sensori sotto il robot misurano questa differenza e capiscono dove si trova la linea rispetto al robot. Il robot non ha sterzo: gira con la cosiddetta trazione differenziale. Se la ruota sinistra gira più veloce della destra il robot curva a destra, se gira più piano curva a sinistra. Il programma ripete lo stesso ciclo centinaia di volte al secondo: legge i sensori, calcola di quanto si è spostata la linea e corregge la velocità dei due motori. Più questo anello chiuso è veloce e stabile, più il robot procede fluido.

ComponenteConsigliatoNota
Scheda di controlloArduino Uno o NanoIl Nano entra più facilmente in un telaio piccolo; vanno bene anche ESP32 o Pico
Sensore di lineaBarra TCRT5000 a 3 o 5 canali, QTR-8ACon il PID una barra a uscite analogiche dà risultati migliori
Driver motoriL298N o TB6612FNGIl TB6612FNG è più piccolo ed efficiente
Motori2 motoriduttori DC (TT 1:48 o N20)I due motori devono essere dello stesso modello e con gli stessi giri
Ruote2 ruote motrici e 1 ruota pivottanteLe ruote da 65 mm di diametro sono le più diffuse
Alimentazione2 batterie Li-ion 18650 (7,4 V) e un interruttoreUna pila da 9 V è troppo debole per i motori
AltroTelaio, pulsante, cavetti jumperUn pulsante semplifica calibrazione e partenza
Arduino Nano
Arduino Nano
L298N
L298N
N20
N20
18650 Li-ion
18650 Li-ion

Il sensore TCRT5000: barre a 2, 3 e 5 sensori

Il TCRT5000 è composto da un LED a infrarossi (950 nm) e da un fototransistor affiancati nello stesso contenitore. Più luce del LED viene riflessa dal pavimento e raggiunge il fototransistor, più il transistor conduce. Il sensore lavora al meglio a circa 2,5 mm di distanza; oltre 1 cm la differenza tra nero e bianco cala rapidamente. I moduli pronti includono un comparatore LM393: nella maggior parte dei moduli il pin DO dà HIGH sul nero e LOW sul bianco in base alla soglia regolata con il potenziometro, e il LED sulla scheda si accende sulla superficie bianca. Il pin AO fornisce il valore analogico grezzo: alto sul nero, basso sul bianco. Trovi tutti i dettagli sul sensore nell'articolo Sensore a infrarossi TCRT5000.

SensoriPosizioneAlgoritmoQuando sceglierlo
2Ai due lati della linea; entrambi vedono normalmente il biancoif-elsePrimo tentativo, pista larga con curve dolci
3Il centrale sulla linea, i laterali sui suoi bordiif-else o PID sempliceProgetti scolastici, gare di livello base
5Fila a passo uniforme, il centrale sulla lineaPID (media pesata)Inseguimento veloce e fluido
8Fila larga (come la QTR-8A)PID e rilevamento degli incrociPiste da gara con curve strette e incroci

Posizione dei sensori: altezza, distanza e sporgenza

Per quanto buono sia il tuo codice, se i sensori sono nel posto sbagliato il robot non può andare bene. Monta la barra dei sensori davanti all'asse delle ruote, parallela al pavimento e simmetrica rispetto all'asse centrale del robot. Queste misure sono un buon punto di partenza:

  • Altezza: Lascia 3–5 mm tra il lato inferiore del sensore e il pavimento. Pololu indica per la sua barra QTR-8A una distanza ottimale di 3 mm e una massima consigliata di 6 mm. Più in alto la differenza tra nero e bianco si riduce e il robot perde la linea.
  • Passo: Nelle barre analogiche la distanza tra due sensori dovrebbe stare tra metà e due terzi della larghezza della linea; per una linea da 20 mm vanno bene 10–13 mm. Così la linea cade quasi sempre sotto due sensori e la posizione viene calcolata in modo fluido.
  • Disposizione digitale a 3 sensori: Il sensore centrale sta proprio sopra la linea e quelli laterali appena fuori dai suoi bordi; per una linea da 20 mm circa 18–22 mm da centro a centro.
  • Sporgenza: Metti i sensori 4–8 cm davanti all'asse delle ruote. Se sono troppo vicini il robot vede le curve in ritardo; se sono troppo lontani anche una piccola rotazione sposta molto i sensori e il robot oscilla.
  • Luce ambiente: Anche la luce del sole e le lampade alogene contengono infrarossi. Schermare i sensori con una gonnellina o del nastro nero rende le letture molto più stabili.
Un errore frequente è montare i sensori storti o troppo alti. Se il robot perde la linea alla prima curva, controlla l'altezza dei sensori prima del codice: bastano pochi millimetri per dimezzare i valori letti. Inoltre il nastro isolante lucido riflette l'infrarosso e una linea nera a volte sembra bianca; se puoi, usa nastro opaco o una linea stampata.

Collegamenti: Arduino Uno, ESP32 e Pico

La tabella qui sotto corrisponde esattamente allo sketch di test dei sensori e agli sketch PID; i collegamenti dell'esempio if-else a 3 sensori sono spiegati nella sua sezione. Numera i sensori da sinistra a destra guardando il robot da dietro: il sensore più a sinistra è il numero 1. Come driver dei motori usiamo un L298N; la funzione dei pin e la logica del ponte H sono spiegate nel dettaglio in Controllo di un motore DC con L298N. Il Pico ha solo tre ingressi analogici, quindi lì usiamo i sensori 1, 3 e 5.

CollegamentoArduino UnoESP32Raspberry Pi Pico
Sensore 1 (più a sinistra) AOA0GPIO 36GP26
Sensore 2 AOA1GPIO 39-
Sensore 3 (centrale) AOA2GPIO 34GP27
Sensore 4 AOA3GPIO 35-
Sensore 5 (più a destra) AOA4GPIO 32GP28
VCC dei sensori5V3V33V3
L298N ENA (velocità motore sinistro)D9GPIO 25GP15
L298N IN1, IN2D8, D7GPIO 26, 27GP14, GP13
L298N ENB (velocità motore destro)D10GPIO 33GP10
L298N IN3, IN4D5, D4GPIO 18, 19GP11, GP12
Pulsante di avvio (altro piedino a GND)D2GPIO 23GP16
GNDGNDGNDGND (in comune con batteria e driver)
Il negativo della batteria, il GND del driver e il GND della scheda devono essere collegati tra loro; senza massa comune i motori si comportano in modo imprevedibile. Non alimentare mai i motori dal pin 5V della scheda. Su ESP32 e Pico alimenta i sensori dal 3V3: l'uscita AO di un modulo alimentato a 5 V può arrivare a 5 V e danneggiare gli ingressi analogici a 3,3 V.

Scegliere driver motori e batteria

L'L298N è economico e robusto, ma i suoi transistor bipolari si mangiano 1,5–3 V: da un pacco da 7,4 V ai motori arrivano solo 5–5,5 V circa. Nei robot piccoli e veloci rende meglio un TB6612FNG o un DRV8833 a MOSFET. Con il TB6612FNG il codice resta uguale: colleghi i fili di ENA, IN1 e IN2 a PWMA, AIN1 e AIN2 (quelli del canale B a PWMB, BIN1 e BIN2) e il pin STBY a VCC. Il DRV8833 non ha un pin di velocità (enable) separato: il segnale PWM va direttamente sui pin IN, quindi devi adattare la funzione drive(). Per l'alimentazione la scelta più pratica sono due batterie Li-ion 18650 in serie: 8,4 V a piena carica, 7,4 V nominali, e forniscono correnti elevate senza fatica. Le pile da 9 V non reggono la corrente dei motori e si scaricano in fretta. Puoi calcolare quanto durerà la tua batteria con lo strumento Durata batteria.

DriverTensione motoreCorrente per canaleCaduta di tensioneGiudizio
L298N5–35 V2 A1,5–3 VRobusto ma ingombrante e poco efficiente
TB6612FNG4,5–13,5 V1,2 A (picco 3,2 A)Molto bassaIl miglior rapporto qualità-prezzo per un seguilinea
DRV88332,7–10,8 V1,5 A (picco 2 A)Molto bassaRobot piccoli con motori N20
L9110S2,5–12 V0,8 ABassaSolo robot leggeri e lenti
Motoriduttore giallo TT con ruota
Motoriduttore giallo TT con ruota da 65 mm: il riduttore abbassa i giri e aumenta la coppia; è il motore più diffuso nei seguilinea e nelle auto robot.

Un esempio di velocità: i motori gialli TT (1:48) girano a circa 200 giri/min a vuoto a 6 V. Una ruota da 65 mm ha una circonferenza di π × 65 ≈ 204 mm, quindi il robot può andare al massimo a 200 × 0,204 ≈ 41 m al minuto, circa 0,7 m al secondo. È più che sufficiente per la maggior parte delle piste scolastiche; la vera sfida è non perdere la linea a quella velocità. I robot più veloci usano micromotoriduttori N20 e un telaio leggero. Quando la tensione della batteria cala, i motori rallentano e i valori PID che hai regolato non vanno più bene; misurare la tensione con un partitore di tensione e scalare la velocità di conseguenza è un miglioramento di livello avanzato.

Passo 1: prova i sensori e trova la soglia

Prima di collegare i motori, scopri cosa vedono i sensori. Lo sketch qui sotto stampa il valore analogico grezzo dei cinque sensori separato da tabulazioni; aprendo nell'IDE Arduino Strumenti > Plotter seriale a 115200 baud vedrai ogni sensore come una linea separata. Fai passare lentamente il robot sopra la linea a mano e guarda come cambiano i valori.

Arduino (C++)
1// 5 sensörün ham değerlerini okur / reads the raw values of 5 sensors
2const int N = 5;
3const int SENSOR[N] = {A0, A1, A2, A3, A4};   // A0 en solda / A0 is the leftmost
4
5void setup() {
6  Serial.begin(115200);
7}
8
9void loop() {
10  for (int i = 0; i < N; i++) {
11    Serial.print(analogRead(SENSOR[i]));
12    Serial.print(i < N - 1 ? '\t' : '\n');    // Serial Plotter için sekme / tabs for the Serial Plotter
13  }
14  delay(50);
15}

A 5 V un tipico modulo TCRT5000 legge 30–200 sulla superficie bianca e 600–1000 sulla linea nera, ma questi numeri cambiano con l'altezza del sensore, il materiale del pavimento e la luce ambiente. La soglia più semplice è: soglia = (valore sul bianco + valore sul nero) / 2. Per esempio, se leggi 80 sul bianco e 820 sul nero, la soglia è 450. Poiché ogni sensore legge in modo un po' diverso, lo sketch PID lo farà da solo: durante la calibrazione memorizza il valore minimo e massimo di ogni sensore, poi riporta ogni lettura tra 0 (bianco) e 1000 (nero) con (lettura − min) × 1000 / (max − min). Così la soglia diventa 500 per tutti i sensori.

Nei moduli con uscita digitale (DO) la soglia si regola con il potenziometro sulla scheda. Gira il trimmer in modo che il LED del modulo resti spento quando il sensore è sopra la linea nera e acceso sopra la superficie bianca; la posizione esattamente a metà tra questi due punti è la più stabile. Fai la regolazione sulla pista di gara e con la stessa illuminazione.

Passo 2: semplice algoritmo if-else (3 sensori)

Per un primo robot funzionante, la via più chiara è scegliere tra poche mosse prefissate in base allo stato dei tre sensori. Collega le uscite DO di un modulo a 3 canali ad A0 (sinistra), A1 (centro) e A2 (destra); i pin analogici della Uno funzionano anche come ingressi digitali. La logica è quella di questa tabella (1 significa che il sensore vede la linea):

SinistraCentroDestraSignificatoMovimento
010Linea al centroDritto
110Linea un po' a sinistraLa ruota sinistra rallenta, curva dolce a sinistra
100Linea molto a sinistraLa ruota sinistra va indietro, curva stretta a sinistra
011Linea un po' a destraLa ruota destra rallenta, curva dolce a destra
001Linea molto a destraLa ruota destra va indietro, curva stretta a destra
111Incrocio o linea d'arrivoTirare dritto
000Linea persaGirare verso l'ultimo lato visto
Arduino (C++)
1// 3 sensörlü basit çizgi izleyen / simple 3-sensor line follower
2const int ENA = 9, IN1 = 8, IN2 = 7;      // sol motor / left motor
3const int ENB = 10, IN3 = 5, IN4 = 4;     // sağ motor / right motor
4const int S_LEFT = A0, S_MID = A1, S_RIGHT = A2;   // sensörlerin DO çıkışları / sensor DO outputs
5const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
6
7const int SPEED = 150;                    // düz gidiş hızı / straight-line speed
8const int SOFT = 80;                      // hafif dönüşte iç teker / inner wheel on a soft turn
9const int BACK = -90;                     // sert dönüşte iç teker geri / inner wheel reverses on a sharp turn
10int lastSide = 0;                         // -1 sol, 1 sağ / -1 left, 1 right
11
12void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
13  left = constrain(left, -255, 255);
14  right = constrain(right, -255, 255);
15  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
16  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
17  analogWrite(ENA, abs(left));
18  analogWrite(ENB, abs(right));
19}
20
21void setup() {
22  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4};
23  for (int p : outputs) pinMode(p, OUTPUT);
24  pinMode(S_LEFT, INPUT);
25  pinMode(S_MID, INPUT);
26  pinMode(S_RIGHT, INPUT);
27}
28
29void loop() {
30  bool l = digitalRead(S_LEFT) != WHITE_LINE;    // true = sensör çizgide / true = sensor on the line
31  bool m = digitalRead(S_MID) != WHITE_LINE;
32  bool r = digitalRead(S_RIGHT) != WHITE_LINE;
33
34  if (m && !l && !r) {                    // tam ortada: düz git / centered: go straight
35    drive(SPEED, SPEED);
36  } else if (l && r) {                    // kavşak ya da bitiş çizgisi: düz geç / junction or finish line: go straight
37    drive(SPEED, SPEED);
38  } else if (l && m) {                    // çizgi biraz solda / line slightly to the left
39    drive(SOFT, SPEED);  lastSide = -1;
40  } else if (r && m) {                    // çizgi biraz sağda / line slightly to the right
41    drive(SPEED, SOFT);  lastSide = 1;
42  } else if (l) {                         // çizgi çok solda / line far to the left
43    drive(BACK, SPEED);  lastSide = -1;
44  } else if (r) {                         // çizgi çok sağda / line far to the right
45    drive(SPEED, BACK);  lastSide = 1;
46  } else {                                // çizgi kayıp: son görülen tarafa dön / line lost: turn to the last seen side
47    if (lastSide < 0)      drive(BACK, SPEED);
48    else if (lastSide > 0) drive(SPEED, BACK);
49    else                   drive(SOFT, SOFT);
50  }
51}

La variabile lastSide ricorda da quale lato il robot ha visto la linea l'ultima volta; se in una curva stretta tutti e tre i sensori perdono la linea, il robot gira da quella parte per ritrovarla invece di andare dritto a caso. Con due sensori la stessa logica è ancora più semplice: i sensori stanno ai due lati della linea, il robot va dritto finché entrambi vedono bianco, gira a sinistra quando il sinistro vede nero e a destra quando lo vede il destro. Il punto debole di questo metodo è che i movimenti sono a scatti: il robot passa solo tra poche velocità fisse, quindi zigzaga un po' anche sui rettilinei ed esce di pista in curva appena aumenti la velocità.

Passo 3: inseguimento fluido con il PID

Il PID applica una correzione proporzionale a quanto il robot si è allontanato dalla linea. Prima si calcola la posizione della linea con i cinque sensori tramite una media pesata: il valore di ogni sensore (0–1000) viene moltiplicato per la sua posizione (0, 1000, 2000, 3000, 4000), si somma tutto e si divide per la somma dei valori. Il risultato è un numero tra 0 e 4000, che vale 2000 quando la linea è esattamente al centro. Per esempio, se i sensori leggono 0, 0, 300, 900, 0, la posizione è (300 × 2000 + 900 × 3000) / 1200 = 2750. L'errore = posizione − 2000: un valore positivo indica che la linea è a destra, uno negativo che è a sinistra.

La correzione è la somma di tre termini: correzione = Kp × errore + Ki × somma degli errori + Kd × (errore − errore precedente). Il termine P sterza con più decisione quanto più grande è lo scostamento. Il termine D guarda quanto velocemente cambia l'errore e frena quando il robot si riavvicina alla linea, smorzando le oscillazioni. Il termine I corregge piccoli errori che restano a lungo da una parte, ma in un seguilinea di solito si lascia a zero. Poi il motore sinistro gira a velocità base + correzione e il destro a velocità base − correzione. Per esempio, con un errore di 800 e Kp = 0,06 la correzione è 48; con velocità base 140 il motore sinistro va a 188, il destro a 92 e il robot curva dolcemente a destra.

Arduino (C++)
1// PID çizgi izleyen: Arduino Uno + 5 analog sensör + L298N
2// PID line follower: Arduino Uno + 5 analog sensors + L298N
3const int ENA = 9, IN1 = 8, IN2 = 7;      // sol motor / left motor
4const int ENB = 10, IN3 = 5, IN4 = 4;     // sağ motor / right motor
5const int BUTTON = 2;                     // diğer bacağı GND'ye / other leg to GND
6const int N = 5;
7const int SENSOR[N] = {A0, A1, A2, A3, A4};   // A0 en solda / A0 is the leftmost
8const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
9const int CENTER = (N - 1) * 500;         // orta konum: 2000 / center position: 2000
10
11float Kp = 0.06, Ki = 0.0, Kd = 0.6;      // başlangıç değerleri / starting values
12int baseSpeed = 140;                      // düzlük hızı (0-255) / straight-line speed (0-255)
13const unsigned long PERIOD_MS = 5;        // sabit döngü süresi / fixed loop period
14
15int minVal[N], maxVal[N];
16int lastPosition = CENTER;
17float integral = 0, lastError = 0;
18unsigned long lastTime = 0;
19
20void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
21  left = constrain(left, -255, 255);
22  right = constrain(right, -255, 255);
23  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
24  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
25  analogWrite(ENA, abs(left));
26  analogWrite(ENB, abs(right));
27}
28
29void waitButton() {
30  while (digitalRead(BUTTON) == HIGH) {}  // basılmasını bekle / wait for a press
31  delay(30);
32  while (digitalRead(BUTTON) == LOW) {}   // bırakılmasını bekle / wait for the release
33  delay(30);
34}
35
36void calibrate() {                        // 5 sn: robotu çizginin üstünde sağa sola gezdir / 5 s: sweep the robot across the line
37  for (int i = 0; i < N; i++) { minVal[i] = 1023; maxVal[i] = 0; }
38  digitalWrite(LED_BUILTIN, HIGH);
39  unsigned long start = millis();
40  while (millis() - start < 5000) {
41    for (int i = 0; i < N; i++) {
42      int v = analogRead(SENSOR[i]);
43      if (v < minVal[i]) minVal[i] = v;
44      if (v > maxVal[i]) maxVal[i] = v;
45    }
46  }
47  digitalWrite(LED_BUILTIN, LOW);
48}
49
50int readPosition() {                      // 0 (en sol) … 4000 (en sağ) / 0 (leftmost) … 4000 (rightmost)
51  long sum = 0, weighted = 0;
52  bool onLine = false;
53  for (int i = 0; i < N; i++) {
54    int range = max(maxVal[i] - minVal[i], 1);
55    long v = (long)(analogRead(SENSOR[i]) - minVal[i]) * 1000 / range;
56    v = constrain(v, 0L, 1000L);          // 0 = beyaz, 1000 = siyah / 0 = white, 1000 = black
57    if (WHITE_LINE) v = 1000 - v;
58    if (v > 500) onLine = true;           // eşik: kalibrasyonun ortası / threshold: middle of the calibration
59    if (v > 50) {                         // küçük gürültüyü yok say / ignore small noise
60      weighted += v * (i * 1000L);
61      sum += v;
62    }
63  }
64  if (!onLine) {                          // çizgi kayıp: son görülen tarafa dön / line lost: turn to the last seen side
65    return (lastPosition < CENTER) ? 0 : (N - 1) * 1000;
66  }
67  lastPosition = weighted / sum;
68  return lastPosition;
69}
70
71void setup() {
72  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4, LED_BUILTIN};
73  for (int p : outputs) pinMode(p, OUTPUT);
74  pinMode(BUTTON, INPUT_PULLUP);
75  drive(0, 0);
76  waitButton();                           // 1. basış: kalibrasyon / 1st press: calibration
77  calibrate();
78  waitButton();                           // 2. basış: yarış / 2nd press: race
79  lastTime = millis();
80}
81
82void loop() {
83  if (millis() - lastTime < PERIOD_MS) return;
84  lastTime += PERIOD_MS;
85
86  float error = readPosition() - CENTER;  // artı: çizgi sağda / positive: line is to the right
87  integral = constrain(integral + error, -20000.0f, 20000.0f);   // birikmeyi sınırla / anti-windup
88  float derivative = error - lastError;
89  lastError = error;
90
91  int correction = (int)(Kp * error + Ki * integral + Kd * derivative);
92  drive(baseSpeed + correction, baseSpeed - correction);
93}

Dopo il caricamento premi una volta il pulsante: il LED sulla scheda si accende per 5 secondi. In quel tempo fai scorrere a mano il robot qualche volta a destra e a sinistra sopra la linea, così ogni sensore vede sia il nero sia il bianco. Quando il LED si spegne, appoggia il robot sulla linea e premi di nuovo il pulsante. Il ciclo gira ogni 5 ms grazie a millis(); un periodo fisso mantiene il termine D sempre sulla stessa scala. Se la linea sparisce del tutto, readPosition() restituisce 0 o 4000 in base all'ultimo lato in cui è stata vista e il robot la cerca con la curva più stretta. Per collegare il pulsante guarda Come collegare un pulsante ad Arduino, per temporizzare senza delay Arduino millis() e delay().

Come regolare Kp e Kd

  1. Tieni bassa la velocità base (per esempio 100) e metti Ki e Kd a zero.
  2. Parti con un Kp piccolo (0,02) e aumentalo un po' alla volta finché il robot non riesce a prendere le curve. Quando il robot inizia a oscillare in modo rapido e regolare attorno alla linea hai raggiunto il limite superiore di Kp; resta un po' sotto quel valore.
  3. Parti con Kd pari a circa 10 volte Kp e aumentalo finché l'oscillazione non scompare. Se Kd è troppo grande il robot vibra e i motori fischiano.
  4. Quando il robot va perfettamente dritto sui rettilinei e fluido in curva, aumenta la velocità base di 10–20 e ripeti i passi 2 e 3.
  5. Prova Ki, con un valore molto piccolo (come 0,0001), solo se il robot scivola sempre da una parte sui rettilinei lunghi.
  6. Annota i valori trovati e la tensione della batteria in quel momento; man mano che la batteria si scarica, gli stessi valori si comportano diversamente.
Con la scala di questo articolo (posizione 0–4000, PWM 0–255, ciclo da 5 ms) un buon intervallo di partenza è 0,04–0,08 per Kp e 0,4–1,2 per Kd. Non mettere Serial.print nel ciclo mentre regoli: ogni stampa rallenta il ciclo e disturba il termine D. Se devi vedere dei valori, stampali al massimo una volta ogni 200 ms.

Seguilinea con ESP32: regola il PID dal telefono

Il grande vantaggio dell'ESP32 è che puoi cambiare i valori del PID dal telefono senza ricaricare il codice ogni volta. La logica è la stessa della versione per Uno; le differenze sono: l'ingresso analogico dell'ESP32 è a 12 bit (0–4095), i sensori vanno collegati ai pin ADC1, cioè GPIO 32–39 (i pin ADC2 non si possono leggere con il Wi-Fi attivo), e i sensori si alimentano a 3,3 V. analogWrite() funziona sia nella versione 2.x sia nella 3.x del core Arduino per ESP32. Installa sul telefono Android un'app come Serial Bluetooth Terminal, collegati al dispositivo "CizgiRobot" e invia comandi come p0.07, d0.9 o s160; il robot usa subito il nuovo valore e te lo rimanda. Il Bluetooth classico esiste solo sull'ESP32 originale (non su S3 e C3) e gli iPhone non supportano questo tipo di collegamento seriale. Se l'ESP32 è nuovo per te, leggi prima Cos'è l'ESP32?.

Arduino (C++)
1// ESP32 PID çizgi izleyen + Bluetooth ile canlı ayar
2// ESP32 PID line follower + live tuning over Bluetooth
3#include "BluetoothSerial.h"
4
5BluetoothSerial SerialBT;
6
7const int ENA = 25, IN1 = 26, IN2 = 27;   // sol motor / left motor
8const int ENB = 33, IN3 = 18, IN4 = 19;   // sağ motor / right motor
9const int BUTTON = 23;                    // diğer bacağı GND'ye / other leg to GND
10const int LED_PIN = 2;                    // DevKit üzerindeki LED / onboard LED on the DevKit
11const int N = 5;
12const int SENSOR[N] = {36, 39, 34, 35, 32};   // yalnız ADC1 pinleri / ADC1 pins only
13const bool WHITE_LINE = false;            // siyah zemin + beyaz çizgi ise true / true for a white line on black
14const int CENTER = (N - 1) * 500;
15
16float Kp = 0.06, Ki = 0.0, Kd = 0.6;
17int baseSpeed = 140;
18const unsigned long PERIOD_MS = 5;
19
20int minVal[N], maxVal[N];
21int lastPosition = CENTER;
22float integral = 0, lastError = 0;
23unsigned long lastTime = 0;
24
25void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
26  left = constrain(left, -255, 255);
27  right = constrain(right, -255, 255);
28  digitalWrite(IN1, left >= 0);  digitalWrite(IN2, left < 0);
29  digitalWrite(IN3, right >= 0); digitalWrite(IN4, right < 0);
30  analogWrite(ENA, abs(left));
31  analogWrite(ENB, abs(right));
32}
33
34void waitButton() {
35  while (digitalRead(BUTTON) == HIGH) {}
36  delay(30);
37  while (digitalRead(BUTTON) == LOW) {}
38  delay(30);
39}
40
41void calibrate() {                        // ESP32 ADC 12 bit: 0-4095 / ESP32 ADC is 12-bit: 0-4095
42  for (int i = 0; i < N; i++) { minVal[i] = 4095; maxVal[i] = 0; }
43  digitalWrite(LED_PIN, HIGH);
44  unsigned long start = millis();
45  while (millis() - start < 5000) {
46    for (int i = 0; i < N; i++) {
47      int v = analogRead(SENSOR[i]);
48      if (v < minVal[i]) minVal[i] = v;
49      if (v > maxVal[i]) maxVal[i] = v;
50    }
51  }
52  digitalWrite(LED_PIN, LOW);
53}
54
55int readPosition() {
56  long sum = 0, weighted = 0;
57  bool onLine = false;
58  for (int i = 0; i < N; i++) {
59    int range = max(maxVal[i] - minVal[i], 1);
60    long v = (long)(analogRead(SENSOR[i]) - minVal[i]) * 1000 / range;
61    v = constrain(v, 0L, 1000L);
62    if (WHITE_LINE) v = 1000 - v;
63    if (v > 500) onLine = true;
64    if (v > 50) {
65      weighted += v * (i * 1000L);
66      sum += v;
67    }
68  }
69  if (!onLine) {
70    return (lastPosition < CENTER) ? 0 : (N - 1) * 1000;
71  }
72  lastPosition = weighted / sum;
73  return lastPosition;
74}
75
76void readTuning() {                       // telefondan "p0.07", "d0.9", "s160" gibi komutlar / commands like "p0.07", "d0.9", "s160" from the phone
77  if (!SerialBT.available()) return;
78  char cmd = SerialBT.read();
79  if (cmd != 'p' && cmd != 'i' && cmd != 'd' && cmd != 's') return;   // satır sonlarını atla / skip line endings
80  float value = SerialBT.parseFloat();
81  if (cmd == 'p') Kp = value;
82  if (cmd == 'i') { Ki = value; integral = 0; }
83  if (cmd == 'd') Kd = value;
84  if (cmd == 's') baseSpeed = (int)value;
85  SerialBT.printf("Kp=%.3f Ki=%.4f Kd=%.3f speed=%d\n", Kp, Ki, Kd, baseSpeed);
86}
87
88void setup() {
89  int outputs[] = {ENA, IN1, IN2, ENB, IN3, IN4, LED_PIN};
90  for (int p : outputs) pinMode(p, OUTPUT);
91  pinMode(BUTTON, INPUT_PULLUP);
92  drive(0, 0);
93  SerialBT.begin("CizgiRobot");           // telefonda görünecek ad / name shown on the phone
94  SerialBT.setTimeout(20);                // parseFloat robotu bekletmesin / keep parseFloat from blocking
95  waitButton();
96  calibrate();
97  waitButton();
98  lastTime = millis();
99}
100
101void loop() {
102  readTuning();
103  if (millis() - lastTime < PERIOD_MS) return;
104  lastTime += PERIOD_MS;
105
106  float error = readPosition() - CENTER;
107  integral = constrain(integral + error, -20000.0f, 20000.0f);
108  float derivative = error - lastError;
109  lastError = error;
110
111  int correction = (int)(Kp * error + Ki * integral + Kd * derivative);
112  drive(baseSpeed + correction, baseSpeed - correction);
113}
Alcuni regolamenti di gara vietano moduli Wi-Fi, Bluetooth o RF sul robot, o addirittura qualsiasi scheda con Wi-Fi. La strada più sicura è usare l'ESP32 per regolazioni e prove e gareggiare con un Arduino Uno, un Nano o un Pico senza Wi-Fi; nel dubbio chiedi agli organizzatori.

Seguilinea con Raspberry Pi Pico e MicroPython

Il Pico ha solo tre ingressi analogici disponibili (GP26, GP27, GP28), quindi il codice qui sotto lavora con tre sensori analogici e calcola la posizione tra 0 e 2000. Se vuoi usare cinque sensori puoi collegare un modulo a 5 canali con uscite digitali a cinque GPIO qualsiasi e contare ogni sensore come 0 o 1000; la media pesata è più grossolana ma funziona lo stesso. read_u16() restituisce valori da 0 a 65535 e la velocità del motore è espressa in percentuale (da -100 a 100). Per installazione e primi passi guarda Cos'è il Raspberry Pi Pico?.

MicroPython
1from machine import Pin, PWM, ADC
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000)        # sol motor hızı / left motor speed
5in1 = Pin(14, Pin.OUT); in2 = Pin(13, Pin.OUT)
6enb = PWM(Pin(10)); enb.freq(1000)        # sağ motor hızı / right motor speed
7in3 = Pin(11, Pin.OUT); in4 = Pin(12, Pin.OUT)
8button = Pin(16, Pin.IN, Pin.PULL_UP)     # diğer bacağı GND'ye / other leg to GND
9led = Pin("LED", Pin.OUT)
10sensors = [ADC(26), ADC(27), ADC(28)]     # sol, orta, sağ / left, middle, right
11WHITE_LINE = False                        # siyah zemin + beyaz çizgi ise True / True for a white line on black
12
13N = len(sensors)
14CENTER = (N - 1) * 500                    # orta konum: 1000 / center position: 1000
15Kp, Ki, Kd = 0.05, 0.0, 0.4
16BASE = 45                                 # taban hız (%) / base speed (%)
17min_val = [65535] * N
18max_val = [0] * N
19last_pos = CENTER
20
21def drive(left, right):                   # her biri -100 … 100 / each -100 … 100
22    left = max(-100, min(100, left))
23    right = max(-100, min(100, right))
24    in1.value(left >= 0); in2.value(left < 0)
25    in3.value(right >= 0); in4.value(right < 0)
26    ena.duty_u16(int(abs(left) * 655.35))
27    enb.duty_u16(int(abs(right) * 655.35))
28
29def wait_button():
30    while button.value():                 # basılmasını bekle / wait for a press
31        time.sleep_ms(10)
32    time.sleep_ms(30)
33    while not button.value():             # bırakılmasını bekle / wait for the release
34        time.sleep_ms(10)
35    time.sleep_ms(30)
36
37def calibrate():                          # 5 sn sağa sola gezdir / sweep for 5 s
38    led.on()
39    start = time.ticks_ms()
40    while time.ticks_diff(time.ticks_ms(), start) < 5000:
41        for i, s in enumerate(sensors):
42            v = s.read_u16()
43            min_val[i] = min(min_val[i], v)
44            max_val[i] = max(max_val[i], v)
45    led.off()
46
47def read_position():                      # 0 (sol) … 2000 (sağ) / 0 (left) … 2000 (right)
48    global last_pos
49    total = weighted = 0
50    on_line = False
51    for i, s in enumerate(sensors):
52        rng = max(1, max_val[i] - min_val[i])
53        v = max(0, min(1000, (s.read_u16() - min_val[i]) * 1000 // rng))
54        if WHITE_LINE:
55            v = 1000 - v
56        if v > 500:
57            on_line = True
58        if v > 50:
59            weighted += v * i * 1000
60            total += v
61    if not on_line:                       # çizgi kayıp / line lost
62        return 0 if last_pos < CENTER else (N - 1) * 1000
63    last_pos = weighted // total
64    return last_pos
65
66drive(0, 0)
67wait_button()                             # 1. basış: kalibrasyon / 1st press: calibration
68calibrate()
69wait_button()                             # 2. basış: yarış / 2nd press: race
70integral = 0
71last_error = 0
72while True:
73    t0 = time.ticks_ms()
74    error = read_position() - CENTER
75    integral = max(-10000, min(10000, integral + error))
76    correction = Kp * error + Ki * integral + Kd * (error - last_error)
77    last_error = error
78    drive(BASE + correction, BASE - correction)
79    while time.ticks_diff(time.ticks_ms(), t0) < 5:   # 5 ms sabit döngü / fixed 5 ms loop
80        pass

Regole di gara e pista

Le regole delle gare per seguilinea cambiano a seconda dell'organizzatore e dell'anno, ma l'idea di base è sempre la stessa: il robot deve completare la pista nel minor tempo possibile senza uscire dalla linea. Per esempio, nel regolamento 2024 di livello base per seguilinea della gara nazionale di robotica MEB in Turchia, la pista è una linea bianca da 20 ± 2 mm su fondo nero. Il robot può misurare al massimo 280 × 180 mm ruote comprese e pesare al massimo 1 kg batteria compresa; la tensione di lavoro non può superare 12 V e sono obbligatori due motoriduttori DC da 6–12 V a forma di L con ingranaggi in plastica, ruote con diametro massimo di 65 mm e una barra con al massimo 8 sensori. In pista ci sono un ponte con pendenza di 13 gradi e un tratto quadrato dove tagliare per il centro costa 20 secondi di penalità; un robot con il corpo completamente sul fondo nero è considerato fuori pista, e uno fermo sulla linea per più di 30 secondi viene squalificato. Altre gare scolastiche e regionali usano il contrario, una linea nera da 20 ± 2 mm su fondo bianco, e chiedono robot più piccoli che non superino i 500 grammi. Insomma, leggi sempre il regolamento dell'anno prima di progettare il robot.

  • Pista invertita: Per una linea bianca su fondo nero imposta a true la variabile WHITE_LINE negli sketch. Se i colori cambiano lungo la pista puoi aggiungere un controllo che cambia modalità quando i sensori esterni iniziano a vedere nero e quello centrale bianco.
  • Curve a 90 gradi: Riduci la velocità base prima della curva o lascia che la ruota interna giri all'indietro; negli sketch di questo articolo la ruota interna va già indietro quando la correzione supera la velocità base.
  • Incroci: Quando tutti i sensori vedono la linea insieme, tira dritto; se devi scegliere una direzione, costruisci una macchina a stati che conti gli incroci.
  • Linee tratteggiate: Quando la linea scompare per poco, prova a proseguire dritto per 100–200 ms invece di girare subito.
  • Rampa e ponte: In salita cambia la distanza tra sensore e pavimento; tieni la barra dei sensori bassa e vicina alle ruote, e rallenta un po' sulla rampa.
  • Pista di allenamento: Incolla nastro isolante nero opaco da 19 mm su cartoncino bianco opaco o su un pannello in forex; nelle prime prove non fare curve con raggio inferiore a 15 cm.

Problemi comuni e soluzioni

ProblemaCausa probabileSoluzione
Il robot zigzaga sui rettilineiKp troppo alto, Kd troppo basso o velocità eccessivaRiduci Kp del 20 % circa, aumenta Kd; se serve abbassa la velocità base
Esce di pista in curvaVelocità alta, barra di sensori stretta o reazione sbagliata quando perde la lineaAbbassa la velocità base, lascia andare indietro la ruota interna, gira verso l'ultimo lato visto
Il robot tira sempre da una parteI motori girano a velocità diverse o la barra non è centrataCentra i sensori, rallenta il motore più veloce con un piccolo fattore (per esempio 0,95)
I sensori vedono nero o bianco ovunqueCalibrazione mancante, sensore troppo alto o trimmer non regolatoAbbassa l'altezza a 3–5 mm, ripeti la calibrazione sulla pista
Arduino si resetta alla partenza dei motoriLa tensione della batteria crolla per un attimo o la massa comune è scarsaAlimenta la scheda a parte o aggiungi un condensatore da 470 µF sull'alimentazione, accorcia i cavi
Il robot cambia comportamento quando la batteria calaLa velocità dei motori scende con la tensioneMisura la tensione della batteria e scala la velocità, oppure monta una batteria carica prima di ogni prova
A casa funziona, in gara noLa luce ambiente o un pavimento lucido disturbano l'infrarossoScherma i sensori con una gonnellina nera, calibra con la luce della gara
Il modo più rapido per isolare un problema è sollevare le ruote da terra e muovere il robot a mano sopra la linea: se le ruote accelerano nel verso giusto, sensori e codice sono a posto e il problema sta nella meccanica o nell'alimentazione. Per una diagnosi dettagliata dei reset guarda Arduino si resetta da solo.

Prossimi passi e idee per progetti

  • Seguilinea che evita gli ostacoli: Aggiungi un HC-SR04 davanti perché il robot aggiri gli ostacoli sulla pista; la logica decisionale è in Robot che evita gli ostacoli.
  • Modalità Bluetooth: Passa dall'inseguimento della linea al comando da telefono con un pulsante; si aggiunge facilmente con il modulo Bluetooth HC-05.
  • Cronometro sul giro: Un robot che rileva la linea di partenza e mostra il tempo sul giro su un display OLED.
  • Risolutore di labirinti: Un robot che conta e memorizza gli incroci e al secondo giro segue il percorso più breve.
  • Robot da magazzino: Un piccolo modello di AGV che si ferma alle stazioni lungo la linea e scarica un carico con un servo.
  • Una base solida: Se non hai ancora montato telaio, motori e batteria, inizia dalla guida Costruire un'auto robot con Arduino.

Un robot seguilinea insegna sensori, algoritmi, motori e alimentazione tutti insieme, e diventa un po' più veloce a ogni giro di regolazione. Prima fai muovere il robot con la versione if-else semplice, poi passa al PID e regola i valori con pazienza. Se vuoi capire come il PWM regola la velocità del motore, Cos'è il PWM? è un buon seguito. Nell'app BlueGrays trovi passo passo, con schemi e codice, i progetti di rilevamento del fondo bianco e nero con TCRT5000, l'auto comandata via Bluetooth con L298N e il robot che evita gli ostacoli.

Domande frequenti

Quanti sensori servono a un robot seguilinea?

Per un robot funzionante bastano 2 sensori, ma con 3 e un semplice codice if-else il risultato è molto più stabile. Per un inseguimento veloce e fluido si consigliano 5 o 8 sensori analogici con il PID. Molte gare ammettono al massimo 8 sensori.

Perché il mio robot seguilinea zigzaga?

La causa più comune è un Kp troppo alto e un Kd troppo basso: il robot corregge troppo ogni scostamento e finisce dall'altra parte della linea. Abbassare un po' Kp, aumentare Kd, ridurre la velocità base e portare i sensori 4–8 cm davanti alle ruote riduce l'oscillazione. Con un semplice codice if-else un leggero zigzag è normale, perché il robot passa solo tra poche velocità fisse.

Si può fare un seguilinea senza PID?

Sì. Con un semplice codice if-else e 2 o 3 sensori il robot segue la linea, e per i progetti scolastici spesso basta. Ma quando la velocità aumenta perde la linea in curva e oscilla sui rettilinei. Se punti a un buon piazzamento in gara, dovrai passare al PID.

Che batteria usare per un robot seguilinea?

La scelta più pratica sono due batterie Li-ion 18650 in serie: danno 7,4 V nominali e forniscono senza fatica la corrente elevata dei motori. Nei robot leggeri si usa anche una LiPo 2S. Una pila da 9 V non regge la corrente dei motori e si scarica in fretta.

Come cambia il codice per una linea bianca su fondo nero?

Basta invertire i valori dei sensori: negli sketch di questo articolo, impostando WHITE_LINE a true il valore normalizzato diventa 1000 − valore e il robot segue la linea bianca. Nei moduli digitali la stessa variabile inverte la lettura di DO. La calibrazione va comunque fatta sulla pista di gara.

A che altezza dal pavimento va il sensore TCRT5000?

Il TCRT5000 lavora al meglio a circa 2,5 mm; in pratica lasciare 3–5 mm tra sensore e pavimento dà buoni risultati. Oltre 1 cm la differenza tra nero e bianco diventa molto piccola e il robot perde la linea. Sulle piste con rampe, montare la barra dei sensori vicino alle ruote riduce la variazione di distanza.

Vedi tutti i progetti
TCRT5000 Rilevamento di superfici bianche e nere
#78Intermedio
Premium

TCRT5000 Rilevamento di superfici bianche e nere

Progetto che rileva il colore del terreno con il sensore TCRT5000 e accende il LED rosso sul terreno nero e il LED verde sul terreno bianco. Insegna la logica di rilevamento della soglia di colore che

4.7(40)
49
Misurazione della distanza con TCRT5000
#77Intermedio
Premium

Misurazione della distanza con TCRT5000

Progetto che misura la quantità di luce riflessa dalla superficie con il sensore a infrarossi TCRT5000 e la visualizza come valore analogico sullo schermo LCD I2C. Insegna le basi del rilevamento otti

4.6(33)
43
Auto controllata via Bluetooth con L298N
#80Avanzato
Premium

Auto controllata via Bluetooth con L298N

Progetto di auto robot 4WD (quattro motori, quattro ruote motrici) che può essere controllata dal telefono con Arduino e il modulo HC-05 Bluetooth. Un totale di quattro ruote sono azionate simultaneam

5.0(38)
64
Robot che evita gli ostacoli
#81Avanzato
Premium

Robot che evita gli ostacoli

Che ne dite di costruire un veicolo intelligente che trova la propria strada? L'Obstacle Avoiding Robot scruta costantemente l'ambiente circostante grazie al sensore a ultrasuoni posto davanti a lui.

5.0(38)
49
HC-SR04 Misura della distanza (display seriale)
#27Principiante

HC-SR04 Misura della distanza (display seriale)

Il sensore a ultrasuoni HC-SR04 misura la distanza e visualizza il risultato in cm sul monitor seriale. Insegna il principio del calcolo della distanza con l'onda sonora.

4.6(44)
66
Accendere un LED con un pulsante (Push)
#9Principiante

Accendere un LED con un pulsante (Push)

Progetto di controllo istantaneo (push) che accende il LED quando il pulsante viene premuto e lo spegne quando viene rilasciato. Insegna l'uso della lettura dei pulsanti e dell'espressione condizional

4.9(112)
190
Dissolvenza LED ESP32 con PWM
#110Principiante
Premium

Dissolvenza LED ESP32 con PWM

Modificare la luminosità del LED in modo fluido da 0 a max, da max a 0. Controlliamo il PWM con analogWrite() come in Arduino - la stessa sintassi con ESP32 Arduino Core 3.0+ funziona senza problemi. La definizione del pin + pinMode + analogWrite è sufficiente.

4.8(36)
54
Raspberry Pi Pico PWM LED Fade (Effetto Respiro, GP14)
#160Principiante
Premium

Raspberry Pi Pico PWM LED Fade (Effetto Respiro, GP14)

Il LED non lampeggia più — respira! 🌬️ Con il **PWM** (modulazione di larghezza d'impulso) del Raspberry Pi Pico, la luminosità sale e scende lentamente. Impara la classe machine.PWM, duty_u16() (luminosità a 16 bit 0–65535), la frequenza PWM, i cicli range() e l'uscita quasi analogica. Base per miscelazione colori RGB, controllo motori, servo e buzzer.

0.0(0)
4