Saltar al contenido
Volver a la ficha del proyecto

Manual del proyecto · Proyecto 10

Dron Acuático

Construcción con impresión 3D y programación de un dron capaz de desplazarse por la superficie del agua: recorre trayectorias predeterminadas (nivel inicial) y esquiva obstáculos con sensores ultrasónicos (nivel avanzado).

Arduino UNO R3 ESP32 DevKit
Dificultad:
Nivel educativo:
Secundario. Ciclo Básico.
Electrónica Programación Mecánica Fabricación digital

Saberes Digitales

INET / EDUCAR — Ministerio de Educación, Cultura, Ciencia y Tecnología de la Nación Argentina

2019

Contenido del manual

  1. 01 Materiales y equipamiento
  2. 02 Un dron que recorre la superficie del agua Nivel inicial
  3. 03 Un dron que esquiva las boyas Nivel avanzado

Materiales y equipamiento

Insumos

Imagen Insumo Cantidad Plataforma
ULN2003
Módulo motor ULN2003 Actúa como driver: comanda los dos motores DC en un único sentido de giro (encendido/apagado). Los giros del dron se logran frenando un motor, no invirtiéndolo. 1 Todas
ULN2003
Motor DC 5 V (1500 rpm) Uno por lado (izquierdo y derecho). Van montados en la superficie del dron, con su electrónica protegida del agua. 2 Todas
Foto de Sensor ultrasónico HC-SR04 Sensor de distancia ultrasónico (HC-SR04) Se usan desde el nivel avanzado (uno al frente y dos laterales). Con ESP32, el pin Echo entrega 5 V: interponé un divisor de dos resistencias hacia el GPIO para no dañar la placa. 3 Todas
Portapilas para 6 pilas AA (con sus pilas) Alimenta los motores a través del módulo ULN2003, de forma independiente de la placa. Baja tensión, apta para el aula. 1 Todas
Filamento PLA para impresora 3D Para imprimir las piezas del casco, los soportes y los rotores de paletas del dron. según modelo Todas
Foto de Protoboard Protoboard 1 Todas
Foto de Cables dupont macho-macho Cable dupont macho-hembra 20 Todas
Foto de Cables dupont macho-macho Cable dupont macho-macho 20 Todas
Foto de Arduino UNO R3 Arduino UNO R3 1 Arduino UNO R3
Foto de Placa ESP32 DevKit Placa ESP32 DevKit v1 1 ESP32 DevKit
Foto de Cable USB tipo B Cable USB tipo B 1 Arduino UNO R3
Foto de Cable micro USB Cable micro USB (con línea de datos) 1 ESP32 DevKit
Foto de Resistencia Resistencias para el divisor de tensión del Echo (por ejemplo 1 kΩ y 2 kΩ) Dos resistencias por sensor (tres sensores) para bajar los 5 V del pin Echo al nivel de 3,3 V que admite el ESP32. 6 ESP32 DevKit

Equipamiento del taller

  • Computadora
  • Impresora 3D
  • Soldador
  • Estaño
  • Alicate
  • Pinza de punta
  • Pinza brusela

Plano de conexionado en protoboard

Arduino UNO Conexionado en protoboard · Dron acuático: 2 motores DC (ULN2003) + 3 sensores HC-SR04 (frente/izquierdo/derecho).

Deslizá horizontalmente para ver el plano completo

Arduino UNO
Motor izquierdo (ULN2003)
ULN2003
IN1VCCGND
Motor derecho (ULN2003)
ULN2003
IN2VCCGND
HC-SR04 · Frente
HC-SR04
VCCTRIGECHOGND
HC-SR04 · Izquierdo
HC-SR04
VCCTRIGECHOGND
HC-SR04 · Derecho
HC-SR04
VCCTRIGECHOGND
ESP32 DevKit v1 Conexionado en protoboard · Dron acuático: 2 motores DC (ULN2003) + 3 sensores HC-SR04 (frente/izquierdo/derecho).

Deslizá horizontalmente para ver el plano completo

ESP32 DevKit v1
Motor izquierdo (ULN2003)
ULN2003
IN1VCCGND
Motor derecho (ULN2003)
ULN2003
IN2VCCGND
HC-SR04 · Frente
HC-SR04
VCCTRIGECHOGND
HC-SR04 · Izquierdo
HC-SR04
VCCTRIGECHOGND
HC-SR04 · Derecho
HC-SR04
VCCTRIGECHOGND
Arduino UNOConexionado en protoboard
Conexiones · Arduino UNO. Convención de colores: alimentación de los sensores VCC = rojo, GND = negro; Trig = amarillo, Echo = azul; señal de control de cada motor (IN del ULN2003) = verde (izquierdo) / naranja (derecho); circuito de potencia del portapilas al ULN2003 y a los motores = violeta.
Desde Hacia Color de cable
Arduino UNO · 5VBus + de la protoboardrojo
Arduino UNO · GNDBus − de la protoboardnegro
Portapilas 6×AA (+)ULN2003 · alimentación de motoresvioleta
Portapilas 6×AA (−)Bus − de la protoboardvioleta
ULN2003 · IN1Arduino UNO · 5verde
ULN2003 · OUT1Motor DC izquierdoverde
ULN2003 · IN2Arduino UNO · 6naranja
ULN2003 · OUT2Motor DC derechonaranja
Ultrasónico frente · VCCBus + de la protoboardrojo
Ultrasónico frente · GNDBus − de la protoboardnegro
Ultrasónico frente · TrigArduino UNO · 2amarillo
Ultrasónico frente · EchoArduino UNO · 3azul
Ultrasónico izquierdo · VCCBus + de la protoboardrojo
Ultrasónico izquierdo · GNDBus − de la protoboardnegro
Ultrasónico izquierdo · TrigArduino UNO · 8amarillo
Ultrasónico izquierdo · EchoArduino UNO · 9azul
Ultrasónico derecho · VCCBus + de la protoboardrojo
Ultrasónico derecho · GNDBus − de la protoboardnegro
Ultrasónico derecho · TrigArduino UNO · 10amarillo
Ultrasónico derecho · EchoArduino UNO · 11azul
ESP32 DevKit v1Conexionado en protoboard
Conexiones · ESP32 DevKit v1. Convención de colores: alimentación de los sensores VCC = rojo, GND = negro; Trig = amarillo, Echo = azul; señal de control de cada motor (IN del ULN2003) = verde (izquierdo) / naranja (derecho); circuito de potencia del portapilas al ULN2003 y a los motores = violeta.
Desde Hacia Color de cable
ESP32 DevKit v1 · 3V3Bus + de la protoboardrojo
ESP32 DevKit v1 · GNDBus − de la protoboardnegro
Portapilas 6×AA (+)ULN2003 · alimentación de motoresvioleta
Portapilas 6×AA (−)Bus − de la protoboardvioleta
ULN2003 · IN1ESP32 DevKit v1 · GPIO26verde
ULN2003 · OUT1Motor DC izquierdoverde
ULN2003 · IN2ESP32 DevKit v1 · GPIO27naranja
ULN2003 · OUT2Motor DC derechonaranja
Ultrasónico frente · VCCBus + de la protoboardrojo
Ultrasónico frente · GNDBus − de la protoboardnegro
Ultrasónico frente · TrigESP32 DevKit v1 · GPIO5amarillo
Ultrasónico frente · EchoESP32 DevKit v1 · GPIO18azul
Ultrasónico izquierdo · VCCBus + de la protoboardrojo
Ultrasónico izquierdo · GNDBus − de la protoboardnegro
Ultrasónico izquierdo · TrigESP32 DevKit v1 · GPIO19amarillo
Ultrasónico izquierdo · EchoESP32 DevKit v1 · GPIO21azul
Ultrasónico derecho · VCCBus + de la protoboardrojo
Ultrasónico derecho · GNDBus − de la protoboardnegro
Ultrasónico derecho · TrigESP32 DevKit v1 · GPIO22amarillo
Ultrasónico derecho · EchoESP32 DevKit v1 · GPIO23azul
Nivel inicial

Un dron que recorre la superficie del agua

Imprimir las piezas

Para armar el dron acuático debemos imprimir sus piezas en la impresora 3D. El modelo se puede descargar de forma libre y gratuita. Una vez descargado, lo imprimimos; cuando estén listas todas las piezas, las ensamblamos para construir el sistema mecánico del dron.

En caso de querer realizar una modificación en el modelo, independientemente del programa de modelado que utilicemos, debemos exportar nuestras piezas en formato .stl, el formato más popular a la hora de intercambiar digitalmente modelos de objetos para ser impresos en 3D.

Las piezas son las siguientes:

  • Carcasa principal inferior (x1) y superior (x1)
  • Carcasa de motores inferior (x1) y superior (x1)
  • Placa (x8)
  • Soporte (x4)
  • Vínculo lateral (x2) y vínculo transversal (x1)
  • Rotor de paletas derecho (x1) e izquierdo (x1)
Render 3D de una de las piezas del dron: una carcasa con superficie curva, orificios de sujeción en las esquinas y una serie de muescas semicirculares en un borde para el paso de los ejes.
Una de las piezas del casco del dron, lista para imprimir en 3D (figura original del proyecto).

Ensamblar el dron

Con todas las piezas impresas, las ensamblamos para construir la estructura mecánica del dron. Los dos rotores de paletas (derecho e izquierdo) van montados sobre el eje de cada motor: son las paletas las que, al girar, empujan el agua y hacen avanzar al dron.

Es importante que los motores y toda la electrónica queden sobre la superficie del dron, por encima de la línea de flotación, y que el compartimento que los aloja los mantenga protegidos del agua. Solo las paletas de los rotores tocan el agua.

Conectar un motor con el módulo ULN2003

Para hacer que los motores del dron se muevan necesitamos un controlador de motores: el módulo ULN2003. No es posible conectar los motores DC directamente a las salidas de la placa, porque los motores requieren más potencia de la que la placa puede entregar. El ULN2003 recibe la señal de baja potencia de una salida digital y deja pasar hacia el motor la corriente de las pilas.

Primero probaremos el funcionamiento con un solo motor, para entender cómo funciona el conjunto. Realizamos las conexiones como se ve en la figura.

Esquema de conexión: placa Arduino UNO con una salida digital que va a una entrada del módulo ULN2003; el módulo alimentado por las pilas comanda un único motor DC conectado a una de sus salidas; masa común entre la placa y las pilas.
Conexión de un solo motor a través del módulo ULN2003 (figura original del proyecto). La señal de la placa entra al módulo y este entrega a el motor la potencia de las pilas.

El motor recibe su energía del portapilas de 6 pilas AA, no de la placa: por eso el sistema puede mover motores sin sobrecargar el Arduino. La placa solo aporta la señal de control, y la masa (GND) se comparte entre la placa y las pilas para que ambos lados “hablen” del mismo cero.

En el Arduino UNO, la señal de control del primer motor sale del pin 5 (la constante PIN_MOTOR_IZQ en el código de los pasos siguientes) hacia una de las entradas del ULN2003.

En el ESP32 el armado es el mismo, pero la salida es un GPIO: el número exacto está en la constante PIN_MOTOR_IZQ al comienzo del código. Evitá los GPIO 0, 2, 12 y 15, que intervienen en el arranque de la placa.

Conectar el segundo motor

Ahora agregamos el segundo motor para poder desplazar el dron. Cada motor comanda un lado del dron (izquierdo y derecho) y se conecta a una entrada y una salida propias del módulo ULN2003, como se ve en la figura.

Esquema de conexión: placa Arduino UNO con dos salidas digitales que van a dos entradas del módulo ULN2003; el módulo comanda dos motores DC, uno por cada salida; ambos motores y la placa comparten la alimentación de las pilas y la masa.
Conexión de los dos motores a través del módulo ULN2003 (figura original del proyecto): uno para el lado izquierdo y otro para el derecho.

En el Arduino UNO, el motor izquierdo se comanda desde el pin 5 (PIN_MOTOR_IZQ) y el motor derecho desde el pin 6 (PIN_MOTOR_DER).

En el ESP32, los dos motores se comandan desde los GPIO indicados en las constantes PIN_MOTOR_IZQ y PIN_MOTOR_DER al comienzo del código.

La lógica del movimiento: avanzar, girar y frenar

Antes de programar el recorrido conviene entender cómo se mueve el dron. Como el módulo ULN2003 comanda cada motor en un solo sentido (encendido o apagado, sin marcha atrás), todos los movimientos se construyen combinando el encendido de los dos motores:

  • Avanzar: los dos motores encendidos. Las dos paletas empujan el agua y el dron va hacia adelante.
  • Girar: se frena uno de los motores y se deja andando el otro. El dron pivotea hacia el lado del motor frenado. Para girar hacia un lado, frenamos el motor de ese lado; para girar hacia el otro, frenamos el motor contrario.
  • Frenar: los dos motores apagados. El dron se detiene.

Programar el recorrido de barrido

Para que el dron pueda recolectar muestras de todo el lago, establecemos un recorrido que le permita abarcar la mayor superficie posible: una trayectoria de barrido en la que avanza, gira, avanza en sentido contrario, y así va cubriendo franjas paralelas hasta completar el área. La distancia que recorre en cada tramo depende del tiempo que le asignemos a cada movimiento antes de cambiar al siguiente.

La programación la realizaremos directamente en el lenguaje de Arduino (C++), usando el Arduino IDE en su versión 2.x, que se descarga gratis desde arduino.cc/en/software. Un programa de Arduino se llama sketch y tiene dos bloques: la función setup(), que se ejecuta una sola vez al inicializarse el dispositivo, y la función loop(), que se ejecuta repetidamente después. Los comentarios (todo lo que sigue a //) no tienen efecto en el programa: están para que el código sea más fácil de interpretar.

El programa es una secuencia de acciones de las que ya conocemos: encender los dos motores para avanzar, frenar uno para girar y esperar un tiempo entre cada cambio. Las constantes con los pines de los motores están al comienzo, y los tiempos de cada tramo se pueden ajustar para adaptar el recorrido a tu batea o pileta de pruebas.

Ver el código completo · dron-recorrido.ino
dron-recorrido.ino Arduino UNO
// Dron acuático — Nivel inicial: recorrido programado.
// El dron barre un área cerrada de la superficie del lago siguiendo una
// trayectoria fija cargada de antemano en la placa (no usa sensores).
// Dos motores DC mueven las paletas; se conducen con un módulo ULN2003.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL de encendido/apagado — puede prender un motor pero
// NO invertir su giro. Por eso el dron no tiene marcha atrás ni giro en el
// lugar: avanza con los dos motores andando y GIRA frenando uno y dejando
// andar el otro (el motor que sigue empujando arrastra al dron hacia el
// lado del motor frenado). Para giro en el lugar o marcha atrás haría falta
// un puente H (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// SEGURIDAD (agua + electrónica): los motores son de baja tensión (5 V,
// alimentados con 6 pilas AA) y trabajan en la SUPERFICIE del agua, no
// sumergidos. Aun así, agua y electricidad no se llevan bien: la placa, el
// ULN2003, las pilas y el cableado van sobre la cubierta del dron, POR
// ENCIMA de la línea de flotación y protegidos de salpicaduras. No hay 220 V
// en ningún punto del proyecto.
//
// Plataforma: Arduino UNO.
// Conexión: entrada IN del ULN2003 del motor IZQUIERDO → pin 5,
//           entrada IN del motor DERECHO → pin 6.
//
// Basado en: Fragmento de código "Programar el movimiento del dron acuático"
// (Paso 9) — Dron Acuático, Saberes Digitales (INET/EDUCAR 2019).
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop,
// angle_rad/deg, motores prendidos "a mano" con digitalWrite sueltos) por
// C++ idiomático con constantes nombradas y funciones de movimiento
// (avanzar / girarDerecha / girarIzquierda / frenar). El recorrido se
// ejecuta UNA vez en setup() y el dron queda detenido: es una trayectoria
// de barrido de un área cerrada, repetirla lo sacaría del área.

// Entradas del módulo ULN2003 que gobiernan cada motor.
const int PIN_MOTOR_IZQ = 5;
const int PIN_MOTOR_DER = 6;

// El ULN2003 es on/off: HIGH pone el motor a andar, LOW lo frena.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;

// Tiempos del recorrido (calibrar en la maqueta: dependen de la potencia de
// los motores y del rozamiento del agua). AVANCE = tramo recto entre giros;
// GIRO = cuánto dura el frenado de un motor para describir ~90°.
const unsigned long TIEMPO_AVANCE_MS = 3000;
const unsigned long TIEMPO_GIRO_MS   = 700;

// Avanzar: los dos motores andando, el dron va derecho hacia adelante.
void avanzar(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_ANDANDO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_ANDANDO);
  delay(ms);
}

// Girar a la derecha: frena el motor derecho y deja andar el izquierdo.
// El empuje del lado izquierdo hace pivotar el dron hacia la derecha.
void girarDerecha(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_ANDANDO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_FRENADO);
  delay(ms);
}

// Girar a la izquierda: el accionar exactamente opuesto al giro a derecha.
void girarIzquierda(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_FRENADO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_ANDANDO);
  delay(ms);
}

// Frenar: los dos motores apagados, el dron se detiene.
void frenar() {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_FRENADO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_FRENADO);
}

void setup() {
  pinMode(PIN_MOTOR_IZQ, OUTPUT);
  pinMode(PIN_MOTOR_DER, OUTPUT);
  frenar(); // arrancamos detenidos hasta empezar el recorrido

  // Recorrido de barrido en "S" (boustrofedón) que cubre un área cerrada:
  // avanzar, dos giros a la derecha, avanzar, dos giros a la izquierda...
  // Es la secuencia de 14 pasos del croquis del original, ya coherente.
  avanzar(TIEMPO_AVANCE_MS);       // 1. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 2. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 3. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 4. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 5. Avanzar
  girarIzquierda(TIEMPO_GIRO_MS);  // 6. Girar a la izquierda
  avanzar(TIEMPO_AVANCE_MS);       // 7. Avanzar
  girarIzquierda(TIEMPO_GIRO_MS);  // 8. Girar a la izquierda
  avanzar(TIEMPO_AVANCE_MS);       // 9. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 10. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 11. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 12. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 13. Avanzar
  frenar();                        // 14. Frenar
}

void loop() {
  // Recorrido terminado: el dron queda quieto con los motores apagados.
}
dron-recorrido.ino ESP32
// Dron acuático — Nivel inicial: recorrido programado.
// El dron barre un área cerrada de la superficie del lago siguiendo una
// trayectoria fija cargada de antemano en la placa (no usa sensores).
// Dos motores DC mueven las paletas; se conducen con un módulo ULN2003.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL de encendido/apagado — puede prender un motor pero
// NO invertir su giro. Por eso el dron no tiene marcha atrás ni giro en el
// lugar: avanza con los dos motores andando y GIRA frenando uno y dejando
// andar el otro (el motor que sigue empujando arrastra al dron hacia el
// lado del motor frenado). Para giro en el lugar o marcha atrás haría falta
// un puente H (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// SEGURIDAD (agua + electrónica): los motores son de baja tensión (5 V,
// alimentados con 6 pilas AA) y trabajan en la SUPERFICIE del agua, no
// sumergidos. Aun así, agua y electricidad no se llevan bien: la placa, el
// ULN2003, las pilas y el cableado van sobre la cubierta del dron, POR
// ENCIMA de la línea de flotación y protegidos de salpicaduras. No hay 220 V
// en ningún punto del proyecto.
//
// NOTA DE ALIMENTACIÓN (ESP32): el ESP32 trabaja a 3,3 V, pero sus salidas
// digitales alcanzan de sobra para excitar las entradas IN del ULN2003 (el
// driver es el que maneja los 5 V de los motores). Alimentá el ULN2003 y los
// motores desde las 6 pilas AA (5 V), NO desde el pin 3V3 de la placa, y uní
// las masas (GND del ESP32 con GND de las pilas).
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: entrada IN del ULN2003 del motor IZQUIERDO → GPIO 26,
//           entrada IN del motor DERECHO → GPIO 27.
//
// Basado en: Fragmento de código "Programar el movimiento del dron acuático"
// (Paso 9) — Dron Acuático, Saberes Digitales (INET/EDUCAR 2019).
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop,
// angle_rad/deg, motores prendidos "a mano" con digitalWrite sueltos) por
// C++ idiomático con constantes nombradas y funciones de movimiento
// (avanzar / girarDerecha / girarIzquierda / frenar). El recorrido se
// ejecuta UNA vez en setup() y el dron queda detenido: es una trayectoria
// de barrido de un área cerrada, repetirla lo sacaría del área. Se portó del
// UNO (pines 5/6) al ESP32 (GPIO 26/27).

// Entradas del módulo ULN2003 que gobiernan cada motor.
const int PIN_MOTOR_IZQ = 26;
const int PIN_MOTOR_DER = 27;

// El ULN2003 es on/off: HIGH pone el motor a andar, LOW lo frena.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;

// Tiempos del recorrido (calibrar en la maqueta: dependen de la potencia de
// los motores y del rozamiento del agua). AVANCE = tramo recto entre giros;
// GIRO = cuánto dura el frenado de un motor para describir ~90°.
const unsigned long TIEMPO_AVANCE_MS = 3000;
const unsigned long TIEMPO_GIRO_MS   = 700;

// Avanzar: los dos motores andando, el dron va derecho hacia adelante.
void avanzar(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_ANDANDO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_ANDANDO);
  delay(ms);
}

// Girar a la derecha: frena el motor derecho y deja andar el izquierdo.
// El empuje del lado izquierdo hace pivotar el dron hacia la derecha.
void girarDerecha(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_ANDANDO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_FRENADO);
  delay(ms);
}

// Girar a la izquierda: el accionar exactamente opuesto al giro a derecha.
void girarIzquierda(unsigned long ms) {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_FRENADO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_ANDANDO);
  delay(ms);
}

// Frenar: los dos motores apagados, el dron se detiene.
void frenar() {
  digitalWrite(PIN_MOTOR_IZQ, MOTOR_FRENADO);
  digitalWrite(PIN_MOTOR_DER, MOTOR_FRENADO);
}

void setup() {
  pinMode(PIN_MOTOR_IZQ, OUTPUT);
  pinMode(PIN_MOTOR_DER, OUTPUT);
  frenar(); // arrancamos detenidos hasta empezar el recorrido

  // Recorrido de barrido en "S" (boustrofedón) que cubre un área cerrada:
  // avanzar, dos giros a la derecha, avanzar, dos giros a la izquierda...
  // Es la secuencia de 14 pasos del croquis del original, ya coherente.
  avanzar(TIEMPO_AVANCE_MS);       // 1. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 2. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 3. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 4. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 5. Avanzar
  girarIzquierda(TIEMPO_GIRO_MS);  // 6. Girar a la izquierda
  avanzar(TIEMPO_AVANCE_MS);       // 7. Avanzar
  girarIzquierda(TIEMPO_GIRO_MS);  // 8. Girar a la izquierda
  avanzar(TIEMPO_AVANCE_MS);       // 9. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 10. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 11. Avanzar
  girarDerecha(TIEMPO_GIRO_MS);    // 12. Girar a la derecha
  avanzar(TIEMPO_AVANCE_MS);       // 13. Avanzar
  frenar();                        // 14. Frenar
}

void loop() {
  // Recorrido terminado: el dron queda quieto con los motores apagados.
}
El recorrido de barrido: el dron avanza, gira, vuelve a avanzar y frena, siguiendo una trayectoria que cubre el área en franjas.

Podés ver el recorrido completo —el dron avanzando y girando para barrer el área— en el simulador, antes de probarlo en el agua:

Simulación del comportamiento

La misma máquina de estados del firmware, portada a TypeScript, maneja el comportamiento del dispositivo. No se ejecuta el binario real: la ejecución del .hex con avr8js está en el roadmap.

Simulación lista. Presioná «Iniciar» para arrancar la secuencia.

Subir el código y navegar

Para subir el código de nuestro programa a la placa, necesitamos:

  1. Conectar la placa a la computadora con el cable USB.
  2. Chequear que en el selector de placa del Arduino IDE esté seleccionada la placa correcta.
  3. Seleccionar el puerto al que está conectada la placa.
  4. Cliquear el botón Subir (la flecha →) en la parte superior de la interfaz.

En el Arduino IDE 2.x, la placa y el puerto se eligen juntos en el menú desplegable de la barra superior: al conectar la placa suele aparecer directamente como opción. Si no aparece, entrá a “Select other board and port” y buscala por nombre.

Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto que se agregó al conectarla (en Windows es un puerto COM numerado; en Linux suele llamarse /dev/ttyACM0 o similar).

Para el ESP32 hay dos pasos previos que se hacen una sola vez: instalar el soporte de placas ESP32 desde el gestor de placas del IDE (buscá “esp32” e instalá el paquete de Espressif) y, si el puerto no aparece, instalar el driver del conversor USB-serie de tu placa (generalmente CP210x o CH340). Después, elegí la placa ESP32 Dev Module (o “DOIT ESP32 DEVKIT V1”) y el puerto correspondiente. Si al subir el IDE queda esperando en “Connecting…”, mantené apretado el botón BOOT de la placa hasta que empiece la carga.

Al terminar de subir, veremos el mensaje de carga completada en la barra inferior del IDE.

Cierre del nivel

Al terminar este nivel, el dron de los investigadores navega solo: impreso en 3D y ensamblado, avanza con sus dos motores comandados por el ULN2003 y gira frenando uno de ellos, siguiendo una trayectoria de barrido que vos programaste y cargaste desde el Arduino IDE 2.x. Ya entendés la relación entre el tiempo de cada tramo y la distancia recorrida, la base de cualquier navegación por tiempo. En el nivel avanzado, el dron va a dejar de ser “ciego”: sensores ultrasónicos le van a permitir detectar las boyas que los guardaparques dejan en el lago, esquivarlas y volver a su recorrido.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El dron está impreso y ensamblado, con los dos motores montados sobre la superficie y la electrónica y las pilas protegidas del agua.
  • El módulo ULN2003 comanda los dos motores DC: con ambos encendidos el dron avanza, y frenando uno de ellos gira hacia ese lado.
  • El dron ejecuta una trayectoria de barrido predeterminada (avanzar, girar, avanzar, frenar) cargada en la placa desde el Arduino IDE 2.x.
Nivel avanzado

Un dron que esquiva las boyas

Conectar el sensor ultrasónico

Incorporamos un sensor de distancia ultrasónico que le permita al dron detectar obstáculos y cambiar el rumbo. El sensor emite un pulso de sonido y mide el tiempo que tarda en volver el eco tras rebotar en un objeto: ese tiempo es proporcional a la distancia. Tiene cuatro pines: dos de alimentación (VCC y GND) y dos de trabajo, Trigger (dispara el pulso) y Echo (detecta su llegada).

Empezamos con un solo sensor, el del frente, sumado al circuito de los dos motores del nivel inicial.

Esquema de conexión: placa Arduino UNO con un sensor ultrasónico HC-SR04 rotulado SENSOR DE DISTANCIA (VCC, Trig, Echo y GND) y el módulo ULN2003 comandando los dos motores DC; todo comparte la alimentación de las pilas y la masa.
El circuito del nivel avanzado con un sensor: el ultrasónico del frente sumado a los dos motores comandados por el ULN2003 (figura original del proyecto).

En el Arduino UNO conectamos el pin Trigger del sensor al pin 8 y el pin Echo al pin 9 de la placa (las constantes PIN_TRIG_FRENTE y PIN_ECHO_FRENTE en el código), con su alimentación de 5 V y GND.

En el ESP32 el armado es el mismo, pero usamos GPIO (los números exactos están en las constantes PIN_TRIG_FRENTE y PIN_ECHO_FRENTE; en esta placa, los GPIO 19 y 21). Un cuidado propio del ESP32: el pin Echo del HC-SR04 entrega 5 V, mientras que sus GPIO admiten como máximo 3,3 V. Por eso, entre el Echo y el GPIO hay que interponer un divisor de tensión de dos resistencias (por ejemplo 1 kΩ y 2 kΩ), para no dañar la placa. El Trigger no necesita divisor, y la alimentación del sensor sigue siendo de 5 V.

Conocer la medición del sensor

Antes de usar el sensor para decidir, conviene entender cómo mide. Para medir, el programa dispara desde el pin Trigger un pulso ultrasónico y mide en el pin Echo cuánto tarda en volver el rebote: como el sonido recorre ida y vuelta unos 58 microsegundos por cada centímetro, dividiendo el tiempo por ese valor se obtiene la distancia en centímetros. Esa cuenta vive en la función medirDistanciaCm() del código del paso 5, que recibe el par de pines Trigger/Echo de un sensor y devuelve su distancia (o 0 si el eco no vuelve).

Si querés ver los valores con tus propios ojos antes de darle al sensor el control de los motores, podés agregar de forma temporal una línea que imprima la distancia (Serial.println(...)) y abrir el monitor serie del Arduino IDE 2.x (el ícono de la lupa en el extremo superior derecho, o Herramientas → Monitor Serie). Vas a ver cómo el número cambia al acercar y alejar un objeto frente al sensor. Es una buena forma de comprobar que el sensor responde bien.

Descartar el «0»: los objetos fuera de rango

Hay un detalle importante de estos sensores: la mayoría devuelve una medición de 0 cuando el objeto está fuera de su alcance máximo. Es decir, si el sensor mide hasta 400 cm y la boya está a 401 cm, la lectura será 0, aunque la boya esté lejísimos.

Esto puede confundir, porque un 0 también podría significar que hay algo pegado al dron. Para resolverlo, el programa ignora el valor 0: solo tiene en cuenta una lectura si es mayor que 0. Así, el dron esquiva una boya cuando la distancia medida es menor que la distancia de seguridad (por ejemplo, 40 cm) y a la vez mayor que 0.

De un sensor a tres: ampliar la visión del dron

Un solo sensor registra solamente los objetos que tiene justo enfrente, lo que deja puntos ciegos a los costados. Por eso agregamos dos sensores más: uno a cada lado, para abarcar un rango más amplio. Ahora el dron tiene tres ojos: frente, izquierda y derecha.

Esquema de conexión: placa Arduino UNO con tres sensores ultrasónicos HC-SR04 rotulados SENSOR DE DISTANCIA en la parte superior y el módulo ULN2003 comandando los dos motores DC; cada sensor con sus pines Trigger y Echo hacia la placa.
El circuito completo del nivel avanzado: tres sensores ultrasónicos (frente y laterales) y los dos motores (figura original del proyecto).

Cada sensor tiene su propio par de pines Trigger/Echo. Esto es clave, y es donde el material original de 2019 tenía dos errores que en esta reedición están corregidos.

En el Arduino UNO, los tres sensores quedan así: el de la izquierda en los pines 2 y 3, el del frente en los pines 8 y 9, y el de la derecha en los pines 10 y 11. En el código son las constantes PIN_TRIG_IZQ/PIN_ECHO_IZQ, PIN_TRIG_FRENTE/PIN_ECHO_FRENTE y PIN_TRIG_DER/PIN_ECHO_DER.

En el ESP32, cada sensor usa su propio par de GPIO: la izquierda en 5 y 18, el frente en 19 y 21 y la derecha en 22 y 23, indicados en las constantes PIN_TRIG_IZQ/PIN_ECHO_IZQ, PIN_TRIG_FRENTE/PIN_ECHO_FRENTE y PIN_TRIG_DER/PIN_ECHO_DER. Recordá el divisor de tensión en cada uno de los tres pines Echo (18, 21 y 23): los tres entregan 5 V y hay que bajarlos a 3,3 V antes de entrar al GPIO.

Programar el esquive de boyas

Ahora unimos todo en el programa de navegación. El dron avanza con los dos motores y, a intervalos regulares (la constante INTERVALO_MEDICION_MS), mide sus tres sensores con la función medirDistanciaCm(). Si cualquiera de los tres detecta una boya —una distancia mayor que 0 y menor que el umbral UMBRAL_OBSTACULO_CM (40 cm), lo que decide la función hayObstaculo()—, el dron inicia un giro de esquive: frena un motor para pivotar y sigue girando durante un tiempo fijo (TIEMPO_GIRO_MS). Cuando ese tiempo termina, vuelve a avanzar.

La regla de esquive es sencilla:

  • Boya al frente → gira hacia el lado (izquierda o derecha) que tenga más espacio libre (lo compara la función espacioLibre(), que trata una lectura 0 como “muy lejos”).
  • Boya a la izquierda → gira a la derecha.
  • Boya a la derecha → gira a la izquierda.

La clave es que la maniobra es no bloqueante: en lugar de un delay() que dejaría al dron “ciego” mientras gira, el programa usa una máquina de estados con dos estados, AVANZANDO y GIRANDO, apoyada en millis(). Cada vuelta de loop() es instantánea: si está girando, revisa si ya se cumplió el tiempo del giro para volver a avanzar; si está avanzando, mide los sensores y decide si esquivar.

Ver el código completo · dron-esquiva.ino
dron-esquiva.ino Arduino UNO
// Dron acuático — Nivel avanzado: esquiva de obstáculos (boyas).
// El dron avanza y usa TRES sensores ultrasónicos (izquierda, frente,
// derecha) para detectar boyas a menos de 40 cm y esquivarlas girando hacia
// el lado que tenga más espacio libre. Cuando el camino se despeja, retoma
// la marcha. Dos motores DC (vía ULN2003) con dirección diferencial.
//
// CÓMO ESQUIVA: mientras avanza, mide los tres sensores. Si alguno detecta
// una boya a menos de 40 cm, entra en un giro TEMPORIZADO no bloqueante hacia
// el lado despejado (frenando un motor) y luego vuelve a avanzar. El giro
// dura un tiempo fijo; se resuelve con millis() (máquina de estados), sin
// delay(), para no quedar "ciego" durante la maniobra.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL on/off — no invierte el giro del motor. El dron no
// tiene marcha atrás ni giro en el lugar: gira frenando un motor y dejando
// andar el otro. Giro en el lugar / marcha atrás requerirían un puente H
// (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// LECTURA 0 = FUERA DE RANGO: el HC-SR04 devuelve 0 cuando el eco no vuelve
// (nada dentro de su alcance, ~400 cm) o por timeout. 0 NO significa "boya
// pegada": significa "camino despejado". Por eso solo tratamos como
// obstáculo las lecturas mayores que 0 y menores que el umbral, y para
// comparar qué lado tiene más lugar, una lectura 0 cuenta como "muy lejos".
//
// SEGURIDAD (agua + electrónica): motores de baja tensión (5 V, 6 pilas AA)
// trabajando en la SUPERFICIE del agua, no sumergidos. Placa, ULN2003, pilas
// y cableado sobre la cubierta, por encima de la línea de flotación y
// protegidos de salpicaduras. No hay 220 V en el proyecto.
//
// Plataforma: Arduino UNO.
// Conexión: motor IZQUIERDO (IN ULN2003) → pin 5, motor DERECHO → pin 6.
//   Sensor IZQUIERDA  TRIG/ECHO → 2 / 3
//   Sensor FRENTE     TRIG/ECHO → 8 / 9
//   Sensor DERECHA    TRIG/ECHO → 10 / 11
//
// Basado en: Fragmentos de código de los pasos "Programar el movimiento" e
// "Incorporar/Programar los tres sensores" — Dron Acuático, Saberes
// Digitales (INET/EDUCAR 2019).
// DESVÍOS respecto del original (estaba muy buggeado):
//   1. BUG CORREGIDO: el original leía getDistance(2,3) para los TRES
//      sensores (copy-paste) — el sensor 2 y el 3 nunca se leían de verdad.
//      Acá cada sensor lee SU par de pines (2/3, 8/9, 10/11).
//   2. El texto del original asignaba el sensor 2 a los pines 6/7, pero el
//      pin 6 es de un motor: chocaban. Se usa 8/9 (lo que el propio código
//      del original ya cableaba).
//   3. Navegación reescrita: el original "esquivaba" con una coreografía de
//      giros a ciegas dentro de setup() (se movía SIEMPRE igual sin importar
//      qué sensor detectaba). Acá la maniobra depende de dónde está la boya
//      y de qué lado hay más espacio.
//   4. Sin cruft de mBlock3 (_delay, _loop, angle_rad/deg); no bloqueante.

// --- Motores (entradas IN del ULN2003) ---
const int PIN_MOTOR_IZQ = 5;
const int PIN_MOTOR_DER = 6;

// El ULN2003 es on/off: HIGH = motor andando, LOW = motor frenado.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;

// --- Sensores ultrasónicos (cada uno con SU par TRIG/ECHO) ---
const int PIN_TRIG_IZQ = 2;
const int PIN_ECHO_IZQ = 3;
const int PIN_TRIG_FRENTE = 8;
const int PIN_ECHO_FRENTE = 9;
const int PIN_TRIG_DER = 10;
const int PIN_ECHO_DER = 11;

// Distancia a la que consideramos que una boya está "en el camino" (cm).
const float UMBRAL_OBSTACULO_CM = 40.0;

// Cuando una lectura es 0 (fuera de rango), la contamos como "muy lejos"
// para las comparaciones de qué lado está más despejado.
const float DISTANCIA_MUY_LEJOS_CM = 9999.0;

// El eco tarda 58 microsegundos por cm (ida y vuelta del sonido a ~343 m/s).
const float MICROSEGUNDOS_POR_CM = 58.0;
const unsigned long TIMEOUT_ECO_US = 30000;

// Ritmo de medición mientras avanza y duración del giro de esquive.
const unsigned long INTERVALO_MEDICION_MS = 100;
const unsigned long TIEMPO_GIRO_MS = 700;

// Máquina de estados no bloqueante.
enum Estado { AVANZANDO, GIRANDO };
Estado estado = AVANZANDO;
unsigned long finGiro = 0;        // instante en que termina el giro actual
unsigned long ultimaMedicion = 0; // última vez que medimos los sensores

// Mide la distancia en cm de un sensor. Devuelve 0 si no llega el eco
// (fuera de rango o sensor desconectado): el llamador interpreta ese 0.
float medirDistanciaCm(int pinTrig, int pinEcho) {
  digitalWrite(pinTrig, LOW);
  delayMicroseconds(2);
  digitalWrite(pinTrig, HIGH); // pulso de disparo de 10 microsegundos
  delayMicroseconds(10);
  digitalWrite(pinTrig, LOW);
  return pulseIn(pinEcho, HIGH, TIMEOUT_ECO_US) / MICROSEGUNDOS_POR_CM;
}

// ¿Esta lectura corresponde a una boya en el camino? Solo si es mayor que 0
// (dentro de rango) y menor que el umbral. El 0 NO cuenta como obstáculo.
bool hayObstaculo(float distancia) {
  return distancia > 0.0 && distancia < UMBRAL_OBSTACULO_CM;
}

// Espacio libre "efectivo" de un lado: una lectura 0 (fuera de rango) se
// trata como muy lejos, así el dron elige girar hacia el lado despejado.
float espacioLibre(float distancia) {
  return distancia <= 0.0 ? DISTANCIA_MUY_LEJOS_CM : distancia;
}

void setMotores(int estadoIzq, int estadoDer) {
  digitalWrite(PIN_MOTOR_IZQ, estadoIzq);
  digitalWrite(PIN_MOTOR_DER, estadoDer);
}

// Inicia un giro temporizado (no bloqueante) hacia la derecha o la izquierda.
// Girar a la derecha = frenar el motor derecho; a la izquierda = frenar el
// izquierdo.
void iniciarGiro(bool haciaDerecha) {
  if (haciaDerecha) {
    setMotores(MOTOR_ANDANDO, MOTOR_FRENADO);
  } else {
    setMotores(MOTOR_FRENADO, MOTOR_ANDANDO);
  }
  finGiro = millis() + TIEMPO_GIRO_MS;
  estado = GIRANDO;
}

void setup() {
  pinMode(PIN_MOTOR_IZQ, OUTPUT);
  pinMode(PIN_MOTOR_DER, OUTPUT);
  pinMode(PIN_TRIG_IZQ, OUTPUT);
  pinMode(PIN_ECHO_IZQ, INPUT);
  pinMode(PIN_TRIG_FRENTE, OUTPUT);
  pinMode(PIN_ECHO_FRENTE, INPUT);
  pinMode(PIN_TRIG_DER, OUTPUT);
  pinMode(PIN_ECHO_DER, INPUT);

  // Arranca avanzando con los dos motores.
  setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
  estado = AVANZANDO;
}

void loop() {
  unsigned long ahora = millis();

  if (estado == GIRANDO) {
    // Maniobra de esquive en curso: al terminar el tiempo, volver a avanzar.
    if (ahora >= finGiro) {
      setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
      estado = AVANZANDO;
    }
    return;
  }

  // estado == AVANZANDO: medimos a intervalos y decidimos si esquivar.
  if (ahora - ultimaMedicion < INTERVALO_MEDICION_MS) {
    return;
  }
  ultimaMedicion = ahora;

  float dIzq = medirDistanciaCm(PIN_TRIG_IZQ, PIN_ECHO_IZQ);
  float dFrente = medirDistanciaCm(PIN_TRIG_FRENTE, PIN_ECHO_FRENTE);
  float dDer = medirDistanciaCm(PIN_TRIG_DER, PIN_ECHO_DER);

  if (hayObstaculo(dFrente)) {
    // Boya al frente: girar hacia el lado con más espacio libre.
    bool haciaDerecha = espacioLibre(dDer) >= espacioLibre(dIzq);
    iniciarGiro(haciaDerecha);
  } else if (hayObstaculo(dIzq)) {
    // Boya a la izquierda: esquivar girando a la derecha.
    iniciarGiro(true);
  } else if (hayObstaculo(dDer)) {
    // Boya a la derecha: esquivar girando a la izquierda.
    iniciarGiro(false);
  }
  // Sin obstáculos: seguimos avanzando (los motores ya están andando).
}
dron-esquiva.ino ESP32
// Dron acuático — Nivel avanzado: esquiva de obstáculos (boyas).
// El dron avanza y usa TRES sensores ultrasónicos (izquierda, frente,
// derecha) para detectar boyas a menos de 40 cm y esquivarlas girando hacia
// el lado que tenga más espacio libre. Cuando el camino se despeja, retoma
// la marcha. Dos motores DC (vía ULN2003) con dirección diferencial.
//
// CÓMO ESQUIVA: mientras avanza, mide los tres sensores. Si alguno detecta
// una boya a menos de 40 cm, entra en un giro TEMPORIZADO no bloqueante hacia
// el lado despejado (frenando un motor) y luego vuelve a avanzar. El giro
// dura un tiempo fijo; se resuelve con millis() (máquina de estados), sin
// delay(), para no quedar "ciego" durante la maniobra.
//
// CÓMO GIRA (dirección diferencial, estilo "tanque"): el ULN2003 es un
// driver UNIDIRECCIONAL on/off — no invierte el giro del motor. El dron no
// tiene marcha atrás ni giro en el lugar: gira frenando un motor y dejando
// andar el otro. Giro en el lugar / marcha atrás requerirían un puente H
// (p. ej. L298N), como sugiere el cierre del proyecto original.
//
// LECTURA 0 = FUERA DE RANGO: el HC-SR04 devuelve 0 cuando el eco no vuelve
// (nada dentro de su alcance, ~400 cm) o por timeout. 0 NO significa "boya
// pegada": significa "camino despejado". Por eso solo tratamos como
// obstáculo las lecturas mayores que 0 y menores que el umbral, y para
// comparar qué lado tiene más lugar, una lectura 0 cuenta como "muy lejos".
//
// SEGURIDAD (agua + electrónica): motores de baja tensión (5 V, 6 pilas AA)
// trabajando en la SUPERFICIE del agua, no sumergidos. Placa, ULN2003, pilas
// y cableado sobre la cubierta, por encima de la línea de flotación y
// protegidos de salpicaduras. No hay 220 V en el proyecto.
//
// NOTA ELÉCTRICA (ESP32, ¡importante!): el ESP32 trabaja a 3,3 V. El HC-SR04
// clásico se alimenta con 5 V y su pin ECHO devuelve el eco a 5 V: eso
// dañaría el GPIO. Es OBLIGATORIO un divisor de tensión en CADA ECHO
// (ECHO —1 kΩ— GPIO —2 kΩ— GND), que baja los 5 V a ~3,3 V. TRIG no necesita
// divisor: los 3,3 V del GPIO alcanzan para dispararlo. Alternativa sin
// divisores: el modelo HC-SR04P, alimentable a 3,3 V. Las salidas del ESP32
// sí alcanzan para las entradas IN del ULN2003; alimentá driver y motores
// desde las pilas (5 V), no desde 3V3, y uní las masas.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: motor IZQUIERDO (IN ULN2003) → GPIO 26, motor DERECHO → GPIO 27.
//   Sensor IZQUIERDA  TRIG/ECHO → GPIO 5 / 18   (ECHO con divisor)
//   Sensor FRENTE     TRIG/ECHO → GPIO 19 / 21  (ECHO con divisor)
//   Sensor DERECHA    TRIG/ECHO → GPIO 22 / 23  (ECHO con divisor)
//
// Basado en: Fragmentos de código de los pasos "Programar el movimiento" e
// "Incorporar/Programar los tres sensores" — Dron Acuático, Saberes
// Digitales (INET/EDUCAR 2019).
// DESVÍOS respecto del original (estaba muy buggeado):
//   1. BUG CORREGIDO: el original leía getDistance(2,3) para los TRES
//      sensores (copy-paste) — el sensor 2 y el 3 nunca se leían de verdad.
//      Acá cada sensor lee SU par de pines.
//   2. El texto del original asignaba el sensor 2 a los pines 6/7, que en el
//      UNO chocan con un motor; en ESP32 se usan GPIO propios sin conflicto.
//   3. Navegación reescrita: el original "esquivaba" con una coreografía de
//      giros a ciegas dentro de setup() (se movía SIEMPRE igual sin importar
//      qué sensor detectaba). Acá la maniobra depende de dónde está la boya
//      y de qué lado hay más espacio.
//   4. Sin cruft de mBlock3 (_delay, _loop, angle_rad/deg); no bloqueante.
//      Se portó del UNO al ESP32 (GPIO seguros + divisor en cada ECHO).

// --- Motores (entradas IN del ULN2003) ---
const int PIN_MOTOR_IZQ = 26;
const int PIN_MOTOR_DER = 27;

// El ULN2003 es on/off: HIGH = motor andando, LOW = motor frenado.
const int MOTOR_ANDANDO = HIGH;
const int MOTOR_FRENADO = LOW;

// --- Sensores ultrasónicos (cada uno con SU par TRIG/ECHO) ---
const int PIN_TRIG_IZQ = 5;
const int PIN_ECHO_IZQ = 18; // con divisor de tensión 1 kΩ / 2 kΩ
const int PIN_TRIG_FRENTE = 19;
const int PIN_ECHO_FRENTE = 21; // con divisor de tensión 1 kΩ / 2 kΩ
const int PIN_TRIG_DER = 22;
const int PIN_ECHO_DER = 23; // con divisor de tensión 1 kΩ / 2 kΩ

// Distancia a la que consideramos que una boya está "en el camino" (cm).
const float UMBRAL_OBSTACULO_CM = 40.0;

// Cuando una lectura es 0 (fuera de rango), la contamos como "muy lejos"
// para las comparaciones de qué lado está más despejado.
const float DISTANCIA_MUY_LEJOS_CM = 9999.0;

// El eco tarda 58 microsegundos por cm (ida y vuelta del sonido a ~343 m/s).
const float MICROSEGUNDOS_POR_CM = 58.0;
const unsigned long TIMEOUT_ECO_US = 30000;

// Ritmo de medición mientras avanza y duración del giro de esquive.
const unsigned long INTERVALO_MEDICION_MS = 100;
const unsigned long TIEMPO_GIRO_MS = 700;

// Máquina de estados no bloqueante.
enum Estado { AVANZANDO, GIRANDO };
Estado estado = AVANZANDO;
unsigned long finGiro = 0;        // instante en que termina el giro actual
unsigned long ultimaMedicion = 0; // última vez que medimos los sensores

// Mide la distancia en cm de un sensor. Devuelve 0 si no llega el eco
// (fuera de rango o sensor desconectado): el llamador interpreta ese 0.
float medirDistanciaCm(int pinTrig, int pinEcho) {
  digitalWrite(pinTrig, LOW);
  delayMicroseconds(2);
  digitalWrite(pinTrig, HIGH); // pulso de disparo de 10 microsegundos
  delayMicroseconds(10);
  digitalWrite(pinTrig, LOW);
  return pulseIn(pinEcho, HIGH, TIMEOUT_ECO_US) / MICROSEGUNDOS_POR_CM;
}

// ¿Esta lectura corresponde a una boya en el camino? Solo si es mayor que 0
// (dentro de rango) y menor que el umbral. El 0 NO cuenta como obstáculo.
bool hayObstaculo(float distancia) {
  return distancia > 0.0 && distancia < UMBRAL_OBSTACULO_CM;
}

// Espacio libre "efectivo" de un lado: una lectura 0 (fuera de rango) se
// trata como muy lejos, así el dron elige girar hacia el lado despejado.
float espacioLibre(float distancia) {
  return distancia <= 0.0 ? DISTANCIA_MUY_LEJOS_CM : distancia;
}

void setMotores(int estadoIzq, int estadoDer) {
  digitalWrite(PIN_MOTOR_IZQ, estadoIzq);
  digitalWrite(PIN_MOTOR_DER, estadoDer);
}

// Inicia un giro temporizado (no bloqueante) hacia la derecha o la izquierda.
// Girar a la derecha = frenar el motor derecho; a la izquierda = frenar el
// izquierdo.
void iniciarGiro(bool haciaDerecha) {
  if (haciaDerecha) {
    setMotores(MOTOR_ANDANDO, MOTOR_FRENADO);
  } else {
    setMotores(MOTOR_FRENADO, MOTOR_ANDANDO);
  }
  finGiro = millis() + TIEMPO_GIRO_MS;
  estado = GIRANDO;
}

void setup() {
  pinMode(PIN_MOTOR_IZQ, OUTPUT);
  pinMode(PIN_MOTOR_DER, OUTPUT);
  pinMode(PIN_TRIG_IZQ, OUTPUT);
  pinMode(PIN_ECHO_IZQ, INPUT);
  pinMode(PIN_TRIG_FRENTE, OUTPUT);
  pinMode(PIN_ECHO_FRENTE, INPUT);
  pinMode(PIN_TRIG_DER, OUTPUT);
  pinMode(PIN_ECHO_DER, INPUT);

  // Arranca avanzando con los dos motores.
  setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
  estado = AVANZANDO;
}

void loop() {
  unsigned long ahora = millis();

  if (estado == GIRANDO) {
    // Maniobra de esquive en curso: al terminar el tiempo, volver a avanzar.
    if (ahora >= finGiro) {
      setMotores(MOTOR_ANDANDO, MOTOR_ANDANDO);
      estado = AVANZANDO;
    }
    return;
  }

  // estado == AVANZANDO: medimos a intervalos y decidimos si esquivar.
  if (ahora - ultimaMedicion < INTERVALO_MEDICION_MS) {
    return;
  }
  ultimaMedicion = ahora;

  float dIzq = medirDistanciaCm(PIN_TRIG_IZQ, PIN_ECHO_IZQ);
  float dFrente = medirDistanciaCm(PIN_TRIG_FRENTE, PIN_ECHO_FRENTE);
  float dDer = medirDistanciaCm(PIN_TRIG_DER, PIN_ECHO_DER);

  if (hayObstaculo(dFrente)) {
    // Boya al frente: girar hacia el lado con más espacio libre.
    bool haciaDerecha = espacioLibre(dDer) >= espacioLibre(dIzq);
    iniciarGiro(haciaDerecha);
  } else if (hayObstaculo(dIzq)) {
    // Boya a la izquierda: esquivar girando a la derecha.
    iniciarGiro(true);
  } else if (hayObstaculo(dDer)) {
    // Boya a la derecha: esquivar girando a la izquierda.
    iniciarGiro(false);
  }
  // Sin obstáculos: seguimos avanzando (los motores ya están andando).
}
La navegación con esquive: el dron avanza, y cuando alguno de los tres sensores detecta una boya cercana, la rodea y retoma el recorrido.

Podés ver el comportamiento completo —el dron avanzando, detectando boyas y esquivándolas— en el simulador:

Simulación del comportamiento

La misma máquina de estados del firmware, portada a TypeScript, maneja el comportamiento del dispositivo. No se ejecuta el binario real: la ejecución del .hex con avr8js está en el roadmap.

Simulación lista. Presioná «Iniciar» para arrancar la secuencia.

Subir el código y navegar entre boyas

Subimos el programa a la placa igual que en el nivel inicial: conectamos la placa por USB, elegimos la placa y el puerto en el menú del Arduino IDE 2.x y cliqueamos Subir (la flecha →).

Para probar el esquive, colocamos algunas “boyas” en la batea o pileta de pruebas: cualquier objeto que flote o sobresalga sirve. El dron debe recorrer el área, detectar cada boya con alguno de sus sensores, rodearla y seguir.

Cierre del nivel

Al terminar este nivel, el dron de los científicos dejó de ser ciego: sus tres sensores ultrasónicos —cada uno leyendo su propio par de pines— vigilan el frente y los costados, descartan las lecturas de 0 de los objetos fuera de rango y disparan una maniobra de esquive cada vez que aparece una boya, para después retomar el barrido. En el camino corregiste dos errores reales del material original: los tres sensores que leían el mismo par de pines y el segundo sensor que chocaba con un motor. Con esto queda cerrado el recorrido del proyecto. Si el grupo quiere seguir, en la ficha están las extensiones sugeridas: trayectorias triangulares para optimizar el barrido, rutas alternativas para esquivar una misma boya, y un doble puente H que le permita al dron dar marcha atrás.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El dron tiene tres sensores ultrasónicos (uno al frente y dos laterales), cada uno leyendo su propio par de pines Trigger/Echo, con la electrónica protegida del agua.
  • El dron mide la distancia a los obstáculos y descarta las lecturas de 0, que corresponden a objetos fuera de rango, no a boyas pegadas al casco.
  • Cuando cualquiera de los tres sensores detecta una boya a menos de la distancia de seguridad, el dron la esquiva y luego retoma su trayectoria.