Document architecture, setup and known gotchas

This commit is contained in:
2026-08-27 19:33:26 +10:00
parent 9ca087836c
commit 24f4dcc08b
+114 -1
View File
@@ -1,3 +1,116 @@
# ps4-lego-bridge # ps4-lego-bridge
PS4 controller to two LEGO Powered Up hubs via two ESP32s (Bluepad32 + Legoino, split across boards) PS4 controller -> two LEGO Powered Up / Technic hubs, using two ESP32 boards.
## Why two boards
The obvious sketch — Bluepad32 for the gamepad, Legoino for the hubs, one ESP32 —
does not work, and it is not a library-version problem.
* **Bluepad32** needs Bluetooth Classic (BR/EDR), because a DualShock 4 is a
BR/EDR device. It gets that from **BTstack**, and it ships as a whole
replacement ESP32 board package rather than a normal library.
* **Legoino** talks to the hubs over BLE using **NimBLE-Arduino**.
BTstack and NimBLE are both Bluetooth *host* stacks. There is one radio and one
VHCI interface on the chip, and whichever stack registers second wins. You
cannot run both in one firmware image. Every mature project in this space
(Anton's Mindstorms LMS-ESP32, the Pybricks bridges) solves it the same way:
one chip owns the gamepad, and it hands the data to something else over a wire.
So: board A runs Bluepad32 and nothing else. Board B runs Legoino and nothing
else. Three jumper wires between them.
## Hardware
* 2x ESP32 dev boards. Both must be the **original ESP32** (WROOM/WROVER).
S3, C3, C6 and H2 have no Bluetooth Classic, so a PS4 pad will not pair.
* 3 jumper wires.
```
Board A (Bluepad32) Board B (Legoino)
GPIO17 TX -------------> GPIO16 RX
GPIO16 RX <------------- GPIO17 TX
GND ------------- GND <- do not skip this one
```
Power both from the same supply if you can. Separate USB bricks are fine as
long as the grounds are tied together.
## Software setup
### Board A — transmitter
1. Arduino IDE -> Preferences -> Additional board manager URLs, add:
`https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json`
2. Boards Manager -> install **esp32_bluepad32**.
3. Tools -> Board -> **esp32_bluepad32** -> ESP32 Dev Module.
Selecting the board from the normal `esp32` package is what produces
`fatal error: Bluepad32.h: No such file or directory`. Bluepad32 is not in
the Library Manager and installing it as a ZIP library will not help.
4. Flash `transmitter/transmitter.ino`.
### Board B — receiver
1. Boards Manager -> the standard **esp32** package by Espressif.
2. Library Manager -> **NimBLE-Arduino**. Pin this to **1.4.x**. Legoino has
not moved to the NimBLE 2.x API, so 2.x gives a wall of compile errors.
3. Library Manager -> **Legoino**.
4. Flash `tools/hub_scanner/hub_scanner.ino` first, note both hub addresses,
paste them into `receiver.ino`, then flash `receiver/receiver.ino`.
## Controls as shipped
| Input | Action |
| ----------------- | ------------------------------- |
| Left stick Y | Left track, hub 1 port A |
| Right stick Y | Right track, hub 1 port B |
| Left stick X | Head rotation, hub 2 port A |
Change the mapping in the frame-handling block of `receiver.ino`.
## Link protocol
ASCII, newline terminated, 115200 8N1:
```
G,<lx>,<ly>,<rx>,<ry>,<buttons>*<XX>\n
```
* axes are Bluepad32 raw values, -512..511
* `buttons` is the 16-bit mask as a decimal number
* `XX` is a two-digit hex XOR checksum of everything before the `*`
Being plain text means you can watch the link with a USB-serial adapter when
something misbehaves.
## Things that bite
* **One hub connects, the other does not.** Legoino shares a single NimBLE
scanner. `receiver.ino` connects them strictly one at a time for this reason.
Do not "optimise" that into two parallel `init()` calls.
* **Motors stutter or the hub drops out.** You are sending commands faster than
the hub can chew. `MOTOR_MIN_INTERVAL_MS` throttles per port; raise it before
blaming the radio.
* **More than 3 hubs later on.** Edit `CONFIG_BT_NIMBLE_MAX_CONNECTIONS` in
`NimBLE-Arduino/src/nimconfig.h`, then restart the IDE to force a rebuild.
* **Wrong motor command.** Technic/Control+ motors want `setTachoMotorSpeed`;
train and simple PU motors want `setBasicMotorSpeed`. Toggle
`USE_TACHO_MOTORS` in `receiver.ino`.
* **Bluepad32 API drift.** v4 renamed `GamepadPtr` to `ControllerPtr` and
`BP32_MAX_GAMEPADS` to `BP32_MAX_CONTROLLERS`. This code uses the v4 names.
## If you really want one board
Two options, both more work than a second ESP32:
* Write the LEGO Wireless Protocol GATT client directly against BTstack inside
the Bluepad32 sketch. Bluepad32 v4 does support BLE, so the radio can do it —
you would just be reimplementing the parts of Legoino you need. The motor
command is only eight bytes.
* Put Pybricks on the hubs and drive them from an LMS-ESP32 running the
prebuilt Bluepad32 LPF2 firmware over the LEGO wire.
---
Created by: Jess Rogerson (yelling commands at Claude.AI)