Сборка робота Wall-E из LEGO с Arduino Nano: пошаговое руководство
Компоненты и материалы
| LEGO Wall-E | ×1 |
| Arduino Nano R3 | ×1 |
| DC-мотор (универсальный) | ×2 |
| Двухканальный драйвер двигателей L298N | ×1 |
| Двухцветный светодиод (3 вывода) | ×1 |
| Инфракрасный датчик | ×1 |
| Зуммер (способный играть разные тона) | ×1 |
| Крона 9В | ×1 |
| Резистор 330 Ом | ×3 |
| Перемычки (универсальные) | ×20 |
Необходимые инструменты и станки
| Паяльник (универсальный) |
Приложения и онлайн-сервисы
| Arduino IDE |
О проекте
Все знают фильм Wall-E (а если нет — срочно смотрите!) и жёлтого героя, пытающегося очистить Землю. В этом проекте я использовал LEGO-версию нашего маленького друга и научил его избегать препятствий. Это был мой первый проект и отличный опыт изучения основ электроники.
Шаг 1 — Код
Как разработчик ПО по профессии, я подумал о том, чего хочу от него, и начал с кода.
// Эта программа управляет LEGO-роботом Wall-E.
// Wall-E ездит по округе. Увидев препятствие, он останавливается, оглядывается и выбирает другой путь.
// Arduino Nano имеет 21 пин для digitalRead и digitalWrite
// PWM-пины 3, 5, 6, 9, 10, 11 для analogWrite
// Пины 0 и 1 для TTL
// Пины 2 и 3 для внешних прерываний
// Пины 10, 11, 12, 13 поддерживают SPI
// Пин 13 — встроенный светодиод
// Пины 14–21 — аналоговые A0–A7 для analogRead
#define INFRA_RED 9 // может быть любым пином
#define GREEN_LED 7 // любой пин, но нужен резистор, ~220 Ом — или на земле 1 кОм
#define RED_LED 8 // любой пин, но нужен резистор, ~220 Ом — или на земле 1 кОм
#define BUZZER 10 // должен быть PWM-пином для частоты, нужен резистор, ~1 кОм
// MR — правый мотор, ML — левый мотор
#define MR_1 A1 // любой пин, сделаем соответствующими пинам на L298N
#define MR_2 A2
#define MR_ENABLE 5 // PWM-пин для управления скоростью
#define ML_1 A3
#define ML_2 A4
#define ML_ENABLE 6 // PWM-пин для управления скоростью
// Нормальная скорость — максимум
const int NORMAL_SPEED = 255;
void setup() {
// Сразу после нажатия Reset ждём, чтобы можно было выключить без повреждения от скачков напряжения
delay(2000);
// Инициализация светодиодов и зуммера
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
// pinMode(BUZZER, OUTPUT); // не обязательно
// Сброс LED на зелёный
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
// Пины DC-моторов
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);
// LED зелёный
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED, HIGH);
// Проверка препятствий
if (digitalRead(INFRA_RED) == LOW) { // LOW = препятствие обнаружено
// LED красный
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);
// Писк при заднем ходе 5 секунд
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); // 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); // C4
delay(300);
tone(BUZZER, 330, 250); // E4
delay(300);
tone(BUZZER, 392, 250); // G4
delay(300);
tone(BUZZER, 523, 500); // C5
delay(550);
}
void playUhOh() {
tone(BUZZER, 523, 250); // C5
delay(300);
tone(BUZZER, 415, 500); // Gis4
delay(600);
}
При отключении работающего мотора могут возникнуть скачки напряжения, повреждающие электронику. Поэтому Wall-E ждёт 2 секунды перед действием. Это значит, что я могу нажать Reset на Arduino и быстро отключить батарею без повреждений.
Он играет мелодию при пробуждении и начинает ехать. Увидев препятствие, останавливается, играет звук «Упс» и оглядывается. Используемый ИК-датчик имеет винтик на задней стороне для настройки дистанции срабатывания. Поэтому в коде нет расчётов расстояния. (Сначала хотел ультразвуковой датчик, но он не влезает в глаза.)
Wall-E сначала проверяет левую сторону, потом правую. Если обе заблокированы — едет назад с писком как строительная техника, затем случайный поворот и продолжение. Если заблокирована только одна сторона — едет на другую. Если обе свободны — выбирает случайно.
Пытался заставить его делать случайные повороты, но эта часть не доделана. Пытаюсь использовать встроенный таймер Arduino. Напишите в комментариях, если есть идеи по оптимизации!
Шаг 2 — Проводка
Первое — встроить ИК-датчик в глаз, чтобы не было заметно. Разобрал его, суперклеем приклеил LEGO-шплинт к датчику (чтобы глаз мог двигаться вверх-вниз), а затем синим пластилином восстановил LEGO-детали вокруг датчика:

Главное про моторы — достаточный крутящий момент, так как конструкция колёс Wall-E требует значительной силы. Пришлось припаять провода к моторам и закрепить их так, чтобы они надёжно соединялись с LEGO. Разобрал колёса, заказал пакет LEGO Technic-шплинтов, обмотал валы моторов гипсовым пластырем (мягкая лента, которая клеится к коже) и вставил в два шплинта, ставших главными осями каждого колеса. Это работало пару минут, но трение было слишком высоким для клея. К счастью, у Technic-шплинтов по бокам есть желобки, куда пластырь зацепился после пропитки суперклеем.

Также немного снял грудь, чтобы просунуть светодиод. Затем соединил все детали и Arduino.

Драйвер моторов в точности влез в его животик:

Схема тоже не очень понятна, но я постарался цветово кодировать провода для наглядности:

По соглашению: все красные провода — плюс («дают» электричество), все чёрные — минус («принимают» электричество). Жёлтый провод справа сверху — сигнал ИК-датчика; оранжевый и зелёный — для трёхвыводного двухцветного светодиода; фиолетовые — управление направлением моторов; синие — управление скоростью (к сожалению, в Fritzing не было двух пинов для скорости, поэтому синие провода просто висят в воздухе).
У драйвера моторов был хороший туториал, упрощавший подключение. Проблема — нехватка пинов питания и земли. Я хотел питать моторы прямо через драйвер для максимальной мощности, но как-то нужно было подать 5В на Arduino и ИК-датчик. Сделал то, чего все в интернете говорят не делать: подключил 5В-выход драйвера к 5В-пину Arduino как вход. С моим драйвером я уверен, что он выдаёт стабильные 5В без скачков, способных повредить Arduino. Если подать нестабилизированное питание на этот пин — скорее всего, сгорит что-то. Сначала хотел использовать Vin, но он понижает напряжение до ~3.8В, чего мало для Arduino. Поэтому использовал свободный Vin для питания ИК-датчика 5В (нет разъёмов-разветвителей). Да, в этот момент чувствовал себя как Франкенштейн. Но заработало!
Шаг 3 — Wall-E в действии
Пара видео с демонстрацией:
И здесь тестировал, что будет, если он застрян в углу:
Это был мой первый маленький проект. Теперь планирую оптимизировать проводку и, возможно, добавить сервопривод для поворота головы. Можно даже купить разветвители и меньший драйвер/плату, чтобы всё влезло в животик.
И так Wall-E عاش долго и счастливо. Конец.
Код
Wall_e_control Arduino
Центральный файл управления Wall-E. Базовый, но всё, что ему нужно сейчас.
// Этот код полностью идентичен коду в Шаге 1
// ... (полный код приведён выше)
Схемы
Значение цветов проводов:
red = напряжение (плюс)
black = земля (минус)
yellow = сигнал ИК-датчика
orange и green = подключения красного и зелёного входов светодиода
purple = управление направлением моторов
blue = управление скоростью моторов (в Fritzing не было двух пинов, которые были у моего драйвера, поэтому выглядят как свободные провода)
wall-e2_3P6X71BCnP.fzz
Производственный процесс
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- Устройство для общения глухосліпых людей с помощью 1Sheeld и Arduino
- Умная копилка: управление монетоприёмником на Arduino Nano
- Автоматическая LEGO Star Wars пушка на Arduino Uno
- Робот-ускользатор на сервоприводе: сборка на Arduino и HC-SR04
- Управление светодиодом по Bluetooth с Arduino: простой DIY-гид
- Интеграция ёмкостного сканера отпечатков DFRobot с Arduino или ESP8266
- Автономный линий-следующий робот на Arduino UNO
- Интерактивный HMI-проект на Nextion с Arduino Mega и датчиками
- Роботизированная рука 6 степеней свободы на Arduino Mega с управлением через Nunchuk