Record the working configuration

- Pin the toolchain versions, with the Legoino/core 3.x error text.
- Note DIR_* direction constants and the corrected control table.
- Add the swapped-hub-address story to step 4, and the Arduino
  prototype-ordering trap to the gotchas list.
This commit is contained in:
2026-09-10 11:40:34 +10:00
parent 39bc43a878
commit 8fac5c893e
+51 -7
View File
@@ -4,6 +4,9 @@ PS4 controller -> two LEGO Technic hubs, using two ESP32 boards.
Driving a Johnny 5 (Short Circuit) MOC — seven motors across two hubs. Driving a Johnny 5 (Short Circuit) MOC — seven motors across two hubs.
**Status: working on hardware.** The configuration in this repo is the one that
runs, not the one that ought to.
## Why two boards ## Why two boards
The obvious sketch — Bluepad32 for the gamepad, Legoino for the hubs, one ESP32 — The obvious sketch — Bluepad32 for the gamepad, Legoino for the hubs, one ESP32 —
@@ -25,6 +28,28 @@ else. Three jumper wires between them.
BTstack, lives in `ps4-lego-onebrain`. It works, but it is a lot more code and BTstack, lives in `ps4-lego-onebrain`. It works, but it is a lot more code and
a lot more ways to be wrong.) a lot more ways to be wrong.)
## Toolchain versions — pin these
| Board | Package / library | Version |
| --- | --- | --- |
| A | esp32_bluepad32 | 4.1.0 |
| B | esp32 (Espressif) | **2.0.17** |
| B | NimBLE-Arduino | **1.4.x** |
| B | Legoino | latest |
Board B is the fussy one. **Core 3.x will not build Legoino.** ESP-IDF 5 changed
`uint32_t` from `unsigned int` to `unsigned long`, so Legoino's own header and
.cpp no longer agree, and it never included `<string>` itself. The symptoms are:
```
'string' in namespace 'std' does not name a type
no declaration matches 'uint32_t LegoinoCommon::ReadUInt32LE(uint8_t*, int)'
```
Downgrade the core rather than patching the library — patching works, but it is
the first of several layers and any Library Manager update silently reverts it.
Board A is unaffected; it uses a separate package.
## Hardware ## Hardware
* 2x ESP32 dev boards. Both must be the **original ESP32** (WROOM/WROVER). * 2x ESP32 dev boards. Both must be the **original ESP32** (WROOM/WROVER).
@@ -123,21 +148,28 @@ the sticks. If they do, board A is finished.
**3. Board B — the hub board.** **3. Board B — the hub board.**
Boards Manager -> the standard **esp32** package by Espressif. Library Manager Boards Manager -> the standard **esp32** package by Espressif, version 2.0.17.
-> **NimBLE-Arduino**, pinned to **1.4.x** (Legoino has not moved to the 2.x Library Manager -> **NimBLE-Arduino** pinned to **1.4.x**, then **Legoino**. See
API, and 2.x gives a wall of compile errors), then **Legoino**. the toolchain table above for why the versions matter.
Flash `tools/hub_scanner/hub_scanner.ino` first. Press each hub's green button 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 and note the addresses it reports, then paste them into `receiver.ino` as
`HUB0_ADDR` and `HUB1_ADDR`. `HUB0_ADDR` and `HUB1_ADDR`.
Nothing else may be holding the hubs. A hub accepts one connection at a time and
stops advertising once taken, so power down any other ESP32 and close the LEGO
Powered Up app before scanning.
**4. Verify the port map before you trust it.** **4. Verify the port map before you trust it.**
Flash `receiver/receiver.ino` with `DEBUG_MOTORS` set to 1. Every motor command 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 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 log matches what physically moves.
address or a motor in the wrong port looks exactly like a software bug and will
waste an afternoon. This step is not optional busywork. On this build the two hub addresses were the
wrong way round, and the symptom — an arm moving when the track stick moved —
looks exactly like a software bug. It is not one, and no amount of reading the
code will find it.
Set `DEBUG_MOTORS` back to 0 once it checks out. Set `DEBUG_MOTORS` back to 0 once it checks out.
@@ -158,13 +190,17 @@ Tank drive — every input drives exactly one motor.
| D-pad left / right | Head turn (hub 1 port B) | | D-pad left / right | Head turn (hub 1 port B) |
| R2 / L2 | Body lift up / down, proportional (hub 0 port D) | | R2 / L2 | Body lift up / down, proportional (hub 0 port D) |
| Square / Circle | Left arm up / down (hub 1 port C) | | Square / Circle | Left arm up / down (hub 1 port C) |
| Triangle / Cross | Right arm up / down (hub 1 port D) | | Cross / Triangle | Right arm up / down (hub 1 port D) |
| L1 held | Precision, 40% track speed | | L1 held | Precision, 40% track speed |
| R1 held | Full, 100% track speed | | R1 held | Full, 100% track speed |
| L1 + R1 | All stop | | L1 + R1 | All stop |
Default track scale is 75%. Stick X axes are unused. Default track scale is 75%. Stick X axes are unused.
**Motor directions** are the `DIR_*` constants at the top of `receiver.ino`. Flip
one to `-1` if an axis runs backwards — mirrored mountings are normal on a
symmetric model. `DIR_RIGHT_TRACK` is `-1` on this build.
**Everything except the tracks runs into a mechanical end stop**, and there is no **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 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 what `HEAD_MAX`, `LIFT_MAX` and `ARM_MAX` are for. Lower them if an axis feels
@@ -189,10 +225,18 @@ stops all motors if nothing valid arrives for 400ms.
## Things that bite ## Things that bite
* **`'Frame' has not been declared`, or any type "not declared" at its own
definition.** The Arduino IDE injects generated prototypes immediately before
the first function definition in the file. Any type used in a signature must be
declared above that point — which is why `HubLink`, `PortState` and `Frame` all
sit in one block near the top. Do not move a function above them.
* **Legoino will not compile.** Core 3.x. See the toolchain table.
* **Nothing arrives at board B.** TX/RX swapped, or no common ground. Both are * **Nothing arrives at board B.** TX/RX swapped, or no common ground. Both are
silent failures. silent failures.
* **Random reboots under load.** Supply, not software. See the current budget * **Random reboots under load.** Supply, not software. See the current budget
above. above.
* **Hubs never connect.** Something else is holding them, or they went to sleep.
Power down other controllers, close the LEGO app, press the green buttons.
* **One hub connects, the other does not.** Legoino shares a single NimBLE * **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 — scanner. `receiver.ino` connects them strictly one at a time for this reason —
do not "optimise" that into two parallel `init()` calls. do not "optimise" that into two parallel `init()` calls.