用 PS2 无线手柄控制 Arduino 坦克机器人
组件与物料
| 组件 | 数量 |
|---|---|
| Arduino UNO 开发板 | 1 |
| SparkFun 双 H 桥驱动模块 L298 | 1 |
| DIY 履带坦克底盘套件 | 1 |
| 索尼 PS2 无线手柄(含接收器) | 1 |
| 18650 3.7V 锂电池 ×3 | 1组 |
| 3 节 18650 电池盒(串联) | 1 |
| 18650 电池充电器 | 1 |
| 公母/公公跳线若干 | 1套 |
| Type-B USB 数据线 | 1 |
| 焊锡铁、焊锡线、EVA 泡棉垫、双面胶、剪刀 | 各1 |
所需软件
- Arduino IDE(最新版)
- PS2X 库(Bill Porter 版,GitHub: 下载地址)
项目简介
本教程演示如何使用 PlayStation 2 无线手柄遥控一辆履带式坦克机器人。核心控制器为 Arduino Uno,接收手柄指令并驱动两路直流电机。相同原理可移植到 NodeMCU、FireBeetle 等开发板,也适用于其他差速驱动机器人。
作者此前已完成 Wi-Fi/Blynk 控制坦克、语音控制机器人 及 自制底盘 FPV 小车,本项目专注于“低延迟、免联网、手柄操控”的经典体验。
步骤 1:工具与材料准备
除上表元件外,还需:
- 焊锡铁/焊锡线 —— 电机引线可能脱焊,需返修
- EVA 泡棉垫 —— 隔离金属底盘与电路板,防短路
- 双面胶 —— 固定泡棉与模块
- 剪刀 —— 裁剪泡棉
步骤 2:机械组装
采用成品坦克底盘套件(含电机、齿轮、履带、螺丝、工具)。组装要点:
- 套件自带内六角扳手和螺丝刀,零工具也能上手
- 备件充足,丢螺丝不影响进度
- 说明书为中文表格,可参考作者录制的组装视频
- 铝合金底板导电,务必用泡棉垫隔离 PCB
步骤 3:电路接线
供电
3 节 18650 串联(11.1V)经电池盒接入 L298N 的 12V 与 GND,同时给 Arduino Vin 供电(或通过 5V 引脚)。
L298N 与 Arduino 接线表
| Arduino 引脚 | L298N 引脚 | 功能 |
|---|---|---|
| D3 | ENA | 左电机 PWM 使能 |
| D4 | IN2 | 左电机方向控制 |
| D5 | IN1 | 左电机方向控制 |
| D6 | ENB | 右电机 PWM 使能 |
| D7 | IN4 | 右电机方向控制 |
| D8 | IN3 | 右电机方向控制 |
| 5V | 5V | 逻辑电源 |
| GND | GND | 共地 |
电机输出
- OUT1/OUT2 → 右电机(+/-)
- OUT3/OUT4 → 左电机(+/-)
PS2 接收器接线
| 接收器引脚 | Arduino 引脚 | 说明 |
|---|---|---|
| 1 (Data) | D12 | 数据 |
| 2 (Command) | D11 | 命令 |
| 4 (GND) | GND | 地 |
| 5 (VCC) | 3.3V | 供电 3.3V |
| 6 (Attention) | D10 | 片选 |
| 7 (Clock) | D13 | 时钟 |
步骤 4:Arduino IDE 环境配置
- 官网下载安装最新 Arduino IDE
- 解压 PS2X 库至
Documents/Arduino/libraries - 重启 IDE,菜单 草图 → 包含库 → PS2X 验证导入成功
步骤 5:PS2X 库核心 API 速查
// 创建手柄对象
PS2X ps2x;
// 初始化并配置引脚:clock, command, attention, data, pressures?, rumble?
error = ps2x.config_gamepad(13, 11, 10, 12, true, true);
// 读取数字按键(返回 true/false)
ps2x.Button(PSB_PAD_UP); // 方向键上
ps2x.Button(PSB_R2); // 右扳机 R2
ps2x.Button(PSB_L2); // 左扳机 L2
// ... 其余按键常量见库文档
// 读取模拟摇杆(返回 0~255)
ps2x.Analog(PSS_LX); // 左摇杆 X
ps2x.Analog(PSS_LY); // 左摇杆 Y
ps2x.Analog(PSS_RX); // 右摇杆 X
ps2x.Analog(PSS_RY); // 右摇杆 Y
步骤 6~9:四种控制模式示例代码
所有示例共用相同接线与初始化,仅 loop() 逻辑不同。完整代码见文末“代码”区。
模式 1:方向键(D-Pad)全速控制
- ↑ 前进、↓ 后退、← 原地左旋、→ 原地右旋
- 电机全速(PWM 1023),无变速
模式 2:左摇杆 X 轴转向 + L2/R2 油门/刹车
- 左摇杆左右控制差速转向
- R2 前进,L2 后退,松开停车
- 类似赛车游戏手感
模式 3:双摇杆差速驱动(推荐)
- 左摇杆 Y 控左履带,右摇杆 Y 控右履带
- 最接近真实坦克操作,可原地掉头、弧线行驶
- 代码设 ±50 死区,抖动不触发
模式 4:单左摇杆混合控制(Arcade Drive)
- 一个摇杆同时控制速度与转向
- 算法参考 ImpulseAdventure 差速转向教程
- 按键资源最富余,适合扩展灯光、炮塔等功能
步骤 10:常见问题与调试
- 上传代码后需拔 USB、接电池、复位板子(常需复位 2~3 次才能配对)
- 串口监视器波特率 57600,观察
Found Controller, configured successful - 若报
No controller found:检查跳线、手柄电量、接收器指示灯 - 电机反转只需交换对应 OUT 线或修改代码方向逻辑
完整代码(四合一)
下方代码已整合四种模式,通过 #define MODE 1~4 切换。上传前请确认引脚定义与接线一致。
#define MODE 3 // 1=D-Pad, 2=L-Stick+L2/R2, 3=Dual-Stick, 4=Single-Stick
#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(F("手柄连接成功"));
else if (error == 1) Serial.println(F("未检测到手柄,检查接线或复位"));
else if (error == 2) Serial.println(F("手柄拒绝命令"));
else if (error == 3) Serial.println(F("不支持压力/震动模式"));
type = ps2x.readType();
if (type == 1) Serial.println(F("DualShock 手柄"));
else if (type == 2) Serial.println(F("GuitarHero 手柄"));
else Serial.println(F("未知手柄类型"));
}
void loop() {
if (error == 1) return;
ps2x.read_gamepad(false, vibrate);
#if MODE == 1
// D-Pad 全速控制
if (ps2x.Button(PSB_PAD_UP)) { setMotors(LOW, HIGH, HIGH, LOW, 1023, 1023); }
else if (ps2x.Button(PSB_PAD_DOWN)) { setMotors(HIGH, LOW, LOW, HIGH, 1023, 1023); }
else if (ps2x.Button(PSB_PAD_LEFT)) { setMotors(LOW, HIGH, LOW, HIGH, 1023, 1023); }
else if (ps2x.Button(PSB_PAD_RIGHT)){ setMotors(HIGH, LOW, HIGH, LOW, 1023, 1023); }
else { stopMotors(); }
#elif MODE == 2
// 左摇杆 X + L2/R2
int nJoyL = map(ps2x.Analog(PSS_LX), 0, 255, 1023, -1023);
int spdR = 1023, spdL = 1023;
if (nJoyL < 0) spdR += nJoyL;
if (nJoyL > 0) spdL -= nJoyL;
if (ps2x.Button(PSB_R2)) setMotors(LOW, HIGH, HIGH, LOW, spdR, spdL);
else if (ps2x.Button(PSB_L2)) setMotors(HIGH, LOW, LOW, HIGH, spdR, spdL);
else stopMotors();
#elif MODE == 3
// 双摇杆差速
int nJoyL = map(ps2x.Analog(PSS_LY), 0, 255, 1023, -1023);
int nJoyR = map(ps2x.Analog(PSS_RY), 0, 255, -1023, 1023);
driveTrack(nJoyR, MOTORA_1, MOTORA_2, ENA);
driveTrack(nJoyL, MOTORB_1, MOTORB_2, ENB);
#elif MODE == 4
// 单摇杆 Arcade Drive
int nJoyX = map(ps2x.Analog(PSS_LX), 0, 255, -1023, 1023);
int nJoyY = map(ps2x.Analog(PSS_LY), 0, 255, 1023, -1023);
arcadeDrive(nJoyX, nJoyY);
#endif
delay(50);
}
void setMotors(byte a1, byte a2, byte b1, byte b2, int spdA, int spdB) {
digitalWrite(MOTORA_1, a1); digitalWrite(MOTORA_2, a2);
digitalWrite(MOTORB_1, b1); digitalWrite(MOTORB_2, b2);
analogWrite(ENA, spdA); analogWrite(ENB, spdB);
}
void stopMotors() { analogWrite(ENA, 0); analogWrite(ENB, 0); }
void driveTrack(int joy, byte in1, byte in2, byte en) {
if (joy > 50) { digitalWrite(in1, HIGH); digitalWrite(in2, LOW); analogWrite(en, 1023); }
else if (joy < -50){ digitalWrite(in1, LOW); digitalWrite(in2, HIGH); analogWrite(en, 1023); }
else { analogWrite(en, 0); }
}
void arcadeDrive(int x, int y) {
// 简化版混合算法,完整版见原教程 Sketch #4
float fwd = y / 1023.0;
float turn = x / 1023.0;
int left = constrain((fwd + turn) * 1023, -1023, 1023);
int right = constrain((fwd - turn) * 1023, -1023, 1023);
driveTrack(left, MOTORA_1, MOTORA_2, ENA);
driveTrack(right, MOTORB_1, MOTORB_2, ENB);
}
原理图

💡 进阶建议:加装超声波避障、舵机云台、FPV 摄像头,或将手柄换为 ESP-NOW/蓝牙手柄实现免接收器控制。祝玩得开心!
制造工艺
- Arduino与树莓派构建互联网控制视频流机器人完整指南
- Unopad:基于 Arduino 的无缝 Ableton Live MIDI 控制器
- 舵机驱动避障机器人:基于 Arduino 与 HC-SR04 构建
- Arduino + Android 蓝牙控制 Roomba Create 2 扫地机器人完整指南
- Arduino Mega 驱动 Wii Nunchuk 控制 6 自由度机械臂
- Arduino Nano + 摇杆控制双步进电机:Visuino 图形化教程
- DIY Arduino 手柄方向盘 - 游戏竞速模拟器自制指南
- 使用 PS2 手柄打造遥控对战机器人
- 通过专用 Android App 蓝牙控制 Arduino 机械臂
- 手把手打造Arduino Nano喂食机器人
- 四轮 Arduino 机器人:Dabble App 蓝牙手柄遥控
- MeArm 机械臂精准手柄控制器:基于 Arduino UNO 的轨迹记录与回放