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).
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Un dron que recorre la superficie del agua Nivel inicial
- 03 Un dron que esquiva las boyas Nivel avanzado
Materiales y equipamiento
Insumos
| Imagen | Insumo | Cantidad | Plataforma |
|---|---|---|---|
| | 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 |
| | 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 |
| | 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 | |
| | Protoboard | 1 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| | Cable dupont macho-macho | 20 | Todas |
| | Arduino UNO R3 | 1 | Arduino UNO R3 |
| | Placa ESP32 DevKit v1 | 1 | ESP32 DevKit |
| | Cable USB tipo B | 1 | Arduino UNO R3 |
| | Cable micro USB (con línea de datos) | 1 | ESP32 DevKit |
| | 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
Deslizá horizontalmente para ver el plano completo
Deslizá horizontalmente para ver el plano completo
| Desde | Hacia | Color de cable |
|---|---|---|
| Arduino UNO · 5V | Bus + de la protoboard | rojo |
| Arduino UNO · GND | Bus − de la protoboard | negro |
| Portapilas 6×AA (+) | ULN2003 · alimentación de motores | violeta |
| Portapilas 6×AA (−) | Bus − de la protoboard | violeta |
| ULN2003 · IN1 | Arduino UNO · 5 | verde |
| ULN2003 · OUT1 | Motor DC izquierdo | verde |
| ULN2003 · IN2 | Arduino UNO · 6 | naranja |
| ULN2003 · OUT2 | Motor DC derecho | naranja |
| Ultrasónico frente · VCC | Bus + de la protoboard | rojo |
| Ultrasónico frente · GND | Bus − de la protoboard | negro |
| Ultrasónico frente · Trig | Arduino UNO · 2 | amarillo |
| Ultrasónico frente · Echo | Arduino UNO · 3 | azul |
| Ultrasónico izquierdo · VCC | Bus + de la protoboard | rojo |
| Ultrasónico izquierdo · GND | Bus − de la protoboard | negro |
| Ultrasónico izquierdo · Trig | Arduino UNO · 8 | amarillo |
| Ultrasónico izquierdo · Echo | Arduino UNO · 9 | azul |
| Ultrasónico derecho · VCC | Bus + de la protoboard | rojo |
| Ultrasónico derecho · GND | Bus − de la protoboard | negro |
| Ultrasónico derecho · Trig | Arduino UNO · 10 | amarillo |
| Ultrasónico derecho · Echo | Arduino UNO · 11 | azul |
| Desde | Hacia | Color de cable |
|---|---|---|
| ESP32 DevKit v1 · 3V3 | Bus + de la protoboard | rojo |
| ESP32 DevKit v1 · GND | Bus − de la protoboard | negro |
| Portapilas 6×AA (+) | ULN2003 · alimentación de motores | violeta |
| Portapilas 6×AA (−) | Bus − de la protoboard | violeta |
| ULN2003 · IN1 | ESP32 DevKit v1 · GPIO26 | verde |
| ULN2003 · OUT1 | Motor DC izquierdo | verde |
| ULN2003 · IN2 | ESP32 DevKit v1 · GPIO27 | naranja |
| ULN2003 · OUT2 | Motor DC derecho | naranja |
| Ultrasónico frente · VCC | Bus + de la protoboard | rojo |
| Ultrasónico frente · GND | Bus − de la protoboard | negro |
| Ultrasónico frente · Trig | ESP32 DevKit v1 · GPIO5 | amarillo |
| Ultrasónico frente · Echo | ESP32 DevKit v1 · GPIO18 | azul |
| Ultrasónico izquierdo · VCC | Bus + de la protoboard | rojo |
| Ultrasónico izquierdo · GND | Bus − de la protoboard | negro |
| Ultrasónico izquierdo · Trig | ESP32 DevKit v1 · GPIO19 | amarillo |
| Ultrasónico izquierdo · Echo | ESP32 DevKit v1 · GPIO21 | azul |
| Ultrasónico derecho · VCC | Bus + de la protoboard | rojo |
| Ultrasónico derecho · GND | Bus − de la protoboard | negro |
| Ultrasónico derecho · Trig | ESP32 DevKit v1 · GPIO22 | amarillo |
| Ultrasónico derecho · Echo | ESP32 DevKit v1 · GPIO23 | azul |
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 .stlFormato .stlFormato de archivo que contiene la forma de un objeto sólido 3D, sin color, texturas ni propiedades físicas. Es el más usado para intercambiar e imprimir modelos 3D; funciona como la 'exportación final' de un diseño, como un PDF de un documento de texto., 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)
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.
Para el aula
Este es un buen momento para conversar sobre flotación y sobre estanqueidad: por qué la electrónica va arriba y protegida, y cómo el reparto del peso influye en que el dron flote equilibrado. No hace falta que quede perfecto: el objetivo es que floten y avancen; el ajuste fino es parte de la exploración de cada grupo.
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 ULN2003ULN2003Circuito integrado que actúa como driver de motores: convierte la señal de control de baja potencia de la placa (una salida digital) en la potencia necesaria para mover un motor DC. No invierte el giro: enciende y apaga el motor en un solo sentido.. No es posible conectar los motores DCMotor DCMotor eléctrico de dos terminales (+ y −), común en vehículos y ventiladores. Gira mientras recibe corriente y su velocidad depende de la potencia entregada. Requiere un circuito intermediario (driver) que le dé la potencia que la placa no puede entregar por sí sola. 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.
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 ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. 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.
Verificá
Antes de energizar, repasá el circuito: la señal de la placa entra a una entrada del ULN2003, el motor queda conectado a la salida correspondiente del módulo, las pilas alimentan el módulo y la masa es común entre la placa y las pilas. Todavía no hay programa, así que el motor no se moverá.
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.
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.
¿Por qué no da marcha atrás?
Discutan en grupo: con este driver, el dron no puede retroceder. ¿Por qué? Piensen qué hace el ULN2003 con la corriente del motor (¿la puede invertir?). Después miren las extensiones de la ficha del proyecto: hay un componente, el doble puente H, que sí permitiría la marcha atrás. ¿Qué ganaría el dron pudiendo retroceder, y qué movimientos nuevos podría hacer?
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 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 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.
} 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.
Para el aula
El material original de 2019 enseñaba este paso con bloques en mBlock3, hoy discontinuado, y
el código que generaba arrastraba funciones sobrantes (_delay, _loop, angle_rad,
angle_deg) que no aportaban nada. En esta reedición ese ruido se limpió: el sketch usa
delay() estándar y constantes nombradas para los pines. La secuencia de barrido es una
oportunidad ideal para trabajar la relación entre tiempo y distancia: pedir a cada
grupo que dibuje en papel la trayectoria que espera y después la contraste con lo que hace el
dron.
Verificá
En el Arduino IDE, apretá el botón de verificar (el tilde ✓). Si abajo aparece “Compilación completada” sin mensajes en rojo, tu programa está bien escrito. Si aparece un error, revisá los punto y coma al final de cada línea y que las llaves abran y cierren.
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.
Un dron que esquiva las boyas
Situación problema
Luego de realizar algunos recorridos de prueba con el dron, los científicos notaron que, en algunos casos, sus trayectorias se veían interrumpidas. Descubrieron que esto se debía a que los guardaparques depositan boyas para el control del nivel del lago en ciertos puntos. Por eso decidieron desarrollar una nueva versión del dron acuático, capaz de percibir los objetos que se encuentran en su camino a una distancia considerable, cambiar su rumbo para esquivarlos y luego volver a su trayectoria inicial.
En esta instancia se implementará el uso de sensores ultrasónicos para que el dron pueda detectar objetos en su proximidad. La información de los sensores se enviará a la placa para que active una nueva trayectoria; luego de esquivar el obstáculo, el dron retomará su recorrido.
Conectar el sensor ultrasónico
Incorporamos un sensor de distancia ultrasónicoSensor de distancia ultrasónicoSensor que mide distancias emitiendo un pulso de sonido ultrasónico y midiendo el tiempo hasta que el eco regresa tras rebotar en un obstáculo. Tiene cuatro pines: VCC y GND para la alimentación, Trigger para disparar el pulso y Echo para detectar su llegada. 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.
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 ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. 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.
El cero engañoso
Piensen en grupo: ¿qué pasaría si el programa no descartara el 0 y lo tratara como “boya pegadísima”? El dron intentaría esquivar todo el tiempo, incluso en aguas despejadas, porque cualquier zona sin obstáculos a la vista devuelve 0. Descartar el 0 es lo que le permite al dron distinguir “no hay nada cerca” de “hay algo muy cerca”.
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.
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.
Para el aula
Los dos errores del código original que aquí quedaron corregidos, y que conviene comentar con el grupo como caso real de depuración:
- Los tres sensores leían el mismo par de pines. El original asignaba a las variables
sensor1,sensor2ysensor3siempre la lectura de un mismo sensor (getDistance(2,3)), un clásico error de copia y pega. Con eso, dos de los tres sensores nunca se leían de verdad y el dron solo reaccionaba a lo que detectara ese único par de pines, anulando el sentido de haber agregado sensores. En esta reedición cada sensor lee su propio par:getDistance(2,3),getDistance(8,9)ygetDistance(10,11). - El segundo sensor chocaba con un motor. El original conectaba el sensor 2 a los pines 6 y 7, pero el pin 6 ya estaba ocupado por uno de los motores. Un mismo pin no puede a la vez comandar un motor y leer un Echo. Aquí el segundo sensor se movió a los pines 8 y 9, dejando los pares de los tres sensores ordenados y sin conflictos (2/3, 8/9, 10/11).
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 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 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).
} 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.
Para el aula
El original enseñaba este paso con bloques en mBlock3 y “esquivaba” con una coreografía fija
de giros escrita dentro de setup(): el dron se movía siempre igual sin importar cuál
sensor detectaba la boya, y encima quedaba bloqueado —a ciegas— durante cada maniobra. En esta
reedición la navegación se reescribió por completo: es reactiva (la maniobra depende de
dónde está la boya y de qué lado hay más espacio) y no bloqueante (máquina de estados con
millis(), sin delay()). Es un buen ejemplo para discutir con el grupo la diferencia entre
un programa que “espera parado” y uno que sigue atento mientras actúa, y por qué eso importa
cuando un robot se mueve en el mundo real. Además, ya con los dos errores de pines corregidos
(cada sensor lee su propio par y ninguno choca con un motor), es un buen caso para mostrar
cómo se depura una guía técnica heredada.
Verificá
En el Arduino IDE, verificá el programa (✓): si compila sin errores en rojo, está bien escrito. Con el sensor del frente, acercá un objeto: el dron (o el simulador) debe iniciar la maniobra de esquive. Probá también acercando algo por cada costado: cada lateral debe disparar el esquive por su lado. Si un lateral no reacciona, revisá que su sensor esté leyendo su propio par de pines y no el del frente.
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.