diff --git a/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/.feather_rp2040_usbhost_tinyusb.test.only b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/.feather_rp2040_usbhost_tinyusb.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad.ino b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad.ino new file mode 100644 index 000000000..974481121 --- /dev/null +++ b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad.ino @@ -0,0 +1,291 @@ +// SPDX-FileCopyrightText: 2024 Liz Clark 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 both device and host, where + * - Device runs on native USB controller (roothub port0) + * - 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 combo_active = false; + +void setup() { + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + Serial.begin(115200); + // Setup HID + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } +} + +#if defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------// + +//------------- Core0 -------------// +void loop() { +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); + Serial.flush(); + if (combo_active) { + turbo_button(); + } +} +#endif + +//--------------------------------------------------------------------+ +// 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 turbo_button() { + if (combo_active) { + while (!usb_hid.ready()) { + yield(); + } + Serial.println("A"); + gp.buttons = GAMEPAD_BUTTON_A; + usb_hid.sendReport(0, &gp, sizeof(gp)); + Serial.println("off"); + delay(2); + gp.buttons = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2); + } +} + +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) { + // Known report when the combo is pressed + //uint8_t combo_report[] = { 0x80, 0x7F, 0x80, 0x7F, 0x28, 0x03, 0x00, 0xFF }; + // Check if the incoming report matches the combo report + bool combo_detected = ((report[4] == combo_report[4]) && (report[5] == combo_report[5]));// len == sizeof(combo_report)) && (memcmp(report, combo_report, sizeof(combo_report)) == 0); + + // Manage the combo state and print messages + if (combo_detected && !combo_active) { + combo_active = true; + Serial.println("combo!"); + } else if (combo_detected && combo_active) { + combo_active = false; + Serial.println("combo released!"); + } + if (!(combo_active)) { + if (!(report[BYTE_LEFT_STICK_X] == LEFT_STICK_X_NEUTRAL)) { + int16_t leftStickX = report[BYTE_LEFT_STICK_X]; + Serial.print("left stick X: "); + Serial.println(leftStickX); + int16_t new_leftStickX = map(leftStickX, 0, 255, -127, 127); + gp.x = new_leftStickX; + } else { + gp.x = 0; + } + if (!(report[BYTE_LEFT_STICK_Y] == LEFT_STICK_Y_NEUTRAL)) { + int16_t leftStickY = report[BYTE_LEFT_STICK_Y]; + Serial.print("left stick Y: "); + Serial.println(leftStickY); + int16_t new_leftStickY = map(leftStickY, 0, 255, -127, 127); + gp.y = new_leftStickY; + } else { + gp.y = 0; + } + if (!(report[BYTE_RIGHT_STICK_X] == RIGHT_STICK_X_NEUTRAL)) { + int8_t rightStickX = report[BYTE_RIGHT_STICK_X]; + Serial.print("right stick X: "); + Serial.println(rightStickX); + int16_t new_rightStickX = map(rightStickX, 0, 255, 127, -127); + gp.z = new_rightStickX; + } else { + gp.z = 0; + } + if (!(report[BYTE_RIGHT_STICK_Y] == RIGHT_STICK_Y_NEUTRAL)) { + int8_t rightStickY = report[BYTE_RIGHT_STICK_Y]; + Serial.print("right stick Y: "); + Serial.println(rightStickY); + int16_t new_rightStickY = map(rightStickY, 0, 255, -127, 127); + gp.rz = new_rightStickY; + } else { + gp.rz = 0; + } + if (!(report[BYTE_DPAD_BUTTONS] == DPAD_NEUTRAL)) { + // D-Pad is active + uint8_t buttonsSelect = report[BYTE_DPAD_BUTTONS]; + switch (buttonsSelect) { + case BUTTON_X: + Serial.println("x"); + gp.buttons = GAMEPAD_BUTTON_X; + break; + case BUTTON_A: + Serial.println("a"); + gp.buttons = GAMEPAD_BUTTON_A; + break; + case BUTTON_B: + Serial.println("b"); + gp.buttons = GAMEPAD_BUTTON_B; + break; + case BUTTON_Y: + Serial.println("y"); + gp.buttons = GAMEPAD_BUTTON_Y; + break; + } + } else { + gp.hat = 0; + gp.buttons = 0; + } + if (!(report[BYTE_DPAD_BUTTONS] == DPAD_NEUTRAL)) { + // D-Pad is active + uint8_t dpadDirection = report[BYTE_DPAD_BUTTONS]; + switch (dpadDirection) { + case DPAD_UP: + Serial.println("up"); + gp.hat = 1; // GAMEPAD_HAT_UP; + break; + case DPAD_UP_RIGHT: + Serial.println("up/right"); + gp.hat = 2; + break; + case DPAD_RIGHT: + Serial.println("right"); + gp.hat = 3; + break; + case DPAD_DOWN_RIGHT: + Serial.println("down/right"); + gp.hat = 4; + break; + case DPAD_DOWN: + Serial.println("down"); + gp.hat = 5; + break; + case DPAD_DOWN_LEFT: + Serial.println("down/left"); + gp.hat = 6; + break; + case DPAD_LEFT: + Serial.println("left"); + gp.hat = 7; + break; + case DPAD_UP_LEFT: + Serial.println("up/left"); + gp.hat = 8; + break; + } + } else { + gp.hat = 0; + } + if (!(report[BYTE_MISC_BUTTONS] == MISC_NEUTRAL)) { + // misc are active + uint8_t miscDirection = report[BYTE_MISC_BUTTONS]; + switch (miscDirection) { + case BUTTON_LEFT_PADDLE: + Serial.println("left paddle"); + gp.buttons = GAMEPAD_BUTTON_TL; + break; + case BUTTON_RIGHT_PADDLE: + Serial.println("right paddle"); + gp.buttons = GAMEPAD_BUTTON_TR; + break; + case BUTTON_LEFT_TRIGGER: + Serial.println("left trigger"); + gp.buttons = GAMEPAD_BUTTON_TL2; + break; + case BUTTON_RIGHT_TRIGGER: + Serial.println("right trigger"); + gp.buttons = GAMEPAD_BUTTON_TR2; + break; + case BUTTON_BACK: + Serial.println("back"); + gp.buttons = GAMEPAD_BUTTON_SELECT; + break; + case BUTTON_START: + Serial.println("start"); + gp.buttons = GAMEPAD_BUTTON_START; + break; + } + } + } else { + gp.buttons = GAMEPAD_BUTTON_A; + } + while (!usb_hid.ready()) { + yield(); + } + usb_hid.sendReport(0, &gp, sizeof(gp)); + // Continue to receive the next report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.println("Error: cannot request to receive report"); + } +} diff --git a/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/gamepad_reports.h b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/gamepad_reports.h new file mode 100644 index 000000000..167aefde6 --- /dev/null +++ b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/gamepad_reports.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// HID reports for Logitech Gamepad F310 +// Update defines and combo_report for your gamepad and/or combo! + +uint8_t combo_report[] = { 0x80, 0x7F, 0x80, 0x7F, 0x28, 0x03, 0x00, 0xFF }; + +// Byte indices for the gamepad report +#define BYTE_LEFT_STICK_X 0 // Left analog stick X-axis +#define BYTE_LEFT_STICK_Y 1 // Left analog stick Y-axis +#define BYTE_RIGHT_STICK_X 2 // Right analog stick X-axis +#define BYTE_RIGHT_STICK_Y 3 // Right analog stick Y-axis +#define BYTE_DPAD_BUTTONS 4 // D-Pad and face buttons +#define BYTE_MISC_BUTTONS 5 // Miscellaneous buttons (triggers, paddles, start, back) +#define BYTE_UNUSED 6 // Unused +#define BYTE_STATUS 7 // Status byte (usually constant) + +// Button masks for Byte[4] (DPAD and face buttons) +#define DPAD_MASK 0x07 // Bits 0-2 for D-Pad direction +#define DPAD_NEUTRAL 0x08 // Bit 3 set when D-Pad is neutral + +// D-Pad directions (use when DPAD_NEUTRAL is not set) +#define DPAD_UP 0x00 // 0000 +#define DPAD_UP_RIGHT 0x01 // 0001 +#define DPAD_RIGHT 0x02 // 0010 +#define DPAD_DOWN_RIGHT 0x03 // 0011 +#define DPAD_DOWN 0x04 // 0100 +#define DPAD_DOWN_LEFT 0x05 // 0101 +#define DPAD_LEFT 0x06 // 0110 +#define DPAD_UP_LEFT 0x07 // 0111 + +// Face buttons (Byte[4] bits 4-7) +#define BUTTON_X 0x18 +#define BUTTON_A 0x28 +#define BUTTON_B 0x48 +#define BUTTON_Y 0x88 + +// Button masks for Byte[5] (MISC buttons) +#define MISC_NEUTRAL 0x00 + +// Miscellaneous buttons (Byte[5]) +#define BUTTON_LEFT_PADDLE 0x01 +#define BUTTON_RIGHT_PADDLE 0x02 +#define BUTTON_LEFT_TRIGGER 0x04 +#define BUTTON_RIGHT_TRIGGER 0x08 +#define BUTTON_BACK 0x10 +#define BUTTON_START 0x20 + +#define LEFT_STICK_X_NEUTRAL 0x80 +#define LEFT_STICK_Y_NEUTRAL 0x7F +#define RIGHT_STICK_X_NEUTRAL 0x80 +#define RIGHT_STICK_Y_NEUTRAL 0x7F diff --git a/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/usbh_helper.h b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/usbh_helper.h new file mode 100644 index 000000000..107f5c09f --- /dev/null +++ b/USB_Host_Turbo_Button_Gamepad/Arduino_USB_Host_Turbo_Button_Gamepad/usbh_helper.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2024 Ha Thach 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 +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz != 120000000UL && cpu_hz != 240000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/.feather_rp2040_usbhost_tinyusb.test.only b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/.feather_rp2040_usbhost_tinyusb.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report-adafruit_feather_usb_host.uf2 b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report-adafruit_feather_usb_host.uf2 new file mode 100644 index 000000000..746c92aa3 Binary files /dev/null and b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report-adafruit_feather_usb_host.uf2 differ diff --git a/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report.ino b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report.ino new file mode 100644 index 000000000..977ce97ce --- /dev/null +++ b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/gamepad_device_report.ino @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: 2024 Liz Clark 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 both device and host, where + * - Device runs on native USB controller (roothub port0) + * - Host depends on MCU: + * - rp2040: bit-banging 2 GPIOs with Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * 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" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Host example will get device descriptors of attached devices and print it out via + * device CDC (Serial) as follows: + * Device 1: ID 046d:c52f + Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 0200 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x046d + idProduct 0xc52f + bcdDevice 2200 + iManufacturer 1 Logitech + iProduct 2 USB Receiver + iSerialNumber 0 + bNumConfigurations 1 + * + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" +#include "tusb.h" + +// Language ID: English +#define LANGUAGE_ID 0x0409 + +typedef struct { + tusb_desc_device_t desc_device; + uint16_t manufacturer[32]; + uint16_t product[48]; + uint16_t serial[16]; + bool mounted; +} dev_info_t; + +// CFG_TUH_DEVICE_MAX is defined by tusb_config header +dev_info_t dev_info[CFG_TUH_DEVICE_MAX] = { 0 }; + +void setup() { + Serial.begin(115200); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + + Serial.println("TinyUSB Dual Device Info Example with HID Report"); +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------// + +//------------- Core0 -------------// +void loop() { +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); + Serial.flush(); +} +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +void print_device_descriptor(tuh_xfer_t *xfer); + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len); + +void print_lsusb(void) { + bool no_device = true; + for (uint8_t daddr = 1; daddr < CFG_TUH_DEVICE_MAX + 1; daddr++) { + // TODO can use tuh_mounted(daddr), but tinyusb has a bug + // use local connected flag instead + dev_info_t *dev = &dev_info[daddr - 1]; + if (dev->mounted) { + Serial.printf("Device %u: ID %04x:%04x %s %s\r\n", daddr, + dev->desc_device.idVendor, dev->desc_device.idProduct, + (char *) dev->manufacturer, (char *) dev->product); + + no_device = false; + } + } + + if (no_device) { + Serial.println("No device connected (except hub)"); + } +} + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + Serial.printf("Device attached, address = %d\r\n", daddr); + + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = true; + + // Get Device Descriptor + tuh_descriptor_get_device(daddr, &dev->desc_device, 18, print_device_descriptor, 0); +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + Serial.printf("Device removed, address = %d\r\n", daddr); + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = false; + + // print device summary + print_lsusb(); +} + +void print_device_descriptor(tuh_xfer_t *xfer) { + if (XFER_RESULT_SUCCESS != xfer->result) { + Serial.printf("Failed to get device descriptor\r\n"); + return; + } + + uint8_t const daddr = xfer->daddr; + dev_info_t *dev = &dev_info[daddr - 1]; + tusb_desc_device_t *desc = &dev->desc_device; + + Serial.printf("Device %u: ID %04x:%04x\r\n", daddr, desc->idVendor, desc->idProduct); + Serial.printf("Device Descriptor:\r\n"); + Serial.printf(" bLength %u\r\n" , desc->bLength); + Serial.printf(" bDescriptorType %u\r\n" , desc->bDescriptorType); + Serial.printf(" bcdUSB %04x\r\n" , desc->bcdUSB); + Serial.printf(" bDeviceClass %u\r\n" , desc->bDeviceClass); + Serial.printf(" bDeviceSubClass %u\r\n" , desc->bDeviceSubClass); + Serial.printf(" bDeviceProtocol %u\r\n" , desc->bDeviceProtocol); + Serial.printf(" bMaxPacketSize0 %u\r\n" , desc->bMaxPacketSize0); + Serial.printf(" idVendor 0x%04x\r\n" , desc->idVendor); + Serial.printf(" idProduct 0x%04x\r\n" , desc->idProduct); + Serial.printf(" bcdDevice %04x\r\n" , desc->bcdDevice); + + // Get String descriptor using Sync API + Serial.printf(" iManufacturer %u ", desc->iManufacturer); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, dev->manufacturer, sizeof(dev->manufacturer))) { + utf16_to_utf8(dev->manufacturer, sizeof(dev->manufacturer)); + Serial.printf((char *) dev->manufacturer); + } + Serial.printf("\r\n"); + + Serial.printf(" iProduct %u ", desc->iProduct); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, dev->product, sizeof(dev->product))) { + utf16_to_utf8(dev->product, sizeof(dev->product)); + Serial.printf((char *) dev->product); + } + Serial.printf("\r\n"); + + Serial.printf(" iSerialNumber %u ", desc->iSerialNumber); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, dev->serial, sizeof(dev->serial))) { + utf16_to_utf8(dev->serial, sizeof(dev->serial)); + Serial.printf((char *) dev->serial); + } + Serial.printf("\r\n"); + + Serial.printf(" bNumConfigurations %u\r\n", desc->bNumConfigurations); + + // print device summary + print_lsusb(); +} + +//--------------------------------------------------------------------+ +// String Descriptor Helper +//--------------------------------------------------------------------+ + +static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) { + // TODO: Check for runover. + (void) utf8_len; + // Get the UTF-16 length out of the data itself. + + for (size_t i = 0; i < utf16_len; i++) { + uint16_t chr = utf16[i]; + if (chr < 0x80) { + *utf8++ = chr & 0xff; + } else if (chr < 0x800) { + *utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } else { + // TODO: Verify surrogate. + *utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } + // TODO: Handle UTF-16 code points that take two entries. + } +} + +// Count how many bytes a utf-16-le encoded string will take in utf-8. +static int _count_utf8_bytes(const uint16_t *buf, size_t len) { + size_t total_bytes = 0; + for (size_t i = 0; i < len; i++) { + uint16_t chr = buf[i]; + if (chr < 0x80) { + total_bytes += 1; + } else if (chr < 0x800) { + total_bytes += 2; + } else { + total_bytes += 3; + } + // TODO: Handle UTF-16 code points that take two entries. + } + return total_bytes; +} + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len) { + size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); + size_t utf8_len = _count_utf8_bytes(temp_buf + 1, utf16_len); + + _convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t *) temp_buf, buf_len); + ((uint8_t *) temp_buf)[utf8_len] = '\0'; +} + +//--------------------------------------------------------------------+ +// 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) +{ + Serial.printf("Received HID report from device %d instance %d: ", dev_addr, instance); + for (uint16_t i = 0; i < len; i++) + { + Serial.printf("%02X ", report[i]); + } + Serial.printf("\n"); + + // Continue to receive the next report + if (!tuh_hid_receive_report(dev_addr, instance)) + { + Serial.printf("Error: cannot request to receive report\n"); + } +} diff --git a/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/usbh_helper.h b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/usbh_helper.h new file mode 100644 index 000000000..107f5c09f --- /dev/null +++ b/USB_Host_Turbo_Button_Gamepad/gamepad_device_report/usbh_helper.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2024 Ha Thach 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 +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz != 120000000UL && cpu_hz != 240000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif