OpenController.ino 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. // OpenController.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. //
  14. // Build: arduino-cli compile -b adafruit:nrf52:feather52840 \
  15. // --build-property "build.extra_flags=-DNRF52840_XXAA {build.flags.usb}" OpenController
  16. #include <Adafruit_TinyUSB.h>
  17. #include <Adafruit_LittleFS.h>
  18. #include <InternalFileSystem.h>
  19. using namespace Adafruit_LittleFS_Namespace;
  20. #include "identity.h"
  21. #include "radio.h"
  22. #include "ctrl_bonds.h"
  23. #include "ctrl_usb.h"
  24. #include "ctrl_link.h"
  25. #include "deck_input.h"
  26. // CDC (Deck link) + one HID control interface fit well within the default config buffer; use a modest
  27. // fixed buffer so enumeration is deterministic.
  28. static uint8_t g_usbCfgDesc[256];
  29. void setup()
  30. {
  31. genSerial();
  32. // safe default; the real session addr is adopted from the puck's E1
  33. rfGenSessionAddr();
  34. InternalFS.begin();
  35. loadCtrlBonds();
  36. // ---- USB: keep CDC (the Deck link) + add the Valve control HID; enumerate as 28DE:1302 ----
  37. USBDevice.detach();
  38. delay(30);
  39. USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);
  40. // FXA… controller pairing identity
  41. USBDevice.setSerialDescriptor(g_unit);
  42. ctrlUsbBegin();
  43. USBDevice.setConfigurationAttribute(
  44. 0x80 | 0x20); // bus-powered | remote-wakeup
  45. USBDevice.attach();
  46. Serial.begin(115200);
  47. for (int i = 0; i < 300 && !USBDevice.mounted(); i++)
  48. delay(10); // wait up to 3s for mount, but NEVER hang
  49. int nb = 0;
  50. for (int i = 0; i < NBOND; i++)
  51. if (g_bond[i].used)
  52. nb++;
  53. Serial.printf("# OpenController up: unit=%s bonds=%d (28DE:1302)\n",
  54. g_unit, nb);
  55. // Hardware watchdog (~8s): a wedged RX busy-wait or blocked CDC write self-recovers without a replug.
  56. NRF_WDT->CONFIG = (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos) |
  57. (WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos);
  58. NRF_WDT->CRV = 8UL * 32768UL - 1;
  59. NRF_WDT->RREN = WDT_RREN_RR0_Msk;
  60. NRF_WDT->TASKS_START = 1;
  61. }
  62. void loop()
  63. {
  64. NRF_WDT->RR[0] = WDT_RR_RR_Reload; // feed the watchdog
  65. // flush dirty bonds / honor a pending reboot-to-wireless
  66. ctrlUsbPoll();
  67. // drain CDC: parse Deck input frames into g_in + forwarding state
  68. deckInputPoll();
  69. // RF: hunt/adopt the puck's E1, answer one poll with report 0x45
  70. ctrlLinkTask();
  71. // report bond + link state back to the Deck UI
  72. deckStatusTask();
  73. }