Guías · 18 min de lectura

Robot seguidor de línea con Arduino: sensores TCRT5000 y PID

Robot seguidor de línea con Arduino: barra TCRT5000, colocación y calibración de sensores, código if-else y PID, ajuste de Kp/Kd, driver, batería y reglas.

Un robot seguidor de línea es un vehículo autónomo que detecta una línea negra (o blanca) en el suelo con sensores infrarrojos y ajusta sin parar la velocidad de sus dos ruedas para mantenerse sobre ella. Es uno de los primeros proyectos de robótica de verdad que se hacen con Arduino, porque reúne en un solo proyecto la lectura de sensores, la toma de decisiones y el control de motores. Los vehículos autónomos (AGV) que transportan material en las fábricas funcionan con la misma idea, y el seguidor de línea es una de las categorías más concurridas en las competiciones escolares de robótica.

En esta guía aprenderás cómo funciona el sensor TCRT5000, en qué se diferencian las barras de 2, 3 y 5 sensores, a qué altura y con qué separación montar los sensores y cómo calibrar el umbral. Primero harás que el robot avance con un código if-else sencillo y después pasarás a una versión con control PID que sigue la línea sin zigzaguear ni salirse de la pista en las curvas. Además del código para Arduino Uno, ESP32 y Raspberry Pi Pico, encontrarás cómo elegir el driver de motores y la batería, las reglas típicas de competición y la solución a los problemas más habituales.

Sensor infrarrojo de reflexión TCRT5000
TCRT5000: un LED infrarrojo de cápsula azul y un fototransistor negro van juntos en el mismo encapsulado; los módulos sensores de línea se construyen alrededor de esta pieza.

¿Cómo funciona un robot seguidor de línea?

Una superficie blanca refleja la mayor parte de la luz infrarroja, mientras que una superficie negra mate absorbe casi toda. Los sensores situados bajo el robot miden esa diferencia y así saben dónde está la línea respecto al robot. El robot no tiene dirección: gira con lo que se llama tracción diferencial. Si la rueda izquierda gira más rápido que la derecha, el robot se curva hacia la derecha, y si gira más despacio, hacia la izquierda. El programa repite el mismo bucle cientos de veces por segundo: leer los sensores, calcular cuánto se ha desviado la línea y corregir la velocidad de los dos motores. Cuanto más rápido y estable sea este lazo cerrado, más fluido irá el robot.

PiezaRecomendadoNota
Placa de controlArduino Uno o NanoEl Nano cabe mejor en un chasis pequeño; también sirven un ESP32 o una Pico
Sensor de líneaBarra TCRT5000 de 3 o 5 canales, QTR-8ACon PID da mejor resultado una barra de salida analógica
Driver de motoresL298N o TB6612FNGEl TB6612FNG es más pequeño y eficiente
Motores2 motores DC con reductora (TT 1:48 o N20)Los dos motores deben ser del mismo modelo y velocidad
Ruedas2 ruedas motrices y 1 rueda locaLas ruedas de 65 mm de diámetro son las más comunes
Alimentación2 baterías Li-ion 18650 (7,4 V) e interruptorUna pila de 9 V no da suficiente corriente para los motores
OtrosChasis, pulsador, cables jumperUn pulsador facilita la calibración y el arranque
Arduino Nano
Arduino Nano
L298N
L298N
N20
N20
18650 Li-ion
18650 Li-ion

El sensor TCRT5000: barras de 2, 3 y 5 sensores

El TCRT5000 está formado por un LED infrarrojo (950 nm) y un fototransistor colocados uno junto al otro en el mismo encapsulado. Cuanta más luz del LED se refleja en el suelo y llega al fototransistor, más conduce el transistor. El sensor funciona mejor a unos 2,5 mm de distancia; por encima de 1 cm la diferencia entre negro y blanco cae rápidamente. Los módulos listos para usar llevan un comparador LM393: en la mayoría, el pin DO da HIGH sobre negro y LOW sobre blanco según el umbral ajustado con el potenciómetro, y el LED de la placa se enciende sobre superficie blanca. El pin AO da el valor analógico en bruto: alto sobre negro, bajo sobre blanco. Tienes todos los detalles del sensor en Sensor infrarrojo TCRT5000.

SensoresColocaciónAlgoritmoCuándo elegirlo
2A ambos lados de la línea; los dos ven blanco normalmenteif-elsePrimer intento, pista ancha con curvas suaves
3El central sobre la línea y los laterales en sus bordesif-else o PID sencilloProyectos escolares, competiciones de nivel básico
5Fila con separación uniforme, el central sobre la líneaPID (media ponderada)Seguimiento rápido y suave
8Fila ancha (como la QTR-8A)PID y detección de crucesPistas de competición con curvas cerradas y cruces

Colocación de los sensores: altura, separación y distancia al eje

Por muy bueno que sea tu código, si los sensores están mal colocados el robot no irá bien. Monta la barra de sensores delante del eje de las ruedas, paralela al suelo y simétrica respecto al centro del robot. Estas medidas son un buen punto de partida:

  • Altura: Deja 3–5 mm entre la cara inferior del sensor y el suelo. Pololu indica para su barra QTR-8A una distancia óptima de 3 mm y una máxima recomendada de 6 mm. Más arriba, la diferencia entre negro y blanco se reduce y el robot pierde la línea.
  • Separación: En las barras analógicas, la distancia entre dos sensores debe estar entre la mitad y dos tercios del ancho de la línea; para una línea de 20 mm van bien 10–13 mm. Así la línea suele quedar bajo dos sensores y la posición se calcula de forma suave.
  • Disposición digital de 3 sensores: El sensor central va justo encima de la línea y los laterales justo fuera de sus bordes; para una línea de 20 mm, unos 18–22 mm de centro a centro.
  • Distancia al eje: Coloca los sensores 4–8 cm por delante del eje de las ruedas. Si están demasiado cerca, el robot detecta las curvas tarde; si están demasiado lejos, un pequeño giro mueve mucho los sensores y el robot tiembla.
  • Luz ambiente: La luz del sol y las lámparas halógenas también contienen infrarrojo. Cubrir los sensores con una falda o cinta negra hace que las lecturas sean mucho más estables.
Un error frecuente es montar los sensores inclinados o demasiado altos. Si el robot pierde la línea en la primera curva, revisa la altura de los sensores antes que el código: unos pocos milímetros pueden reducir las lecturas a la mitad. Además, la cinta aislante brillante refleja el infrarrojo y una línea negra puede parecer blanca; si puedes, usa cinta mate o una línea impresa.

Conexiones: Arduino Uno, ESP32 y Pico

La tabla siguiente coincide exactamente con el código de prueba de sensores y los códigos PID; las conexiones del ejemplo if-else de 3 sensores se explican en su propia sección. Numera los sensores de izquierda a derecha mirando el robot desde atrás: el sensor de más a la izquierda es el número 1. Como driver de motores usamos un L298N; la función de cada pin y la lógica del puente H se explican en detalle en Control de motor DC con L298N. La Pico solo tiene tres entradas analógicas, así que en ella usamos los sensores 1, 3 y 5.

ConexiónArduino UnoESP32Raspberry Pi Pico
Sensor 1 (el de la izquierda) AOA0GPIO 36GP26
Sensor 2 AOA1GPIO 39-
Sensor 3 (centro) AOA2GPIO 34GP27
Sensor 4 AOA3GPIO 35-
Sensor 5 (el de la derecha) AOA4GPIO 32GP28
VCC de los sensores5V3V33V3
L298N ENA (velocidad motor izquierdo)D9GPIO 25GP15
L298N IN1, IN2D8, D7GPIO 26, 27GP14, GP13
L298N ENB (velocidad motor derecho)D10GPIO 33GP10
L298N IN3, IN4D5, D4GPIO 18, 19GP11, GP12
Pulsador de arranque (otra pata a GND)D2GPIO 23GP16
GNDGNDGNDGND (común con batería y driver)
El negativo de la batería, el GND del driver y el GND de la placa deben estar unidos; sin masa común, los motores se comportan de forma errática. No alimentes nunca los motores desde el pin 5V de la placa. En el ESP32 y la Pico alimenta los sensores desde 3V3: la salida AO de un módulo alimentado a 5 V puede llegar a 5 V y dañar las entradas analógicas de 3,3 V.

Cómo elegir el driver de motores y la batería

El L298N es barato y resistente, pero sus transistores bipolares se comen entre 1,5 y 3 V: de un pack de 7,4 V a los motores solo les llegan unos 5–5,5 V. En robots pequeños y rápidos funciona mejor un TB6612FNG o un DRV8833 con MOSFET. Con el TB6612FNG el código no cambia: conectas los cables de ENA, IN1 e IN2 a PWMA, AIN1 y AIN2 (los del canal B a PWMB, BIN1 y BIN2) y el pin STBY a VCC. El DRV8833 no tiene un pin de velocidad (enable) aparte: la señal PWM va directamente a los pines IN, así que tendrás que adaptar la función drive(). Para alimentarlo, lo más práctico son dos baterías Li-ion 18650 en serie: 8,4 V cargadas, 7,4 V nominales, y entregan corriente alta sin problemas. Las pilas de 9 V no aguantan la corriente de los motores y se agotan enseguida. Puedes calcular cuánto durará tu batería con la herramienta Duración de la batería.

DriverTensión del motorCorriente por canalCaída de tensiónValoración
L298N5–35 V2 A1,5–3 VResistente, pero grande y poco eficiente
TB6612FNG4,5–13,5 V1,2 A (pico 3,2 A)Muy bajaLa mejor relación calidad-precio para un seguidor
DRV88332,7–10,8 V1,5 A (pico 2 A)Muy bajaRobots pequeños con motores N20
L9110S2,5–12 V0,8 ABajaSolo robots ligeros y lentos
Motor DC amarillo TT con reductora y rueda
Motor amarillo TT con reductora y rueda de 65 mm: la caja de engranajes reduce las vueltas y aumenta el par; es el motor más común en seguidores de línea y coches robot.

Un ejemplo de velocidad: los motores amarillos TT (1:48) giran a unas 200 rpm sin carga a 6 V. Una rueda de 65 mm tiene una circunferencia de π × 65 ≈ 204 mm, así que el robot puede ir como máximo a 200 × 0,204 ≈ 41 m por minuto, unos 0,7 m por segundo. Es más que suficiente para la mayoría de pistas escolares; lo difícil es no perder la línea a esa velocidad. Los robots más rápidos usan micromotores N20 con reductora y un chasis ligero. Al bajar la tensión de la batería los motores se frenan y los valores PID que ajustaste dejan de servir; medir la tensión con un divisor de tensión y escalar la velocidad en función de ella es una mejora de nivel avanzado.

Paso 1: Prueba los sensores y encuentra el umbral

Antes de conectar los motores, averigua qué ven los sensores. El código siguiente imprime el valor analógico en bruto de los cinco sensores separado por tabuladores; si abres en el IDE de Arduino Herramientas > Serial Plotter a 115200 baudios verás cada sensor como una línea distinta. Pasa el robot despacio por encima de la línea con la mano y observa cómo cambian los valores.

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 módulo TCRT5000 típico da entre 30 y 200 sobre blanco y entre 600 y 1000 sobre la línea negra, aunque estas cifras dependen de la altura del sensor, del material del suelo y de la luz ambiente. El umbral más sencillo es: umbral = (valor en blanco + valor en negro) / 2. Por ejemplo, si lees 80 en blanco y 820 en negro, el umbral es 450. Como cada sensor lee un poco distinto, el código PID lo hará de forma automática: durante la calibración guarda el valor mínimo y máximo de cada sensor y luego escala cada lectura entre 0 (blanco) y 1000 (negro) con (lectura − mín) × 1000 / (máx − mín). Así el umbral queda en 500 para todos los sensores.

En los módulos con salida digital (DO) el umbral se ajusta con el potenciómetro de la placa. Gira el trimmer hasta que el LED del módulo esté apagado con el sensor sobre la línea negra y encendido sobre la superficie blanca; la posición justo en medio de esos dos puntos es la más estable. Haz el ajuste sobre la pista en la que vas a competir y con la misma iluminación.

Paso 2: Algoritmo if-else sencillo (3 sensores)

Para un primer robot que funcione, lo más claro es elegir entre unos cuantos movimientos fijos según el estado de los tres sensores. Conecta las salidas DO de un módulo de 3 canales a A0 (izquierda), A1 (centro) y A2 (derecha); los pines analógicos del Uno también sirven como entradas digitales. La lógica es la de esta tabla (1 significa que el sensor ve la línea):

Izq.CentroDer.SignificadoMovimiento
010Línea en el centroRecto
110Línea un poco a la izquierdaLa rueda izquierda frena, giro suave a la izquierda
100Línea muy a la izquierdaLa rueda izquierda va hacia atrás, giro brusco a la izquierda
011Línea un poco a la derechaLa rueda derecha frena, giro suave a la derecha
001Línea muy a la derechaLa rueda derecha va hacia atrás, giro brusco a la derecha
111Cruce o línea de metaSeguir recto
000Línea perdidaGirar hacia el último lado 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 variable lastSide recuerda en qué lado vio el robot la línea por última vez; si en una curva cerrada los tres sensores pierden la línea, el robot gira hacia ese lado para recuperarla en lugar de seguir recto sin rumbo. Con dos sensores la lógica es todavía más sencilla: los sensores van a ambos lados de la línea, el robot sigue recto mientras los dos ven blanco, gira a la izquierda cuando el izquierdo ve negro y a la derecha cuando lo ve el derecho. El punto débil de este método es que los movimientos van a saltos: el robot solo cambia entre unas pocas velocidades fijas, así que zigzaguea un poco incluso en las rectas y se sale en las curvas en cuanto subes la velocidad.

Paso 3: Seguimiento suave con PID

El PID aplica una corrección proporcional a lo que el robot se ha desviado de la línea. Primero se calcula la posición de la línea con los cinco sensores mediante una media ponderada: el valor de cada sensor (0–1000) se multiplica por su propia posición (0, 1000, 2000, 3000, 4000), se suma todo y se divide entre la suma de los valores. El resultado es un número entre 0 y 4000, que vale 2000 cuando la línea está justo en el centro. Por ejemplo, si los sensores leen 0, 0, 300, 900, 0, la posición es (300 × 2000 + 900 × 3000) / 1200 = 2750. El error = posición − 2000: un valor positivo indica que la línea está a la derecha y uno negativo, a la izquierda.

La corrección es la suma de tres términos: corrección = Kp × error + Ki × suma de errores + Kd × (error − error anterior). El término P gira con más fuerza cuanto mayor es la desviación. El término D mira lo rápido que cambia el error y frena cuando el robot se acerca a la línea, amortiguando la oscilación. El término I corrige pequeños errores que se mantienen mucho tiempo hacia un lado, pero en un seguidor de línea casi siempre se deja a cero. Después, el motor izquierdo gira a velocidad base + corrección y el derecho a velocidad base − corrección. Por ejemplo, con un error de 800 y Kp = 0,06 la corrección es 48; con velocidad base 140, el motor izquierdo va a 188, el derecho a 92 y el robot gira suavemente a la derecha.

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}

Después de cargar el código, pulsa el botón una vez: el LED de la placa se enciende durante 5 segundos. En ese tiempo, desliza el robot con la mano a izquierda y derecha sobre la línea varias veces para que cada sensor vea tanto el negro como el blanco. Cuando el LED se apague, pon el robot sobre la línea y vuelve a pulsar el botón. El bucle se ejecuta cada 5 ms gracias a millis(); un periodo fijo hace que el término D tenga siempre la misma escala. Si la línea desaparece por completo, readPosition() devuelve 0 o 4000 según el último lado en que se vio y el robot la busca con el giro más cerrado. Para conectar el pulsador consulta Cómo conectar un pulsador a Arduino, y para temporizar sin delay, Arduino millis() y delay().

Cómo ajustar Kp y Kd

  1. Mantén baja la velocidad base (por ejemplo, 100) y pon Ki y Kd a cero.
  2. Empieza con un Kp pequeño (0,02) y súbelo poco a poco hasta que el robot tome las curvas. Cuando el robot empiece a oscilar rápido y de forma regular alrededor de la línea, habrás llegado al límite superior de Kp; quédate un poco por debajo.
  3. Empieza Kd en unas 10 veces Kp y súbelo hasta que desaparezca la oscilación. Si Kd es demasiado grande, el robot tiembla y los motores zumban.
  4. Cuando el robot vaya totalmente recto en las rectas y fluido en las curvas, sube la velocidad base de 10 en 10 o de 20 en 20 y repite los pasos 2 y 3.
  5. Prueba Ki, con un valor muy pequeño (como 0,0001), solo si el robot se desvía siempre hacia un lado en rectas largas.
  6. Anota los valores que encuentres y la tensión de la batería en ese momento; al descargarse, los mismos valores se comportan de otra manera.
Con la escala de este artículo (posición 0–4000, PWM 0–255, bucle de 5 ms), un buen rango de partida es 0,04–0,08 para Kp y 0,4–1,2 para Kd. No pongas Serial.print dentro del bucle mientras ajustas: cada impresión ralentiza el bucle y estropea el término D. Si necesitas ver valores, imprímelos como mucho una vez cada 200 ms.

Seguidor de línea con ESP32: ajusta el PID desde el móvil

La gran ventaja del ESP32 es que puedes cambiar los valores del PID desde el móvil sin volver a cargar el código cada vez. La lógica es la misma que en la versión para Uno; las diferencias son: la entrada analógica del ESP32 es de 12 bits (0–4095), los sensores deben ir en pines ADC1, es decir, GPIO 32–39 (los pines ADC2 no se pueden leer con el Wi-Fi activo), y los sensores se alimentan a 3,3 V. analogWrite() funciona en las versiones 2.x y 3.x del core de Arduino para ESP32. Instala en tu móvil Android una app como Serial Bluetooth Terminal, conéctate al dispositivo "CizgiRobot" y envía órdenes como p0.07, d0.9 o s160; el robot usa el nuevo valor al instante y te lo devuelve. El Bluetooth clásico solo existe en el ESP32 original (no en el S3 ni en el C3) y los iPhone no admiten este tipo de conexión serie. Si el ESP32 es nuevo para ti, lee primero ¿Qué es el 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}
Algunos reglamentos de competición prohíben los módulos Wi-Fi, Bluetooth o RF en el robot, e incluso las placas con Wi-Fi. Lo más seguro es usar el ESP32 para ajustar y entrenar y competir con un Arduino Uno, un Nano o una Pico sin Wi-Fi; si tienes dudas, pregunta a la organización.

Seguidor de línea con Raspberry Pi Pico y MicroPython

La Pico solo tiene tres entradas analógicas disponibles (GP26, GP27, GP28), así que el código siguiente funciona con tres sensores analógicos y calcula la posición entre 0 y 2000. Si quieres usar cinco sensores, puedes conectar un módulo de 5 canales con salidas digitales a cinco GPIO cualesquiera y contar cada sensor como 0 o 1000; la media ponderada es más tosca, pero sigue funcionando. read_u16() devuelve valores de 0 a 65535 y la velocidad del motor se da en porcentaje (de -100 a 100). Para la instalación y los primeros pasos, consulta ¿Qué es la 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

Reglas de competición y pista

Las reglas de las competiciones de seguidores de línea cambian según el organizador y el año, pero la idea básica es la misma: el robot debe completar la pista en el menor tiempo posible sin salirse de la línea. Por ejemplo, en el reglamento 2024 de nivel básico de seguidor de línea de la competición nacional de robótica MEB de Turquía, la pista es una línea blanca de 20 ± 2 mm sobre fondo negro. El robot puede medir como máximo 280 × 180 mm con las ruedas y pesar como máximo 1 kg con las baterías; la tensión de trabajo no puede superar 12 V y debe usar dos motores DC de 6–12 V con reductora en L y engranajes de plástico, ruedas de 65 mm de diámetro como máximo y una barra de 8 sensores como máximo. La pista tiene un puente con 13 grados de pendiente y un tramo cuadrado en el que atajar por el medio cuesta 20 segundos de penalización; si el cuerpo del robot queda totalmente sobre el fondo negro se considera fuera de pista, y si se queda parado sobre la línea más de 30 segundos queda descalificado. Otras competiciones escolares y regionales usan lo contrario, una línea negra de 20 ± 2 mm sobre fondo blanco, y piden robots más pequeños de 500 gramos como máximo. En resumen: lee siempre el reglamento de ese año antes de diseñar tu robot.

  • Pista invertida: Para una línea blanca sobre fondo negro, pon a true la variable WHITE_LINE de los códigos. Si los colores cambian dentro de la pista, puedes añadir una comprobación que cambie de modo cuando los sensores exteriores empiecen a ver negro y el central blanco.
  • Curvas de 90 grados: Baja la velocidad base antes de la curva o deja que la rueda interior gire hacia atrás; en los códigos de este artículo la rueda interior ya va hacia atrás cuando la corrección supera la velocidad base.
  • Cruces: Cuando todos los sensores ven la línea a la vez, sigue recto; si necesitas elegir dirección, monta una máquina de estados que cuente los cruces.
  • Líneas discontinuas: Cuando la línea desaparezca un momento, prueba a seguir recto 100–200 ms en lugar de girar enseguida.
  • Rampa y puente: En la pendiente cambia la distancia entre el sensor y el suelo; mantén la barra de sensores baja y cerca de las ruedas, y reduce un poco la velocidad en la rampa.
  • Pista de entrenamiento: Pega cinta aislante negra mate de 19 mm sobre cartulina blanca mate o cartón pluma; en los primeros intentos no hagas curvas con un radio menor de 15 cm.

Problemas frecuentes y soluciones

ProblemaCausa probableSolución
El robot zigzaguea en las rectasKp demasiado alto, Kd demasiado bajo o demasiada velocidadBaja Kp un 20 % aproximadamente, sube Kd; si hace falta, baja la velocidad base
Se sale de la pista en las curvasVelocidad alta, barra de sensores estrecha o mala reacción al perder la líneaBaja la velocidad base, deja que la rueda interior vaya hacia atrás y gira hacia el último lado visto
El robot siempre tira hacia un ladoLos motores giran a distinta velocidad o la barra no está centradaCentra los sensores y reduce el motor más rápido con un pequeño factor (por ejemplo, 0,95)
Los sensores ven negro o blanco en todas partesSin calibrar, sensor demasiado alto o trimmer mal ajustadoBaja la altura a 3–5 mm y repite la calibración sobre la pista
El Arduino se reinicia al arrancar los motoresLa tensión de la batería cae de golpe o la masa común es deficienteAlimenta la placa por separado o añade un condensador de 470 µF a la alimentación, acorta los cables
El robot cambia de comportamiento al bajar la bateríaLa velocidad del motor baja con la tensiónMide la tensión de la batería y escala la velocidad, o pon una batería cargada antes de cada carrera
Funciona en casa, pero no en la competiciónLa luz ambiente o un suelo brillante alteran el infrarrojoTapa los sensores con una falda negra y calibra con la luz de la competición
La forma más rápida de acotar un problema es levantar las ruedas del suelo y mover el robot con la mano sobre la línea: si las ruedas aceleran en el sentido correcto, los sensores y el código están bien y el problema está en la mecánica o en la alimentación. Para un diagnóstico detallado de los reinicios, consulta El Arduino se reinicia solo.

Siguientes pasos e ideas de proyectos

  • Seguidor que esquiva obstáculos: Añade un HC-SR04 delante para que el robot rodee los obstáculos de la pista; la lógica de decisión está en Robot evita obstáculos.
  • Modo Bluetooth: Pasa del seguimiento de línea al control con el móvil pulsando un botón; se añade fácilmente con el módulo Bluetooth HC-05.
  • Cronómetro de vuelta: Un robot que detecta la línea de salida y muestra el tiempo por vuelta en una pantalla OLED.
  • Resolvedor de laberintos: Un robot que cuenta y memoriza los cruces y en la segunda vuelta sigue el camino más corto.
  • Robot de almacén: Un pequeño modelo de AGV que se detiene en estaciones de la línea y suelta una carga con un servo.
  • Una base sólida: Si aún no has montado el chasis, los motores y la batería, empieza por la guía Cómo hacer un coche robot con Arduino.

Un robot seguidor de línea enseña sensores, algoritmos, motores y alimentación a la vez, y va un poco más rápido con cada ronda de ajustes. Primero haz que el robot avance con la versión if-else sencilla, después pasa al PID y ajusta los valores con paciencia. Si quieres saber cómo regula el PWM la velocidad del motor, ¿Qué es el PWM? es una buena continuación. En la app BlueGrays encontrarás paso a paso, con esquemas y código, los proyectos de detección de superficie blanca y negra con TCRT5000, el coche controlado por Bluetooth con L298N y el robot que esquiva obstáculos.

Preguntas frecuentes

¿Cuántos sensores necesita un robot seguidor de línea?

Con 2 sensores basta para un robot que funcione, pero con 3 y un código if-else sencillo el resultado es mucho más estable. Para un seguimiento rápido y suave se recomiendan 5 u 8 sensores analógicos con PID. Muchas competiciones permiten como máximo 8 sensores.

¿Por qué mi seguidor de línea zigzaguea?

La causa más habitual es un Kp demasiado alto y un Kd demasiado bajo: el robot corrige en exceso cada desviación y cruza al otro lado de la línea. Bajar un poco Kp, subir Kd, reducir la velocidad base y adelantar los sensores 4–8 cm respecto a las ruedas reduce la oscilación. Con un código if-else sencillo es normal un ligero zigzag, porque el robot solo cambia entre unas pocas velocidades fijas.

¿Se puede hacer un seguidor de línea sin PID?

Sí. Con un código if-else sencillo y 2 o 3 sensores el robot sigue la línea, y para proyectos escolares suele ser suficiente. Pero al subir la velocidad pierde la línea en las curvas y oscila en las rectas. Si buscas un buen puesto en una competición, tendrás que pasar al PID.

¿Qué batería usar en un robot seguidor de línea?

Lo más práctico son dos baterías Li-ion 18650 en serie: dan 7,4 V nominales y entregan sin problema la corriente alta que piden los motores. En robots ligeros también se usa una LiPo 2S. Una pila de 9 V no puede dar la corriente de los motores y se agota enseguida.

¿Cómo cambia el código para una línea blanca sobre fondo negro?

Basta con invertir los valores de los sensores: en los códigos de este artículo, al poner WHITE_LINE a true el valor normalizado pasa a ser 1000 − valor y el robot sigue la línea blanca. En los módulos digitales la misma variable invierte la lectura de DO. Aun así, debes calibrar sobre la pista en la que vas a competir.

¿A qué altura del suelo debe ir el sensor TCRT5000?

El TCRT5000 funciona mejor a unos 2,5 mm; en la práctica, dejar 3–5 mm entre el sensor y el suelo da buenos resultados. Por encima de 1 cm la diferencia entre negro y blanco se reduce mucho y el robot pierde la línea. En pistas con rampas, montar la barra de sensores cerca de las ruedas reduce el cambio de distancia.

Ver todos los proyectos
TCRT5000 Detección de superficie blanca y negra
#78Intermedio
Premium

TCRT5000 Detección de superficie blanca y negra

Proyecto que detecta el color del suelo con el sensor TCRT5000 y enciende el LED rojo en suelo negro y el LED verde en suelo blanco. Enseña la lógica de detección del umbral de color que constituye la

4.7(40)
49
Medición de distancia con TCRT5000
#77Intermedio
Premium

Medición de distancia con TCRT5000

Proyecto que mide la cantidad de luz reflejada por la superficie con el sensor infrarrojo TCRT5000 y la muestra como un valor analógico en la pantalla LCD I2C. Enseña los fundamentos de la detección ó

4.6(33)
43
Coche controlado por Bluetooth con L298N
#80Avanzado
Premium

Coche controlado por Bluetooth con L298N

Proyecto de coche robot 4WD (cuatro motores, tracción en las cuatro ruedas) que se puede controlar desde el teléfono con Arduino y el módulo Bluetooth HC-05. Un total de cuatro ruedas se accionan simu

5.0(38)
64
Robot para evitar obstáculos
#81Avanzado
Premium

Robot para evitar obstáculos

¿Qué le parece construir un vehículo inteligente que encuentre su propio camino? El Robot Evitador de Obstáculos explora constantemente su entorno gracias al sensor ultrasónico que lleva delante. Cuan

5.0(38)
49
HC-SR04 Medición de distancia (pantalla en serie)
#27Principiante

HC-SR04 Medición de distancia (pantalla en serie)

Sensor ultrasónico HC-SR04 para medir distancias y mostrar el resultado en cm en el monitor serie. Enseña el principio del cálculo de distancias con ondas sonoras.

4.6(44)
66
Encender un LED con pulsador (Push)
#9Principiante

Encender un LED con pulsador (Push)

Proyecto de control instantáneo (push) que enciende el LED al pulsar el botón y lo apaga al soltarlo. Enseña el uso de la lectura de botones y la expresión condicional con digitalRead.

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

ESP32 LED Fade con PWM

Cambiar el brillo del LED suavemente de 0 a max, de max a 0. Controlamos PWM con analogWrite() como en Arduino - la misma sintaxis con ESP32 Arduino Core 3.0+ funciona sin problemas. Pin definición + pinMode + analogWrite es suficiente.

4.8(36)
54
Raspberry Pi Pico PWM LED Fade (Efecto Respiración, GP14)
#160Principiante
Premium

Raspberry Pi Pico PWM LED Fade (Efecto Respiración, GP14)

¡El LED ya no solo parpadea — respira! 🌬️ Con el **PWM** (modulación por ancho de pulso) del Raspberry Pi Pico, el brillo sube y baja lentamente. Aprende la clase machine.PWM, duty_u16() (brillo de 16 bits 0–65535), la frecuencia PWM, bucles range() y salida tipo analógica. Base para mezcla de color RGB, control de motores, servos y buzzer.

0.0(0)
4