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.
#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