Community project
Ecosistema Acuático Interactivo
The Interactive Aquatic Ecosystem is an Arduino-based project that brings a miniature boat to life with motion-sensing and distance-tracking capabilities. Using a PIR motion sensor to detect nearby activity and an ultrasonic distance sensor to measure water depth or obstacles, the system controls a servo motor that steers the boat in response to environmental changes. The guide provides a complete wiring diagram, parts list, step-by-step assembly instructions, and firmware code to get the ecosystem up and running.
Builders will learn how to integrate multiple sensors with Arduino, manage power distribution for servo motors, and implement real-time sensor fusion to create interactive behavior. The project demonstrates practical techniques for sensor calibration, serial communication, and servo control that apply to countless maker projects beyond aquatic systems.
Wiring diagram

Gather all the parts
Assemble it in 6 steps
1. Coloca el Arduino y separa las dos alimentaciones
Deja el Arduino Uno accesible dentro del muelle y conecta su cable USB al computador; ese cable alimenta el Uno y lleva los datos. No conectes todavía la fuente externa de 5 V al servo. La fuente externa regulada de 5 V y 2 A mínimo es solamente para el servo.
- Mantén el cable USB conectado directamente al computador para que TouchDesigner pueda hablar con el Arduino.
- No conectes una fuente externa de 5 V al pin VIN del Arduino: VIN está pensado para un voltaje mayor y el montaje puede funcionar mal.
2. Cablea el sensor de presencia
En el HC-SR501, conecta VCC al pin 5V del Arduino (alimentación), GND a cualquier pin GND del Arduino (tierra) y OUT al pin digital D2 (señal). Deja los dos pequeños ajustes del sensor aproximadamente a la mitad al comenzar.
- Tras encenderlo, espera alrededor de un minuto sin moverte cerca: el sensor necesita estabilizarse.
- Orienta la cúpula blanca hacia la zona desde la que llegará el público.
- No dejes el cable GND sin conectar: sin una tierra común el sensor puede dar lecturas falsas.
3. Cablea el sensor de distancia
En el HC-SR04, conecta VCC al pin 5V del Arduino (alimentación), GND a GND del Arduino (tierra), TRIG al pin digital D6 (señal que inicia la medición) y ECHO al pin digital D7 (señal que devuelve la distancia). Coloca sus dos círculos mirando hacia la zona de interacción y sin que un borde del muelle los tape.
- El sensor mide mejor si apunta aproximadamente a la altura del torso de las personas.
- Evita colocarlo donde el barquito o una pared curva queden directamente delante, porque generarían ecos constantes.
- No apuntes el sensor hacia una superficie muy inclinada o blanda: el eco puede no volver y aparecerá DIST:-1.
4. Prepara la alimentación estable del servo
En la protoboard, lleva el positivo de la fuente externa regulada de 5 V al carril rojo y su GND al carril azul o negro. Conecta también un cable desde ese carril GND al pin GND del Arduino: esta unión hace que el Arduino y el servo entiendan la misma señal. Inserta el capacitor de 470 µF entre los mismos carriles: la pata larga marcada con + al carril rojo de 5 V y la pata con franja - al carril de GND.
- Un adaptador USB de pared de 5 V y 2 A mínimo sirve como fuente del servo si tienes una forma segura de sacar sus cables de 5 V y GND.
- Mantén estos cables de alimentación del servo cortos y algo más gruesos que los cables de señal si es posible.
- No inviertas el capacitor: la pata marcada con una franja debe ir a GND; invertirlo puede calentarlo o dañarlo.
- No alimentes el servo desde el pin 5V del Arduino ni desde la protoboard alimentada por ese pin: al moverse puede pedir picos de corriente y reiniciar el Arduino.
5. Conecta y monta el barquito
En el SG90, conecta el cable naranja o amarillo a D9 del Arduino (señal), el cable rojo al carril rojo de 5 V de la fuente externa (alimentación) y el cable marrón o negro al carril GND de esa fuente (tierra). Fija el brazo plástico del servo a un mecanismo ligero, como un hilo o varilla fina, que empuje suavemente el barco; el papel no debe cargar directamente el eje con peso o rozamiento.
- Con el barco centrado, monta el brazo del servo aproximadamente en posición horizontal; el programa inicia cerca de 90 grados.
- Deja holgura mecánica: el programa limita el recorrido a 15–165 grados para reducir golpes contra los topes.
- Asegúrate de que el brazo del servo no pueda atrapar dedos, cables ni el papel al moverse.
- Si el servo vibra, el Arduino se reinicia o el USB se desconecta, corta la alimentación y revisa la fuente externa, el capacitor y la conexión GND común.
6. Conecta el USB y prueba la comunicación
Con todos los cables revisados, conecta el Arduino al computador por USB y deja encendida la fuente de 5 V del servo. El proyecto envía una línea de datos unas 20 veces por segundo. En TouchDesigner usa el puerto serie del Arduino a 115200 baudios y divide cada línea por comas y luego por dos puntos.
- Cada línea tiene este aspecto: PIR:1,DIST:35,SERVO:70. PIR vale 1 cuando detecta movimiento; DIST es la distancia en centímetros y -1 significa que no llegó eco; SERVO es el ángulo real aproximado.
- Para pedir un ángulo desde TouchDesigner, envía una línea terminada en salto de línea: SERVO:120. Para volver al balanceo automático, envía AUTO:1; para mantener el último ángulo indicado, envía AUTO:0.
- No conectes nada a D0 o D1: esos pines se reservan para el USB que comunica con TouchDesigner.
Review all connections
1. Connections between "servo_boat" and "Arduino"
2. Connections between "pir_presence" and "Arduino"
3. Connections between "ultrasonic_distance" and "Arduino"
4. Connections between "servo_supply" and "Arduino"
5. Connections between "servo_capacitor" and "Arduino"
Deploy the firmware
#include <Arduino.h>
#include <Servo.h>
#include <string.h>
#include <stdlib.h>
// Forward declarations
int readDistanceCm();
void processSerialCommand();
void updateServo();
void sendReport();
const uint8_t PIR_PIN = 2;
const uint8_t TRIG_PIN = 6;
const uint8_t ECHO_PIN = 7;
const uint8_t SERVO_PIN = 9;
const unsigned long REPORT_INTERVAL_MS = 50; // 20 updates per second
const unsigned long SERVO_INTERVAL_MS = 20;
const int SERVO_MIN_ANGLE = 15;
const int SERVO_MAX_ANGLE = 165;
Servo boatServo;
int distanceCm = -1;
int servoAngle = 90;
int manualTargetAngle = 90;
bool autoMotion = true;
unsigned long lastReportMs = 0;
unsigned long lastServoMs = 0;
int readDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 30 ms timeout corresponds to about 5 m and prevents the loop from hanging.
unsigned long durationUs = pulseIn(ECHO_PIN, HIGH, 30000UL);
if (durationUs == 0) {
return -1;
}
int cm = (int)(durationUs / 58UL);
if (cm < 2 || cm > 400) {
return -1;
}
return cm;
}
void processSerialCommand() {
static char command[32];
static uint8_t index = 0;
while (Serial.available() > 0) {
char c = (char)Serial.read();
if (c == '\r') {
continue;
}
if (c == '\n') {
command[index] = '\0';
if (strncmp(command, "SERVO:", 6) == 0) {
int requested = atoi(command + 6);
manualTargetAngle = constrain(requested, SERVO_MIN_ANGLE, SERVO_MAX_ANGLE);
autoMotion = false;
} else if (strncmp(command, "AUTO:", 5) == 0) {
autoMotion = atoi(command + 5) != 0;
}
index = 0;
} else if (index < sizeof(command) - 1) {
command[index++] = c;
} else {
index = 0;
}
}
}
void updateServo() {
unsigned long now = millis();
if (now - lastServoMs < SERVO_INTERVAL_MS) {
return;
}
lastServoMs = now;
int targetAngle = manualTargetAngle;
if (autoMotion) {
// Gentle 4-second rocking movement for the physical paper boat.
float phase = (now % 4000UL) * 6.2831853f / 4000.0f;
targetAngle = 90 + (int)(18.0f * sin(phase));
}
// Move only one degree per update so the boat does not jerk.
if (servoAngle < targetAngle) {
servoAngle++;
} else if (servoAngle > targetAngle) {
servoAngle--;
}
boatServo.write(servoAngle);
}
void sendReport() {
int pir = digitalRead(PIR_PIN) == HIGH ? 1 : 0;
distanceCm = readDistanceCm();
Serial.print(F("PIR:"));
Serial.print(pir);
Serial.print(F(",DIST:"));
Serial.print(distanceCm);
Serial.print(F(",SERVO:"));
Serial.println(servoAngle);
}
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(TRIG_PIN, LOW);
Serial.begin(115200);
boatServo.attach(SERVO_PIN);
boatServo.write(servoAngle);
}
void loop() {
processSerialCommand();
updateServo();
unsigned long now = millis();
if (now - lastReportMs >= REPORT_INTERVAL_MS) {
lastReportMs = now;
sendReport();
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




