Управление робот-танком Arduino с беспроводным джойстиком PS2
Компоненты и расходники
| Arduino UNO | × | 1 |
| Драйвер двигателей L298N (Dual H-Bridge) | × | 1 |
| Набор для сборки робот-танка (DIY robotic tank kit) | × | 1 |
| Беспроводной контроллер Sony PS2 | × | 1 |
| Аккумуляторы 18650 3.7V (3 шт.) | × | 3 |
| Держатель для 3х аккумуляторов 18650 (3S) | × | 1 |
| Зарядное устройство для 18650 | × | 1 |
| Перемычки (male-female, male-male) | × | 1 |
| USB-кабель Type-B | × | 1 |
Инструменты
- Паяльник и припой
- Лист ЭВА-пены (или другой диэлектрик) для изоляции плат от алюминиевого шасси
- Двусторонний скотч
- Ножницы
Приложения и сервисы
- Arduino IDE
О проекте
В этом уроке показано, как управлять робот-танком с помощью беспроводного джойстика PlayStation 2. В ядре проекта используется плата Arduino Uno. Она принимает команды от контроллера и задаёт скорость двигателей. Принципы можно перенести на другие платы (NodeMCU, FireBeetle и др.) и другие модели роботов.
Ранее я создавал танк под управление через Blynk (Wi-Fi, управление со смартфона: кнопки, слайдеры, акселерометр). Также экспериментировал с голосовым управлением через Google Assistant, Adafruit.io и IFTTT — полезно для людей с ограниченными возможностями. Можно собирать роботов из подручных материалов без 3D-принтеров и лазерной резки.




Шаг 1: Инструменты и материалы

Используемые инструменты: паяльник, ЭВА-пена для изоляции, двусторонний скотч, ножницы.
Железо: Arduino Uno, модуль L298N, набор танка (двигатели, гусеницы, крепеж — инструменты в комплекте), пульт PS2, 3 аккумулятора 18650, держатель 3S, зарядка, перемыжки, USB-кабель.
Ссылки на магазины — лишь рекомендации, покупайте где удобно.


Шаг 2: Сборка робота
Набор удивил качеством: все детали на месте, есть запасные винты и гайки, в комплекте все необходимые ключи и отвёртка — отлично для новичков. Минус — документация на китайском, мало уроков в сети. Поэтому я снял видео сборки. Важно: шасси алюминиевое, платы могут замыкаться на корпусе — используйте ЭВА-пену.
Шаг 3: Схема подключения

Батарейный блок (3×18650 = 11.1 В) на тыле танка питает 12-вольтовые двигатели. Модуль L298N усиливает 5-вольтовые сигналы Arduino до 12 В и позволяет реверсировать двигатели.

Распиновка
Arduino Uno:
- D3 → H-Bridge ENA
- D5 → H-Bridge IN1
- D4 → H-Bridge IN2
- D8 → H-Bridge IN3
- D7 → H-Bridge IN4
- D6 → H-Bridge ENB
- 5V → H-Bridge 5V
- GND → H-Bridge GND
- D10 → PS2 приёмник pin 6 (attention)
- D11 → PS2 приёмник pin 2 (command)
- D12 → PS2 приёмник pin 1 (data)
- D13 → PS2 приёмник pin 7 (clock)
- 3.3V → PS2 приёмник pin 5 (power)
- GND → PS2 приёмник pin 4 (ground)
H-Bridge:
- ENA → Arduino D3
- IN1 → Arduino D5
- IN2 → Arduino D4
- IN3 → Arduino D8
- IN4 → Arduino D7
- ENB → Arduino D6
- 5V → Arduino 5V
- GND → Arduino GND
- GND → батарея «-»
- 12V → батарея «+»
- OUT1 → правый двигатель «-»
- OUT2 → правый двигатель «+»
- OUT3 → левый двигатель «+»
- OUT4 → левый двигатель «-»
PS2 приёмник:
- Pin 1 (data) → Arduino D12
- Pin 2 (command) → Arduino D11
- Pin 4 (GND) → Arduino GND
- Pin 5 (3.3V) → Arduino 3.3V
- Pin 6 (attention) → Arduino D10
- Pin 7 (clock) → Arduino D13


Шаг 4: Настройка Arduino IDE
- Скачайте и установите последнюю версию Arduino IDE с arduino.cc.
- Установите библиотеку PS2X от Bill Porter: скачайте с GitHub, распакуйте и скопируйте папки в
librariesArduino IDE.
Шаг 5: Библиотека PS2X — обзор

PS2-контроллер даёт 20 цифровых кнопок и 2 аналоговых стика — огромные возможности для управления. Библиотека PS2X определяет класс PS2X с методами чтения цифровых и аналоговых входов.
Создание объекта: PS2X ps2x;
Настройка пинов: error = ps2x.config_gamepad(clock, command, attention, data, pressures?, rumble?);
Чтение кнопки: ps2x.Button(button) — возвращает true/false.
Кнопки: D-pad (UP, DOWN, LEFT, RIGHT), Action (TRIANGLE, CIRCLE, CROSS, SQUARE), Triggers (L1, R1, L2, R2), Sticks (L3, R3), Menu (SELECT, START).
Чтение стика: ps2x.Analog(stick_axis) — значение 0–255. Оси: PSS_LY, PSS_LX, PSS_RY, PSS_RX.
Шаг 6: Скетч №1 — кнопки D-pad

Эмуляция управления старых консолей (Sega, SNES). Кнопки D-pad — только вкл/выкл, двигатели работают на полной мощности.
- UP — вперёд
- DOWN — назад
- LEFT — поворот налево
- RIGHT — поворот направо

Код (фрагмент цикла):
// ВПЕРЁД
if (ps2x.Button(PSB_PAD_UP)) {
digitalWrite(MOTORA_1, LOW);
digitalWrite(MOTORA_2, HIGH);
digitalWrite(MOTORB_1, HIGH);
digitalWrite(MOTORB_2, LOW);
analogWrite(ENB, 1023);
analogWrite(ENA, 1023);
Serial.println("Move forward");
}
// НАПРАВО
if (ps2x.Button(PSB_PAD_RIGHT)) {
digitalWrite(MOTORA_1, HIGH);
digitalWrite(MOTORA_2, LOW);
digitalWrite(MOTORB_1, HIGH);
digitalWrite(MOTORB_2, LOW);
analogWrite(ENB, 1023);
analogWrite(ENA, 1023);
Serial.println("Turn right");
}
// НАЛЕВО
if (ps2x.Button(PSB_PAD_LEFT)) {
digitalWrite(MOTORA_1, LOW);
digitalWrite(MOTORA_2, HIGH);
digitalWrite(MOTORB_1, LOW);
digitalWrite(MOTORB_2, HIGH);
analogWrite(ENB, 1023);
analogWrite(ENA, 1023);
Serial.println("Turn left");
}
// НАЗАД
if (ps2x.Button(PSB_PAD_DOWN)) {
digitalWrite(MOTORA_1, HIGH);
digitalWrite(MOTORA_2, LOW);
digitalWrite(MOTORB_1, LOW);
digitalWrite(MOTORB_2, HIGH);
analogWrite(ENB, 1023);
analogWrite(ENA, 1023);
Serial.println("Move back");
}
// СТОП
if (!ps2x.Button(PSB_PAD_DOWN) && !ps2x.Button(PSB_PAD_UP) &&
!ps2x.Button(PSB_PAD_RIGHT) && !ps2x.Button(PSB_PAD_LEFT)) {
analogWrite(ENB, 0);
analogWrite(ENA, 0);
}
delay(50);
Шаг 7: Скетч №2 — левый аналоговый стик + кнопки L2/R2

Аналоговый стик задаёт поворот, L2/R2 — газа/тормоз/задний ход (как в Mario Kart).

Считываем X левого стика, маппим 0–255 → -1023…1023, корректируем скорость каждого гусеничника. R2 — вперёд, L2 — назад, ни одной — стоп.
Шаг 8: Скетч №3 — два аналоговых стика (дифференциальное управление)

Левый стик — левый гусеничник, правый — правый. Максимальный реализм управления танком.

Читаем Y обеих стиков, маппим, задаём направление и включаем PWM=1023 (можно сделать переменным). Мёртвая зона ±50.
Шаг 9: Скетч №4 — только левый стик (микшер)

Один стик управляет и скоростью, и поворотом (микширование X и Y). Экономит кнопки, но сложнее в освоении. Алгоритм из этой статьи.

Шаг 10: Заключение
Проект сделан максимально понятным и обучающим. Если понравилось — поставьте лайк и поделитесь! Другие мои проекты по робототехнике:
Поддержать будущие проекты: BTC 1FiWFYSjRaL7sLdr5wr6h86QkMA6pQxkXJ

Код (полные скетчи)
Скетч №1 — D-pad
// PS2 Tank by Igor Fonseca @2019
#include <PS2X_lib.h>
#define ENA 3
#define MOTORA_1 4
#define MOTORA_2 5
#define MOTORB_1 8
#define MOTORB_2 7
#define ENB 6
PS2X ps2x;
int error = 0;
byte type = 0;
byte vibrate = 0;
void setup() {
pinMode(ENA, OUTPUT); pinMode(MOTORA_1, OUTPUT); pinMode(MOTORA_2, OUTPUT);
pinMode(ENB, OUTPUT); pinMode(MOTORB_1, OUTPUT); pinMode(MOTORB_2, OUTPUT);
digitalWrite(ENA, 0); digitalWrite(ENB, 0);
Serial.begin(57600);
error = ps2x.config_gamepad(13, 11, 10, 12, true, true);
if (error == 0) Serial.println("Found Controller, configured successful");
else if (error == 1) Serial.println("No controller found, check wiring or reset the Arduino");
else if (error == 2) Serial.println("Controller found but not accepting commands");
else if (error == 3) Serial.println("Controller refusing to enter Pressures mode, may not support it.");
type = ps2x.readType();
switch (type) {
case 0: Serial.println("Unknown Controller type"); break;
case 1: Serial.println("DualShock Controller Found"); break;
case 2: Serial.println("GuitarHero Controller Found"); break;
}
}
void loop() {
if (error == 1) return;
ps2x.read_gamepad(false, vibrate);
// ... (код движения как выше) ...
delay(50);
}
Скетч №2 — Аналоговый стик + L2/R2
// ... те же настройки ...
int motor_right_speed = 0;
int motor_left_speed = 0;
void loop() {
if (error == 1) return;
ps2x.read_gamepad(false, vibrate);
int nJoyL = ps2x.Analog(PSS_LX);
nJoyL = map(nJoyL, 0, 255, 1023, -1023);
motor_right_speed = 1023;
motor_left_speed = 1023;
if (nJoyL < 0) motor_right_speed += nJoyL;
if (nJoyL > 0) motor_left_speed -= nJoyL;
if (ps2x.Button(PSB_R2)) { // вперёд
digitalWrite(MOTORA_1, LOW); digitalWrite(MOTORA_2, HIGH);
digitalWrite(MOTORB_1, HIGH); digitalWrite(MOTORB_2, LOW);
analogWrite(ENA, motor_right_speed); analogWrite(ENB, motor_left_speed);
}
if (ps2x.Button(PSB_L2)) { // назад
digitalWrite(MOTORA_1, HIGH); digitalWrite(MOTORA_2, LOW);
digitalWrite(MOTORB_1, LOW); digitalWrite(MOTORB_2, HIGH);
analogWrite(ENA, motor_right_speed); analogWrite(ENB, motor_left_speed);
}
if (!ps2x.Button(PSB_L2) && !ps2x.Button(PSB_R2)) {
analogWrite(ENA, 0); analogWrite(ENB, 0);
}
delay(50);
}
Скетч №3 — Два стика
void loop() {
if (error == 1) return;
ps2x.read_gamepad(false, vibrate);
int nJoyL = ps2x.Analog(PSS_LY);
int nJoyR = ps2x.Analog(PSS_RY);
nJoyL = map(nJoyL, 0, 255, 1023, -1023);
nJoyR = map(nJoyR, 0, 255, -1023, 1023);
// Правый двигатель (MOTORA)
if (nJoyR > 50) { digitalWrite(MOTORA_1, HIGH); digitalWrite(MOTORA_2, LOW); analogWrite(ENA, 1023); }
else if (nJoyR < -50) { digitalWrite(MOTORA_1, LOW); digitalWrite(MOTORA_2, HIGH); analogWrite(ENA, 1023); }
else { analogWrite(ENA, 0); }
// Левый двигатель (MOTORB)
if (nJoyL > 50) { digitalWrite(MOTORB_1, HIGH); digitalWrite(MOTORB_2, LOW); analogWrite(ENB, 1023); }
else if (nJoyL < -50) { digitalWrite(MOTORB_1, LOW); digitalWrite(MOTORB_2, HIGH); analogWrite(ENB, 1023); }
else { analogWrite(ENB, 0); }
delay(50);
}
Скетч №4 — Один левый стик (микшер)
void loop() {
if (error == 1) return;
ps2x.read_gamepad(false, vibrate);
int nJoyX = ps2x.Analog(PSS_LX);
int nJoyY = ps2x.Analog(PSS_LY);
nJoyX = map(nJoyX, 0, 255, -1023, 1023);
nJoyY = map(nJoyY, 0, 255, 1023, -1023);
float nMotPremixL, nMotPremixR, nPivSpeed, fPivScale;
const float fPivYLimit = 1023.0;
if (nJoyY >= 0) {
nMotPremixL = (nJoyX >= 0) ? 1023.0 : (1023.0 + nJoyX);
nMotPremixR = (nJoyX >= 0) ? (1023.0 - nJoyX) : 1023.0;
} else {
nMotPremixL = (nJoyX >= 0) ? (1023.0 - nJoyX) : 1023.0;
nMotPremixR = (nJoyX >= 0) ? 1023.0 : (1023.0 + nJoyX);
}
nMotPremixL = nMotPremixL * nJoyY / 1023.0;
nMotPremixR = nMotPremixR * nJoyY / 1023.0;
nPivSpeed = nJoyX;
fPivScale = (abs(nJoyY) > fPivYLimit) ? 0.0 : (1.0 - abs(nJoyY) / fPivYLimit);
int nMotMixL = (1.0 - fPivScale) * nMotPremixL + fPivScale * nPivSpeed;
int nMotMixR = (1.0 - fPivScale) * nMotPremixR + fPivScale * (-nPivSpeed);
motor_left_speed = nMotMixL;
motor_right_speed = nMotMixR;
// Установка направления и PWM для левого двигателя
if (motor_left_speed > 50) { digitalWrite(MOTORA_1, LOW); digitalWrite(MOTORA_2, HIGH); }
else if (motor_left_speed < -50) { digitalWrite(MOTORA_1, HIGH); digitalWrite(MOTORA_2, LOW); }
else { digitalWrite(MOTORA_1, LOW); digitalWrite(MOTORA_2, LOW); }
// Правый двигатель
if (motor_right_speed > 50) { digitalWrite(MOTORB_1, HIGH); digitalWrite(MOTORB_2, LOW); }
else if (motor_right_speed < -50) { digitalWrite(MOTORB_1, LOW); digitalWrite(MOTORB_2, HIGH); }
else { digitalWrite(MOTORB_1, LOW); digitalWrite(MOTORB_2, LOW); }
analogWrite(ENA, abs(motor_left_speed));
analogWrite(ENB, abs(motor_right_speed));
delay(50);
}
Схемы
Производственный процесс
- Создание интернет-управляемого видеostreaming-робота на Arduino и Raspberry Pi
- Unopad: Бесшовный Arduino MIDI-контроллер для Ableton Live
- Робот-ускользатор на сервоприводе: сборка на Arduino и HC-SR04
- Управление Roomba Create 2 через Arduino и Android: пошаговое руководство
- Роботизированная рука 6 степеней свободы на Arduino Mega с управлением через Nunchuk
- Управление двумя шаговыми двигателями через джойстик на Arduino Nano — простой урок (Visuino)
- Своё рулевое управление (джойстик) для игр на Arduino
- Создание боевого робота с управлением от геймпада PS2 на базе Arduino
- Управление манипулятором LittleArm через Android-приложение по Bluetooth
- Робот-«едобот» на Arduino Nano: открывает рот при подаче еды (HC-SR04 + сервопривод)