├── images ├── usbhmidi.jpg ├── usbhprinter.jpg ├── esp32_usb_host_cable_1.jpg └── esp32_usb_host_cable_2.jpg ├── .gitignore ├── LICENSE ├── usbhhelp.hpp ├── examples ├── usbhprinter │ └── usbhprinter.ino ├── usbhhidboot │ └── usbhhidboot.ino ├── descdump │ └── descdump.ino └── usbhmidi │ └── usbhmidi.ino ├── README.md └── show_desc.hpp /images/usbhmidi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchgadget/esp32-usb-host-demos/HEAD/images/usbhmidi.jpg -------------------------------------------------------------------------------- /images/usbhprinter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchgadget/esp32-usb-host-demos/HEAD/images/usbhprinter.jpg -------------------------------------------------------------------------------- /images/esp32_usb_host_cable_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchgadget/esp32-usb-host-demos/HEAD/images/esp32_usb_host_cable_1.jpg -------------------------------------------------------------------------------- /images/esp32_usb_host_cable_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchgadget/esp32-usb-host-demos/HEAD/images/esp32_usb_host_cable_2.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | tags 35 | test.sh 36 | *~ 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | -------------------------------------------------------------------------------- /usbhhelp.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const TickType_t HOST_EVENT_TIMEOUT = 1; 26 | const TickType_t CLIENT_EVENT_TIMEOUT = 1; 27 | 28 | usb_host_client_handle_t Client_Handle; 29 | usb_device_handle_t Device_Handle; 30 | typedef void (*usb_host_enum_cb_t)(const usb_config_desc_t *config_desc); 31 | static usb_host_enum_cb_t _USB_host_enumerate; 32 | 33 | void _client_event_callback(const usb_host_client_event_msg_t *event_msg, void *arg) 34 | { 35 | esp_err_t err; 36 | switch (event_msg->event) 37 | { 38 | /**< A new device has been enumerated and added to the USB Host Library */ 39 | case USB_HOST_CLIENT_EVENT_NEW_DEV: 40 | ESP_LOGI("", "New device address: %d", event_msg->new_dev.address); 41 | err = usb_host_device_open(Client_Handle, event_msg->new_dev.address, &Device_Handle); 42 | if (err != ESP_OK) ESP_LOGI("", "usb_host_device_open: %x", err); 43 | 44 | usb_device_info_t dev_info; 45 | err = usb_host_device_info(Device_Handle, &dev_info); 46 | if (err != ESP_OK) ESP_LOGI("", "usb_host_device_info: %x", err); 47 | ESP_LOGI("", "speed: %d dev_addr %d vMaxPacketSize0 %d bConfigurationValue %d", 48 | dev_info.speed, dev_info.dev_addr, dev_info.bMaxPacketSize0, 49 | dev_info.bConfigurationValue); 50 | 51 | const usb_device_desc_t *dev_desc; 52 | err = usb_host_get_device_descriptor(Device_Handle, &dev_desc); 53 | if (err != ESP_OK) ESP_LOGI("", "usb_host_get_device_desc: %x", err); 54 | show_dev_desc(dev_desc); 55 | 56 | const usb_config_desc_t *config_desc; 57 | err = usb_host_get_active_config_descriptor(Device_Handle, &config_desc); 58 | if (err != ESP_OK) ESP_LOGI("", "usb_host_get_config_desc: %x", err); 59 | (*_USB_host_enumerate)(config_desc); 60 | break; 61 | /**< A device opened by the client is now gone */ 62 | case USB_HOST_CLIENT_EVENT_DEV_GONE: 63 | ESP_LOGI("", "Device Gone handle: %x", event_msg->dev_gone.dev_hdl); 64 | break; 65 | default: 66 | ESP_LOGI("", "Unknown value %d", event_msg->event); 67 | break; 68 | } 69 | } 70 | 71 | // Reference: esp-idf/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c 72 | 73 | void usbh_setup(usb_host_enum_cb_t enumeration_cb) 74 | { 75 | const usb_host_config_t config = { 76 | .intr_flags = ESP_INTR_FLAG_LEVEL1, 77 | }; 78 | esp_err_t err = usb_host_install(&config); 79 | ESP_LOGI("", "usb_host_install: %x", err); 80 | 81 | const usb_host_client_config_t client_config = { 82 | .is_synchronous = false, 83 | .max_num_event_msg = 5, 84 | .async = { 85 | .client_event_callback = _client_event_callback, 86 | .callback_arg = Client_Handle 87 | } 88 | }; 89 | err = usb_host_client_register(&client_config, &Client_Handle); 90 | ESP_LOGI("", "usb_host_client_register: %x", err); 91 | 92 | _USB_host_enumerate = enumeration_cb; 93 | } 94 | 95 | void usbh_task(void) 96 | { 97 | uint32_t event_flags; 98 | static bool all_clients_gone = false; 99 | static bool all_dev_free = false; 100 | 101 | esp_err_t err = usb_host_lib_handle_events(HOST_EVENT_TIMEOUT, &event_flags); 102 | if (err == ESP_OK) { 103 | if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { 104 | ESP_LOGI("", "No more clients"); 105 | all_clients_gone = true; 106 | } 107 | if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { 108 | ESP_LOGI("", "No more devices"); 109 | all_dev_free = true; 110 | } 111 | } 112 | else { 113 | if (err != ESP_ERR_TIMEOUT) { 114 | ESP_LOGI("", "usb_host_lib_handle_events: %x flags: %x", err, event_flags); 115 | } 116 | } 117 | 118 | err = usb_host_client_handle_events(Client_Handle, CLIENT_EVENT_TIMEOUT); 119 | if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { 120 | ESP_LOGI("", "usb_host_client_handle_events: %x", err); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/usbhprinter/usbhprinter.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "show_desc.hpp" 3 | #include "usbhhelp.hpp" 4 | 5 | bool isPrinter = false; 6 | bool isBiDirectional = false; 7 | 8 | const size_t PRINTER_OUT_BUFFERS = 8; 9 | usb_transfer_t *PrinterOut = NULL; 10 | usb_transfer_t *PrinterIn = NULL; 11 | 12 | static void printer_transfer_cb(usb_transfer_t *transfer) 13 | { 14 | ESP_LOGI("", "printer_transfer_cb context: %d", transfer->context); 15 | if (Device_Handle == transfer->device_handle) { 16 | int in_xfer = transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; 17 | if (transfer->status == 0) { 18 | if (in_xfer) { 19 | uint8_t *const p = transfer->data_buffer; 20 | for (int i = 0; i < transfer->actual_num_bytes; i++) { 21 | ESP_LOGI("", "printer in: %02x", p[i]); 22 | } 23 | esp_err_t err = usb_host_transfer_submit(transfer); 24 | if (err != ESP_OK) { 25 | ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err); 26 | } 27 | } 28 | } 29 | else { 30 | ESP_LOGI("", "transfer->status %d", transfer->status); 31 | } 32 | } 33 | } 34 | 35 | void check_interface_desc_printer(const void *p) 36 | { 37 | const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; 38 | 39 | // USB Printer Class Specification 1.1 40 | if ((intf->bInterfaceClass == USB_CLASS_PRINTER) && 41 | (intf->bInterfaceSubClass == 1)) 42 | { 43 | /* Protocol 44 | * 00 Reserved, undefined. 45 | * 01 Unidirectional interface. 46 | * 02 Bi-directional interface. 47 | * 03 1284.4 compatible bi-directional interface. 48 | * 04-FEh Reserved for future use. 49 | * FFh Vendor-specific printers do not use class-specific protocols. 50 | */ 51 | // No idea how to support 1284.4 so ignore it. 52 | if ((intf->bInterfaceProtocol == 1) || (intf->bInterfaceProtocol == 2)) { 53 | isBiDirectional = (intf->bInterfaceProtocol == 2); 54 | if (isBiDirectional) { 55 | if (intf->bNumEndpoints < 2) { 56 | isPrinter = false; 57 | return; 58 | } 59 | } 60 | else { 61 | if (intf->bNumEndpoints < 1) { 62 | isPrinter = false; 63 | return; 64 | } 65 | } 66 | isPrinter = true; 67 | ESP_LOGI("", "Claiming a %s-directional printer!", (isBiDirectional)?"bi":"uni"); 68 | esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle, 69 | intf->bInterfaceNumber, intf->bAlternateSetting); 70 | if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err); 71 | } 72 | } 73 | } 74 | 75 | void prepare_endpoints(const void *p) 76 | { 77 | const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; 78 | esp_err_t err; 79 | 80 | // must be bulk for printer 81 | if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_BULK) { 82 | ESP_LOGI("", "Not bulk endpoint: 0x%02x", endpoint->bmAttributes); 83 | return; 84 | } 85 | if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { 86 | err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &PrinterIn); 87 | if (err != ESP_OK) { 88 | PrinterIn = NULL; 89 | ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err); 90 | return; 91 | } 92 | PrinterIn->device_handle = Device_Handle; 93 | PrinterIn->bEndpointAddress = endpoint->bEndpointAddress; 94 | PrinterIn->callback = printer_transfer_cb; 95 | PrinterIn->context = NULL; 96 | PrinterIn->num_bytes = endpoint->wMaxPacketSize; 97 | esp_err_t err = usb_host_transfer_submit(PrinterIn); 98 | if (err != ESP_OK) { 99 | ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err); 100 | } 101 | } 102 | else { 103 | err = usb_host_transfer_alloc(endpoint->wMaxPacketSize*PRINTER_OUT_BUFFERS, 0, &PrinterOut); 104 | if (err != ESP_OK) { 105 | PrinterOut = NULL; 106 | ESP_LOGI("", "usb_host_transfer_alloc Out fail: %x", err); 107 | return; 108 | } 109 | ESP_LOGI("", "Out data_buffer_size: %d", PrinterOut->data_buffer_size); 110 | PrinterOut->device_handle = Device_Handle; 111 | PrinterOut->bEndpointAddress = endpoint->bEndpointAddress; 112 | PrinterOut->callback = printer_transfer_cb; 113 | PrinterOut->context = NULL; 114 | // PrinterOut->flags |= USB_TRANSFER_FLAG_ZERO_PACK; 115 | } 116 | } 117 | 118 | void show_config_desc_full(const usb_config_desc_t *config_desc) 119 | { 120 | // Full decode of config desc. 121 | const uint8_t *p = &config_desc->val[0]; 122 | uint8_t bLength; 123 | for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { 124 | bLength = *p; 125 | if ((i + bLength) <= config_desc->wTotalLength) { 126 | const uint8_t bDescriptorType = *(p + 1); 127 | switch (bDescriptorType) { 128 | case USB_B_DESCRIPTOR_TYPE_DEVICE: 129 | ESP_LOGI("", "USB Device Descriptor should not appear in config"); 130 | break; 131 | case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: 132 | show_config_desc(p); 133 | break; 134 | case USB_B_DESCRIPTOR_TYPE_STRING: 135 | ESP_LOGI("", "USB string desc TBD"); 136 | break; 137 | case USB_B_DESCRIPTOR_TYPE_INTERFACE: 138 | show_interface_desc(p); 139 | check_interface_desc_printer(p); 140 | break; 141 | case USB_B_DESCRIPTOR_TYPE_ENDPOINT: 142 | show_endpoint_desc(p); 143 | prepare_endpoints(p); 144 | break; 145 | default: 146 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", *p); 147 | break; 148 | } 149 | } 150 | else { 151 | ESP_LOGI("", "USB Descriptor invalid"); 152 | return; 153 | } 154 | } 155 | } 156 | 157 | void setup() 158 | { 159 | Serial.begin(115200); 160 | Serial.setTimeout(0); 161 | usbh_setup(show_config_desc_full); 162 | } 163 | 164 | void loop() 165 | { 166 | usbh_task(); 167 | 168 | // ESP32 S2 Typewriter 169 | // Read line from serial monitor and write to printer. 170 | String aLine = Serial.readStringUntil('\n'); // Read line ending with newline 171 | 172 | if (aLine.length() > 0) { 173 | // readStringUntil removes the newline so add it back 174 | aLine.concat('\n'); 175 | PrinterOut->num_bytes = aLine.length(); 176 | memcpy(PrinterOut->data_buffer, aLine.c_str(), PrinterOut->num_bytes); 177 | esp_err_t err = usb_host_transfer_submit(PrinterOut); 178 | if (err != ESP_OK) { 179 | ESP_LOGI("", "usb_host_transfer_submit Out fail: %x", err); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /examples/usbhhidboot/usbhhidboot.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | #include "show_desc.hpp" 27 | #include "usbhhelp.hpp" 28 | 29 | bool isKeyboard = false; 30 | bool isKeyboardReady = false; 31 | uint8_t KeyboardInterval; 32 | bool isKeyboardPolling = false; 33 | elapsedMillis KeyboardTimer; 34 | 35 | const size_t KEYBOARD_IN_BUFFER_SIZE = 8; 36 | usb_transfer_t *KeyboardIn = NULL; 37 | 38 | void keyboard_transfer_cb(usb_transfer_t *transfer) 39 | { 40 | if (Device_Handle == transfer->device_handle) { 41 | isKeyboardPolling = false; 42 | if (transfer->status == 0) { 43 | if (transfer->actual_num_bytes == 8) { 44 | uint8_t *const p = transfer->data_buffer; 45 | ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x", 46 | p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); 47 | } 48 | else { 49 | ESP_LOGI("", "Keyboard boot hid transfer too short or long"); 50 | } 51 | } 52 | else { 53 | ESP_LOGI("", "transfer->status %d", transfer->status); 54 | } 55 | } 56 | } 57 | 58 | void check_interface_desc_boot_keyboard(const void *p) 59 | { 60 | const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; 61 | 62 | if ((intf->bInterfaceClass == USB_CLASS_HID) && 63 | (intf->bInterfaceSubClass == 1) && 64 | (intf->bInterfaceProtocol == 1)) { 65 | isKeyboard = true; 66 | ESP_LOGI("", "Claiming a boot keyboard!"); 67 | esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle, 68 | intf->bInterfaceNumber, intf->bAlternateSetting); 69 | if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err); 70 | } 71 | } 72 | 73 | void prepare_endpoint(const void *p) 74 | { 75 | const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; 76 | esp_err_t err; 77 | 78 | // must be interrupt for HID 79 | if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) { 80 | ESP_LOGI("", "Not interrupt endpoint: 0x%02x", endpoint->bmAttributes); 81 | return; 82 | } 83 | if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { 84 | err = usb_host_transfer_alloc(KEYBOARD_IN_BUFFER_SIZE, 0, &KeyboardIn); 85 | if (err != ESP_OK) { 86 | KeyboardIn = NULL; 87 | ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err); 88 | return; 89 | } 90 | KeyboardIn->device_handle = Device_Handle; 91 | KeyboardIn->bEndpointAddress = endpoint->bEndpointAddress; 92 | KeyboardIn->callback = keyboard_transfer_cb; 93 | KeyboardIn->context = NULL; 94 | isKeyboardReady = true; 95 | KeyboardInterval = endpoint->bInterval; 96 | ESP_LOGI("", "USB boot keyboard ready"); 97 | } 98 | else { 99 | ESP_LOGI("", "Ignoring interrupt Out endpoint"); 100 | } 101 | } 102 | 103 | void show_config_desc_full(const usb_config_desc_t *config_desc) 104 | { 105 | // Full decode of config desc. 106 | const uint8_t *p = &config_desc->val[0]; 107 | static uint8_t USB_Class = 0; 108 | uint8_t bLength; 109 | for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { 110 | bLength = *p; 111 | if ((i + bLength) <= config_desc->wTotalLength) { 112 | const uint8_t bDescriptorType = *(p + 1); 113 | switch (bDescriptorType) { 114 | case USB_B_DESCRIPTOR_TYPE_DEVICE: 115 | ESP_LOGI("", "USB Device Descriptor should not appear in config"); 116 | break; 117 | case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: 118 | show_config_desc(p); 119 | break; 120 | case USB_B_DESCRIPTOR_TYPE_STRING: 121 | ESP_LOGI("", "USB string desc TBD"); 122 | break; 123 | case USB_B_DESCRIPTOR_TYPE_INTERFACE: 124 | USB_Class = show_interface_desc(p); 125 | check_interface_desc_boot_keyboard(p); 126 | break; 127 | case USB_B_DESCRIPTOR_TYPE_ENDPOINT: 128 | show_endpoint_desc(p); 129 | if (isKeyboard && KeyboardIn == NULL) prepare_endpoint(p); 130 | break; 131 | case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: 132 | // Should not be config config? 133 | ESP_LOGI("", "USB device qual desc TBD"); 134 | break; 135 | case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION: 136 | // Should not be config config? 137 | ESP_LOGI("", "USB Other Speed TBD"); 138 | break; 139 | case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER: 140 | // Should not be config config? 141 | ESP_LOGI("", "USB Interface Power TBD"); 142 | break; 143 | case 0x21: 144 | if (USB_Class == USB_CLASS_HID) { 145 | show_hid_desc(p); 146 | } 147 | break; 148 | default: 149 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", bDescriptorType); 150 | break; 151 | } 152 | } 153 | else { 154 | ESP_LOGI("", "USB Descriptor invalid"); 155 | return; 156 | } 157 | } 158 | } 159 | 160 | void setup() 161 | { 162 | usbh_setup(show_config_desc_full); 163 | } 164 | 165 | void loop() 166 | { 167 | usbh_task(); 168 | 169 | if (isKeyboardReady && !isKeyboardPolling && (KeyboardTimer > KeyboardInterval)) { 170 | KeyboardIn->num_bytes = 8; 171 | esp_err_t err = usb_host_transfer_submit(KeyboardIn); 172 | if (err != ESP_OK) { 173 | ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err); 174 | } 175 | isKeyboardPolling = true; 176 | KeyboardTimer = 0; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /examples/descdump/descdump.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "show_desc.hpp" 27 | #include "usbhhelp.hpp" 28 | 29 | void show_config_desc_full(const usb_config_desc_t *config_desc) 30 | { 31 | #if 0 32 | // Hex dump full config descriptor so it can be fed into 33 | // http://eleccelerator.com/usbdescreqparser/ 34 | const uint8_t *val = &config_desc->val[0]; 35 | for (int i = 0; i < config_desc->wTotalLength; i+=16) { 36 | if ((i + 15) < config_desc->wTotalLength) { 37 | ESP_LOGI("", "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", 38 | val[i] , val[i+1], val[i+2], val[i+3], 39 | val[i+4], val[i+5], val[i+6], val[i+7], 40 | val[i+8], val[i+9], val[i+10], val[i+11], 41 | val[i+12], val[i+13], val[i+14], val[i+15]); 42 | } 43 | else { 44 | for (;i < config_desc->wTotalLength; i++) { 45 | ESP_LOGI("", "%02X", val[i]); 46 | } 47 | break; 48 | } 49 | } 50 | #endif 51 | 52 | // Full decode of config desc. 53 | const uint8_t *p = &config_desc->val[0]; 54 | static uint8_t USB_Class = 0; 55 | uint8_t bLength; 56 | for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { 57 | bLength = *p; 58 | if ((i + bLength) <= config_desc->wTotalLength) { 59 | const uint8_t bDescriptorType = *(p + 1); 60 | switch (bDescriptorType) { 61 | case USB_B_DESCRIPTOR_TYPE_DEVICE: 62 | ESP_LOGI("", "USB Device Descriptor should not appear in config"); 63 | break; 64 | case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: 65 | show_config_desc(p); 66 | break; 67 | case USB_B_DESCRIPTOR_TYPE_STRING: 68 | ESP_LOGI("", "USB string desc TBD"); 69 | break; 70 | case USB_B_DESCRIPTOR_TYPE_INTERFACE: 71 | USB_Class = show_interface_desc(p); 72 | break; 73 | case USB_B_DESCRIPTOR_TYPE_ENDPOINT: 74 | show_endpoint_desc(p); 75 | break; 76 | case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: 77 | // Should not be in config? 78 | ESP_LOGI("", "USB device qual desc TBD"); 79 | break; 80 | case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION: 81 | // Should not be in config? 82 | ESP_LOGI("", "USB Other Speed TBD"); 83 | break; 84 | case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER: 85 | // Should not be in config? 86 | ESP_LOGI("", "USB Interface Power TBD"); 87 | break; 88 | case USB_B_DESCRIPTOR_TYPE_OTG: 89 | // Should not be in config? 90 | ESP_LOGI("", "USB OTG TBD"); 91 | break; 92 | case USB_B_DESCRIPTOR_TYPE_DEBUG: 93 | // Should not be in config? 94 | ESP_LOGI("", "USB DEBUG TBD"); 95 | break; 96 | case USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION: 97 | show_interface_assoc(p); 98 | break; 99 | // Class specific descriptors have overlapping values. 100 | case 0x21: 101 | switch (USB_Class) { 102 | case USB_CLASS_HID: 103 | show_hid_desc(p); 104 | break; 105 | case USB_CLASS_APP_SPEC: 106 | ESP_LOGI("", "App Spec Class descriptor TBD"); 107 | break; 108 | default: 109 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 110 | break; 111 | } 112 | break; 113 | case 0x22: 114 | switch (USB_Class) { 115 | default: 116 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 117 | break; 118 | } 119 | break; 120 | case 0x23: 121 | switch (USB_Class) { 122 | default: 123 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 124 | break; 125 | } 126 | break; 127 | case 0x24: 128 | switch (USB_Class) { 129 | case USB_CLASS_AUDIO: 130 | ESP_LOGI("", "Audio Class Descriptor 0x24 TBD"); 131 | break; 132 | case USB_CLASS_COMM: 133 | ESP_LOGI("", "Comm Class CS_INTERFACE 0x24 TBD"); 134 | break; 135 | default: 136 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 137 | break; 138 | } 139 | break; 140 | case 0x25: 141 | switch (USB_Class) { 142 | case USB_CLASS_AUDIO: 143 | ESP_LOGI("", "Audio Class Descriptor 0x25 TBD"); 144 | break; 145 | case USB_CLASS_COMM: 146 | ESP_LOGI("", "Comm Class CS_ENDPOINT 0x25 TBD"); 147 | break; 148 | default: 149 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 150 | break; 151 | } 152 | break; 153 | default: 154 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%02x Class: 0x%02x", bDescriptorType, USB_Class); 155 | break; 156 | } 157 | } 158 | else { 159 | ESP_LOGI("", "USB Descriptor invalid"); 160 | return; 161 | } 162 | } 163 | } 164 | 165 | void setup() 166 | { 167 | usbh_setup(show_config_desc_full); 168 | } 169 | 170 | void loop() 171 | { 172 | usbh_task(); 173 | } 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 S2 USB Host Jumpstart 2 | 3 | A collection of experiments and demos using Espressif ESP32 S2 in USB host 4 | mode. Most of the sketches have little or no C++ or Arduino dependencies so can 5 | be converted to ESP-IDF programs. The ESP32 S3 does not have Arduino support so 6 | converting the sketches back to C ESP-IDF programs would be useful. 7 | 8 | The code is based on ESP32 USB host tests and examples included with ESP-IDF. 9 | 10 | Use at your own risk. Error handling is lacking. These are demos so do not do 11 | anything useful except show it is possible to communicate with real world USB 12 | devices. More work is needed to make them useful. 13 | 14 | To see the sketch output on the serial monitor set the Core Debug Level to 15 | Verbose. 16 | 17 | ## Software 18 | 19 | * Arduino IDE 1.8.16 20 | * arduino-esp32 (stable) 2.0.1 21 | 22 | ## Hardware 23 | 24 | * Espressif ESP-32-S2-DevKitC-1 board 25 | * USB OTG to USB host cable 26 | * USB break out board to access VBUS 27 | 28 | The USB printer is self-powered which means it is powered by its battery. It 29 | does not need the USB VBUS 5V and does not charge its battery from VBUS. 30 | 31 | ``` 32 | ESP32-S2-DevKitC-1 -> USB OTG to USB host -> USB cable -> USB Printer 33 | ``` 34 | 35 | ![USB printer connected to ESP32 S2](./images/usbhprinter.jpg) 36 | 37 | The above configuration fails for bus-powered devices such as keyboards because 38 | they depend on VBUS providing 5V. The DevKit USB port does not provide 5V on 39 | VBUS. It only works as a power input, not output. A true USB OTG port is 40 | able to switch the direction of VBUS based on the USB micro connector ID pin. 41 | 42 | The following is my workaround for this. 43 | 44 | ``` 45 | ESP32-S2-DevKitC-1 -> USB OTG to USB host -> USB breakout -> USB MIDI 46 | 5V VBUS 47 | | ^ 48 | v | 49 | .-----------------------------------------. 50 | ``` 51 | 52 | ![USB MIDI keyboard connected to ESP32 S2](./images/usbhmidi.jpg) 53 | 54 | The yellow wire connects 5V out on the dev board to the USB breakout to power 55 | the USB MIDI keyboard. 56 | 57 | A more reliable solution is to make a cable using USB connector breakout 58 | boards that looks like the USB OTG to USB host cable but with an extra flying 59 | wire for VBUS. 60 | 61 | The following cable was made by cutting a regular micro USB cable in half. 62 | Using the side with the micro USB connector, separate the 4 wires. Solder 63 | on Dupont wires also cut in half with matching colors. The other connector 64 | is a USB Type A receptacle breakout board. The red VBUS wire is not connected 65 | because the ESP32 S2 board does not provide 5V out on this pin. The long orange 66 | wire connects 5V out from the ESP32 S2 DevKit board to VBUS. 67 | 68 | ![USB host cable](./images/esp32_usb_host_cable_1.jpg) 69 | 70 | ![USB host cable connected to ESP32 S2 DevKitC](./images/esp32_usb_host_cable_2.jpg) 71 | 72 | An alternative is to cut open a USB OTG to USB host cable, cut the red power 73 | wire then solder a flying wire to VBUS. 74 | 75 | ## dumpdesc -- USB Descriptor Dump 76 | 77 | Arduino sketch that shows frequently used USB descriptors in human readable 78 | form. 79 | 80 | Sample output 81 | ``` 82 | [ 15501][I][show_desc.hpp:54] show_config_desc(): bLength: 9 83 | [ 15506][I][show_desc.hpp:55] show_config_desc(): bDescriptorType(config): 2 84 | [ 15513][I][show_desc.hpp:56] show_config_desc(): wTotalLength: 216 85 | [ 15519][I][show_desc.hpp:57] show_config_desc(): bNumInterfaces: 4 86 | [ 15525][I][show_desc.hpp:58] show_config_desc(): bConfigurationValue: 1 87 | [ 15531][I][show_desc.hpp:59] show_config_desc(): iConfiguration: 0 88 | [ 15537][I][show_desc.hpp:64] show_config_desc(): bmAttributes(, Remote Wakeup): 0xa0 89 | [ 15545][I][show_desc.hpp:65] show_config_desc(): bMaxPower: 50 = 100 mA 90 | ``` 91 | 92 | ## usbhmidi -- USB Host MIDI 93 | 94 | Arduino sketch that is just enough to get a USB MIDI keyboard (and may other 95 | MIDI devices) talking MIDI over USB. It might be possible to make this work 96 | with the USB transport layer of the Forty Seven Effects MIDI Library. But I 97 | not sure I will ever have time for this. 98 | 99 | USB MIDI Event Packet Format (always 4 bytes). 100 | 101 | |Byte 0 |Byte 1 |Byte 2 |Byte 3 102 | |-------|-------|-------|------ 103 | |CN+CIN |MIDI_0 |MIDI_1 |MIDI_2 104 | 105 | * CN = Cable Number (0x0..0xf) specifies virtual MIDI jack/cable 106 | * CIN = Code Index Number (0x0..0xf) classifies the 3 MIDI bytes. See Table 107 | 4-1 in the MIDI 1.0 spec at usb.org. 108 | 109 | Sample output showing MIDI note on/off from a keyboard 110 | ``` 111 | [ 69235][I][usbhmidi.ino:30] midi_transfer_cb(): midi_transfer_cb context: 0 112 | [ 69235][I][usbhmidi.ino:37] midi_transfer_cb(): midi: 08 80 3c 40 113 | [ 74603][I][usbhmidi.ino:30] midi_transfer_cb(): midi_transfer_cb context: 1 114 | [ 74604][I][usbhmidi.ino:37] midi_transfer_cb(): midi: 09 90 3c 7a 115 | [ 74690][I][usbhmidi.ino:30] midi_transfer_cb(): midi_transfer_cb context: 2 116 | [ 74690][I][usbhmidi.ino:37] midi_transfer_cb(): midi: 08 80 3c 40 117 | ``` 118 | 119 | ## usbhhidboot -- USB Host HID Boot Keyboard 120 | 121 | Arduino sketch that is just enough to get a USB HID keyboard working. It uses 122 | HID boot mode which means it does not need to fetch or parse the HID report 123 | descriptor. More work required to convert the HID reports into ASCII chars and 124 | key up/down events. Does not implement SetProtocol so some keyboards may 125 | require this. 126 | 127 | Maybe model this on the official USB Host KeyboardController object. 128 | https://www.arduino.cc/en/Reference/KeyboardControllerConstructor 129 | 130 | Some explanation of USB HID reports. 131 | https://wiki.osdev.org/USB_Human_Interface_Devices 132 | 133 | Sample output with additional comments 134 | ``` 135 | // All keys up 136 | [ 29449][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 00 00 00 00 00 00 00 00 137 | // Shift key down 138 | [ 31770][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 02 00 00 00 00 00 00 00 139 | [ 32265][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 02 00 00 00 00 00 00 00 140 | // Shift and 'A' keys down 141 | [ 33514][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 02 00 04 00 00 00 00 00 142 | [ 34026][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 02 00 04 00 00 00 00 00 143 | // All keys up 144 | [ 34185][I][usbhhidboot.ino:36] keyboard_transfer_cb(): data: 00 00 00 00 00 00 00 00 145 | ``` 146 | 147 | ## usbhprinter -- USB Host Printer Class 148 | 149 | Arduino sketch that is just enough to print one line on a USB thermal receipt 150 | printer. Would be nice to create a subclass from the Arduino stream class so 151 | it appears similar to a Serial device. And an ESC POS library to print in 152 | graphics mode, double wide, italics, bold, etc. Lots more work required. 153 | 154 | ~~The sketch currently prints 1 line with "Hello from ESP32 S2".~~ 155 | 156 | The sketch reads a line from the serial monitor and writes it to the printer. 157 | This proves USB printer communication is working. This may be the first ESP32 158 | typewriter. 159 | -------------------------------------------------------------------------------- /show_desc.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const size_t USB_HID_DESC_SIZE = 9; 26 | 27 | typedef union { 28 | struct { 29 | uint8_t bLength; /**< Size of the descriptor in bytes */ 30 | uint8_t bDescriptorType; /**< Constant name specifying type of HID descriptor. */ 31 | uint16_t bcdHID; /**< USB HID Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H) */ 32 | uint8_t bCountryCode; /**< Numeric expression identifying country code of the localized hardware. */ 33 | uint8_t bNumDescriptor; /**< Numeric expression specifying the number of class descriptors. */ 34 | uint8_t bHIDDescriptorType; /**< Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ 35 | uint16_t wHIDDescriptorLength; /**< Numeric expression that is the total size of the Report descriptor. */ 36 | uint8_t bHIDDescriptorTypeOpt; /**< Optional constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ 37 | uint16_t wHIDDescriptorLengthOpt; /**< Optional numeric expression that is the total size of the Report descriptor. */ 38 | } USB_DESC_ATTR; 39 | uint8_t val[USB_HID_DESC_SIZE]; 40 | } usb_hid_desc_t; 41 | 42 | void show_dev_desc(const usb_device_desc_t *dev_desc) 43 | { 44 | ESP_LOGI("", "bLength: %d", dev_desc->bLength); 45 | ESP_LOGI("", "bDescriptorType(device): %d", dev_desc->bDescriptorType); 46 | ESP_LOGI("", "bcdUSB: 0x%x", dev_desc->bcdUSB); 47 | ESP_LOGI("", "bDeviceClass: 0x%02x", dev_desc->bDeviceClass); 48 | ESP_LOGI("", "bDeviceSubClass: 0x%02x", dev_desc->bDeviceSubClass); 49 | ESP_LOGI("", "bDeviceProtocol: 0x%02x", dev_desc->bDeviceProtocol); 50 | ESP_LOGI("", "bMaxPacketSize0: %d", dev_desc->bMaxPacketSize0); 51 | ESP_LOGI("", "idVendor: 0x%x", dev_desc->idVendor); 52 | ESP_LOGI("", "idProduct: 0x%x", dev_desc->idProduct); 53 | ESP_LOGI("", "bcdDevice: 0x%x", dev_desc->bcdDevice); 54 | ESP_LOGI("", "iManufacturer: %d", dev_desc->iManufacturer); 55 | ESP_LOGI("", "iProduct: %d", dev_desc->iProduct); 56 | ESP_LOGI("", "iSerialNumber: %d", dev_desc->iSerialNumber); 57 | ESP_LOGI("", "bNumConfigurations: %d", dev_desc->bNumConfigurations); 58 | } 59 | 60 | void show_config_desc(const void *p) 61 | { 62 | const usb_config_desc_t *config_desc = (const usb_config_desc_t *)p; 63 | 64 | ESP_LOGI("", "bLength: %d", config_desc->bLength); 65 | ESP_LOGI("", "bDescriptorType(config): %d", config_desc->bDescriptorType); 66 | ESP_LOGI("", "wTotalLength: %d", config_desc->wTotalLength); 67 | ESP_LOGI("", "bNumInterfaces: %d", config_desc->bNumInterfaces); 68 | ESP_LOGI("", "bConfigurationValue: %d", config_desc->bConfigurationValue); 69 | ESP_LOGI("", "iConfiguration: %d", config_desc->iConfiguration); 70 | ESP_LOGI("", "bmAttributes(%s%s%s): 0x%02x", 71 | (config_desc->bmAttributes & USB_BM_ATTRIBUTES_SELFPOWER)?"Self Powered":"", 72 | (config_desc->bmAttributes & USB_BM_ATTRIBUTES_WAKEUP)?", Remote Wakeup":"", 73 | (config_desc->bmAttributes & USB_BM_ATTRIBUTES_BATTERY)?", Battery Powered":"", 74 | config_desc->bmAttributes); 75 | ESP_LOGI("", "bMaxPower: %d = %d mA", config_desc->bMaxPower, config_desc->bMaxPower*2); 76 | } 77 | 78 | uint8_t show_interface_desc(const void *p) 79 | { 80 | const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; 81 | 82 | ESP_LOGI("", "bLength: %d", intf->bLength); 83 | ESP_LOGI("", "bDescriptorType (interface): %d", intf->bDescriptorType); 84 | ESP_LOGI("", "bInterfaceNumber: %d", intf->bInterfaceNumber); 85 | ESP_LOGI("", "bAlternateSetting: %d", intf->bAlternateSetting); 86 | ESP_LOGI("", "bNumEndpoints: %d", intf->bNumEndpoints); 87 | ESP_LOGI("", "bInterfaceClass: 0x%02x", intf->bInterfaceClass); 88 | ESP_LOGI("", "bInterfaceSubClass: 0x%02x", intf->bInterfaceSubClass); 89 | ESP_LOGI("", "bInterfaceProtocol: 0x%02x", intf->bInterfaceProtocol); 90 | ESP_LOGI("", "iInterface: %d", intf->iInterface); 91 | return intf->bInterfaceClass; 92 | } 93 | 94 | void show_endpoint_desc(const void *p) 95 | { 96 | const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; 97 | const char *XFER_TYPE_NAMES[] = { 98 | "Control", "Isochronous", "Bulk", "Interrupt" 99 | }; 100 | ESP_LOGI("", "bLength: %d", endpoint->bLength); 101 | ESP_LOGI("", "bDescriptorType (endpoint): %d", endpoint->bDescriptorType); 102 | ESP_LOGI("", "bEndpointAddress(%s): 0x%02x", 103 | (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK)?"In":"Out", 104 | endpoint->bEndpointAddress); 105 | ESP_LOGI("", "bmAttributes(%s): 0x%02x", 106 | XFER_TYPE_NAMES[endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK], 107 | endpoint->bmAttributes); 108 | ESP_LOGI("", "wMaxPacketSize: %d", endpoint->wMaxPacketSize); 109 | ESP_LOGI("", "bInterval: %d", endpoint->bInterval); 110 | } 111 | 112 | void show_hid_desc(const void *p) 113 | { 114 | usb_hid_desc_t *hid = (usb_hid_desc_t *)p; 115 | ESP_LOGI("", "bLength: %d", hid->bLength); 116 | ESP_LOGI("", "bDescriptorType (HID): %d", hid->bDescriptorType); 117 | ESP_LOGI("", "bcdHID: 0x%04x", hid->bcdHID); 118 | ESP_LOGI("", "bCountryCode: %d", hid->bCountryCode); 119 | ESP_LOGI("", "bNumDescriptor: %d", hid->bNumDescriptor); 120 | ESP_LOGI("", "bDescriptorType: %d", hid->bHIDDescriptorType); 121 | ESP_LOGI("", "wDescriptorLength: %d", hid->wHIDDescriptorLength); 122 | if (hid->bNumDescriptor > 1) { 123 | ESP_LOGI("", "bDescriptorTypeOpt: %d", hid->bHIDDescriptorTypeOpt); 124 | ESP_LOGI("", "wDescriptorLengthOpt: %d", hid->wHIDDescriptorLengthOpt); 125 | } 126 | } 127 | 128 | void show_interface_assoc(const void *p) 129 | { 130 | usb_iad_desc_t *iad = (usb_iad_desc_t *)p; 131 | ESP_LOGI("", "bLength: %d", iad->bLength); 132 | ESP_LOGI("", "bDescriptorType: %d", iad->bDescriptorType); 133 | ESP_LOGI("", "bFirstInterface: %d", iad->bFirstInterface); 134 | ESP_LOGI("", "bInterfaceCount: %d", iad->bInterfaceCount); 135 | ESP_LOGI("", "bFunctionClass: 0x%02x", iad->bFunctionClass); 136 | ESP_LOGI("", "bFunctionSubClass: 0x%02x", iad->bFunctionSubClass); 137 | ESP_LOGI("", "bFunctionProtocol: 0x%02x", iad->bFunctionProtocol); 138 | ESP_LOGI("", "iFunction: %d", iad->iFunction); 139 | } 140 | -------------------------------------------------------------------------------- /examples/usbhmidi/usbhmidi.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 touchgadgetdev@gmail.com 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include "show_desc.hpp" 26 | #include "usbhhelp.hpp" 27 | 28 | //#define MIDIOUTTEST 1 29 | #if MIDIOUTTEST 30 | #include 31 | elapsedMillis MIDIOutTimer; 32 | #endif 33 | 34 | bool isMIDI = false; 35 | bool isMIDIReady = false; 36 | 37 | const size_t MIDI_IN_BUFFERS = 8; 38 | const size_t MIDI_OUT_BUFFERS = 8; 39 | usb_transfer_t *MIDIOut = NULL; 40 | usb_transfer_t *MIDIIn[MIDI_IN_BUFFERS] = {NULL}; 41 | 42 | // USB MIDI Event Packet Format (always 4 bytes) 43 | // 44 | // Byte 0 |Byte 1 |Byte 2 |Byte 3 45 | // -------|-------|-------|------ 46 | // CN+CIN |MIDI_0 |MIDI_1 |MIDI_2 47 | // 48 | // CN = Cable Number (0x0..0xf) specifies virtual MIDI jack/cable 49 | // CIN = Code Index Number (0x0..0xf) classifies the 3 MIDI bytes. 50 | // See Table 4-1 in the MIDI 1.0 spec at usb.org. 51 | // 52 | 53 | static void midi_transfer_cb(usb_transfer_t *transfer) 54 | { 55 | ESP_LOGI("", "midi_transfer_cb context: %d", transfer->context); 56 | if (Device_Handle == transfer->device_handle) { 57 | int in_xfer = transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; 58 | if ((transfer->status == 0) && in_xfer) { 59 | uint8_t *const p = transfer->data_buffer; 60 | for (int i = 0; i < transfer->actual_num_bytes; i += 4) { 61 | if ((p[i] + p[i+1] + p[i+2] + p[i+3]) == 0) break; 62 | ESP_LOGI("", "midi: %02x %02x %02x %02x", 63 | p[i], p[i+1], p[i+2], p[i+3]); 64 | } 65 | esp_err_t err = usb_host_transfer_submit(transfer); 66 | if (err != ESP_OK) { 67 | ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err); 68 | } 69 | } 70 | else { 71 | ESP_LOGI("", "transfer->status %d", transfer->status); 72 | } 73 | } 74 | } 75 | 76 | void check_interface_desc_MIDI(const void *p) 77 | { 78 | const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; 79 | 80 | // USB MIDI 81 | if ((intf->bInterfaceClass == USB_CLASS_AUDIO) && 82 | (intf->bInterfaceSubClass == 3) && 83 | (intf->bInterfaceProtocol == 0)) 84 | { 85 | isMIDI = true; 86 | ESP_LOGI("", "Claiming a MIDI device!"); 87 | esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle, 88 | intf->bInterfaceNumber, intf->bAlternateSetting); 89 | if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err); 90 | } 91 | } 92 | 93 | void prepare_endpoints(const void *p) 94 | { 95 | const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; 96 | esp_err_t err; 97 | 98 | // must be bulk for MIDI 99 | if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_BULK) { 100 | ESP_LOGI("", "Not bulk endpoint: 0x%02x", endpoint->bmAttributes); 101 | return; 102 | } 103 | if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { 104 | for (int i = 0; i < MIDI_IN_BUFFERS; i++) { 105 | err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIIn[i]); 106 | if (err != ESP_OK) { 107 | MIDIIn[i] = NULL; 108 | ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err); 109 | } 110 | else { 111 | MIDIIn[i]->device_handle = Device_Handle; 112 | MIDIIn[i]->bEndpointAddress = endpoint->bEndpointAddress; 113 | MIDIIn[i]->callback = midi_transfer_cb; 114 | MIDIIn[i]->context = (void *)i; 115 | MIDIIn[i]->num_bytes = endpoint->wMaxPacketSize; 116 | esp_err_t err = usb_host_transfer_submit(MIDIIn[i]); 117 | if (err != ESP_OK) { 118 | ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err); 119 | } 120 | } 121 | } 122 | } 123 | else { 124 | err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIOut); 125 | if (err != ESP_OK) { 126 | MIDIOut = NULL; 127 | ESP_LOGI("", "usb_host_transfer_alloc Out fail: %x", err); 128 | return; 129 | } 130 | ESP_LOGI("", "Out data_buffer_size: %d", MIDIOut->data_buffer_size); 131 | MIDIOut->device_handle = Device_Handle; 132 | MIDIOut->bEndpointAddress = endpoint->bEndpointAddress; 133 | MIDIOut->callback = midi_transfer_cb; 134 | MIDIOut->context = NULL; 135 | // MIDIOut->flags |= USB_TRANSFER_FLAG_ZERO_PACK; 136 | } 137 | isMIDIReady = ((MIDIOut != NULL) && (MIDIIn[0] != NULL)); 138 | } 139 | 140 | void show_config_desc_full(const usb_config_desc_t *config_desc) 141 | { 142 | // Full decode of config desc. 143 | const uint8_t *p = &config_desc->val[0]; 144 | uint8_t bLength; 145 | for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { 146 | bLength = *p; 147 | if ((i + bLength) <= config_desc->wTotalLength) { 148 | const uint8_t bDescriptorType = *(p + 1); 149 | switch (bDescriptorType) { 150 | case USB_B_DESCRIPTOR_TYPE_DEVICE: 151 | ESP_LOGI("", "USB Device Descriptor should not appear in config"); 152 | break; 153 | case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: 154 | show_config_desc(p); 155 | break; 156 | case USB_B_DESCRIPTOR_TYPE_STRING: 157 | ESP_LOGI("", "USB string desc TBD"); 158 | break; 159 | case USB_B_DESCRIPTOR_TYPE_INTERFACE: 160 | show_interface_desc(p); 161 | if (!isMIDI) check_interface_desc_MIDI(p); 162 | break; 163 | case USB_B_DESCRIPTOR_TYPE_ENDPOINT: 164 | show_endpoint_desc(p); 165 | if (isMIDI && !isMIDIReady) { 166 | prepare_endpoints(p); 167 | } 168 | break; 169 | case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: 170 | // Should not be in config? 171 | ESP_LOGI("", "USB device qual desc TBD"); 172 | break; 173 | case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION: 174 | // Should not be in config? 175 | ESP_LOGI("", "USB Other Speed TBD"); 176 | break; 177 | case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER: 178 | // Should not be in config? 179 | ESP_LOGI("", "USB Interface Power TBD"); 180 | break; 181 | default: 182 | ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", *p); 183 | break; 184 | } 185 | } 186 | else { 187 | ESP_LOGI("", "USB Descriptor invalid"); 188 | return; 189 | } 190 | } 191 | } 192 | 193 | void setup() 194 | { 195 | usbh_setup(show_config_desc_full); 196 | } 197 | 198 | void loop() 199 | { 200 | usbh_task(); 201 | 202 | #ifdef MIDIOUTTEST 203 | if (isMIDIReady && (MIDIOutTimer > 1000)) { 204 | ESP_LOGI("", "MIDI send 4 bytes"); 205 | MIDIOut->num_bytes = 4; 206 | memcpy(MIDIOut->data_buffer, "\x09\x90\x3c\x7a", 4); 207 | err = usb_host_transfer_submit(MIDIOut); 208 | if (err != ESP_OK) { 209 | ESP_LOGI("", "usb_host_transfer_submit Out fail: %x", err); 210 | } 211 | MIDIOutTimer = 0; 212 | } 213 | #endif 214 | } 215 | --------------------------------------------------------------------------------