Construye un Robot Wall-E de LEGO con Arduino Nano: Guía Paso a Paso
Componentes y suministros
| LEGO Wall-E | ×1 | Tuve que conseguir uno de segunda mano porque ya no los fabrican |
| Arduino Nano R3 | ×1 | |
| Motor DC (genérico) | ×2 | |
| Controlador de Motor Dual L298N | ×1 | |
| LED Bicolor | ×1 | con 3 pines |
| Sensor Infrarrojo | ×1 | |
| Zumbador (Buzzer) | ×1 | Uno que pueda tocar diferentes tonos |
| Batería 9V | ×1 | |
| Resistencia 330 ohm | ×3 | |
| Cables jumper (genéricos) | ×20 |
Herramientas y máquinas necesarias
- Soldador (genérico)
Aplicaciones y servicios en línea
- Arduino IDE
Sobre este proyecto
Todo el mundo conoce la película Wall-E (¡y si no, ve a verla ya!) y al héroe amarillo que intenta limpiar la Tierra. En este proyecto, usé una versión de Lego de nuestro pequeño amigo y le enseñé a evitar obstáculos. Este fue mi primer proyecto y una gran experiencia de aprendizaje para entender los fundamentos de la electrónica.
Paso 1 - El Código
Como desarrollador de software de oficio, pensé en qué quería que hiciera y empecé con el código.
// Este programa controla el robot Wall-E de Lego.
// Wall-E conduce por ahí. Cuando ve un obstáculo, se detiene a mirar alrededor y elige otro camino.
// Arduino Nano tiene 21 pines para digitalRead y digitalWrite
// Pines PWM 3, 5, 6, 9, 10, 11 para analogWrite
// Pines 0 y 1 para TTL
// Pines 2 y 3 para interrupciones externas
// Pines 10, 11, 12, 13 para comunicación SPI
// Pin 13 para LED interno
// Pines 14 a 21 son también pines analógicos A0 a A7 para analogRead
#define INFRA_RED 9 // puede ser cualquier pin
#define GREEN_LED 7 // cualquier pin pero necesita resistencia, quizá 220 Ohm - o pin ground 1 kOhm
#define RED_LED 8 // cualquier pin pero necesita resistencia, quizá 220 Ohm - o pin ground 1 kOhm
#define BUZZER 10 // debe ser pin PWM para frecuencia, necesita resistencia, quizá 1 kOhm
// MR es motor derecho, ML es motor izquierdo
#define MR_1 A1 // cualquier pin, hagamos que correspondan a números de pines en shield L298N
#define MR_2 A2
#define MR_ENABLE 5 // necesita ser pin PWM para control de velocidad
#define ML_1 A3
#define ML_2 A4
#define ML_ENABLE 6 // necesita ser pin PWM para control de velocidad
// Velocidad normal al máximo
const int NORMAL_SPEED = 255;
void setup() {
// justo después de presionar reset, esperar un poco para poder apagarlo sin dañar componentes por picos de voltaje
delay(2000);
// inicializar LEDs y buzzer
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
// pinMode(BUZZER, OUTPUT); // no necesario
// reset LED a verde
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
// pines motor DC
pinMode(MR_ENABLE, OUTPUT); // motor derecho
pinMode(MR_1, OUTPUT);
pinMode(MR_2, OUTPUT);
pinMode(ML_ENABLE, OUTPUT); // motor izquierdo
pinMode(ML_1, OUTPUT);
pinMode(ML_2, OUTPUT);
// inicializar infrarrojo
pinMode(INFRA_RED, INPUT);
// inicializar generador aleatorio para giros aleatorios
randomSeed(analogRead(0));
// decir hola
playHello();
}
void loop() {
// operaciones normales
driveForwards(NORMAL_SPEED);
// LED verde
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
// comprobar obstáculos
if (digitalRead(INFRA_RED) == LOW) { // LOW significa obstáculo detectado
// cambiar LED a rojo
digitalWrite(GREEN_LED, LOW);
digitalWrite(RED_LED, HIGH);
// parar motores
stopDriving();
// sonido uh-oh
playUhOh();
// comprobar izquierda
turnLeft(500);
boolean obstacleLeft = false;
if (digitalRead(INFRA_RED) == LOW) {
obstacleLeft = true;
}
// volver al centro
delay(100);
turnRight(500);
// esperar un poco, no queremos parecer apurados
delay(500);
// comprobar derecha
turnRight(500);
boolean obstacleRight = false;
if (digitalRead(INFRA_RED) == LOW) {
obstacleRight = true;
}
// volver al centro
delay(100);
turnLeft(500);
// ahora ver cómo salir
if (obstacleLeft && obstacleRight) {
driveBackwards(NORMAL_SPEED / 3);
// beep mientras retrocede por 5 segundos
for (int i = 0; i < 5; i++) {
tone(BUZZER, 1000, 500);
delay(1000);
}
// para evitar atascarse, girar aleatoriamente antes de continuar
randomTurn(800, 1600);
} else if (obstacleLeft) {
turnRight(1000);
} else if (obstacleRight) {
turnLeft(1000);
} else {
randomTurn(1000, 1800);
}
}
// cosas aleatorias para más interacción
int number = random(100); // número aleatorio 0-99
if (number == 0) {
randomTurn(200,2000);
}
}
void driveForwards(int speed) {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, HIGH);
digitalWrite(ML_1, HIGH);
digitalWrite(ML_2, LOW);
setSpeed(speed);
}
void driveBackwards(int speed) {
digitalWrite(MR_1, HIGH);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, HIGH);
setSpeed(speed);
}
void turnLeft(int duration) {
digitalWrite(MR_1, HIGH);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, HIGH);
digitalWrite(ML_2, LOW);
setSpeed(NORMAL_SPEED / 2);
delay(duration);
stopDriving();
}
void turnRight(int duration) {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, HIGH);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, HIGH);
setSpeed(NORMAL_SPEED / 2);
delay(duration);
stopDriving();
}
void stopDriving() {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, LOW);
digitalWrite(MR_ENABLE, LOW);
digitalWrite(ML_ENABLE, LOW);
}
void setSpeed(int speed) {
speed = constrain(speed, 0, 255);
analogWrite(MR_ENABLE, speed);
analogWrite(ML_ENABLE, speed);
}
void randomTurn(int minimum, int maximum) {
unsigned long time = millis();
int duration = random(minimum, maximum);
if (time % 2) {
turnRight(duration);
} else {
turnLeft(duration);
}
}
void playHello() {
tone(BUZZER, 262, 250); // Do4
delay(300);
tone(BUZZER, 330, 250); // Mi4
delay(300);
tone(BUZZER, 392, 250); // Sol4
delay(300);
tone(BUZZER, 523, 500); // Do5
delay(550);
}
void playUhOh() {
tone(BUZZER, 523, 250); // Do5
delay(300);
tone(BUZZER, 415, 500); // Sol#4
delay(600);
}
Cuando desconectas un motor en marcha, pueden ocurrir picos de voltaje y dañar tu electrónica. Por eso hago que Wall-E espere dos segundos antes de hacer algo. Eso significa que puedo simplemente presionar el botón Reset en el Arduino y desconectar rápidamente la batería sin dañar nada.
Él reproduce una pequeña melodía al despertar y luego empieza a conducir. Cuando ve un obstáculo, se detiene, reproduce un sonido "Uh-oh", y mira alrededor para determinar el mejor camino. El tipo de sensor infrarrojo que usé tiene un tornillo en su parte trasera que permite determinar la distancia a la que crea una señal. Por eso no hay cálculos de distancia en el código. (Quería usar un sensor ultrasónico primero, pero no cabe con sus ojos.)
Wall-E primero verifica si el lado izquierdo está libre, y luego si el derecho está libre. Si ambos lados están bloqueados, retrocede mientras pita como maquinaria pesada en una obra, y luego gira en una dirección aleatoria y continúa. Si solo un lado está bloqueado, continúa por el otro. Si ambos lados están libres, elige uno al azar y continúa su camino.
Intenté que hiciera giros aleatorios, pero esa parte no está del todo completada. Estoy intentando usar el temporizador interno de Arduino. ¡Avísame en los comentarios si tienes idea de cómo optimizarlo!
Paso 2 - Cableándolo
Lo primero fue integrar el sensor infrarrojo en el ojo para que no se note mucho. Lo desarmé, pegué un pin de Lego al sensor (para que su ojo pueda moverse arriba y abajo) y luego usé blu-tack para volver a poner las piezas de Lego alrededor del sensor:
Lo más importante de los motores era que tuvieran suficiente torque, porque la configuración de las ruedas de Wall-E necesita bastante fuerza para moverse. Tuve que soldar cables a los motores y fijarlos de forma que conectaran de forma segura al Lego. Desarmé sus ruedas, pedí una bolsa llena de pines Technic de Lego, envolví cinta de yeso (¿así se llama? Es como cinta suave que va sobre la piel) alrededor de los ejes de los motores, y los metí en dos pines que se convirtieron en el eje principal de cada rueda. Esto funcionó unos minutos, pero luego la fricción fue demasiado alta para que el pegamento de la cinta aguantara. Por suerte, los pines Technic tienen pequeñas ranuras en el lateral, así que la cinta sí tuvo donde agarrarse, lo cual hicieron felizmente después de empaparse en superpegamento.
También quité un poco de su pecho para meter el LED. Luego conecté todas las partes y el Arduino.
El shield del motor justo cabía en su barriga:
El diagrama no es muy claro tampoco, pero intenté codificar los cables por colores para mejor visión:
Por convención, todos los cables rojos son positivos ("entregan" electricidad) y todos los negros son negativos ("reciben" electricidad). El cable amarillo en la esquina superior derecha lleva la señal del sensor infrarrojo; los cables naranja y verde son para el LED bicolor de tres pines, los cables morados son para decirle al shield del motor en qué dirección girar los motores, y los cables azules le dicen al shield qué tan rápido girarlos.
El shield del motor tenía un muy buen tutorial que facilitó su conexión y uso. Desafortunadamente, la pieza de Fritzing no tenía los dos pines para configurar la velocidad, así que los cables azules terminan aleatoriamente en el shield en el diagrama.
Un problema más que enfrenté al montarlo todo fue la falta de pines de Voltaje y Tierra. Quería alimentar los motores directamente a través del shield para que obtuvieran la mayor potencia posible, pero de alguna manera tenía que alimentar 5V al Arduino y al sensor infrarrojo también. Así que hice lo que prácticamente todo el mundo en la web dice que no debería hacer: conecté la salida 5V del shield al pin 5V del Arduino como entrada. Ahora con el shield que uso, puedo estar absolutamente seguro de que entrega 5V regulados sin picos feos que puedan dañar mi Arduino. Si conectas una fuente no regulada a ese pin, probablemente frías algo. Quería usar el pin Vin al principio, pero ese tiene un mecanismo interno que regula todo hacia abajo, así que mis 5V habrían pasado a 3.8V más o menos, lo cual no es suficiente para que el Arduino funcione correctamente. En su lugar, usé el Vin libre para alimentar el sensor infrarrojo con 5V, porque no tenía divisores de cable, y sabía que también saldrían 5V de ahí. Sí, empezó a sentirse un poco como Frankenstein en este punto. ¡Pero funcionó!
Paso 3 - Wall-E en Acción
Aquí hay un par de videos mostrándolo en acción.
Y aquí probé qué haría si estaba atrapado en una esquina.
Así que este fue mi primer pequeño proyecto. Ahora planeo optimizar las conexiones de cable y tal vez añadir un motor servo para que pueda girar la cabeza. ¡Incluso podría comprar divisores y un shield de motor y placa más pequeños para que todo quepa en su barriga!
Y así Wall-E vivió feliz para siempre. Fin.
Código
Wall_e_control
// Este programa controla el robot Wall-E de Lego.
// Wall-E conduce por ahí. Cuando ve un obstáculo, se detiene a mirar alrededor y elige otro camino.
// Arduino Nano tiene 21 pines para digitalRead y digitalWrite
// Pines PWM 3, 5, 6, 9, 10, 11 para analogWrite
// Pines 0 y 1 para TTL
// Pines 2 y 3 para interrupciones externas
// Pines 10, 11, 12, 13 para comunicación SPI
// Pin 13 para LED interno
// Pines 14 a 21 son también pines analógicos A0 a A7 para analogRead
#define INFRA_RED 9
#define GREEN_LED 7
#define RED_LED 8
#define BUZZER 10
// MR es motor derecho, ML es motor izquierdo
#define MR_1 A1
#define MR_2 A2
#define MR_ENABLE 5
#define ML_1 A3
#define ML_2 A4
#define ML_ENABLE 6
const int NORMAL_SPEED = 255;
void setup() {
delay(2000);
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
pinMode(MR_ENABLE, OUTPUT);
pinMode(MR_1, OUTPUT);
pinMode(MR_2, OUTPUT);
pinMode(ML_ENABLE, OUTPUT);
pinMode(ML_1, OUTPUT);
pinMode(ML_2, OUTPUT);
pinMode(INFRA_RED, INPUT);
randomSeed(analogRead(0));
playHello();
}
void loop() {
driveForwards(NORMAL_SPEED);
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
if (digitalRead(INFRA_RED) == LOW) {
digitalWrite(GREEN_LED, LOW);
digitalWrite(RED_LED, HIGH);
stopDriving();
playUhOh();
turnLeft(500);
boolean obstacleLeft = false;
if (digitalRead(INFRA_RED) == LOW) {
obstacleLeft = true;
}
delay(100);
turnRight(500);
delay(500);
turnRight(500);
boolean obstacleRight = false;
if (digitalRead(INFRA_RED) == LOW) {
obstacleRight = true;
}
delay(100);
turnLeft(500);
if (obstacleLeft && obstacleRight) {
driveBackwards(NORMAL_SPEED / 3);
for (int i = 0; i < 5; i++) {
tone(BUZZER, 1000, 500);
delay(1000);
}
randomTurn(800, 1600);
} else if (obstacleLeft) {
turnRight(1000);
} else if (obstacleRight) {
turnLeft(1000);
} else {
randomTurn(1000, 1800);
}
}
int number = random(100);
if (number == 0) {
randomTurn(200,2000);
}
}
void driveForwards(int speed) {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, HIGH);
digitalWrite(ML_1, HIGH);
digitalWrite(ML_2, LOW);
setSpeed(speed);
}
void driveBackwards(int speed) {
digitalWrite(MR_1, HIGH);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, HIGH);
setSpeed(speed);
}
void turnLeft(int duration) {
digitalWrite(MR_1, HIGH);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, HIGH);
digitalWrite(ML_2, LOW);
setSpeed(NORMAL_SPEED / 2);
delay(duration);
stopDriving();
}
void turnRight(int duration) {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, HIGH);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, HIGH);
setSpeed(NORMAL_SPEED / 2);
delay(duration);
stopDriving();
}
void stopDriving() {
digitalWrite(MR_1, LOW);
digitalWrite(MR_2, LOW);
digitalWrite(ML_1, LOW);
digitalWrite(ML_2, LOW);
digitalWrite(MR_ENABLE, LOW);
digitalWrite(ML_ENABLE, LOW);
}
void setSpeed(int speed) {
speed = constrain(speed, 0, 255);
analogWrite(MR_ENABLE, speed);
analogWrite(ML_ENABLE, speed);
}
void randomTurn(int minimum, int maximum) {
unsigned long time = millis();
int duration = random(minimum, maximum);
if (time % 2) {
turnRight(duration);
} else {
turnLeft(duration);
}
}
void playHello() {
tone(BUZZER, 262, 250);
delay(300);
tone(BUZZER, 330, 250);
delay(300);
tone(BUZZER, 392, 250);
delay(300);
tone(BUZZER, 523, 500);
delay(550);
}
void playUhOh() {
tone(BUZZER, 523, 250);
delay(300);
tone(BUZZER, 415, 500);
delay(600);
}
Esquemáticos
Significado de los colores de cables:
rojo = voltaje (positivo)
negro = tierra (negativo)
amarillo = señal para sensor infrarrojo
naranja y verde = conexiones para entrada LED rojo y verde
morado = control dirección motor
azul = control velocidad motor (desafortunadamente, la pieza Fritzing no tenía los dos pines que mi puente H tenía para estas conexiones, así que parece cables sueltos por el momento)
Proceso de manufactura
- Sensor K30 CO₂: Monitorización en tiempo real de la calidad del aire interior
- Facilitar la comunicación sordo‑ciego usando 1Sheeld y Arduino
- Alcancía Inteligente: Controla un Aceptador de Monedas con Arduino Nano
- Construye un lanzador automático LEGO Star Wars con Arduino Uno
- Robot evasor de obstáculos con servo: Construcción con Arduino y HC-SR04
- Controla un LED por Bluetooth con Arduino – Guía DIY paso a paso
- Integración del sensor de huella capacitivo DFRobot (ID809) con Arduino o ESP8266
- Robot Seguidor de Línea Autónomo con Arduino UNO: Guía Paso a Paso
- Proyecto HMI Interactivo con Nextion, Arduino Mega y Múltiples Sensores
- Brazo Robótico de 6 Grados de Libertad Controlado por Nunchuk con Arduino
- Control Preciso de Servo con Arduino y MPU6050 (SG90 / MG996R)