Add Bluepad32 PS4 transmitter sketch

This commit is contained in:
2026-08-27 19:32:02 +10:00
parent 5e6cdd8a49
commit 6ef199814b
+152
View File
@@ -0,0 +1,152 @@
/*
* transmitter.ino -- PS4 gamepad -> UART bridge (ESP32 "A")
*
* Board package: esp32_bluepad32 (NOT the plain esp32 package)
* 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.
*
* 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,<lx>,<ly>,<rx>,<ry>,<buttons>*<XX>\n
* lx/ly/rx/ry : -512..511 (Bluepad32 raw axis range)
* buttons : 16-bit mask, decimal
* XX : XOR checksum of everything before the '*', two hex digits
*
* Created by: Jess Rogerson (yelling commands at Claude.AI)
*/
#include <Bluepad32.h>
// ---------------------------------------------------------------- settings
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 HEARTBEAT_MS = 200; // resend even if idle
// ------------------------------------------------------------------ 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 uint16_t lastButtons = 0;
// -------------------------------------------------------------- callbacks
void onConnectedController(ControllerPtr ctl) {
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
if (myControllers[i] == nullptr) {
myControllers[i] = ctl;
Serial.printf("Controller connected in slot %d\n", i);
digitalWrite(STATUS_LED_PIN, HIGH);
return;
}
}
Serial.println("Controller connected but no free slot");
}
void onDisconnectedController(ControllerPtr ctl) {
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
if (myControllers[i] == ctl) {
myControllers[i] = nullptr;
Serial.printf("Controller disconnected from slot %d\n", i);
digitalWrite(STATUS_LED_PIN, LOW);
return;
}
}
}
// ----------------------------------------------------------------- helpers
static uint8_t xorChecksum(const char *s) {
uint8_t c = 0;
while (*s) c ^= (uint8_t)*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);
Serial2.printf("%s*%02X\n", payload, xorChecksum(payload));
}
// 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
if (buttons != lastButtons) 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;
return (millis() - lastHeartbeat) >= HEARTBEAT_MS;
}
// -------------------------------------------------------------------- main
void setup() {
Serial.begin(115200);
Serial2.begin(LINK_BAUD, SERIAL_8N1, LINK_RX_PIN, LINK_TX_PIN);
pinMode(STATUS_LED_PIN, OUTPUT);
digitalWrite(STATUS_LED_PIN, LOW);
Serial.printf("Bluepad32 firmware: %s\n", BP32.firmwareVersion());
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.
BP32.forgetBluetoothKeys();
// We only care about real gamepads, not the mouse/keyboard virtual device.
BP32.enableVirtualDevice(false);
Serial.println("Ready - put the PS4 pad in pairing mode (SHARE + PS)");
}
void loop() {
bool dataUpdated = BP32.update();
ControllerPtr gp = nullptr;
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
if (myControllers[i] && myControllers[i]->isConnected() &&
myControllers[i]->isGamepad()) {
gp = myControllers[i];
break;
}
}
unsigned long now = millis();
if (now - lastSend < SEND_INTERVAL_MS) return;
lastSend = now;
if (gp == nullptr) {
// Nothing connected: keep sending neutral so the receiver's failsafe
// never has to guess.
sendFrame(0, 0, 0, 0, 0);
lastHeartbeat = now;
lastLx = lastLy = lastRx = lastRy = 0;
lastButtons = 0;
return;
}
int lx = gp->axisX();
int ly = gp->axisY();
int rx = gp->axisRX();
int ry = gp->axisRY();
uint16_t buttons = gp->buttons();
if (dataUpdated || worthSending(lx, ly, rx, ry, buttons)) {
sendFrame(lx, ly, rx, ry, buttons);
lastLx = lx; lastLy = ly; lastRx = rx; lastRy = ry;
lastButtons = buttons;
lastHeartbeat = now;
}
}