Tutorial · Principiante · 20 min de lectura

Construye un robot seguidor de línea: de sensores a dirección PID

Cómo funciona un seguidor de línea: sensores de reflectancia, un error de posición ponderada y un lazo PID que dirige un chasis diferencial con suavidad.

Un seguidor de línea es el clásico primer robot autónomo: lee una línea bajo sus sensores y dirige para mantenerse sobre ella. Por debajo, es un pequeño problema de control —convertir lecturas de sensores en una corrección de dirección— lo que lo hace una forma perfecta de aprender control por realimentación. Síguelo en el simulador de seguidor de línea.

Detecta la línea

Un sensor de reflectancia emite luz infrarroja hacia abajo y mide cuánta rebota. Una línea oscura refleja menos que la superficie clara que la rodea, así que cada sensor informa si está sobre la línea. Una barra de varios sensores en el frontal del robot te dice no solo si estás sobre la línea, sino dónde está la línea respecto al centro.

Cinco sensores son un buen punto de partida. Tres funcionan a baja velocidad; los robots de competición suelen usar ocho para resolución más fina.

Cinco sensores de línea en fila, cada uno etiquetado con un peso de menos dos a la izquierda hasta cero en el centro hasta más dos a la derecha, y cada uno mostrando una lectura uno o cero debajo. Los dos sensores en pesos cero y más uno leen uno y están resaltados; el resto lee cero. La fórmula de media ponderada calcula la suma de pesos por lecturas dividida por la suma de lecturas, dando cero más uno sobre dos igual a más cero coma cinco. Una advertencia indica que todo ceros significa línea perdida, que es un caso distinto y no un error de cero.
Cinco lecturas de encendido/apagado colapsan en un número con signo sobre el que el lazo de dirección puede actuar. Su signo es la dirección, su tamaño es cuánto te desvías. Descargar SVG

Convierte lecturas en un número de error

En lugar de reaccionar a sensores individuales, combínalos en una única posición ponderada. Da a cada sensor un valor de posición (negativo a la izquierda, cero en el centro, positivo a la derecha) y toma la media ponderada por cuánto ve la línea cada uno:

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

El resultado es tu error: cero significa centrado, negativo significa que la línea está a la izquierda, positivo a la derecha. Un error suave y continuo es justo lo que necesita un controlador.

Dirige con PID

Alimenta ese error a un controlador PID. La corrección acelera una rueda y frena la otra, girando el robot de vuelta hacia la línea:

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

La ganancia proporcional lleva la mayor parte de la dirección; la ganancia derivativa amortigua la oscilación en curvas; un pequeño término integral elimina cualquier deriva constante. Si eres nuevo eligiendo ganancias, trabaja primero cómo sintonizar un controlador PID.

Arregla los fallos comunes

  • Oscilar alrededor de la línea: la ganancia proporcional es demasiado alta o la derivativa demasiado baja. Reduce primero la velocidad, luego resintoniza. En hardware real, un delay() largo en el bucle añade tiempo muerto que anula el término derivativo.
  • Perder la línea en giros cerrados: el robot supera su tasa de actualización de sensores. Baja la velocidad, ensancha la barra o reutiliza la última dirección conocida de la línea para que el robot gire de vuelta en lugar de salirse recto.
  • Derivar hacia un lado: calibra los sensores. Pásalos sobre la línea y el fondo, registra el mínimo y máximo de cada uno, y normaliza las lecturas en vivo contra ese rango.

Todo el bucle, en un sitio

Los fragmentos de arriba se convierten en un robot funcional solo cuando la temporización, la calibración y el caso de línea perdida se manejan juntos:

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);
}

Cinco cosas ahí dentro son las que separan un robot que sigue una línea de uno que casi lo hace.

dt real. Tanto I como D dividen por tiempo transcurrido. Un bucle cuya duración cambia al añadir funciones —y lo hará— calcula ambos contra una mentira si supones un periodo constante.

El umbral v > 100. Sin él, cinco canales cada uno leyendo un pequeño valor de suelo arrastran la media ponderada hacia el centro sin importar dónde esté la línea. El síntoma es un robot que sigue bien una línea marcada y mal una tenue.

Mantener el último error al perder la línea. Cero significa «centrado», así que un robot que pone cero conduce recto a toda velocidad —lejos de la línea, que casi seguro estaba curvando. Mantener el último error lo mantiene girando hacia donde ya giraba.

Derivativa sobre la medida. Diferenciar el error hace que un cambio de consigna produzca un pico enorme. Aquí la consigna siempre es cero así que apenas importa, pero el hábito merece formarse.

Integración condicional. Una vez que la corrección satura, seguir acumulando integral solo guarda un sobreimpulso para devolver después.

Un orden de sintonía que funciona

No sintonices las tres a la vez. Pon KI = 0 y KD = 0, luego:

Paso Cambio Observa Detente cuando
1 Sube KP desde un valor pequeño El robot empieza a responder a la línea Siga una curva suave, serpenteando un poco
2 Sigue subiendo KP El serpenteo crece hasta oscilación Oscile de forma estable —anota este valor como Ku
3 Pon KP a la mitad de Ku La respuesta es rápida, con sobreimpulso —
4 Sube KD El serpenteo se amortigua Las rectas quedan limpias; más KD lo vuelve nervioso
5 Solo si sigue descentrado de forma constante, añade un poco de KI El desplazamiento se cierra Se cierra —KI rara vez hace falta en un seguidor de línea
6 Sube la velocidad BASE Todo se degrada junto Resintoniza KP y KD a la nueva velocidad

El paso 6 es el que la gente olvida: las ganancias dependen de la velocidad. Una sintonía perfecta a 140 de ciclo de trabajo oscilará a 220, porque la misma corrección de dirección ahora produce un cambio mayor en el rumbo por unidad de tiempo. Espera resintonizar siempre que cambies la velocidad de forma significativa.

La integral es genuinamente rara vez útil aquí. La consigna de un seguidor de línea es cero y las perturbaciones son transitorias, así que normalmente no hay desplazamiento en estado estacionario que KI deba cerrar. Si ves un desplazamiento persistente, sospecha trim desigual de motores o un sensor sin calibrar antes de recurrir a la ganancia integral.

La tasa del bucle limita todo en silencio

Tasa del bucle Intervalo Distancia recorrida a 0,4 m/s
20 Hz 50 ms 20 mm entre decisiones
50 Hz 20 ms 8 mm
200 Hz 5 ms 2 mm
500 Hz 2 ms 0,8 mm

Un robot que decide cada 20 mm no puede seguir una curva con precisión sin importar cómo se fijen las ganancias, y ningún término derivativo recupera información que nunca se muestreó.

Los culpables habituales son fáciles de eliminar: un delay() en cualquier parte del bucle, Serial.print de más de unos pocos valores a 9600 baudios, y leer sensores que no usas. Eliminarlos suele llevar una primera construcción de 50 Hz a varios cientos, y el seguimiento mejora sin tocar una ganancia. Mide la tasa antes de sintonizar, no después.

Solución de problemas

Síntoma Causa probable Solución
Serpentea en rectas KP demasiado alta o KD demasiado baja Reduce KP a la mitad, luego sube KD
Oscila cada vez más rápido KP por encima de la ganancia última Reduce hasta que se asiente
Recorta esquinas Demasiado rápido para el adelanto del sensor Baja la velocidad, o mueve el array adelante
Se sale al perder la línea Error puesto a cero al perder la línea Mantén el último error
Sigue bien en una dirección, mal en la otra Array no nivelado, o trim de motores Nivela el array; ajusta cada motor
Deriva hacia un lado Sensores sin calibrar, o disparidad de motores Calibra por canal, luego ajusta
Bien en el banco, errante en el suelo La altura de marcha cambió 3–8 mm, montaje rígido, recalibrar
Ayer funcionaba, hoy lento Tensión de batería Las ganancias son propiedades de la planta —resintoniza o cierra un lazo de velocidad
Vibra solo a baja velocidad Por debajo de la banda muerta de los motores Mide el ciclo de arranque de cada motor y compensa
Nada responde a KD Un delay() en el bucle El tiempo muerto anula por completo la acción derivativa

Pruébalo tú mismo

En el simulador de seguidor de línea puedes dibujar una pista, cambiar el número de sensores y la velocidad, y sintonizar las ganancias mientras observas el error y el tiempo de vuelta. Todo lo que aprendas sobre el error ponderado y el lazo de dirección se traslada directo a un robot Arduino.

Explora el grafo

Forma parte de estos montajes

Proyectos y rutas de aprendizaje que incluyen este tutorial.

Lecturas adicionales

Referencias