The Feather RP2040 USB Host and Metro RP2350 devices both support USB Host and can be used with this game controller.
Wiring Feather RP2040 USB Host
Connecting to the Feather RP2040 USB Host requires plugging in a USB C cable to the Feather's Host port and connecting the other end to a USB Hub breakout such as the CH334F.
Wiring Metro RP2350
Connecting to the Metro RP2350 USB Host port requires soldering pins to the broken out USB Host connections as shown in this guide page. Make the following connections between the Metro USB Host pins and CH334F host connection opposite the USB C connector.
- GND to GND with Black or Blue
- D+ to D+ with Green
- D- to D- with White
- 5V to 5V with Red
Note that the data pins are swapped on the Metro compared to the CH334F breakout. On the Metro D- is next to 5V, whereas on the CH334F D- is next to GND.
Be sure to connect D- on the breakout to D- on the Metro, and D+ on the breakout to D+ on the Metro.
USB Host is under active development. At the time of writing this, the SNES-like controller only works when connected through a USB hub such as the CH334F.
Install the Libraries
You can install the libraries for this project using the Library Manager in the Arduino IDE.
Click the Library Manager icon, search for Adafruit TinyUSB Arduino, and select the Adafruit TinyUSB Library.
If asked about dependencies click "Install All".
Then install the Pico PIO USB library. Click the Library Manager icon menu item again, search for PIO USB, and select the Pico PIO USB library by sekigon-gonnoc.
Code Prep
The code consists of a main .ino program file and two header files. The header files store configuration for USB host on the RP2040 and HID device reports for your gamepad. You'll need all three of these files to properly compile and run the project. These files are available in the .ZIP folder below or on GitHub.
snes_gamepad_simpletest.ino:
// SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*********************************************************************
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
MIT license, check LICENSE for more information
Copyright (c) 2019 Ha Thach for Adafruit Industries
All text above, and the splash screen below must be included in
any redistribution
*********************************************************************/
/* This example demonstrates use of usb host with a SNES-like game controller
* - Host depends on MCU:
* - rp2040: bit-banging 2 GPIOs with Pico-PIO-USB library (roothub port1)
*
* Requirements:
* - For rp2040:
* - Pico-PIO-USB library
* - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1
* - Provide VBus (5v) and GND for peripheral
* - CPU Speed must be either 120 or 240 MHz. Selected via "Menu -> CPU Speed"
*/
// USBHost is defined in usbh_helper.h
#include "usbh_helper.h"
#include "tusb.h"
#include "Adafruit_TinyUSB.h"
#include "gamepad_reports.h"
// HID report descriptor using TinyUSB's template
// Single Report (no ID) descriptor
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_GAMEPAD()
};
// USB HID object
Adafruit_USBD_HID usb_hid;
// Report payload defined in src/class/hid/hid.h
// - For Gamepad Button Bit Mask see hid_gamepad_button_bm_t
// - For Gamepad Hat Bit Mask see hid_gamepad_hat_t
hid_gamepad_report_t gp;
bool printed_blank = false;
void setup() {
Serial.begin(115200);
// configure pio-usb: defined in usbh_helper.h
rp2040_configure_pio_usb();
// run host stack on controller (rhport) 1
USBHost.begin(1);
delay(3000);
Serial.print("USB D+ Pin:");
Serial.println(PIN_USB_HOST_DP);
Serial.print("USB 5V Pin:");
Serial.println(PIN_5V_EN);
}
void loop() {
USBHost.task();
Serial.flush();
}
//--------------------------------------------------------------------+
// HID Host Callback Functions
//--------------------------------------------------------------------+
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
{
Serial.printf("HID device mounted (address %d, instance %d)\n", dev_addr, instance);
// Start receiving HID reports
if (!tuh_hid_receive_report(dev_addr, instance))
{
Serial.printf("Error: cannot request to receive report\n");
}
}
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
{
Serial.printf("HID device unmounted (address %d, instance %d)\n", dev_addr, instance);
}
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) {
if (report[BYTE_DPAD_LEFT_RIGHT] != DPAD_NEUTRAL ||
report[BYTE_DPAD_UP_DOWN] != DPAD_NEUTRAL ||
report[BYTE_ABXY_BUTTONS] != BUTTON_NEUTRAL ||
report[BYTE_OTHER_BUTTONS] != BUTTON_MISC_NEUTRAL){
printed_blank = false;
//debug print report data
// Serial.print("Report data: ");
// for (int i = 0; i < len; i++) {
// Serial.print(report[i], HEX);
// Serial.print(" ");
// }
// Serial.println();
if (report[BYTE_DPAD_LEFT_RIGHT] == DPAD_LEFT){
Serial.print("Left ");
}else if (report[BYTE_DPAD_LEFT_RIGHT] == DPAD_RIGHT){
Serial.print("Right ");
}
if (report[BYTE_DPAD_UP_DOWN] == DPAD_UP){
Serial.print("Up ");
}else if (report[BYTE_DPAD_UP_DOWN] == DPAD_DOWN){
Serial.print("Down ");
}
if ((report[BYTE_ABXY_BUTTONS] & BUTTON_A) == BUTTON_A){
Serial.print("A ");
}
if ((report[BYTE_ABXY_BUTTONS] & BUTTON_B) == BUTTON_B){
Serial.print("B ");
}
if ((report[BYTE_ABXY_BUTTONS] & BUTTON_X) == BUTTON_X){
Serial.print("X ");
}
if ((report[BYTE_ABXY_BUTTONS] & BUTTON_Y) == BUTTON_Y){
Serial.print("Y ");
}
if ((report[BYTE_OTHER_BUTTONS] & BUTTON_LEFT_SHOULDER) == BUTTON_LEFT_SHOULDER){
Serial.print("Left Shoulder ");
}
if ((report[BYTE_OTHER_BUTTONS] & BUTTON_RIGHT_SHOULDER) == BUTTON_RIGHT_SHOULDER){
Serial.print("Right Shoulder ");
}
if ((report[BYTE_OTHER_BUTTONS] & BUTTON_START) == BUTTON_START){
Serial.print("Start ");
}
if ((report[BYTE_OTHER_BUTTONS] & BUTTON_SELECT) == BUTTON_SELECT){
Serial.print("Select ");
}
Serial.println();
} else {
if (! printed_blank){
Serial.println("NEUTRAL");
printed_blank = true;
}
}
// Continue to receive the next report
if (!tuh_hid_receive_report(dev_addr, instance)) {
Serial.println("Error: cannot request to receive report");
}
}
gamepad_reports.h:
// SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries // // SPDX-License-Identifier: MIT // HID reports for USB SNES-like controller // Byte indices for the gamepad report #define BYTE_DPAD_LEFT_RIGHT 0 // D-Pad left and right #define BYTE_DPAD_UP_DOWN 1 // D-Pad up and down // bytes 2,3,4 unused #define BYTE_ABXY_BUTTONS 5 // A, B, X, Y #define BYTE_OTHER_BUTTONS 6 // Shoulders, start, and select #define DPAD_NEUTRAL 0x7F // D-Pad directions #define DPAD_UP 0x00 #define DPAD_RIGHT 0xFF #define DPAD_DOWN 0xFF #define DPAD_LEFT 0x00 // Face buttons (Byte[5]) #define BUTTON_NEUTRAL 0x0F #define BUTTON_X 0x1F #define BUTTON_A 0x2F #define BUTTON_B 0x4F #define BUTTON_Y 0x8F // Miscellaneous buttons (Byte[6]) #define BUTTON_MISC_NEUTRAL 0x00 #define BUTTON_LEFT_SHOULDER 0x01 #define BUTTON_RIGHT_SHOULDER 0x02 #define BUTTON_SELECT 0x10 #define BUTTON_START 0x20
Upload and Test
Before uploading, you'll need to update some settings in the Boards menu. Select the appropriate board that you are using, either Adafruit Feather RP2040 USB Host, or Adafruit Metro RP2350. Under USB Stack select Adafruit TinyUSB. Then, upload the sketch to your board. You can use the Serial Monitor in the Arduino IDE for debugging any errors.
Serial Output
The code will connect to the SNES-like controller and read data coming from it. When it detects that buttons are pressed it will print which ones are down into the serial output.
The Controller is Not Acting Right
The program presented works for the generic SNES controller sold by Adafruit. Controllers from other sources may have key mappings a bit different from the Adafruit controller. This will help you adjust the code to your controller.
In gamepad_reports.h are the following constants:
// HID reports for USB SNES-like controller // Byte indices for the gamepad report #define BYTE_DPAD_LEFT_RIGHT 0 // D-Pad left and right #define BYTE_DPAD_UP_DOWN 1 // D-Pad up and down // bytes 2,3,4 unused #define BYTE_ABXY_BUTTONS 5 // A, B, X, Y #define BYTE_OTHER_BUTTONS 6 // Shoulders, start, and select #define DPAD_NEUTRAL 0x7F // D-Pad directions #define DPAD_UP 0x00 #define DPAD_RIGHT 0xFF #define DPAD_DOWN 0xFF #define DPAD_LEFT 0x00 // Face buttons (Byte[5]) #define BUTTON_NEUTRAL 0x0F #define BUTTON_X 0x1F #define BUTTON_A 0x2F #define BUTTON_B 0x4F #define BUTTON_Y 0x8F // Miscellaneous buttons (Byte[6]) #define BUTTON_MISC_NEUTRAL 0x00 #define BUTTON_LEFT_SHOULDER 0x01 #define BUTTON_RIGHT_SHOULDER 0x02 #define BUTTON_SELECT 0x10 #define BUTTON_START 0x20
These are the values obtained with the Adafruit controller. You can change the numbers to the ones output by your controller. For example a customer stated they changed BTN_DPAD_UPDOWN_INDEX to 4 from 1 and BTN_DPAD_RIGHTLEFT_INDEX to 3 from 0.
How do you find the right numbers?
- Trial and error based on the output of the program (shown above)
- You can use a HID key mapper website. https://joypad.ai/ is one such site (not tried)
Change the appropriate value(s) and recompile the sketch.
If you are positive you bought your controller from Adafruit and it's not mapped right, you can post to the Adafruit forums for assistance.
Page last edited October 14, 2025
Text editor powered by tinymce.