[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Guy Dupont\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "## WHAT?\n\nA mouse with a game of Pong running inside it's firmware, rendered on screen by moving the cursor really fast!\n\n## Demo\n\nhttps://github.com/user-attachments/assets/f5d2ad66-0942-43ed-bbec-080b894ee467\n\nNotes:\n- The game stops/starts when I _click_ the scroll wheel. Otherwise the mouse acts normally.\n- The score is rendered after a point is scored by showing the cursor somewhere between the left and right \"paddles\". The closer to a given paddle, the more that player is winning by (it's a ratio). If the cursor is in the middle, the score is tied.\n\n## Hardware Details\nMy code runs on an [Seeed Studio XIAO RP2040](https://wiki.seeedstudio.com/XIAO-RP2040/) dev board, which - using the magic of [TinyUSB](https://docs.tinyusb.org/en/latest/) - can act as both a custom USB device _and_ host.  I detached the USB connection from the inside of a cheap HP mouse and fed it into the XIAO's USB port (this goes to the computer). I then wire up the aux USB connection using the XIAO's power/GPIO pins, which goes back to the original mouse's PCB. This creates a USB interceptor that can be programmed to do whatever! Also see my [Mouse/Keyboard effects pedal](https://github.com/dupontgu/hidden_agenda_pedal) :P.\n\n<img src=\".docs/mouse_apart.jpg\" style=\"width: 700px;\"/>\n\n## How It Works\nMost modern computer mice using relative positioning - they report _changes_ in their movement. If you move the mouse slowly to the left, it might spit out a bunch of packets where the x component is just -1 (meaning the mouse moved 1 \"pixel\" to the left). However! It is possible to implement a USB mouse that uses _absolute_ positioning. It can send an exact position on your monitor (percentage X, percentage Y) that the cursor should move to instantly. This is commonly used for touchscreen drivers - you want the cursor to appear where the finger touches the screen.  This firmware emulates an absolute positioning mouse and quickly moves the mouse cursor between points of interest. It moves fast enough that the cursor (kinda) appears in all positions at once and gives the impression that it is in multiple places at once. I have implemented a simple game of Pong to run in the firmware, and set the cursor's points of interest to be the 2 paddles and the ball while the game is active. So the game runs completely inside the mouse!\n\n## Setup\n1. You'll need hardware! Any RP2040 based board wired up with a second USB port will do, but I recommend Adafruit's [Feather RP2040 with USB Host](https://www.adafruit.com/product/5723).\n2. Follow [Adafruit's guide](https://learn.adafruit.com/adafruit-feather-rp2040-with-usb-type-a-host/arduino-ide-setup) for getting set up with the Arduino IDE.\n3. Install the Adafruit TinyUSB Library and the Pico PIO USB Library (again, [guide](https://learn.adafruit.com/adafruit-feather-rp2040-with-usb-type-a-host/usb-host-device-info)).\n4. Ensure that your USB D+/D- pins are [set correctly](./usbh_helper.h).\n5. Build and run!\n"
  },
  {
    "path": "game_mouse.ino",
    "content": "/*********************************************************************\n Adafruit invests time and resources providing this open source code,\n please support Adafruit and open-source hardware by purchasing\n products from Adafruit!\n\n MIT license, check LICENSE for more information\n Copyright (c) 2019 Ha Thach for Adafruit Industries\n All text above, and the splash screen below must be included in\n any redistribution\n*********************************************************************/\n\n/**\nAdapted from Adafruit's TinyUSB sample remapper demo:\nhttps://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/DualRole/HID/hid_remapper\n\nby Guy Dupont, August 2024\n**/\n\n// USBHost is defined in usbh_helper.h\n#include \"usbh_helper.h\"\n#include \"pong.h\"\n\n// adapted from https://github.com/jonathanedgecombe/absmouse/blob/master/src/AbsMouse.cpp\nuint8_t const abs_mouse_desc[] = {\n  0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)\n  0x09, 0x02,        // Usage (Mouse)\n  0xA1, 0x01,        // Collection (Application)\n  0x09, 0x01,        //   Usage (Pointer)\n  0xA1, 0x00,        //   Collection (Physical)\n  0x85, 0x04,        //     Report ID (4)\n  0x05, 0x09,        //     Usage Page (Button)\n  0x19, 0x01,        //     Usage Minimum (0x01)\n  0x29, 0x03,        //     Usage Maximum (0x03)\n  0x15, 0x00,        //     Logical Minimum (0)\n  0x25, 0x01,        //     Logical Maximum (1)\n  0x95, 0x03,        //     Report Count (3)\n  0x75, 0x01,        //     Report Size (1)\n  0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)\n  0x95, 0x01,        //     Report Count (1)\n  0x75, 0x05,        //     Report Size (5)\n  0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)\n  0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)\n  0x09, 0x30,        //     Usage (X)\n  0x09, 0x31,        //     Usage (Y)\n  0x16, 0x00, 0x00,  //     Logical Minimum (0)\n  0x26, 0xFF, 0x7F,  //     Logical Maximum (32767)\n  0x36, 0x00, 0x00,  //     Physical Minimum (0)\n  0x46, 0xFF, 0x7F,  //     Physical Maximum (32767)\n  0x75, 0x10,        //     Report Size (16)\n  0x95, 0x02,        //     Report Count (2)\n  0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)\n  0xC0,              //   End Collection\n  0xC0               // End Collection\n};\n\nuint8_t const desc_hid_report[] = {\n  TUD_HID_REPORT_DESC_MOUSE()\n};\n\n// two HID devices, one for absolute mouse (the game) and one for normal mouse (passthrough)\nAdafruit_USBD_HID usb_hid_abs(abs_mouse_desc, sizeof(abs_mouse_desc), HID_ITF_PROTOCOL_NONE, 2, true);\nAdafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_MOUSE, 2, true);\nGame game;\n\nlong lastFrameTime = 0;\n\n// these numbers are virtual/relative and don't correspond to monitor size\nconst int SCREEN_WIDTH = 800;\nconst int SCREEN_HEIGHT = 600;\nconst int PADDLE_WIDTH = 10;\nconst int PADDLE_HEIGHT = 100;\nconst int HALF_PADDLE_HEIGHT = PADDLE_HEIGHT / 2;\nconst int BALL_SIZE = 10;\nconst float MAX_Y_VEL = 8.0;\n\nint8_t mouseVelocity;\nbool gameRunning = false;\n\nvoid setup() {\n  Serial.begin(115200);\n  usb_hid_abs.begin();\n  usb_hid.begin();\n  initGame(&game, 10.0, SCREEN_HEIGHT, SCREEN_WIDTH, BALL_SIZE, PADDLE_HEIGHT, PADDLE_WIDTH);\n}\n\n// renders the cursor on screen in a specific spot.\n// x and y should be floats between 0.0 and 1.0\n// (0, 0) -> top left of screen, (1, 1) -> bottom right\nvoid renderCursor(float x, float y) {\n  static uint8_t reportOut[5] = { 0, 0, 0, 0, 0 };\n  uint16_t adjX = (uint16_t)((x * 20000) + 6383);  // padding is (32767 - [max]) / 2\n  uint16_t adjY = (uint16_t)((y * 26000) + 3383);\n  reportOut[1] = adjX & 0xFF;\n  reportOut[2] = (adjX >> 8) & 0xFF;\n  reportOut[3] = adjY & 0xFF;\n  reportOut[4] = (adjY >> 8) & 0xFF;\n  if (usb_hid_abs.ready()) {\n    // report id 4 was hardcoded in descriptor above\n    usb_hid_abs.sendReport(4, &reportOut, 5);\n  }\n}\n\nvoid loop() {\n  if (gameRunning) {\n    long timeNow = millis();\n    if ((timeNow - lastFrameTime) >= 16) {\n      lastFrameTime = timeNow;\n      float leftPaddleVel = paddleAutoPilot(&game, &(game.lPaddle), 5.0, 2);\n      float rightPaddleVel = mouseVelocity;\n      mouseVelocity = 0;\n      tick(&game, leftPaddleVel, rightPaddleVel);\n    }\n\n    Frame frame = nextSubframe(&game);\n    renderCursor(frame.x, frame.y);\n  }\n\n  // ok to delay in this loop, different values produce different POV artifacts\n  delay(7);\n}\n\n//------------- Core1 -------------//\nvoid setup1() {\n  // configure pio-usb: defined in usbh_helper.h\n  rp2040_configure_pio_usb();\n\n  // run host stack on controller (rhport) 1\n  // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the\n  // host bit-banging processing works done in core1 to free up core0 for other works\n  USBHost.begin(1);\n}\n\nvoid loop1() {\n  USBHost.task();\n}\n\n//--------------------------------------------------------------------+\n// TinyUSB Host callbacks\n//--------------------------------------------------------------------+\nextern \"C\" {\n\n  // Invoked when device with hid interface is mounted\n  // Report descriptor is also available for use.\n  // tuh_hid_parse_report_descriptor() can be used to parse common/simple enough\n  // descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,\n  // it will be skipped therefore report_desc = NULL, desc_len = 0\n  void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {\n    (void)desc_report;\n    (void)desc_len;\n    uint16_t vid, pid;\n    tuh_vid_pid_get(dev_addr, &vid, &pid);\n\n    Serial.printf(\"HID device address = %d, instance = %d is mounted\\r\\n\", dev_addr, instance);\n    Serial.printf(\"VID = %04x, PID = %04x\\r\\n\", vid, pid);\n\n    uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);\n    if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) {\n      Serial.printf(\"HID Mouse\\r\\n\");\n      if (!tuh_hid_receive_report(dev_addr, instance)) {\n        Serial.printf(\"Error: cannot request to receive report\\r\\n\");\n      }\n    }\n  }\n\n  // Invoked when device with hid interface is un-mounted\n  void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {\n    Serial.printf(\"HID device address = %d, instance = %d is unmounted\\r\\n\", dev_addr, instance);\n  }\n\n  // Invoked when received report from device via interrupt endpoint\n  void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {\n    static uint8_t reportOut[5] = { 0, 0, 0, 0, 0 };\n    static bool scrollButtonDown;\n    // different mice might have different lengths here! My shitty HP mouse only reports [buttons, x, y]\n    if (len == 3) {\n      // scroll wheel click is 3rd bit, use that to start/stop the game\n      if (report[0] & 0b100) {\n        if (!scrollButtonDown) {\n          scrollButtonDown = true;\n          gameRunning = !gameRunning;\n        }\n      } else {\n        scrollButtonDown = false;\n      }\n\n      if (!gameRunning) {\n        // if game's not running, copy mouse report to passthrough\n        reportOut[0] = report[0];\n        reportOut[1] = report[1];\n        reportOut[2] = report[2];\n        usb_hid.sendReport(0, reportOut, 5);\n      } else {\n        // if game is happening, copy latest Y velocity to be used by game logic\n        mouseVelocity = (int8_t)report[2];\n      }\n    }\n    // continue to request to receive report\n    if (!tuh_hid_receive_report(dev_addr, instance)) {\n      Serial.printf(\"Error: cannot request to receive report\\r\\n\");\n    }\n  }\n}\n"
  },
  {
    "path": "pong.h",
    "content": "#define RENDER_SCORE_FRAMES_MAX 80\n\ntypedef struct\n{\n  float x, y;\n  float velX, velY;\n} Ball;\n\ntypedef struct\n{\n  float x, y;\n  float velY;\n  int score;\n} Paddle;\n\ntypedef struct\n{\n  Ball ball;\n  Paddle lPaddle;\n  Paddle rPaddle;\n  float maxYVel;\n  int screenHeight;\n  int screenWidth;\n  int ballSize;\n  int paddleHeight;\n  int paddleWidth;\n  int halfPaddleHeight;\n  int renderScoreCounter;\n  uint8_t subFrame;\n} Game;\n\ntypedef struct\n{\n  float x, y;\n} Frame;\n\nvoid initGame(Game *gameHolder, float maxBallVelocity, float screenHeight, float screenWidth, int ballSize, int paddleHeight, int paddleWidth) {\n  Paddle left = { 10, (screenHeight - paddleHeight) / 2, 0, 0 };\n  gameHolder->lPaddle = left;\n  Paddle right = { screenWidth - 20, (screenHeight - paddleHeight) / 2, 0, 0 };\n  gameHolder->rPaddle = right;\n  // TODO - pass in ball x velocity as argument\n  Ball ball = { .x = screenWidth / 2, .y = screenHeight / 2, .velX = -5, .velY = 1.6 };\n  gameHolder->ball = ball;\n  gameHolder->maxYVel = maxBallVelocity;\n  gameHolder->screenHeight = screenHeight;\n  gameHolder->screenWidth = screenWidth;\n  gameHolder->ballSize = ballSize;\n  gameHolder->paddleHeight = paddleHeight;\n  gameHolder->paddleWidth = paddleWidth;\n  gameHolder->halfPaddleHeight = paddleHeight / 2;\n  gameHolder->renderScoreCounter = 0;\n}\n\nint distFromPaddleCenter(Game *game, Ball *ball, Paddle *paddle) {\n  return ball->y - (paddle->y + game->halfPaddleHeight);\n}\n\nfloat yVelocityFromPaddleIntersect(Game *game, int distFromPaddleCenter) {\n  return ((float)distFromPaddleCenter / (float)game->halfPaddleHeight) * game->maxYVel;\n}\n\nvoid movePaddle(Game *game, Paddle *paddle) {\n  paddle->y += paddle->velY;\n\n  // Keep paddle within screen bounds\n  if (paddle->y < 0) {\n    paddle->y = 0;\n  } else if (paddle->y > game->screenHeight - game->paddleHeight) {\n    paddle->y = game->screenHeight - game->paddleHeight;\n  }\n}\n\nvoid moveBall(Game *game) {\n  Ball *ball = &(game->ball);\n  Paddle *leftPaddle = &(game->lPaddle);\n  Paddle *rightPaddle = &(game->rPaddle);\n  ball->x += ball->velX;\n  ball->y += ball->velY;\n\n  // Bounce off top and bottom walls\n  if (ball->y <= 0 || ball->y >= game->screenHeight - game->ballSize) {\n    ball->velY = -ball->velY;\n  }\n\n  int leftPaddleYDist = distFromPaddleCenter(game, ball, leftPaddle);\n  int rightPaddleYDist = distFromPaddleCenter(game, ball, rightPaddle);\n\n  // Bounce off paddles\n  if (ball->x <= leftPaddle->x + game->paddleWidth && ball->y + game->ballSize >= leftPaddle->y && abs(leftPaddleYDist) < game->halfPaddleHeight) {\n    ball->velX = -ball->velX;\n    ball->x = leftPaddle->x + game->paddleWidth;\n    ball->velY = yVelocityFromPaddleIntersect(game, leftPaddleYDist);\n  } else if (ball->x + game->ballSize >= rightPaddle->x && ball->y + game->ballSize >= rightPaddle->y && abs(rightPaddleYDist) < game->halfPaddleHeight) {\n    ball->velX = -ball->velX;\n    ball->x = rightPaddle->x - game->ballSize;\n    ball->velY = yVelocityFromPaddleIntersect(game, rightPaddleYDist);\n  }\n\n  bool pointScored = false;\n  // Reset ball if it goes past the paddles\n  if (ball->x < 0) {\n    pointScored = true;\n    game->rPaddle.score++;\n    Serial.println(\"r score\");\n  } else if (ball->x > game->screenWidth) {\n    pointScored = true;\n    game->lPaddle.score++;\n    Serial.println(\"l score\");\n  }\n\n  if (pointScored) {\n    game->renderScoreCounter = RENDER_SCORE_FRAMES_MAX;\n    ball->x = game->screenWidth / 2;\n    ball->y = game->screenHeight / 2;\n    ball->velX = -ball->velX;\n    ball->velY = 1;\n  }\n}\n\n// quick an dirty AI, can be used with either/both paddles\n// call with the desired paddle, then pass the returned velocity value back in with the next \"tick\"\nfloat paddleAutoPilot(Game *game, Paddle *paddle, float maxVelocity, int randomSeed) {\n  float guessVelocity = 0.0;\n  int scoreSeed = game->lPaddle.score + game->rPaddle.score + randomSeed;\n  bool targetOffsetNegative = (scoreSeed % 2) == 1;\n  int targetOffset = ((scoreSeed + 1) * 7) % game->halfPaddleHeight;\n  if (targetOffsetNegative) {\n    targetOffset = -targetOffset;\n  }\n  float yDistFromBall = game->ball.y - (paddle->y + game->halfPaddleHeight + targetOffset);\n  if (yDistFromBall > maxVelocity) {\n    guessVelocity = maxVelocity;\n  } else if (yDistFromBall < -maxVelocity) {\n    guessVelocity = -maxVelocity;\n  }\n  return guessVelocity;\n}\n\nvoid tick(Game *game, float leftPaddleVel, float rightPaddleVel) {\n  if (game->renderScoreCounter > 0) {\n    game->renderScoreCounter -= 1;\n  } else {\n    game->lPaddle.velY = leftPaddleVel;\n    game->rPaddle.velY = rightPaddleVel;\n    movePaddle(game, &(game->lPaddle));\n    movePaddle(game, &(game->rPaddle));\n    moveBall(game);\n  }\n}\n\nFrame nextSubframe(Game *game) {\n  Frame frame;\n  if (game->renderScoreCounter > 0) {\n    // score is rendered by showing the mouse cursor in three spots\n    // outer spots show the bounds, inner spot indicates which player is winning\n    // the closer the inner spot is to either edge indicates how much that player is up by\n    // when the game is tied, the inner spot should sit right in the middle\n    float pointRatio = (float)(game->rPaddle.score + 1) / (float)(game->lPaddle.score + 1);\n    if (pointRatio < 1.0) {\n      pointRatio /= 2.0;\n    } else {\n      pointRatio = 1.0 - ((1.0 / pointRatio) / 2.0);\n    }\n    switch (game->subFrame++) {\n      case 0:\n        frame.x = 0;\n        frame.y = 0.5;\n        break;\n      case 1:\n        frame.x = pointRatio;\n        frame.y = 0.5;\n        break;\n      case 2:\n        frame.x = 1;\n        frame.y = 0.5;\n      default:\n        game->subFrame = 0;\n    }\n  } else {\n    switch (game->subFrame++) {\n      case 0:\n        frame.x = game->lPaddle.x / (float)game->screenWidth;\n        frame.y = (game->lPaddle.y + game->halfPaddleHeight) / (float)game->screenHeight;\n        break;\n      case 1:\n        frame.x = game->ball.x / (float)game->screenWidth;\n        frame.y = game->ball.y / (float)game->screenHeight;\n        break;\n      case 2:\n        frame.x = game->rPaddle.x / (float)game->screenWidth;\n        frame.y = (game->rPaddle.y + game->halfPaddleHeight) / (float)game->screenHeight;\n      default:\n        game->subFrame = 0;\n    }\n  }\n  return frame;\n}\n"
  },
  {
    "path": "usbh_helper.h",
    "content": "/*********************************************************************\n Adafruit invests time and resources providing this open source code,\n please support Adafruit and open-source hardware by purchasing\n products from Adafruit!\n\n MIT license, check LICENSE for more information\n Copyright (c) 2019 Ha Thach for Adafruit Industries\n All text above, and the splash screen below must be included in\n any redistribution\n*********************************************************************/\n\n/**\nAdapted from Adafruit's TinyUSB sample remapper demo:\nhttps://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/DualRole/HID/hid_remapper\n\nby Guy Dupont, August 2024\n**/\n\n#ifndef USBH_HELPER_H\n#define USBH_HELPER_H\n\n#ifdef ARDUINO_ARCH_RP2040\n  // pio-usb is required for rp2040 host\n  #include \"pio_usb.h\"\n\n  // Pin D+ for host, D- = D+ + 1\n  #ifndef PIN_USB_HOST_DP\n  // THIS IS SPECIFIC TO MY BOARD, use 16 for D+ with usb host feather\n  #define PIN_USB_HOST_DP  3\n  #endif\n\n  // Pin for enabling Host VBUS. comment out if not used\n  #ifndef PIN_5V_EN\n  #define PIN_5V_EN        18\n  #endif\n\n  #ifndef PIN_5V_EN_STATE\n  #define PIN_5V_EN_STATE  1\n  #endif\n#endif // ARDUINO_ARCH_RP2040\n\n#include \"Adafruit_TinyUSB.h\"\n\n#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421\n  // USB Host using MAX3421E: SPI, CS, INT\n  #include \"SPI.h\"\n\n  #if defined(ARDUINO_METRO_ESP32S2)\n    Adafruit_USBH_Host USBHost(&SPI, 15, 14);\n  #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2)\n    Adafruit_USBH_Host USBHost(&SPI, 33, 15);\n  #else\n    // Default CS and INT are pin 10, 9\n    Adafruit_USBH_Host USBHost(&SPI, 10, 9);\n  #endif\n#else\n  // Native USB Host such as rp2040\n  Adafruit_USBH_Host USBHost;\n#endif\n\n//--------------------------------------------------------------------+\n// Helper Functions\n//--------------------------------------------------------------------+\n\n#ifdef ARDUINO_ARCH_RP2040\nstatic void rp2040_configure_pio_usb(void) {\n  //while ( !Serial ) delay(10);   // wait for native usb\n  Serial.println(\"Core1 setup to run TinyUSB host with pio-usb\");\n\n  // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB\n  uint32_t cpu_hz = clock_get_hz(clk_sys);\n  if (cpu_hz != 120000000UL && cpu_hz != 240000000UL) {\n    while (!Serial) {\n      delay(10);   // wait for native usb\n    }\n    Serial.printf(\"Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\\r\\n\", cpu_hz);\n    Serial.printf(\"Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \\r\\n\");\n    while (1) {\n      delay(1);\n    }\n  }\n\n#ifdef PIN_5V_EN\n  pinMode(PIN_5V_EN, OUTPUT);\n  digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE);\n#endif\n\n  pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;\n  pio_cfg.pin_dp = PIN_USB_HOST_DP;\n\n#if defined(ARDUINO_RASPBERRY_PI_PICO_W)\n  // For pico-w, PIO is also used to communicate with cyw43\n  // Therefore we need to alternate the pio-usb configuration\n  // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46\n  pio_cfg.sm_tx      = 3;\n  pio_cfg.sm_rx      = 2;\n  pio_cfg.sm_eop     = 3;\n  pio_cfg.pio_rx_num = 0;\n  pio_cfg.pio_tx_num = 1;\n  pio_cfg.tx_ch      = 9;\n#endif\n\n  USBHost.configure_pio_usb(1, &pio_cfg);\n}\n#endif\n\n#endif\n"
  }
]