Tutorial · Principiante · 20 min

Leer una matriz de sensores IR de reflectancia para seguir líneas

Cómo un seguidor de línea convierte una fila de sensores IR en un error de dirección: analógico o digital, calibración y la fórmula de promedio ponderado.

Un robot seguidor de línea solo es tan bueno como el número sobre el que dirige. Ese número proviene de una fila de sensores infrarrojos de reflectancia apuntando al suelo: la línea es de un color, el fondo de otro, y los sensores informan cuánta luz IR rebota. Esta guía convierte esa fila de lecturas en una posición de línea única y suave: el error sobre el que actúa tu controlador.

Cómo ve la línea un sensor de reflectancia

Cada sensor combina un LED IR con un fototransistor. El LED ilumina hacia abajo; el fototransistor mide la reflexión. Una línea negra mate refleja poco IR (lectura baja); el fondo más claro refleja más (lectura alta). Barre la matriz sobre una pista negra sobre blanco y cada sensor oscila entre un valor «sobre la línea» y un valor «fuera de la línea».

Dos cosas cambian esos valores brutos entre pasadas: la altura del sensor sobre la superficie y la luz ambiental. Por eso nunca fijes umbrales en código: calibra. Los mismos sensores apuntando hacia abajo a un borde de ring en lugar de a una línea se convierten en un detector de borde, donde una lectura perdida le cuesta al robot el combate.

Sensores analógicos frente a digitales

  • Los sensores analógicos dan un valor continuo (p. ej. 0–1023 en un ADC de Arduino). Puedes saber a qué distancia está la línea de cada sensor, lo que permite calcular una posición suave y dirigir de forma proporcional.
  • Los sensores digitales solo informan encendido/apagado tras un umbral interno. Más sencillos de cablear, pero pierdes la información intermedia, así que la dirección es más basta.

Para un seguimiento proporcional de línea —el tipo que se ajusta con un controlador PID— usa lecturas analógicas. Este es el enfoque que modela el simulador de seguidor de línea.

Calibra primero

Antes de una pasada, barre cada sensor sobre la línea y sobre el fondo y registra el mínimo y el máximo de cada sensor. Luego normaliza cada lectura en vivo a 0–1000 contra su propio rango:

// Calibración por sensor capturada durante un barrido.
int minv[N], maxv[N];

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

Calibrar por sensor cancela el brillo desigual de los LED y las pequeñas diferencias de altura a lo largo de la barra, para que un sensor no sesgue en silencio toda la estimación.

De lecturas a una posición de línea

Ahora reduce la matriz a una sola posición con un promedio ponderado por reflectancia. Da a cada sensor una posición fija (…−2, −1, 0, +1, +2… a lo largo de la barra) y pondérala por su lectura normalizada. El resultado es la ubicación de la línea respecto al centro de la barra: positiva a un lado, negativa al otro, cero cuando la línea queda exactamente en el centro.

Gráfico de barras de cinco sensores de reflectancia a lo largo de la barra del robot. La línea se sitúa a la derecha del centro, por lo que los sensores del lado derecho leen con mayor intensidad. Una línea discontinua marca el centro de la barra (error cero) y una línea continua marca la posición ponderada por reflectancia en más 0,69, el error de dirección calculado.
El promedio ponderado de las lecturas de los sensores proporciona una posición suave de la línea: aquí la línea se encuentra a la derecha del centro, por lo que el error es positivo y el controlador gira de vuelta hacia cero. Descargar SVG
// Posiciones de los sensores a lo largo de la barra, p. ej. { -2000,-1000,0,1000,2000 }.
long readLinePosition() {
  long weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    int v = normalized(analogRead(pins[i]), i);
    weighted += (long)v * position[i];
    total    += v;
  }
  if (total == 0) return lastPosition;  // línea perdida — reutiliza la última dirección
  lastPosition = weighted / total;
  return lastPosition;                  // 0 = centrado, signo = a qué lado
}

Dos detalles hacen que esto sea robusto:

  • Divide entre la lectura total, no entre el número de sensores, de modo que una línea tenue y una línea marcada se asignen a la misma escala de posición.
  • Cuando todos los sensores pierden la línea (total == 0), reutiliza la última posición conocida en lugar de devolver cero. Eso hace que el robot siga girando hacia donde estaba la línea, en lugar de salirse en línea recta en una curva cerrada.

¿Cuántos sensores?

Cinco es un buen punto de partida: suficiente para discernir dirección y magnitud mientras se mantiene el cálculo fácil de inspeccionar. Tres funciona a baja velocidad; los robots de competición usan ocho o más para una resolución más fina a alta velocidad. Más sensores compran principalmente una estimación de posición más suave en curvas cerradas.

La rutina de calibración, al completo

“Calibra primero” es fácil de decir y fácil de hacer mal. Aquí está la rutina completa, incluyendo la protección que impide que un canal muerto arruine la estimación:

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

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

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

  // Un canal que nunca vio contraste convertiría el ruido en valores normalizados
  // enormes y arrastraría toda la estimación hacia sí mismo.
  for (int i = 0; i < N; i++) {
    if (maxv[i] - minv[i] < 50) {
      Serial.print(F("channel dead or never crossed the line: "));
      Serial.println(i);
    }
  }
}

Pasea la matriz sobre la línea durante toda la ventana de calibración. Un robot inmóvil registra la misma lectura como su mínimo y como su máximo, produciendo un intervalo nulo y un sensor que no aporta nada. Girar el robot sobre sí mismo encima de la línea durante tres segundos es la maniobra estándar, y funciona porque cada canal cruza la cinta.

Recalibra al inicio de cada recorrido, no una sola vez en el montaje. La luz ambiental cambia, la altura de marcha cambia cuando el pack de baterías se mueve, y la cinta acumula polvo. Esta es la razón más común por la que un seguidor de línea funciona en una mesa y falla en otra.

Eliminación de la luz ambiental

La luz solar contiene una enorme cantidad de infrarrojo a 940 nm, y el fototransistor no puede distinguirla del reflejo de tu propio emisor. Un robot que funciona en interiores y falla cerca de una ventana sufre exactamente este fenómeno.

Si los emisores de tu matriz se pueden conmutar —la mayoría tiene un pin LEDON o CTRL—, la solución es completa y cuesta una lectura extra por canal:

int ambientSubtracted(int i) {
  digitalWrite(LEDON, LOW);
  delayMicroseconds(200);              // deja que el fototransistor se estabilice
  const int dark = analogRead(PIN[i]); // solo luz ambiental

  digitalWrite(LEDON, HIGH);
  delayMicroseconds(200);
  const int lit = analogRead(PIN[i]);  // ambiental + nuestro emisor

  return lit - dark;                   // solo nuestro emisor
}

Cualquier elemento constante del entorno se cancela con exactitud. El coste es duplicar el tiempo de muestreo, lo que en un seguidor de línea rápido es relevante; pero convierte un sensor poco fiable en uno fiable, y es la técnica que emplean las matrices comerciales.

También tiene un segundo beneficio a destacar: con los emisores apagados entre lecturas, su consumo medio se reduce drásticamente. Los emisores de una matriz de 8 canales pueden demandar 100–200 mA continuos, lo que en una batería pequeña representa una parte sustancial del presupuesto de energía.

Perder la línea y recuperarla

El caso total == 0 anterior merece más atención de la habitual, ya que es donde un seguidor de línea falla de forma más visible.

Devolver cero es la implementación obvia y la equivocada: cero significa “perfectamente centrado”, por lo que el robot acelera en línea recta —alejándose de la línea que acaba de perder, que casi con seguridad estaba en curva.

Devolver la última posición hace que el robot siga girando hacia el sentido en el que ya lo hacía, que es donde se fue la línea. Ese único cambio marca la diferencia entre un robot que se recupera de una esquina cerrada y uno que se sale de la pista.

Una versión más completa añade un tiempo de espera, de modo que un robot genuinamente extraviado se detenga en lugar de girar en espiral para siempre:

long lastPosition = 0;
unsigned long lostSince = 0;

long readLinePosition() {
  long weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    const int v = normalized(analogRead(PIN[i]), i);
    if (v > 100) {                    // ignora canales que están claramente en el suelo
      weighted += (long)v * POSITION[i];
      total    += v;
    }
  }

  if (total == 0) {
    if (lostSince == 0) lostSince = millis();
    if (millis() - lostSince > 1500) stopMotors();   // realmente perdido
    return lastPosition;              // sigue girando hacia donde íbamos
  }

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

La altura de marcha decide el contraste

Los sensores de reflectancia tienen un punto dulce estrecho, y es la variable mecánica que más a menudo se disfraza de problema de software.

Ride height What you see in the calibration span
1–3 mm Mayor rango, pero la matriz roza con cualquier irregularidad del suelo
3–8 mm La banda utilizable. La mayoría de matrices especifican unos 3 mm
8–15 mm Rango reduciéndose notablemente; la normalización empieza a amplificar el ruido
> 15 mm Efectivamente ciega en la mayoría de superficies

Siguen dos consecuencias. Móntala rígida —una matriz en un soporte flexible cambia la altura cuando el robot acelera, lo que cambia las lecturas, que el bucle de control interpreta como que la línea se mueve. Y móntala nivelada, porque una matriz inclinada da a cada canal una altura efectiva distinta y por tanto una calibración distinta, lo que se muestra como un robot que sigue bien en una dirección y mal en la otra.

Cuántos sensores, cuantificado

Channels Position resolution across a 19 mm line Good for
3 Esencialmente izquierda / centro / derecha Entender la idea
5 ~4 regiones discretas, suave entre ellas Un primer seguidor de línea en una pista suave
8 ~7 regiones Pistas rápidas, curvas cerradas, detección de cruces
12–16 Fina Robots de competición a velocidad

El número que más importa que la cantidad es el ancho de la matriz respecto a la línea. Para seguir líneas sin más, la matriz debe ser lo bastante ancha para que al menos un sensor esté sobre el suelo en cada borde de la línea, es decir, más o menos el doble del ancho de la cinta.

Para la detección de cruces en laberintos de líneas el requisito es más estricto: el robot reconoce una ramificación cuando ve que los sensores exteriores se oscurecen mientras el centro sigue sobre la línea, así que la matriz debe tener al menos tres veces el ancho de la cinta. Una matriz de 8 canales con 8 mm de separación mide unos 60 mm, lo justo para cinta de 19 mm.

Solución de problemas

Symptom Likely cause Fix
Funciona en una superficie, falla en otra Calibración obsoleta o ausente Recalibra al inicio de cada pasada
Un canal arrastra la estimación Emisor muerto — su rango es mínimo Comprueba el maxv − minv de cada canal; protégelo
El robot sigue recto cuando pierde la línea Devolver 0 al perder la línea Devuelve la última posición en su lugar
Sigue sobre cinta marcada, vaga sobre cinta desvaída Ruido del suelo entrando en el promedio Añade el umbral v > 100
Falla cerca de una ventana Infrarrojo ambiental Resta con emisor apagado, o cubre la matriz
La posición salta cuando la línea cruza sensores No normalizado por canal Calibra cada canal contra su propio mínimo y máximo
Las lecturas derivan en una pasada larga Batería cayendo, así que los emisores se atenúan Comprueba el riel bajo carga
Todos los canales leen lo mismo Matriz demasiado alta 3–8 mm, luego recalibra
Sigue bien en un sentido, mal en el otro Matriz no nivelada Cada canal tiene una altura efectiva distinta

A dónde va esto ahora

Ese valor único de posición es el término de error. Mételo en un bucle de control que acelera una rueda y frena la otra, y tienes un seguidor de línea. Aprende ese bucle en Ajustar un controlador PID, y luego prueba todo de principio a fin en el simulador de seguidor de línea.

Explora el grafo

Forma parte de estos montajes

Proyectos y rutas de aprendizaje que incluyen este tutorial.

Lecturas adicionales

Referencias