diff --git a/transmitter/transmitter.ino b/transmitter/transmitter.ino index 4d14a07..50e292b 100644 --- a/transmitter/transmitter.ino +++ b/transmitter/transmitter.ino @@ -5,18 +5,23 @@ * Board: "ESP32 Dev Module" listed under esp32_bluepad32 * Libraries: none (Bluepad32 ships inside the board package) * - * Reads a Bluetooth Classic gamepad via Bluepad32 and streams the stick / - * button state out of Serial2 as plain ASCII lines. The second ESP32 (see - * ../receiver) picks those up and drives the LEGO hubs over BLE. + * Reads a Bluetooth Classic gamepad via Bluepad32 and streams the full stick, + * trigger, d-pad and button state out of Serial2 as plain ASCII lines. The + * second ESP32 (see ../receiver) picks those up and drives the LEGO hubs. + * + * This board does NOT talk to the hubs and must not have Legoino or NimBLE + * installed. Keeping BTstack and NimBLE on separate chips is the entire point. * * Wiring to the receiver board: * TX GPIO17 -> RX GPIO16 on receiver * RX GPIO16 <- TX GPIO17 on receiver (unused today, reserved for acks) * GND -> GND (mandatory - common ground) * - * Line format: G,,,,,*\n + * Line format: G,,,,,,,,*\n * lx/ly/rx/ry : -512..511 (Bluepad32 raw axis range) * buttons : 16-bit mask, decimal + * dpad : 8-bit mask, decimal + * l2/r2 : analog triggers, 0..1023 * XX : XOR checksum of everything before the '*', two hex digits * * Created by: Jess Rogerson (yelling commands at Claude.AI) @@ -29,16 +34,21 @@ static const int LINK_TX_PIN = 17; static const int LINK_RX_PIN = 16; static const long LINK_BAUD = 115200; static const int STATUS_LED_PIN = 2; -static const unsigned long SEND_INTERVAL_MS = 20; // 50 Hz +static const unsigned long SEND_INTERVAL_MS = 20; // 50 Hz ceiling static const unsigned long HEARTBEAT_MS = 200; // resend even if idle +// Set to 1 to echo every outgoing frame to the USB serial monitor. +#define DEBUG_FRAMES 0 + // ------------------------------------------------------------------ state ControllerPtr myControllers[BP32_MAX_CONTROLLERS]; static unsigned long lastSend = 0; static unsigned long lastHeartbeat = 0; static int lastLx = 0, lastLy = 0, lastRx = 0, lastRy = 0; +static int lastL2 = 0, lastR2 = 0; static uint16_t lastButtons = 0; +static uint8_t lastDpad = 0; // -------------------------------------------------------------- callbacks void onConnectedController(ControllerPtr ctl) { @@ -71,20 +81,30 @@ static uint8_t xorChecksum(const char *s) { return c; } -static void sendFrame(int lx, int ly, int rx, int ry, uint16_t buttons) { - char payload[64]; - snprintf(payload, sizeof(payload), "G,%d,%d,%d,%d,%u", lx, ly, rx, ry, buttons); +static void sendFrame(int lx, int ly, int rx, int ry, + uint16_t buttons, uint8_t dpad, int l2, int r2) { + char payload[80]; + snprintf(payload, sizeof(payload), "G,%d,%d,%d,%d,%u,%u,%d,%d", + lx, ly, rx, ry, buttons, dpad, l2, r2); Serial2.printf("%s*%02X\n", payload, xorChecksum(payload)); +#if DEBUG_FRAMES + Serial.printf("%s*%02X\n", payload, xorChecksum(payload)); +#endif } -// Only bother transmitting when something actually moved, or on heartbeat. -static bool worthSending(int lx, int ly, int rx, int ry, uint16_t buttons) { - const int DELTA = 4; // ignore stick jitter of a few counts +// Only transmit when something actually moved, or on heartbeat. +static bool worthSending(int lx, int ly, int rx, int ry, + uint16_t buttons, uint8_t dpad, int l2, int r2) { + const int DELTA = 4; // ignore stick jitter of a few counts + const int TRIG = 8; // triggers are noisier at rest if (buttons != lastButtons) return true; + if (dpad != lastDpad) return true; if (abs(lx - lastLx) >= DELTA) return true; if (abs(ly - lastLy) >= DELTA) return true; if (abs(rx - lastRx) >= DELTA) return true; if (abs(ry - lastRy) >= DELTA) return true; + if (abs(l2 - lastL2) >= TRIG) return true; + if (abs(r2 - lastR2) >= TRIG) return true; return (millis() - lastHeartbeat) >= HEARTBEAT_MS; } @@ -100,12 +120,12 @@ void setup() { BP32.setup(&onConnectedController, &onDisconnectedController); - // Clear stored pairings so a controller that was last paired to a phone - // will bind here. Comment this out once you are happy with the pairing, - // otherwise you re-pair on every boot. + // Bluepad32 keeps pairing keys in NVS and reflashing does NOT clear them. + // Leave this in for the first pairing, then comment it out - otherwise you + // re-pair the pad on every boot. BP32.forgetBluetoothKeys(); - // We only care about real gamepads, not the mouse/keyboard virtual device. + // We only want real gamepads, not the mouse/keyboard virtual device. BP32.enableVirtualDevice(false); Serial.println("Ready - put the PS4 pad in pairing mode (SHARE + PS)"); @@ -130,10 +150,12 @@ void loop() { if (gp == nullptr) { // Nothing connected: keep sending neutral so the receiver's failsafe // never has to guess. - sendFrame(0, 0, 0, 0, 0); + sendFrame(0, 0, 0, 0, 0, 0, 0, 0); lastHeartbeat = now; lastLx = lastLy = lastRx = lastRy = 0; + lastL2 = lastR2 = 0; lastButtons = 0; + lastDpad = 0; return; } @@ -141,12 +163,17 @@ void loop() { int ly = gp->axisY(); int rx = gp->axisRX(); int ry = gp->axisRY(); + int l2 = gp->brake(); + int r2 = gp->throttle(); uint16_t buttons = gp->buttons(); + uint8_t dpad = gp->dpad(); - if (dataUpdated || worthSending(lx, ly, rx, ry, buttons)) { - sendFrame(lx, ly, rx, ry, buttons); + if (dataUpdated || worthSending(lx, ly, rx, ry, buttons, dpad, l2, r2)) { + sendFrame(lx, ly, rx, ry, buttons, dpad, l2, r2); lastLx = lx; lastLy = ly; lastRx = rx; lastRy = ry; + lastL2 = l2; lastR2 = r2; lastButtons = buttons; + lastDpad = dpad; lastHeartbeat = now; } }