Rewrite as a step-by-step build guide for the Johnny 5 setup
Adds bring-up order (pair the pad before wiring, verify the port map with DEBUG_MOTORS before trusting it), the tank drive control table, the extended link protocol, and the wiring failure modes.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# ps4-lego-bridge
|
||||
|
||||
PS4 controller -> two LEGO Powered Up / Technic hubs, using two ESP32 boards.
|
||||
PS4 controller -> two LEGO Technic hubs, using two ESP32 boards.
|
||||
|
||||
Driving a Johnny 5 (Short Circuit) MOC — seven motors across two hubs.
|
||||
|
||||
## Why two boards
|
||||
|
||||
@@ -14,13 +16,15 @@ does not work, and it is not a library-version problem.
|
||||
|
||||
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.
|
||||
cannot run both in one firmware image.
|
||||
|
||||
So: board A runs Bluepad32 and nothing else. Board B runs Legoino and nothing
|
||||
else. Three jumper wires between them.
|
||||
|
||||
(The single-board version, which speaks the LEGO protocol directly against
|
||||
BTstack, lives in `ps4-lego-onebrain`. It works, but it is a lot more code and
|
||||
a lot more ways to be wrong.)
|
||||
|
||||
## Hardware
|
||||
|
||||
* 2x ESP32 dev boards. Both must be the **original ESP32** (WROOM/WROVER).
|
||||
@@ -31,85 +35,127 @@ else. Three jumper wires between them.
|
||||
Board A (Bluepad32) Board B (Legoino)
|
||||
GPIO17 TX -------------> GPIO16 RX
|
||||
GPIO16 RX <------------- GPIO17 TX
|
||||
GND ------------- GND <- do not skip this one
|
||||
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.
|
||||
TX goes to RX, not TX to TX. If nothing arrives, that swap is the first thing
|
||||
to check.
|
||||
|
||||
## Software setup
|
||||
Power both from the same supply if you can. Separate USB bricks are fine as long
|
||||
as the grounds are tied together — without a common ground the UART has no
|
||||
shared voltage reference and you get garbage or silence.
|
||||
|
||||
### Board A — transmitter
|
||||
## Step by step
|
||||
|
||||
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`.
|
||||
**1. Board A — the gamepad board.**
|
||||
|
||||
### Board B — receiver
|
||||
Preferences -> Additional board manager URLs, add:
|
||||
|
||||
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`.
|
||||
```
|
||||
https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json
|
||||
```
|
||||
|
||||
## Controls as shipped
|
||||
Boards Manager -> install **esp32_bluepad32**. Tools -> Board -> pick ESP32 Dev
|
||||
Module from under **esp32_bluepad32**, not the plain `esp32` group. Getting that
|
||||
wrong is what produces `fatal error: Bluepad32.h: No such file or directory`.
|
||||
|
||||
| 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 |
|
||||
Flash `transmitter/transmitter.ino`. Nothing else needs installing — Bluepad32
|
||||
lives inside the board package. Do not install Legoino or NimBLE on this board.
|
||||
|
||||
Change the mapping in the frame-handling block of `receiver.ino`.
|
||||
**2. Pair the pad, before wiring anything.**
|
||||
|
||||
Open Serial Monitor at 115200. Hold SHARE + PS on the controller until the light
|
||||
bar flashes. You want `Controller connected in slot 0`. Once it pairs reliably,
|
||||
comment out `BP32.forgetBluetoothKeys()` in `setup()` — it is in there to clear
|
||||
stale pairings, and leaving it means re-pairing on every boot.
|
||||
|
||||
Set `DEBUG_FRAMES` to 1 temporarily and confirm frames stream past as you move
|
||||
the sticks. If they do, board A is finished.
|
||||
|
||||
**3. Board B — the hub board.**
|
||||
|
||||
Boards Manager -> the standard **esp32** package by Espressif. Library Manager
|
||||
-> **NimBLE-Arduino**, pinned to **1.4.x** (Legoino has not moved to the 2.x
|
||||
API, and 2.x gives a wall of compile errors), then **Legoino**.
|
||||
|
||||
Flash `tools/hub_scanner/hub_scanner.ino` first. Press each hub's green button
|
||||
and note the addresses it reports, then paste them into `receiver.ino` as
|
||||
`HUB0_ADDR` and `HUB1_ADDR`.
|
||||
|
||||
**4. Verify the port map before you trust it.**
|
||||
|
||||
Flash `receiver/receiver.ino` with `DEBUG_MOTORS` set to 1. Every motor command
|
||||
logs which hub and port it lands on. Move one control at a time and check the
|
||||
log matches what physically moves. This is worth doing properly — a swapped hub
|
||||
address or a motor in the wrong port looks exactly like a software bug and will
|
||||
waste an afternoon.
|
||||
|
||||
Set `DEBUG_MOTORS` back to 0 once it checks out.
|
||||
|
||||
**5. Wire the boards together** per the diagram above, power both, and drive it.
|
||||
|
||||
Onboard LED on board A is solid when the pad is connected. On board B it is
|
||||
solid when both hubs are connected.
|
||||
|
||||
## Controls
|
||||
|
||||
Tank drive — every input drives exactly one motor.
|
||||
|
||||
| Input | Function |
|
||||
| --- | --- |
|
||||
| Left stick Y | Left track (hub 0 port B) |
|
||||
| Right stick Y | Right track (hub 0 port A) |
|
||||
| D-pad up / down | Head tilt (hub 1 port A) |
|
||||
| D-pad left / right | Head turn (hub 1 port B) |
|
||||
| R2 / L2 | Body lift up / down, proportional (hub 0 port D) |
|
||||
| Square / Circle | Left arm up / down (hub 1 port C) |
|
||||
| Triangle / Cross | Right arm up / down (hub 1 port D) |
|
||||
| L1 held | Precision, 40% track speed |
|
||||
| R1 held | Full, 100% track speed |
|
||||
| L1 + R1 | All stop |
|
||||
|
||||
Default track scale is 75%. Stick X axes are unused.
|
||||
|
||||
**Everything except the tracks runs into a mechanical end stop**, and there is no
|
||||
position feedback, so holding a direction at a stop stalls the motor. That is
|
||||
what `HEAD_MAX`, `LIFT_MAX` and `ARM_MAX` are for. Lower them if an axis feels
|
||||
forceful, and do not hold a direction once an axis has stopped moving.
|
||||
|
||||
## Link protocol
|
||||
|
||||
ASCII, newline terminated, 115200 8N1:
|
||||
|
||||
```
|
||||
G,<lx>,<ly>,<rx>,<ry>,<buttons>*<XX>\n
|
||||
G,<lx>,<ly>,<rx>,<ry>,<buttons>,<dpad>,<l2>,<r2>*<XX>\n
|
||||
```
|
||||
|
||||
* axes are Bluepad32 raw values, -512..511
|
||||
* `buttons` is the 16-bit mask as a decimal number
|
||||
* `buttons` is the 16-bit mask, `dpad` the 8-bit mask, both decimal
|
||||
* `l2`/`r2` are the analog triggers, 0..1023
|
||||
* `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.
|
||||
Plain text means you can watch the link with any USB-serial adapter when
|
||||
something misbehaves. The receiver drops any frame that fails the checksum, and
|
||||
stops all motors if nothing valid arrives for 400ms.
|
||||
|
||||
## Things that bite
|
||||
|
||||
* **Nothing arrives at board B.** TX/RX swapped, or no common ground. Both are
|
||||
silent failures.
|
||||
* **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.
|
||||
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 a hub drops out.** Commands are outrunning the hub. Raise
|
||||
`MOTOR_MIN_GAP_MS` (per port) or `HUB_MIN_GAP_MS` (per hub). Hub 1 carries four
|
||||
motors, which is why the per-hub limit exists at all.
|
||||
* **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.
|
||||
`USE_TACHO_MOTORS`.
|
||||
* **Button masks.** The values in `receiver.ino` are Bluepad32's standard
|
||||
layout. If a face button does the wrong thing, set `DEBUG_BUTTONS` in the
|
||||
transmitter, press each one, and correct the constants.
|
||||
* **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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user