Tutorial · Principiante · 18 min de lectura

Barrido de sensor ultrasónico con servomotor

Monta un HC-SR04 sobre un SG90 y bárrelo a varios ángulos: el robot medirá la distancia en abanico y elegirá la dirección más despejada en vez de adivinar.

Un único sensor de distancia mirando al frente puede decirle a un robot que hay algo delante, pero no hacia dónde está libre. Así que gira a ciegas, a menudo justo contra otro obstáculo o un callejón sin salida. Montar el HC-SR04 en un servo SG90 arregla esto: el robot se para, mira a izquierda y derecha, y gira hacia la dirección más abierta—el mismo truco que hace una persona en una esquina ciega.

Por qué un sensor fijo atrapa al robot

Imagina un robot que solo ve recto hacia delante. Se encuentra con una pared, sabe que tiene que girar, pero no tiene ni idea de si izquierda o derecha es mejor. Si elige mal se mete en la esquina de la que acaba de salir. Un sensor montado en un servo convierte un número (la distancia de frente) en un pequeño mapa del espacio alrededor del frente del robot, así que el giro es una decisión informada y no una moneda al aire.

Montar el sensor en el servo

Fija el HC-SR04 a la horquilla del servo y monta el servo en el frente-centro del chasis, orientado de forma que la horquilla barra el sensor de izquierda a derecha (no de arriba abajo). En la posición central del servo el sensor debe mirar recto al frente. Eso te da un barrido limpio desde un lado, pasando por recto, hasta el otro lado.

Barrer y muestrear

Avanza el servo por unos cuantos ángulos y toma una lectura filtrada en cada uno. Reutiliza la función de distancia limpia de leer el HC-SR04—un ping crudo a mitad de barrido es demasiado ruidoso para dirigir con él.

Vista cenital estilo radar de un robot en la parte de abajo barriendo un sensor ultrasónico a lo largo de cinco ángulos de 30 a 150 grados. Unos arcos de rango tenues marcan 20, 40 y 60 cm. Cuatro rayos son cortos y terminan en puntos de obstáculo (22, 34, 18, 41 cm); un rayo a 120 grados es largo (68 cm), resaltado en cian y etiquetado como «más abierto, gira aquí».
El barrido mide distancia en cada ángulo; la lectura más larga es el rumbo más abierto, así que el robot gira hacia ahí en vez de adivinar. Descargar SVG
#include <Servo.h>
Servo scanner;

const int ANGLES[] = {30, 60, 90, 120, 150};
const int N = 5;

int bestAngle() {
  long best = -1;
  int bestA = 90;
  for (int i = 0; i < N; i++) {
    scanner.write(ANGLES[i]);
    delay(180);                 // let the servo REACH the angle first
    long d = medianCm();        // filtered read from the previous tutorial
    if (d > best) { best = d; bestA = ANGLES[i]; }
  }
  scanner.write(90);            // re-centre for driving
  return bestA;                 // > 90 = open to the left, < 90 = right
}

No leas a mitad de movimiento

El bug más habitual aquí es leer antes de que el servo llegue. servo.write() vuelve al instante, pero al SG90 le lleva tiempo girar hasta el ángulo nuevo. Haz el ping demasiado pronto y la distancia pertenece a donde el sensor estaba, no a donde le pediste que apuntara. Un delay corto después de cada write()—suficiente para que el servo se asiente— hace que toda lectura sea fiable.

Convertir el barrido en una decisión

El ángulo con mayor distancia es la dirección más abierta. Un par de refinamientos lo mantienen sensato:

  • Prefiere recto delante en caso de empate. Si dos direcciones leen parecido, sesga hacia el centro para que el robot no serpentee sin necesidad.
  • ¿Todas las direcciones cerca? Eso es un callejón sin salida—retrocede y vuelve a barrer en vez de meter el morro más adentro.

Velocidad frente a cobertura

Más ángulos dan una imagen más fina pero un bucle más lento, y un bucle lento significa que el robot reacciona tarde. De tres a cinco ángulos—izquierda, delante, derecha, más un par intermedios—es el punto dulce habitual. Pasa esa decisión de «más abierto» al algoritmo de evitación de obstáculos, y prueba todo el comportamiento en el simulador de evitación de obstáculos antes de construirlo.

¿Cuánto tarda el servo en realidad?

«Un retardo corto» es la instrucción más vaga del código de arriba, y merece la pena sustituirla por un número que hayas medido. Un SG90 está especificado en unos 0,1 s por 60° a 4,8 V, sin carga — así que el tiempo de asentamiento depende de cuánto tiene que recorrer:

Paso de barrido Tiempo de recorrido (spec) Más asentamiento Usar
30° 50 ms ~30 ms 80 ms
60° 100 ms ~40 ms 140 ms
90° 150 ms ~40 ms 190 ms
180° (volver al centro desde un extremo) 300 ms ~50 ms 350 ms

Dos cosas hacen que la cifra real sea peor que la especificación. La carga: un HC-SR04 con su cable en la horquilla es poco pero no cero, y ralentiza el servo y añade sobreoscilación. La tensión: a 4,0 V desde un pack que se hunde, el mismo servo es notablemente más lento que a 6 V.

Mide el tuyo en vez de fiarte de la tabla. Ordena un paso de 60° y cronometra cuánto tarda en que la lectura de distancia deje de cambiar:

void measureSettle() {
  scanner.write(60);  delay(500);            // known start
  scanner.write(120);                        // command the step
  const unsigned long t0 = millis();
  long prev = -1;
  for (int i = 0; i < 40; i++) {             // sample every 10 ms
    const long d = pingCm();
    Serial.print(millis() - t0); Serial.print(' '); Serial.println(d);
    delay(10);
  }
}

La lectura se asienta en un momento concreto. Usa ese número más un margen pequeño, y habrás eliminado la mayor fuente de sinsentido de todo el comportamiento.

Barrer en una dirección es más rápido que saltar de un lado a otro

El código de arriba avanza 30 → 60 → 90 → 120 → 150, que ya es un barrido único, y es deliberado. Leer ángulos en un orden disperso significa que cada paso es grande, y el coste de asentamiento es por paso:

Patrón Recorrido total Coste de asentamiento a ~1,7 ms/grado
Barrido en orden (30→150) 120° ~200 ms más cinco asentamientos cortos
Desde el centro (90, 60, 120, 30, 150) 300° ~510 ms
Vuelta al centro entre cada uno 480° ~820 ms

Un barrido completo es un momento ciego para el robot — está parado y sin hacer progreso — así que reducir su duración a la mitad es una mejora real en cómo se siente el robot. Barre en orden y, si barres repetidamente, alterna el sentido para no pagar nunca el viaje de vuelta.

Barrido no bloqueante

El delay(180) en cada paso significa que un barrido de cinco ángulos bloquea casi un segundo. En un robot que está parado de todos modos eso es tolerable, pero también significa que los finales de carrera no se leen y que cualquier otro comportamiento queda congelado.

El mismo tratamiento de máquina de estados que se usó para el ping se aplica aquí:

enum ScanState { SCAN_IDLE, SCAN_MOVING, SCAN_READING };

ScanState scanState = SCAN_IDLE;
uint8_t   scanIndex = 0;
unsigned long scanSince = 0;
long      scanResult[N];
bool      scanComplete = false;

void updateScan() {
  switch (scanState) {
    case SCAN_IDLE:
      break;

    case SCAN_MOVING:
      if (millis() - scanSince < SETTLE_MS) return;      // measured, above
      scanState = SCAN_READING;
      break;

    case SCAN_READING:
      scanResult[scanIndex] = medianCm();
      if (++scanIndex >= N) { scanComplete = true; scanState = SCAN_IDLE; scanner.write(90); }
      else { scanner.write(ANGLES[scanIndex]); scanSince = millis(); scanState = SCAN_MOVING; }
      break;
  }
}

El bucle principal llama a updateScan() y sigue. Los finales de carrera se siguen leyendo, la máquina de estados sigue corriendo, y el barrido termina cuando termina.

Interpolar entre ángulos

Cinco lecturas dan cinco rumbos, pero la apertura real rara vez está exactamente en uno de ellos. Un centroide ponderado sobre las lecturas abiertas da un rumbo entre las muestras, de la misma forma que una matriz de reflectancia da la posición de una línea entre sensores:

// Weight each angle by how open it is, ignoring anything below the stop threshold.
float openBearing() {
  float weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    if (scanResult[i] < STOP_CM) continue;             // blocked, contributes nothing
    const float w = scanResult[i] - STOP_CM;           // how much clearance beyond the threshold
    weighted += w * ANGLES[i];
    total    += w;
  }
  return (total > 0) ? (weighted / total) : 90.0f;     // 90 = straight ahead
}

Esto es una mejora real sobre «quedarse con el máximo». Quedarse con el máximo significa que una puerta muestreada ligeramente descentrada manda al robot al borde de la puerta; el centroide lo manda al centro. También degrada con elegancia — si dos ángulos adyacentes están abiertos, el resultado está entre ellos en vez de ser arbitrariamente uno de los dos.

El cono limita lo que un barrido puede contarte

El haz de un HC-SR04 tiene un semiángulo de unos 15°, lo que a 1 m es un cono de unos 53 cm de diámetro. Muestrear a intervalos de 30° por tanto se solapa muchísimo — las lecturas adyacentes están mirando casi el mismo espacio.

Separación de muestras A 1 m Consecuencia
15° Los conos se solapan casi por completo Tiempo perdido; ninguna información nueva
30° Solapamiento sustancial El punto dulce práctico
45° Pequenos huecos entre conos Aceptable; puede perderse un objeto estrecho
60° Huecos reales Una pata de silla puede quedarse entre dos muestras sin verse

La consecuencia importante es que un barrido no te da una resolución angular mejor que la del haz. Una lectura de 68 cm a 120° significa «hay algo a 68 cm, en algún sitio a unos 15° de 120°» — lo cual está bien para elegir una dirección de giro y es inútil para medir la anchura de un hueco. Si necesitas esto último, un VL53L0X en el mismo servo tiene un punto efectivo mucho más estrecho y da un barrido realmente resuelto.

Resolución de problemas

Síntoma Causa probable Solución
El robot gira con confianza contra una pared Lectura antes de que el servo se asentara Mide el tiempo de asentamiento y úsalo
Las lecturas se difuminan entre ángulos Lo mismo, en menor grado Aumenta el retardo de asentamiento
El barrido tarda más de un segundo Retardos bloqueantes, u orden de ángulos disperso Máquina de estados no bloqueante; barre en orden
La placa se reinicia durante un barrido Corriente del servo a través del Arduino Fuente de 5 V aparte, masa común
El robot va hacia el borde de una puerta Quedarse con el ángulo máximo Usa el centroide ponderado
El control de velocidad de los motores dejó de funcionar La biblioteca Servo toma el Timer 1 (pines 9, 10) Mueve el PWM de motores a 3, 5, 6 o 11
El servo vibra entre lecturas Ruido de alimentación, o pulsos temporizados por software Condensador en el servo; un PCA9685 si persiste
Una pata de silla no se detecta Separación de muestras mayor que el haz Separación de 30°, o un sensor de haz estrecho
El barrido está bien, el robot sigue atascándose La navegación reactiva no tiene memoria Lo esperable — añade un escape por detección de atasco

Explora el grafo

Forma parte de estos montajes

Proyectos y rutas de aprendizaje que incluyen este tutorial.

Lecturas adicionales

Referencias