Files
ps4-lego-bridge/README.md
T

4.7 KiB

ps4-lego-bridge

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.

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)