Four-wheel rover driven by Waveshare DDSM115 hub motors, controlled from a Raspberry Pi 5 with a USB gamepad.
The Pi sends JSON commands over USB serial to a Waveshare DDSM driver board (integrated ESP32), which drives four direct-drive hub motors.
Assembled rover with four DDSM115 hub motors and the driver board
Driver board wiring and Raspberry Pi connection
🎥 Demo video — the rover under gamepad control
git clone <this-repo> && cd HubDrive-Rover
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python src/list_ports.py # find the board's serial port
.venv/bin/python src/rover_control.py --port /dev/ttyACM0 --gamepad /dev/input/event5If either device path is wrong — and the defaults often are, since the numbers shift between reboots — see docs/DIAGNOSTICS.md.
Before the first drive, run §5 of docs/DIAGNOSTICS.md to confirm your gamepad's axis ranges. The control script assumes 0–255 axes centred at 128; pads that report −32768..32767 need
JOYSTICK_CENTERchanged or the sticks will read as permanently deflected.
HubDrive-Rover/
├── src/
│ ├── rover_control.py # Main gamepad-driven control script
│ ├── serial_console.py # Interactive JSON console to the board
│ └── list_ports.py # List available serial ports
├── docs/
│ ├── DIAGNOSTICS.md # Terminal commands for troubleshooting
│ └── PROTOCOL.md # DDSM JSON command reference
├── images/
├── videos/
└── requirements.txt
| Script | What it does |
|---|---|
rover_control.py |
The main program. Reads the gamepad, drives all four motors, stops them on exit. |
serial_console.py |
Type raw JSON at the board and see replies. Use for motor-ID assignment and firmware poking. |
list_ports.py |
Prints every serial port with its description. |
For gamepad axis mapping, use §5–6 of docs/DIAGNOSTICS.md — those report each axis's min/max/resting value, which a plain event dump does not.
| Part | Notes |
|---|---|
| Raspberry Pi 5 | Main control computer |
| Waveshare DDSM115 hub motor ×4 | Low speed, high torque, direct drive |
| Waveshare DDSM driver board | Integrated ESP32, 2.4G WiFi, ESP-NOW |
| USB gamepad | Any evdev-compatible pad |
| USB cable | Pi to driver board |
Motors need their own power supply. USB alone enumerates the board but will not turn the wheels.
Each motor needs a distinct ID (1–4) assigned before four-wheel control works — see docs/PROTOCOL.md.
| Input | Action |
|---|---|
| Left stick — Y axis | Forward / backward |
| Right stick — X axis | Turn left / right while driving |
| Right stick — Y axis | Rotate in place (clockwise / anticlockwise) |
| L1 | Increase speed (+10 RPM) |
| R1 | Decrease speed (−10 RPM) |
Ctrl-C |
Stop motors and exit |
Turn sharpness scales with stick deflection — a slight push gives a wide arc, a full push the tightest turn. The inside wheels slow to 35% rather than stopping, so the rover arcs rather than pivoting.
Speed starts at 50 RPM and floors at 10 RPM.
Axis assignments assume a standard pad layout. On some controllers
ABS_RZis a trigger rather than a stick axis — if rotation fires continuously at startup, that's the cause. Check with §5 of docs/DIAGNOSTICS.md.
Commands are newline-terminated JSON at 115200 baud:
{"T":10010,"id":1,"cmd":50,"act":3}Full reference, including wheel layout and sign conventions, in docs/PROTOCOL.md.
docs/DIAGNOSTICS.md covers, in order:
- Is the controller detected? (
lsusb,dmesg) - Which serial port is it? (plus stable
by-idpaths) - Am I allowed to open it? (the
dialoutgroup — a common first-run blocker) - Is the gamepad detected, and which
eventN? - What are my gamepad's real axis ranges?
- Which axis does each stick move?
- Is the board talking back?
- Emergency: stop the motors now
- What's holding the serial port open?
- Repo hygiene checks
- No rate limiting. The control loop sends a full four-motor frame on every gamepad axis event, each blocking ~80ms. Fast stick movement builds a backlog and the rover lags behind the input.
JOYSTICK_CENTERis hardcoded to 128. Correct for 0–255 pads only; not auto-detected fromabsinfo.
Manya Jain
Licensed under the Apache License, Version 2.0 — see LICENSE.
You may use, modify and redistribute this code, including commercially, provided you retain the copyright notice and state any changes you made. The license also grants a patent licence from contributors, and provides the code "as is" without warranty — relevant here, since this drives real motors.