├── README.md ├── XInputPad.c ├── XInputPad.h ├── descriptor_xinput.h ├── main.c ├── tusb_config.h └── usb_descriptors.c /README.md: -------------------------------------------------------------------------------- 1 | # tinyusb-xinput 2 | 3 | working xinput example for rp2040 with tinyusb. 4 | To compile use platform.io with https://github.com/Wiz-IO/wizio-pico 5 | sdk 6 | It is basically finished. To use it, just feed the needed buttonpresses 7 | into XboxButtonData which is located in 8 | https://github.com/fluffymadness/tinyusb-xinput/blob/master/XInputPad.h 9 | -------------------------------------------------------------------------------- /XInputPad.c: -------------------------------------------------------------------------------- 1 | #include "XInputPad.h" 2 | 3 | ReportDataXinput XboxButtonData; -------------------------------------------------------------------------------- /XInputPad.h: -------------------------------------------------------------------------------- 1 | #ifndef _XINPUTPAD_H_ 2 | #define _XINPUTPAD_H_ 3 | #include 4 | 5 | typedef struct { 6 | uint8_t rid; 7 | uint8_t rsize; 8 | uint8_t digital_buttons_1; 9 | uint8_t digital_buttons_2; 10 | uint8_t lt; 11 | uint8_t rt; 12 | int l_x; 13 | int l_y; 14 | int r_x; 15 | int r_y; 16 | uint8_t reserved_1[6]; 17 | } ReportDataXinput; 18 | extern ReportDataXinput XboxButtonData; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /descriptor_xinput.h: -------------------------------------------------------------------------------- 1 | #ifndef DESCRIPTOR_XINPUT 2 | #define DESCRIPTOR_XINPUT 3 | 4 | #include 5 | #include 6 | #include 7 | #include "common/tusb_common.h" 8 | #include "device/usbd.h" 9 | #include "tusb.h" 10 | #include "pico/stdlib.h" 11 | 12 | tusb_desc_device_t const xinputDeviceDescriptor = {.bLength = sizeof(tusb_desc_device_t), 13 | .bDescriptorType = TUSB_DESC_DEVICE, 14 | .bcdUSB = 0x0200, 15 | .bDeviceClass = 0xFF, 16 | .bDeviceSubClass = 0xFF, 17 | .bDeviceProtocol = 0xFF, 18 | .bMaxPacketSize0 = 19 | CFG_TUD_ENDPOINT0_SIZE, 20 | 21 | .idVendor = 0x045E, 22 | .idProduct = 0x028E, 23 | .bcdDevice = 0x0572, 24 | 25 | .iManufacturer = 0x01, 26 | .iProduct = 0x02, 27 | .iSerialNumber = 0x03, 28 | 29 | .bNumConfigurations = 0x01}; 30 | 31 | 32 | const uint8_t xinputConfigurationDescriptor[] = { 33 | //Configuration Descriptor: 34 | 0x09, //bLength 35 | 0x02, //bDescriptorType 36 | 0x30,0x00, //wTotalLength (48 bytes) 37 | 0x01, //bNumInterfaces 38 | 0x01, //bConfigurationValue 39 | 0x00, //iConfiguration 40 | 0x80, //bmAttributes (Bus-powered Device) 41 | 0xFA, //bMaxPower (500 mA) 42 | 43 | //Interface Descriptor: 44 | 0x09, //bLength 45 | 0x04, //bDescriptorType 46 | 0x00, //bInterfaceNumber 47 | 0x00, //bAlternateSetting 48 | 0x02, //bNumEndPoints 49 | 0xFF, //bInterfaceClass (Vendor specific) 50 | 0x5D, //bInterfaceSubClass 51 | 0x01, //bInterfaceProtocol 52 | 0x00, //iInterface 53 | 54 | //Unknown Descriptor: 55 | 0x10, 56 | 0x21, 57 | 0x10, 58 | 0x01, 59 | 0x01, 60 | 0x24, 61 | 0x81, 62 | 0x14, 63 | 0x03, 64 | 0x00, 65 | 0x03, 66 | 0x13, 67 | 0x02, 68 | 0x00, 69 | 0x03, 70 | 0x00, 71 | 72 | //Endpoint Descriptor: 73 | 0x07, //bLength 74 | 0x05, //bDescriptorType 75 | 0x81, //bEndpointAddress (IN endpoint 1) 76 | 0x03, //bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data) 77 | 0x20,0x00, //wMaxPacketSize (1 x 32 bytes) 78 | 0x04, //bInterval (4 frames) 79 | 80 | //Endpoint Descriptor: 81 | 82 | 0x07, //bLength 83 | 0x05, //bDescriptorType 84 | 0x02, //bEndpointAddress (OUT endpoint 2) 85 | 0x03, //bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data) 86 | 0x20,0x00, //wMaxPacketSize (1 x 32 bytes) 87 | 0x08, //bInterval (8 frames) 88 | }; 89 | 90 | 91 | // string descriptor table 92 | char const *string_desc_arr_xinput[] = { 93 | (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) 94 | "GENERIC", // 1: Manufacturer 95 | "XINPUT CONTROLLER", // 2: Product 96 | "1.0" // 3: Serials 97 | }; 98 | 99 | 100 | 101 | #endif -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "pico/stdlib.h" 31 | #include "tusb.h" 32 | #include "pico/time.h" 33 | #include "device/usbd_pvt.h" 34 | #include "XInputPad.h" 35 | 36 | static inline uint32_t board_millis(void) { 37 | return to_ms_since_boot(get_absolute_time()); 38 | } 39 | 40 | uint8_t endpoint_in=0; 41 | uint8_t endpoint_out=0; 42 | 43 | static void sendReportData(void) { 44 | 45 | // Poll every 1ms 46 | const uint32_t interval_ms = 1; 47 | static uint32_t start_ms = 0; 48 | 49 | if (board_millis() - start_ms < interval_ms) return; // not enough time 50 | start_ms += interval_ms; 51 | 52 | // Remote wakeup 53 | if (tud_suspended()) { 54 | // Wake up host if we are in suspend mode 55 | // and REMOTE_WAKEUP feature is enabled by host 56 | tud_remote_wakeup(); 57 | } 58 | XboxButtonData.rsize = 20; 59 | if ((tud_ready()) && ((endpoint_in != 0)) && (!usbd_edpt_busy(0, endpoint_in))){ 60 | usbd_edpt_claim(0, endpoint_in); 61 | usbd_edpt_xfer(0, endpoint_in, &XboxButtonData, 20); 62 | usbd_edpt_release(0, endpoint_in); 63 | } 64 | } 65 | 66 | int main(void) { 67 | stdio_init_all(); 68 | tusb_init(); 69 | while (1) { 70 | sendReportData(); 71 | tud_task(); // tinyusb device task 72 | } 73 | return 0; 74 | } 75 | 76 | 77 | static void xinput_init(void) { 78 | 79 | } 80 | 81 | static void xinput_reset(uint8_t __unused rhport) { 82 | 83 | } 84 | 85 | static uint16_t xinput_open(uint8_t __unused rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { 86 | //+16 is for the unknown descriptor 87 | uint16_t const drv_len = sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints*sizeof(tusb_desc_endpoint_t) + 16; 88 | TU_VERIFY(max_len >= drv_len, 0); 89 | 90 | uint8_t const * p_desc = tu_desc_next(itf_desc); 91 | uint8_t found_endpoints = 0; 92 | while ( (found_endpoints < itf_desc->bNumEndpoints) && (drv_len <= max_len) ) 93 | { 94 | tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; 95 | if ( TUSB_DESC_ENDPOINT == tu_desc_type(desc_ep) ) 96 | { 97 | TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); 98 | 99 | if ( tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN ) 100 | { 101 | endpoint_in = desc_ep->bEndpointAddress; 102 | }else 103 | { 104 | endpoint_out = desc_ep->bEndpointAddress; 105 | } 106 | found_endpoints += 1; 107 | } 108 | p_desc = tu_desc_next(p_desc); 109 | } 110 | return drv_len; 111 | } 112 | 113 | static bool xinput_device_control_request(uint8_t __unused rhport, tusb_control_request_t const *request) { 114 | return true; 115 | } 116 | 117 | static bool xinput_control_complete_cb(uint8_t __unused rhport, tusb_control_request_t __unused const *request) { 118 | return true; 119 | } 120 | //callback after xfer_transfer 121 | static bool xinput_xfer_cb(uint8_t __unused rhport, uint8_t __unused ep_addr, xfer_result_t __unused result, uint32_t __unused xferred_bytes) { 122 | return true; 123 | } 124 | 125 | 126 | static usbd_class_driver_t const xinput_driver ={ 127 | #if CFG_TUSB_DEBUG >= 2 128 | .name = "XINPUT", 129 | #endif 130 | .init = xinput_init, 131 | .reset = xinput_reset, 132 | .open = xinput_open, 133 | .control_request = xinput_device_control_request, 134 | .control_complete = xinput_control_complete_cb, 135 | .xfer_cb = xinput_xfer_cb, 136 | .sof = NULL 137 | }; 138 | 139 | // Implement callback to add our custom driver 140 | usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) { 141 | *driver_count = 1; 142 | return &xinput_driver; 143 | } -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" 31 | { 32 | #endif 33 | 34 | //-------------------------------------------------------------------- 35 | // COMMON CONFIGURATION 36 | //-------------------------------------------------------------------- 37 | 38 | // defined by compiler flags for flexibility 39 | #ifndef CFG_TUSB_MCU 40 | #error CFG_TUSB_MCU must be defined 41 | #endif 42 | 43 | #if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 44 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 45 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) 46 | #else 47 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 48 | #endif 49 | 50 | #ifndef CFG_TUSB_OS 51 | #define CFG_TUSB_OS OPT_OS_PICO 52 | #endif 53 | 54 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 55 | // #define CFG_TUSB_DEBUG 0 56 | 57 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 58 | * Tinyusb use follows macros to declare transferring memory so that they can be put 59 | * into those specific section. 60 | * e.g 61 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 62 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 63 | */ 64 | #ifndef CFG_TUSB_MEM_SECTION 65 | #define CFG_TUSB_MEM_SECTION 66 | #endif 67 | 68 | #ifndef CFG_TUSB_MEM_ALIGN 69 | #define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) 70 | #endif 71 | 72 | //-------------------------------------------------------------------- 73 | // DEVICE CONFIGURATION 74 | //-------------------------------------------------------------------- 75 | 76 | #ifndef CFG_TUD_ENDPOINT0_SIZE 77 | #define CFG_TUD_ENDPOINT0_SIZE 64 78 | #endif 79 | 80 | //------------- CLASS -------------// 81 | #define CFG_TUD_CDC 0 82 | #define CFG_TUD_MSC 0 83 | #define CFG_TUD_HID 0 84 | #define CFG_TUD_MIDI 0 85 | #define CFG_TUD_VENDOR 0 86 | #define CFG_TUSB_DEBUG 0 87 | 88 | 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | 93 | #endif /* _TUSB_CONFIG_H_ */ 94 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "descriptor_xinput.h" 27 | #include "tusb.h" 28 | 29 | 30 | /* A combination of interfaces must have a unique product id, since PC will save 31 | * device driver after the first plug. Same VID/PID with different interface e.g 32 | * MSC (first), then CDC (later) will possibly cause system error on PC. 33 | * 34 | * Auto ProductID layout's Bitmap: 35 | * [MSB] HID | MSC | CDC [LSB] 36 | */ 37 | #define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) 38 | 39 | // Invoked when received GET DEVICE DESCRIPTOR 40 | // Application return pointer to descriptor 41 | uint8_t const *tud_descriptor_device_cb(void) { 42 | return (uint8_t const *)&xinputDeviceDescriptor; 43 | } 44 | 45 | //--------------------------------------------------------------------+ 46 | // HID Report Descriptor 47 | //--------------------------------------------------------------------+ 48 | 49 | // Invoked when received GET HID REPORT DESCRIPTOR 50 | // Application return pointer to descriptor 51 | // Descriptor contents must exist long enough for transfer to complete 52 | uint8_t const *tud_hid_descriptor_report_cb(void) { 53 | return 0; 54 | } 55 | 56 | //--------------------------------------------------------------------+ 57 | // Configuration Descriptor 58 | //--------------------------------------------------------------------+ 59 | 60 | 61 | 62 | // Invoked when received GET CONFIGURATION DESCRIPTOR 63 | // Application return pointer to descriptor 64 | // Descriptor contents must exist long enough for transfer to complete 65 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { 66 | (void)index; // for multiple configurations 67 | return xinputConfigurationDescriptor; 68 | return 0; 69 | } 70 | //--------------------------------------------------------------------+ 71 | // String Descriptors 72 | //--------------------------------------------------------------------+ 73 | 74 | static uint16_t _desc_str[32]; 75 | 76 | // Invoked when received GET STRING DESCRIPTOR request 77 | // Application return pointer to descriptor, whose contents must exist long 78 | // enough for transfer to complete 79 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { 80 | 81 | (void)langid; 82 | 83 | uint8_t chr_count; 84 | 85 | if (index == 0) { 86 | memcpy(&_desc_str[1], string_desc_arr_xinput[0], 2); 87 | chr_count = 1; 88 | } else { 89 | // Convert ASCII string into UTF-16 90 | 91 | if (!(index < sizeof(string_desc_arr_xinput) / sizeof(string_desc_arr_xinput[0]))) 92 | return NULL; 93 | 94 | const char *str = string_desc_arr_xinput[index]; 95 | 96 | // Cap at max char 97 | chr_count = strlen(str); 98 | if (chr_count > 31) 99 | chr_count = 31; 100 | 101 | for (uint8_t i = 0; i < chr_count; i++) { 102 | _desc_str[1 + i] = str[i]; 103 | } 104 | } 105 | 106 | // first byte is length (including header), second byte is string type 107 | _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); 108 | 109 | return _desc_str; 110 | 111 | 112 | } 113 | 114 | --------------------------------------------------------------------------------