ReversePuckFirmware.ino 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // ReversePuckFirmware.ino -- emulated Steam Controller 2 ("Triton"/IBEX) for an nRF52840 Pro Micro.
  2. //
  3. // The mirror image of OpenPuck: instead of receiving a real controller over RF and presenting it to a
  4. // host, this firmware RECEIVES input from a Steam Deck over USB-CDC and TRANSMITS it over the puck RF
  5. // link, so a puck (real Valve or OpenPuck) presents a "Steam Controller 2026" to whatever host the puck
  6. // is plugged into. See ARCHITECTURE.md (OpenPuck) for the puck side; the protocol is symmetric.
  7. //
  8. // identity / ctrl_bonds -- FXA controller serial, the (up to 2) bonds to pucks (esb/bond[_2])
  9. // radio / ctrl_link -- shared bare-metal RADIO; the inverse RF role (answer the puck's polls)
  10. // triton / report45 -- the shared input model g_in + the report-0x45 ENCODER
  11. // deck_input -- the USB-CDC frame link to the deck/ forwarder app
  12. // ctrl_usb -- the USB 28DE:1302 face + steamless pairing command channel
  13. // webusb_ctrl / fw_update -- the browser-panel vendor channel: list/remove paired pucks, DFU, flash new fw
  14. //
  15. // Build (the vendor TX FIFO must hold the 0xAC bond list whole -- see webusb_ctrl.cpp):
  16. // arduino-cli compile -b adafruit:nrf52:feather52840 \
  17. // --build-property "build.extra_flags=-DNRF52840_XXAA {build.flags.usb} -DCFG_TUD_VENDOR_TX_BUFSIZE=256" \
  18. // ReversePuckFirmware
  19. // or: make reversepuck (top-level Makefile target, bakes the flag in)
  20. #include <Adafruit_TinyUSB.h>
  21. #include <Adafruit_LittleFS.h>
  22. #include <InternalFileSystem.h>
  23. using namespace Adafruit_LittleFS_Namespace;
  24. #include "identity.h"
  25. #include "radio.h"
  26. #include "ctrl_bonds.h"
  27. #include "ctrl_usb.h"
  28. #include "ctrl_link.h"
  29. #include "deck_input.h"
  30. #include "webusb_ctrl.h"
  31. #include "fw_update.h"
  32. // CDC (Deck link) + one HID control interface fit well within the default config buffer; use a modest
  33. // fixed buffer so enumeration is deterministic.
  34. static uint8_t g_usbCfgDesc[256];
  35. void setup()
  36. {
  37. // FIRST, before anything touches flash or USB: apply a panel-staged firmware update / full-board wipe if
  38. // one was committed on the previous boot. A pending wipe is honored before an apply. Both run from RAM and
  39. // never return when armed (they reset); the common path (nothing armed) is a couple of flash reads.
  40. fwupWipeIfArmed();
  41. fwupApplyIfArmed();
  42. genSerial();
  43. // safe default; the real session addr is adopted from the puck's E1
  44. rfGenSessionAddr();
  45. InternalFS.begin();
  46. loadCtrlBonds();
  47. // ---- USB: keep CDC (the Deck link) + add the Valve control HID; enumerate as 28DE:1302 ----
  48. USBDevice.detach();
  49. delay(30);
  50. USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);
  51. // FXA… controller pairing identity
  52. USBDevice.setSerialDescriptor(g_unit);
  53. ctrlUsbBegin();
  54. webusbCtrlBegin(); // vendor interface for the browser panel (manage pucks / DFU / flash)
  55. USBDevice.setConfigurationAttribute(
  56. 0x80 | 0x20); // bus-powered | remote-wakeup
  57. USBDevice.attach();
  58. Serial.begin(115200);
  59. for (int i = 0; i < 300 && !USBDevice.mounted(); i++)
  60. delay(10); // wait up to 3s for mount, but NEVER hang
  61. int nb = 0;
  62. for (int i = 0; i < NBOND; i++)
  63. if (g_bond[i].used)
  64. nb++;
  65. Serial.printf(
  66. "# ReversePuck controller up: unit=%s bonds=%d (28DE:1302)\n",
  67. g_unit, nb);
  68. // Hardware watchdog (~8s): a wedged RX busy-wait or blocked CDC write self-recovers without a replug.
  69. NRF_WDT->CONFIG = (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos) |
  70. (WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos);
  71. NRF_WDT->CRV = 8UL * 32768UL - 1;
  72. NRF_WDT->RREN = WDT_RREN_RR0_Msk;
  73. NRF_WDT->TASKS_START = 1;
  74. }
  75. void loop()
  76. {
  77. NRF_WDT->RR[0] = WDT_RR_RR_Reload; // feed the watchdog
  78. // flush dirty bonds / honor a pending reboot-to-wireless
  79. ctrlUsbPoll();
  80. // browser panel over WebUSB: list/remove paired pucks, DFU, stream a firmware update
  81. webusbCtrlPoll();
  82. // drain CDC: parse Deck input frames into g_in + forwarding state
  83. deckInputPoll();
  84. // RF: hunt/adopt the puck's E1, answer one poll with report 0x45
  85. ctrlLinkTask();
  86. // report bond + link state back to the Deck UI
  87. deckStatusTask();
  88. // flush the newest relayed haptic to the Deck (rate-limited, off the RF path)
  89. deckHapticTask();
  90. }