diff --git a/cores/esp32/esp32-hal-tinyusb.c b/cores/esp32/esp32-hal-tinyusb.c new file mode 100644 index 00000000..0f55fa27 --- /dev/null +++ b/cores/esp32/esp32-hal-tinyusb.c @@ -0,0 +1,682 @@ + +#include "sdkconfig.h" +#if CONFIG_USB_ENABLED +#include + +#include "esp_log.h" + +#include "soc/soc.h" +#include "soc/efuse_reg.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/usb_struct.h" +#include "soc/usb_reg.h" +#include "soc/usb_wrap_reg.h" +#include "soc/usb_wrap_struct.h" +#include "soc/periph_defs.h" +#include "soc/timer_group_struct.h" +#include "soc/system_reg.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" + +#include "esp_efuse.h" +#include "esp_efuse_table.h" + +#include "tinyusb.h" +#include "esp32-hal.h" + +#include "esp32-hal-tinyusb.h" + +typedef char tusb_str_t[127]; + +static bool WEBUSB_ENABLED = false; + +static tusb_str_t WEBUSB_URL = ""; +static tusb_str_t USB_DEVICE_PRODUCT = ""; +static tusb_str_t USB_DEVICE_MANUFACTURER = ""; +static tusb_str_t USB_DEVICE_SERIAL = ""; + +static uint8_t USB_DEVICE_ATTRIBUTES = 0; +static uint16_t USB_DEVICE_POWER = 0; + +/* + * Device Descriptor + * */ +static tusb_desc_device_t tinyusb_device_descriptor = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = CFG_TUD_ENDOINT0_SIZE, + + .idVendor = 0, + .idProduct = 0, + .bcdDevice = 0, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +/* + * String Descriptors + * */ +#define MAX_STRING_DESCRIPTORS 20 +static uint32_t tinyusb_string_descriptor_len = 4; +static char * tinyusb_string_descriptor[MAX_STRING_DESCRIPTORS] = { + // array of pointer to string descriptors + "\x09\x04", // 0: is supported language is English (0x0409) + USB_DEVICE_MANUFACTURER,// 1: Manufacturer + USB_DEVICE_PRODUCT, // 2: Product + USB_DEVICE_SERIAL, // 3: Serials, should use chip ID +}; + + +/* Microsoft OS 2.0 registry property descriptor +Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx +device should create DeviceInterfaceGUIDs. It can be done by driver and +in case of real PnP solution device should expose MS "Microsoft OS 2.0 +registry property descriptor". Such descriptor can insert any record +into Windows registry per device/configuration/interface. In our case it +will insert "DeviceInterfaceGUIDs" multistring property. + +GUID is freshly generated and should be OK to use. + +https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ +(Section Microsoft OS compatibility descriptors) + */ + +#define MS_OS_20_DESC_LEN 0xB2 + +static uint8_t const tinyusb_ms_os_20_descriptor[] = +{ + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A), + + // Function Subset header: length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08-0x08-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), + U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, + 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + //bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. + '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00, + '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, + '8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00, + '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +TU_VERIFY_STATIC(sizeof(tinyusb_ms_os_20_descriptor) == MS_OS_20_DESC_LEN, "Incorrect size"); + + +/* + * BOS Descriptor (required for webUSB) + * */ +#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) + +enum { + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2 +}; + +static uint8_t const tinyusb_bos_descriptor[] = { + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), + + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) +}; + +/* + * URL Descriptor (required for webUSB) + * */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bScheme; + char url[127]; +} tinyusb_desc_webusb_url_t; + +static tinyusb_desc_webusb_url_t tinyusb_url_descriptor = { + .bLength = 3, + .bDescriptorType = 3, // WEBUSB URL type + .bScheme = 1, // URL Scheme Prefix: 0: "http://", 1: "https://", 255: "" + .url = "" +}; + +/* + * Configuration Descriptor + * */ +static uint32_t tinyusb_loaded_interfaces_mask = 0; +static uint8_t tinyusb_loaded_interfaces_num = 0; +static uint16_t tinyusb_config_descriptor_len = 0; +static uint8_t * tinyusb_config_descriptor = NULL; + +/* + * Endpoint Usage Tracking + * */ +typedef union { + struct { + uint32_t in:16; + uint32_t out:16; + }; + uint32_t val; +} tinyusb_endpoints_usage_t; + +static tinyusb_endpoints_usage_t tinyusb_endpoints; + + +/* + * TinyUSB Callbacks + * */ + +/** + * @brief Invoked when received GET CONFIGURATION DESCRIPTOR. + */ +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + //log_d("%u", index); + return tinyusb_config_descriptor; +} + +/** + * @brief Invoked when received GET DEVICE DESCRIPTOR. + */ +uint8_t const *tud_descriptor_device_cb(void) +{ + //log_d(""); + return (uint8_t const *)&tinyusb_device_descriptor; +} + +/** + * @brief Invoked when received GET STRING DESCRIPTOR request. + */ +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + //log_d("%u (0x%x)", index, langid); + static uint16_t _desc_str[127]; + uint8_t chr_count; + + if (index == 0) { + memcpy(&_desc_str[1], tinyusb_string_descriptor[0], 2); + chr_count = 1; + } else { + // Convert ASCII string into UTF-16 + if (index >= tinyusb_string_descriptor_len) { + return NULL; + } + const char *str = tinyusb_string_descriptor[index]; + // Cap at max char + chr_count = strlen(str); + if (chr_count > 126) { + chr_count = 126; + } + for (uint8_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + } + + // first byte is len, second byte is string type + _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2); + + return _desc_str; +} + +/** + * @brief Invoked when received GET BOS DESCRIPTOR request. + */ +uint8_t const * tud_descriptor_bos_cb(void) +{ + //log_d(""); + return tinyusb_bos_descriptor; +} + +__attribute__ ((weak)) bool tinyusb_vendor_control_request_cb(uint8_t rhport, tusb_control_request_t const * request){ return false; } +__attribute__ ((weak)) bool tinyusb_vendor_control_complete_cb(uint8_t rhport, tusb_control_request_t const * request){ return true; } + +/** + * @brief Handle WebUSB and Vendor requests. + */ +bool tud_vendor_control_request_cb(uint8_t rhport, tusb_control_request_t const * request) +{ + if(WEBUSB_ENABLED && (request->bRequest == VENDOR_REQUEST_WEBUSB + || (request->bRequest == VENDOR_REQUEST_MICROSOFT && request->wIndex == 7))){ + if(request->bRequest == VENDOR_REQUEST_WEBUSB){ + // match vendor request in BOS descriptor + // Get landing page url + tinyusb_url_descriptor.bLength = 3 + strlen(WEBUSB_URL); + snprintf(tinyusb_url_descriptor.url, 126, "%s", WEBUSB_URL); + return tud_control_xfer(rhport, request, (void*) &tinyusb_url_descriptor, tinyusb_url_descriptor.bLength); + } + // Get Microsoft OS 2.0 compatible descriptor + uint16_t total_len; + memcpy(&total_len, tinyusb_ms_os_20_descriptor + 8, 2); + return tud_control_xfer(rhport, request, (void*) tinyusb_ms_os_20_descriptor, total_len); + } + return tinyusb_vendor_control_request_cb(rhport, request); +} + +bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_request_t const * request) +{ + if(!WEBUSB_ENABLED || !(request->bRequest == VENDOR_REQUEST_WEBUSB + || (request->bRequest == VENDOR_REQUEST_MICROSOFT && request->wIndex == 7))){ + return tinyusb_vendor_control_complete_cb(rhport, request); + } + return true; +} + +/* + * Private API + * */ + +__attribute__ ((weak)) uint16_t tusb_cdc_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } +__attribute__ ((weak)) uint16_t tusb_dfu_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } +__attribute__ ((weak)) uint16_t tusb_hid_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } +__attribute__ ((weak)) uint16_t tusb_msc_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } +__attribute__ ((weak)) uint16_t tusb_midi_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } +__attribute__ ((weak)) uint16_t tusb_vendor_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } + +__attribute__ ((weak)) uint16_t tusb_custom_get_descriptor_len() { return 0; } +__attribute__ ((weak)) uint16_t tusb_custom_load_descriptor(uint8_t * dst, uint8_t * itf) { return 0; } + +static uint16_t tinyusb_load_descriptor(tinyusb_interface_t interface, uint8_t * dst, uint8_t * itf) +{ + switch (interface) { + case USB_INTERFACE_CDC: + return tusb_cdc_load_descriptor(dst, itf); + break; + case USB_INTERFACE_DFU: + return tusb_dfu_load_descriptor(dst, itf); + break; + case USB_INTERFACE_HID: + return tusb_hid_load_descriptor(dst, itf); + break; + case USB_INTERFACE_VENDOR: + return tusb_vendor_load_descriptor(dst, itf); + break; + case USB_INTERFACE_MSC: + return tusb_msc_load_descriptor(dst, itf); + break; + case USB_INTERFACE_MIDI: + return tusb_midi_load_descriptor(dst, itf); + break; + case USB_INTERFACE_CUSTOM: + return tusb_custom_load_descriptor(dst, itf); + break; + default: + + break; + } + return 0; +} + +static bool tinyusb_reserve_in_endpoint(uint8_t endpoint){ + if(endpoint > 6 || (tinyusb_endpoints.in & BIT(endpoint)) != 0){ + return false; + } + tinyusb_endpoints.in |= BIT(endpoint); + return true; +} + +static bool tinyusb_reserve_out_endpoint(uint8_t endpoint){ + if(endpoint > 6 || (tinyusb_endpoints.out & BIT(endpoint)) != 0){ + return false; + } + tinyusb_endpoints.out |= BIT(endpoint); + return true; +} + +static bool tinyusb_has_available_fifos(void){ + uint8_t max_endpoints = 4, active_endpoints = 0; + if (tinyusb_loaded_interfaces_mask & BIT(USB_INTERFACE_CDC)) { + max_endpoints = 5; //CDC endpoint 0x85 is actually not linked to FIFO and not used + } + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.in & BIT(i)) != 0){ + active_endpoints++; + } + } + + return active_endpoints < max_endpoints; +} + +static bool tinyusb_load_enabled_interfaces(){ + tinyusb_config_descriptor_len += TUD_CONFIG_DESC_LEN; + tinyusb_config_descriptor = (uint8_t *)malloc(tinyusb_config_descriptor_len); + if (tinyusb_config_descriptor == NULL) { + log_e("Descriptor Malloc Failed"); + return false; + } + uint8_t * dst = tinyusb_config_descriptor + TUD_CONFIG_DESC_LEN; + + for(int i=0; i> 4); + *srl++ = nibble_to_hex_char(b & 0xf); + } + *srl++ = '\0'; +} + +static void tinyusb_apply_device_config(tinyusb_device_config_t *config){ + if(config->product_name){ + snprintf(USB_DEVICE_PRODUCT, 126, "%s", config->product_name); + } + + if(config->manufacturer_name){ + snprintf(USB_DEVICE_MANUFACTURER, 126, "%s", config->manufacturer_name); + } + + if(config->serial_number && config->serial_number[0]){ + snprintf(USB_DEVICE_SERIAL, 126, "%s", config->serial_number); + } else { + set_usb_serial_num(); + } + + if(config->webusb_url){ + snprintf(WEBUSB_URL, 126, "%s", config->webusb_url); + } + + WEBUSB_ENABLED = config->webusb_enabled; + USB_DEVICE_ATTRIBUTES = config->usb_attributes; + USB_DEVICE_POWER = config->usb_power_ma; + + tinyusb_device_descriptor.bcdUSB = config->usb_version; + tinyusb_device_descriptor.idVendor = config->vid; + tinyusb_device_descriptor.idProduct = config->pid; + tinyusb_device_descriptor.bcdDevice = config->fw_version; + tinyusb_device_descriptor.bDeviceClass = config->usb_class; + tinyusb_device_descriptor.bDeviceSubClass = config->usb_subclass; + tinyusb_device_descriptor.bDeviceProtocol = config->usb_protocol; +} + +// USB Device Driver task +// This top level thread processes all usb events and invokes callbacks +static void usb_device_task(void *param) { + (void)param; + while(1) tud_task(); // RTOS forever loop +} + +/* + * PUBLIC API + * */ + +esp_err_t tinyusb_init(tinyusb_device_config_t *config) { + static bool initialized = false; + if(initialized){ + return ESP_OK; + } + initialized = true; + + tinyusb_endpoints.val = 0; + tinyusb_apply_device_config(config); + if (!tinyusb_load_enabled_interfaces()) { + initialized = false; + return ESP_FAIL; + } + tinyusb_config_t tusb_cfg = { + .external_phy = false // In the most cases you need to use a `false` value + }; + esp_err_t err = tinyusb_driver_install(&tusb_cfg); + if (err != ESP_OK) { + initialized = false; + return err; + } + xTaskCreate(usb_device_task, "usbd", 4096, NULL, 24, NULL); + return err; +} + +esp_err_t tinyusb_enable_interface(tinyusb_interface_t interface) +{ + uint16_t descriptor_len = 0; + switch (interface) { + case USB_INTERFACE_CDC: + descriptor_len = CFG_TUD_CDC * TUD_CDC_DESC_LEN; + break; + case USB_INTERFACE_DFU: + descriptor_len = CFG_TUD_DFU_RT * TUD_DFU_RT_DESC_LEN; + break; + case USB_INTERFACE_HID: + descriptor_len = CFG_TUD_HID * TUD_HID_DESC_LEN; + break; + case USB_INTERFACE_MSC: + descriptor_len = CFG_TUD_MSC * TUD_MSC_DESC_LEN; + break; + case USB_INTERFACE_MIDI: + descriptor_len = CFG_TUD_MIDI * TUD_MIDI_DESC_LEN; + break; + case USB_INTERFACE_VENDOR: + descriptor_len = CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN; + break; + case USB_INTERFACE_CUSTOM: + descriptor_len = tusb_custom_get_descriptor_len(); + break; + default: + + break; + } + if (descriptor_len) { + tinyusb_config_descriptor_len += descriptor_len; + tinyusb_loaded_interfaces_mask |= (1U << interface); + log_d("Driver %u enabled", interface); + return ESP_OK; + } + log_e("Driver %u not enabled", interface); + return ESP_FAIL; +} + +uint8_t tinyusb_add_string_descriptor(const char * str){ + if(str == NULL || tinyusb_string_descriptor_len >= MAX_STRING_DESCRIPTORS){ + return 0; + } + uint8_t index = tinyusb_string_descriptor_len; + tinyusb_string_descriptor[tinyusb_string_descriptor_len++] = (char*)str; + return index; +} + +uint8_t tinyusb_get_free_duplex_endpoint(void){ + if(!tinyusb_has_available_fifos()){ + log_e("No available IN endpoints"); + return 0; + } + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.in & BIT(i)) == 0 && (tinyusb_endpoints.out & BIT(i)) == 0){ + tinyusb_endpoints.in |= BIT(i); + tinyusb_endpoints.out |= BIT(i); + return i; + } + } + log_e("No available duplex endpoints"); + return 0; +} + +uint8_t tinyusb_get_free_in_endpoint(void){ + if(!tinyusb_has_available_fifos()){ + log_e("No available IN endpoints"); + return 0; + } + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.in & BIT(i)) == 0 && (tinyusb_endpoints.out & BIT(i)) != 0){ + tinyusb_endpoints.in |= BIT(i); + return i; + } + } + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.in & BIT(i)) == 0){ + tinyusb_endpoints.in |= BIT(i); + return i; + } + } + return 0; +} + +uint8_t tinyusb_get_free_out_endpoint(void){ + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.out & BIT(i)) == 0 && (tinyusb_endpoints.in & BIT(i)) != 0){ + tinyusb_endpoints.out |= BIT(i); + return i; + } + } + for(uint8_t i=1; i<7; i++){ + if((tinyusb_endpoints.out & BIT(i)) == 0){ + tinyusb_endpoints.out |= BIT(i); + return i; + } + } + return 0; +} + +/* +void usb_dw_reg_dump(void) +{ +#define USB_PRINT_REG(r) printf("USB0." #r " = 0x%x;\n", USB0.r) +#define USB_PRINT_IREG(i, r) printf("USB0.in_ep_reg[%u]." #r " = 0x%x;\n", i, USB0.in_ep_reg[i].r) +#define USB_PRINT_OREG(i, r) printf("USB0.out_ep_reg[%u]." #r " = 0x%x;\n", i, USB0.out_ep_reg[i].r) + uint8_t i; + USB_PRINT_REG(gotgctl); + USB_PRINT_REG(gotgint); + USB_PRINT_REG(gahbcfg); + USB_PRINT_REG(gusbcfg); + USB_PRINT_REG(grstctl); + USB_PRINT_REG(gintsts); + USB_PRINT_REG(gintmsk); + USB_PRINT_REG(grxstsr); + USB_PRINT_REG(grxstsp); + USB_PRINT_REG(grxfsiz); + USB_PRINT_REG(gnptxsts); + USB_PRINT_REG(gpvndctl); + USB_PRINT_REG(ggpio); + USB_PRINT_REG(guid); + USB_PRINT_REG(gsnpsid); + USB_PRINT_REG(ghwcfg1); + USB_PRINT_REG(ghwcfg2); + USB_PRINT_REG(ghwcfg3); + USB_PRINT_REG(ghwcfg4); + USB_PRINT_REG(glpmcfg); + USB_PRINT_REG(gpwrdn); + USB_PRINT_REG(gdfifocfg); + USB_PRINT_REG(gadpctl); + USB_PRINT_REG(hptxfsiz); + USB_PRINT_REG(hcfg); + USB_PRINT_REG(hfir); + USB_PRINT_REG(hfnum); + USB_PRINT_REG(hptxsts); + USB_PRINT_REG(haint); + USB_PRINT_REG(haintmsk); + USB_PRINT_REG(hflbaddr); + USB_PRINT_REG(hprt); + USB_PRINT_REG(dcfg); + USB_PRINT_REG(dctl); + USB_PRINT_REG(dsts); + USB_PRINT_REG(diepmsk); + USB_PRINT_REG(doepmsk); + USB_PRINT_REG(daint); + USB_PRINT_REG(daintmsk); + USB_PRINT_REG(dtknqr1); + USB_PRINT_REG(dtknqr2); + USB_PRINT_REG(dvbusdis); + USB_PRINT_REG(dvbuspulse); + USB_PRINT_REG(dtknqr3_dthrctl); + USB_PRINT_REG(dtknqr4_fifoemptymsk); + USB_PRINT_REG(deachint); + USB_PRINT_REG(deachintmsk); + USB_PRINT_REG(pcgctrl); + USB_PRINT_REG(pcgctrl1); + USB_PRINT_REG(gnptxfsiz); + for (i = 0; i < 4; i++) { + printf("USB0.dieptxf[%u] = 0x%x;\n", i, USB0.dieptxf[i]); + } +// for (i = 0; i < 16; i++) { +// printf("USB0.diepeachintmsk[%u] = 0x%x;\n", i, USB0.diepeachintmsk[i]); +// } +// for (i = 0; i < 16; i++) { +// printf("USB0.doepeachintmsk[%u] = 0x%x;\n", i, USB0.doepeachintmsk[i]); +// } + for (i = 0; i < 7; i++) { + printf("// EP %u:\n", i); + USB_PRINT_IREG(i, diepctl); + USB_PRINT_IREG(i, diepint); + USB_PRINT_IREG(i, dieptsiz); + USB_PRINT_IREG(i, diepdma); + USB_PRINT_IREG(i, dtxfsts); + USB_PRINT_OREG(i, doepctl); + USB_PRINT_OREG(i, doepint); + USB_PRINT_OREG(i, doeptsiz); + USB_PRINT_OREG(i, doepdma); + } +} + */ +#endif /* CONFIG_USB_ENABLED */ diff --git a/cores/esp32/esp32-hal-tinyusb.h b/cores/esp32/esp32-hal-tinyusb.h new file mode 100644 index 00000000..f75dabd3 --- /dev/null +++ b/cores/esp32/esp32-hal-tinyusb.h @@ -0,0 +1,86 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "esp32-hal.h" + +#if CONFIG_IDF_TARGET_ESP32S2 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tinyusb.h" + +typedef struct { + uint16_t vid; + uint16_t pid; + const char * product_name; + const char * manufacturer_name; + const char * serial_number; + uint16_t fw_version; + + uint16_t usb_version; + uint8_t usb_class; + uint8_t usb_subclass; + uint8_t usb_protocol; + uint8_t usb_attributes; + uint16_t usb_power_ma; + + bool webusb_enabled; + const char * webusb_url; +} tinyusb_device_config_t; + +#define TINYUSB_CONFIG_DEFAULT() { \ + .vid = USB_ESPRESSIF_VID, \ + .pid = 0x0002, \ + .product_name = CONFIG_USB_DESC_PRODUCT_STRING, \ + .manufacturer_name = CONFIG_USB_DESC_MANUFACTURER_STRING, \ + .serial_number = CONFIG_USB_DESC_SERIAL_STRING, \ + .fw_version = CONFIG_USB_DESC_BCDDEVICE, \ + .usb_version = 0x0200, \ + .usb_class = TUSB_CLASS_MISC, \ + .usb_subclass = MISC_SUBCLASS_COMMON, \ + .usb_protocol = MISC_PROTOCOL_IAD, \ + .usb_attributes = TUSB_DESC_CONFIG_ATT_SELF_POWERED, \ + .usb_power_ma = 500, \ + .webusb_enabled = false, \ + .webusb_url = "espressif.github.io/arduino-esp32/webusb.html" \ +} + +esp_err_t tinyusb_init(tinyusb_device_config_t *config); + +// The following definitions and functions are to be used only by the drivers +typedef enum { + USB_INTERFACE_CDC, + USB_INTERFACE_DFU, + USB_INTERFACE_HID, + USB_INTERFACE_VENDOR, + USB_INTERFACE_MSC, + USB_INTERFACE_MIDI, + USB_INTERFACE_CUSTOM, + USB_INTERFACE_MAX +} tinyusb_interface_t; + +esp_err_t tinyusb_enable_interface(tinyusb_interface_t interface); +uint8_t tinyusb_add_string_descriptor(const char * str); +uint8_t tinyusb_get_free_duplex_endpoint(void); +uint8_t tinyusb_get_free_in_endpoint(void); +uint8_t tinyusb_get_free_out_endpoint(void); + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_IDF_TARGET_ESP32S2 */ diff --git a/libraries/USB/examples/USBSerial/.skip.esp32 b/libraries/USB/examples/USBSerial/.skip.esp32 new file mode 100644 index 00000000..e69de29b diff --git a/libraries/USB/examples/USBSerial/USBSerial.ino b/libraries/USB/examples/USBSerial/USBSerial.ino new file mode 100644 index 00000000..2309bce7 --- /dev/null +++ b/libraries/USB/examples/USBSerial/USBSerial.ino @@ -0,0 +1,74 @@ +#include "USB.h" +USBCDC USBSerial; + +static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data){ + if(event_base == ARDUINO_USB_EVENTS){ + arduino_usb_event_data_t * data = (arduino_usb_event_data_t*)event_data; + switch (event_id){ + case ARDUINO_USB_STARTED_EVENT: + Serial.println("USB PLUGGED"); + break; + case ARDUINO_USB_STOPPED_EVENT: + Serial.println("USB UNPLUGGED"); + break; + case ARDUINO_USB_SUSPEND_EVENT: + Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en); + break; + case ARDUINO_USB_RESUME_EVENT: + Serial.println("USB RESUMED"); + break; + + default: + break; + } + } else if(event_base == ARDUINO_USB_CDC_EVENTS){ + arduino_usb_cdc_event_data_t * data = (arduino_usb_cdc_event_data_t*)event_data; + switch (event_id){ + case ARDUINO_USB_CDC_CONNECTED_EVENT: + Serial.println("CDC CONNECTED"); + break; + case ARDUINO_USB_CDC_DISCONNECTED_EVENT: + Serial.println("CDC DISCONNECTED"); + break; + case ARDUINO_USB_CDC_LINE_STATE_EVENT: + Serial.printf("CDC LINE STATE: dtr: %u, rts: %u\n", data->line_state.dtr, data->line_state.rts); + break; + case ARDUINO_USB_CDC_LINE_CODING_EVENT: + Serial.printf("CDC LINE CODING: bit_rate: %u, data_bits: %u, stop_bits: %u, parity: %u\n", data->line_coding.bit_rate, data->line_coding.data_bits, data->line_coding.stop_bits, data->line_coding.parity); + break; + case ARDUINO_USB_CDC_RX_EVENT: + Serial.printf("CDC RX: %u\n", data->rx.len); + { + uint8_t buf[data->rx.len]; + size_t len = USBSerial.read(buf, data->rx.len); + Serial.write(buf, len); + } + break; + + default: + break; + } + } +} + + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + + USB.onEvent(usbEventCallback); + USB.productName("ESP32S2-USB"); + USB.begin(); + + USBSerial.onEvent(usbEventCallback); + USBSerial.begin(); +} + +void loop() { + while(Serial.available()){ + size_t l = Serial.available(); + uint8_t b[l]; + l = Serial.read(b, l); + USBSerial.write(b, l); + } +} diff --git a/libraries/USB/keywords.txt b/libraries/USB/keywords.txt new file mode 100644 index 00000000..8b1442df --- /dev/null +++ b/libraries/USB/keywords.txt @@ -0,0 +1,24 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +USB KEYWORD1 +USBCDC KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +end KEYWORD2 +onEvent KEYWORD2 +enableReset KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/libraries/USB/library.properties b/libraries/USB/library.properties new file mode 100644 index 00000000..a3aac631 --- /dev/null +++ b/libraries/USB/library.properties @@ -0,0 +1,9 @@ +name=USB +version=1.0 +author=Hristo Gochkov +maintainer=Hristo Gochkov +sentence=ESP32S2 USB Library +paragraph= +category=Communication +url= +architectures=esp32 diff --git a/libraries/USB/src/USB.cpp b/libraries/USB/src/USB.cpp new file mode 100644 index 00000000..736ab60a --- /dev/null +++ b/libraries/USB/src/USB.cpp @@ -0,0 +1,287 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp32-hal.h" +#include "esp32-hal-tinyusb.h" +#include "USB.h" +#if CONFIG_USB_ENABLED + +extern "C" { +#include "tinyusb.h" +} + +ESP_EVENT_DEFINE_BASE(ARDUINO_USB_EVENTS); + +static esp_event_loop_handle_t arduino_usb_event_loop_handle = NULL; + +esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait){ + if(arduino_usb_event_loop_handle == NULL){ + return ESP_FAIL; + } + return esp_event_post_to(arduino_usb_event_loop_handle, event_base, event_id, event_data, event_data_size, ticks_to_wait); +} +esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg){ + if(arduino_usb_event_loop_handle == NULL){ + return ESP_FAIL; + } + return esp_event_handler_register_with(arduino_usb_event_loop_handle, event_base, event_id, event_handler, event_handler_arg); +} + +static bool tinyusb_device_mounted = false; +static bool tinyusb_device_suspended = false; + +// Invoked when device is mounted (configured) +void tud_mount_cb(void){ + tinyusb_device_mounted = true; + arduino_usb_event_data_t p = {0}; + arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STARTED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); +} + +// Invoked when device is unmounted +void tud_umount_cb(void){ + tinyusb_device_mounted = false; + arduino_usb_event_data_t p = {0}; + arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); +} + +// Invoked when usb bus is suspended +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en){ + tinyusb_device_suspended = true; + arduino_usb_event_data_t p = {0}; + p.suspend.remote_wakeup_en = remote_wakeup_en; + arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_SUSPEND_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void){ + tinyusb_device_suspended = false; + arduino_usb_event_data_t p = {0}; + arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_RESUME_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); +} + +ESPUSB::ESPUSB(size_t task_stack_size, uint8_t event_task_priority) +:vid(USB_ESPRESSIF_VID) +,pid(0x0002) +,product_name(ARDUINO_BOARD) +,manufacturer_name("Espressif Systems") +,serial_number("") +,fw_version(0x0100) +,usb_version(0x0200)// at least 2.1 or 3.x for BOS & webUSB +,usb_class(TUSB_CLASS_MISC) +,usb_subclass(MISC_SUBCLASS_COMMON) +,usb_protocol(MISC_PROTOCOL_IAD) +,usb_attributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED) +,usb_power_ma(500) +,webusb_enabled(false) +,webusb_url("espressif.github.io/arduino-esp32/webusb.html") +,_started(false) +{ + esp_event_loop_args_t event_task_args = { + .queue_size = 5, + .task_name = "arduino_usb_events", + .task_priority = event_task_priority, + .task_stack_size = task_stack_size, + .task_core_id = tskNO_AFFINITY + }; + if (esp_event_loop_create(&event_task_args, &arduino_usb_event_loop_handle) != ESP_OK) { + log_e("esp_event_loop_create failed"); + } +} + +ESPUSB::~ESPUSB(){ + if (arduino_usb_event_loop_handle) { + esp_event_loop_delete(arduino_usb_event_loop_handle); + arduino_usb_event_loop_handle = NULL; + } +} + +bool ESPUSB::begin(){ + if(!_started){ + tinyusb_device_config_t tinyusb_device_config = { + .vid = vid, + .pid = pid, + .product_name = product_name.c_str(), + .manufacturer_name = manufacturer_name.c_str(), + .serial_number = serial_number.c_str(), + .fw_version = fw_version, + .usb_version = usb_version, + .usb_class = usb_class, + .usb_subclass = usb_subclass, + .usb_protocol = usb_protocol, + .usb_attributes = usb_attributes, + .usb_power_ma = usb_power_ma, + .webusb_enabled = webusb_enabled, + .webusb_url = webusb_url.c_str() + }; + _started = tinyusb_init(&tinyusb_device_config) == ESP_OK; + } + return _started; +} + +void ESPUSB::onEvent(esp_event_handler_t callback){ + onEvent(ARDUINO_USB_ANY_EVENT, callback); +} +void ESPUSB::onEvent(arduino_usb_event_t event, esp_event_handler_t callback){ + arduino_usb_event_handler_register_with(ARDUINO_USB_EVENTS, event, callback, this); +} + +ESPUSB::operator bool() const +{ + return _started && tinyusb_device_mounted; +} + + +bool ESPUSB::VID(uint16_t v){ + if(!_started){ + vid = v; + } + return !_started; +} +uint16_t ESPUSB::VID(void){ + return vid; +} + +bool ESPUSB::PID(uint16_t p){ + if(!_started){ + pid = p; + } + return !_started; +} +uint16_t ESPUSB::PID(void){ + return pid; +} + +bool ESPUSB::firmwareVersion(uint16_t version){ + if(!_started){ + fw_version = version; + } + return !_started; +} +uint16_t ESPUSB::firmwareVersion(void){ + return fw_version; +} + +bool ESPUSB::usbVersion(uint16_t version){ + if(!_started){ + usb_version = version; + } + return !_started; +} +uint16_t ESPUSB::usbVersion(void){ + return usb_version; +} + +bool ESPUSB::usbPower(uint16_t mA){ + if(!_started){ + usb_power_ma = mA; + } + return !_started; +} +uint16_t ESPUSB::usbPower(void){ + return usb_power_ma; +} + +bool ESPUSB::usbClass(uint8_t _class){ + if(!_started){ + usb_class = _class; + } + return !_started; +} +uint8_t ESPUSB::usbClass(void){ + return usb_class; +} + +bool ESPUSB::usbSubClass(uint8_t subClass){ + if(!_started){ + usb_subclass = subClass; + } + return !_started; +} +uint8_t ESPUSB::usbSubClass(void){ + return usb_subclass; +} + +bool ESPUSB::usbProtocol(uint8_t protocol){ + if(!_started){ + usb_protocol = protocol; + } + return !_started; +} +uint8_t ESPUSB::usbProtocol(void){ + return usb_protocol; +} + +bool ESPUSB::usbAttributes(uint8_t attr){ + if(!_started){ + usb_attributes = attr; + } + return !_started; +} +uint8_t ESPUSB::usbAttributes(void){ + return usb_attributes; +} + +bool ESPUSB::webUSB(bool enabled){ + if(!_started){ + webusb_enabled = enabled; + } + return !_started; +} +bool ESPUSB::webUSB(void){ + return webusb_enabled; +} + +bool ESPUSB::productName(const char * name){ + if(!_started){ + product_name = name; + } + return !_started; +} +const char * ESPUSB::productName(void){ + return product_name.c_str(); +} + +bool ESPUSB::manufacturerName(const char * name){ + if(!_started){ + manufacturer_name = name; + } + return !_started; +} +const char * ESPUSB::manufacturerName(void){ + return manufacturer_name.c_str(); +} + +bool ESPUSB::serialNumber(const char * name){ + if(!_started){ + serial_number = name; + } + return !_started; +} +const char * ESPUSB::serialNumber(void){ + return serial_number.c_str(); +} + +bool ESPUSB::webUSBURL(const char * name){ + if(!_started){ + webusb_url = name; + } + return !_started; +} +const char * ESPUSB::webUSBURL(void){ + return webusb_url.c_str(); +} + +ESPUSB USB; + +#endif /* CONFIG_USB_ENABLED */ diff --git a/libraries/USB/src/USB.h b/libraries/USB/src/USB.h new file mode 100644 index 00000000..5dfac3e6 --- /dev/null +++ b/libraries/USB/src/USB.h @@ -0,0 +1,115 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "sdkconfig.h" +#if CONFIG_USB_ENABLED + +#include "Arduino.h" +#include "USBCDC.h" + +#include "esp_event.h" + +ESP_EVENT_DECLARE_BASE(ARDUINO_USB_EVENTS); + +typedef enum { + ARDUINO_USB_ANY_EVENT = ESP_EVENT_ANY_ID, + ARDUINO_USB_STARTED_EVENT = 0, + ARDUINO_USB_STOPPED_EVENT, + ARDUINO_USB_SUSPEND_EVENT, + ARDUINO_USB_RESUME_EVENT, + ARDUINO_USB_MAX_EVENT, +} arduino_usb_event_t; + +typedef union { + struct { + bool remote_wakeup_en; + } suspend; +} arduino_usb_event_data_t; + +class ESPUSB { + public: + ESPUSB(size_t event_task_stack_size=2048, uint8_t event_task_priority=5); + ~ESPUSB(); + + void onEvent(esp_event_handler_t callback); + void onEvent(arduino_usb_event_t event, esp_event_handler_t callback); + + bool VID(uint16_t v); + uint16_t VID(void); + + bool PID(uint16_t p); + uint16_t PID(void); + + bool firmwareVersion(uint16_t version); + uint16_t firmwareVersion(void); + + bool usbVersion(uint16_t version); + uint16_t usbVersion(void); + + bool usbPower(uint16_t mA); + uint16_t usbPower(void); + + bool usbClass(uint8_t _class); + uint8_t usbClass(void); + + bool usbSubClass(uint8_t subClass); + uint8_t usbSubClass(void); + + bool usbProtocol(uint8_t protocol); + uint8_t usbProtocol(void); + + bool usbAttributes(uint8_t attr); + uint8_t usbAttributes(void); + + bool webUSB(bool enabled); + bool webUSB(void); + + bool productName(const char * name); + const char * productName(void); + + bool manufacturerName(const char * name); + const char * manufacturerName(void); + + bool serialNumber(const char * name); + const char * serialNumber(void); + + bool webUSBURL(const char * name); + const char * webUSBURL(void); + + bool begin(); + operator bool() const; + + private: + uint16_t vid; + uint16_t pid; + String product_name; + String manufacturer_name; + String serial_number; + uint16_t fw_version; + uint16_t usb_version; + uint8_t usb_class; + uint8_t usb_subclass; + uint8_t usb_protocol; + uint8_t usb_attributes; + uint16_t usb_power_ma; + bool webusb_enabled; + String webusb_url; + + bool _started; +}; + +extern ESPUSB USB; + +#endif /* CONFIG_USB_ENABLED */ diff --git a/libraries/USB/src/USBCDC.cpp b/libraries/USB/src/USBCDC.cpp new file mode 100644 index 00000000..9e33a3c9 --- /dev/null +++ b/libraries/USB/src/USBCDC.cpp @@ -0,0 +1,353 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp32-hal.h" +#include "esp32-hal-tinyusb.h" +#include "USBCDC.h" +#if CONFIG_USB_ENABLED + +ESP_EVENT_DEFINE_BASE(ARDUINO_USB_CDC_EVENTS); +esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait); +esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg); + +extern "C" { +#include "tinyusb.h" + +#if CFG_TUD_DFU_RT +uint16_t tusb_dfu_load_descriptor(uint8_t * dst, uint8_t * itf) +{ + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB DFU_RT"); + LOAD_DEFAULT_TUSB_DRIVER(dfu_rt); + +#define DFU_ATTR_CAN_DOWNLOAD 1 +#define DFU_ATTR_CAN_UPLOAD 2 +#define DFU_ATTR_MANIFESTATION_TOLERANT 4 +#define DFU_ATTR_WILL_DETACH 8 +#define DFU_ATTRS (DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT) + + uint8_t descriptor[TUD_DFU_RT_DESC_LEN] = { + // Interface number, string index, attributes, detach timeout, transfer size */ + TUD_DFU_RT_DESCRIPTOR(*itf, str_index, DFU_ATTRS, 700, 64) + }; + *itf+=1; + memcpy(dst, descriptor, TUD_DFU_RT_DESC_LEN); + return TUD_DFU_RT_DESC_LEN; +} +#endif /* CFG_TUD_DFU_RT */ + +#if CFG_TUD_CDC +uint16_t tusb_cdc_load_descriptor(uint8_t * dst, uint8_t * itf) +{ + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB CDC"); + LOAD_DEFAULT_TUSB_DRIVER(cdc); + // Interface number, string index, attributes, detach timeout, transfer size */ + uint8_t descriptor[TUD_CDC_DESC_LEN] = { + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(*itf, str_index, 0x85, 64, 0x03, 0x84, 64) + }; + *itf+=2; + memcpy(dst, descriptor, TUD_CDC_DESC_LEN); + return TUD_CDC_DESC_LEN; +} +#endif /* CFG_TUD_CDC */ +} + +#if CFG_TUD_CDC +#define MAX_USB_CDC_DEVICES 2 +USBCDC * devices[MAX_USB_CDC_DEVICES] = {NULL, NULL}; + +#if CFG_TUD_DFU_RT +// Invoked on DFU_DETACH request to reboot to the bootloader +void tud_dfu_rt_reboot_to_dfu(void) +{ + if(devices[0] != NULL){ + devices[0]->_onDFU(); + } +} +#endif /* CFG_TUD_DFU_RT */ + +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){ + devices[itf]->_onLineState(dtr, rts); + } +} + +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) +{ + if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){ + devices[itf]->_onLineCoding(p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits); + } +} + +void tud_cdc_rx_cb(uint8_t itf) +{ + if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){ + devices[itf]->_onRX(); + } +} + +//void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); + +USBCDC::USBCDC(uint8_t itfn) : itf(itfn), bit_rate(0), stop_bits(0), parity(0), data_bits(0), dtr(false), rts(false), connected(false), reboot_enable(true), rx_queue(NULL) { + tinyusb_enable_interface(USB_INTERFACE_CDC); + tinyusb_enable_interface(USB_INTERFACE_DFU); + if(itf < MAX_USB_CDC_DEVICES){ + devices[itf] = this; + } +} + +void USBCDC::onEvent(esp_event_handler_t callback){ + onEvent(ARDUINO_USB_CDC_ANY_EVENT, callback); +} +void USBCDC::onEvent(arduino_usb_cdc_event_t event, esp_event_handler_t callback){ + arduino_usb_event_handler_register_with(ARDUINO_USB_CDC_EVENTS, event, callback, this); +} + +void USBCDC::begin(size_t rx_queue_len) +{ + if(rx_queue){ + return; + } + rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t)); + if(!rx_queue){ + return; + } + tinyusb_persist_set_mode(REBOOT_PERSIST); +} + +void USBCDC::end() +{ + tinyusb_persist_set_mode(REBOOT_NO_PERSIST); +} + +void USBCDC::_onDFU(void){ + if(reboot_enable){ + tinyusb_persist_set_mode(REBOOT_BOOTLOADER_DFU); + esp_restart(); + } +} + +enum { CDC_LINE_IDLE, CDC_LINE_1, CDC_LINE_2, CDC_LINE_3 }; +void USBCDC::_onLineState(bool _dtr, bool _rts){ + static uint8_t lineState = CDC_LINE_IDLE; + dtr = _dtr; + rts = _rts; + + if(reboot_enable){ + if(!dtr && rts){ + if(lineState == CDC_LINE_IDLE){ + lineState++; + } else { + lineState = CDC_LINE_IDLE; + } + } else if(dtr && rts){ + if(lineState == CDC_LINE_1){ + lineState++; + } else { + lineState = CDC_LINE_IDLE; + } + } else if(dtr && !rts){ + if(lineState == CDC_LINE_2){ + lineState++; + } else { + lineState = CDC_LINE_IDLE; + } + } else if(!dtr && !rts){ + if(lineState == CDC_LINE_3){ + tinyusb_persist_set_mode(REBOOT_BOOTLOADER); + esp_restart(); + } else { + lineState = CDC_LINE_IDLE; + } + } + } + + if(lineState == CDC_LINE_IDLE){ + if(dtr && rts && !connected){ + connected = true; + arduino_usb_cdc_event_data_t p = {0}; + arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_CONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY); + } else if(!dtr && !rts && connected){ + connected = false; + arduino_usb_cdc_event_data_t p = {0}; + arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_DISCONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY); + } + arduino_usb_cdc_event_data_t l = {0}; + l.line_state.dtr = dtr; + l.line_state.rts = rts; + arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_LINE_STATE_EVENT, &l, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY); + } else { + //[I][USBSerial.cpp:76] _onLineState(): dtr: 0, rts: 1 + //[I][USBSerial.cpp:76] _onLineState(): dtr: 1, rts: 1 + //[I][USBSerial.cpp:76] _onLineState(): dtr: 1, rts: 0 + //[I][USBSerial.cpp:76] _onLineState(): dtr: 0, rts: 0 + log_d("CDC RESET: itf: %u, dtr: %u, rts: %u, state: %u", itf, dtr, rts, lineState); + } + +} + +void USBCDC::_onLineCoding(uint32_t _bit_rate, uint8_t _stop_bits, uint8_t _parity, uint8_t _data_bits){ + if(bit_rate != _bit_rate || data_bits != _data_bits || stop_bits != _stop_bits || parity != _parity){ + bit_rate = _bit_rate; + data_bits = _data_bits; + stop_bits = _stop_bits; + parity = _parity; + arduino_usb_cdc_event_data_t p = {0}; + p.line_coding.bit_rate = bit_rate; + p.line_coding.data_bits = data_bits; + p.line_coding.stop_bits = stop_bits; + p.line_coding.parity = parity; + arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_LINE_CODING_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY); + } +} + +void USBCDC::_onRX(){ + uint8_t buf[CONFIG_USB_CDC_RX_BUFSIZE+1]; + uint32_t count = tud_cdc_n_read(itf, buf, CONFIG_USB_CDC_RX_BUFSIZE); + for(uint32_t i=0; i= MAX_USB_CDC_DEVICES || rx_queue == NULL){ + return -1; + } + return uxQueueMessagesWaiting(rx_queue); +} + +int USBCDC::peek(void) +{ + if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){ + return -1; + } + uint8_t c; + if(xQueuePeek(rx_queue, &c, 0)) { + return c; + } + return -1; +} + +int USBCDC::read(void) +{ + if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){ + return -1; + } + uint8_t c = 0; + if(xQueueReceive(rx_queue, &c, 0)) { + return c; + } + return -1; +} + +size_t USBCDC::read(uint8_t *buffer, size_t size) +{ + if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){ + return -1; + } + uint8_t c = 0; + size_t count = 0; + while(count < size && xQueueReceive(rx_queue, &c, 0)){ + buffer[count++] = c; + } + return count; +} + +void USBCDC::flush(void) +{ + if(itf >= MAX_USB_CDC_DEVICES){ + return; + } + tud_cdc_n_write_flush(itf); +} + +int USBCDC::availableForWrite(void) +{ + if(itf >= MAX_USB_CDC_DEVICES){ + return -1; + } + return tud_cdc_n_write_available(itf); +} + +size_t USBCDC::write(const uint8_t *buffer, size_t size){ + if(itf >= MAX_USB_CDC_DEVICES){ + return 0; + } + size_t tosend = size, sofar = 0; + while(tosend){ + uint32_t space = tud_cdc_n_write_available(itf); + if(!space){ + delay(1); + continue; + } + if(tosend < space){ + space = tosend; + } + uint32_t sent = tud_cdc_n_write(itf, buffer + sofar, space); + if(!sent){ + log_e("tud_cdc_n_write failed!"); + return sofar; + } + sofar += sent; + tosend -= sent; + tud_cdc_n_write_flush(itf); + } + return sofar; +} + +size_t USBCDC::write(uint8_t c) +{ + if(itf >= MAX_USB_CDC_DEVICES){ + return 0; + } + return write(&c, 1); +} + +uint32_t USBCDC::baudRate() +{ + return bit_rate; +} + +USBCDC::operator bool() const +{ + if(itf >= MAX_USB_CDC_DEVICES){ + return false; + } + return connected; +} + +#endif /* CONFIG_USB_CDC_ENABLED */ + +#endif /* CONFIG_USB_ENABLED */ diff --git a/libraries/USB/src/USBCDC.h b/libraries/USB/src/USBCDC.h new file mode 100644 index 00000000..f944e52c --- /dev/null +++ b/libraries/USB/src/USBCDC.h @@ -0,0 +1,128 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include + +#include "Stream.h" +#include "esp32-hal.h" +#if CONFIG_USB_CDC_ENABLED + +#include "esp_event.h" + +ESP_EVENT_DECLARE_BASE(ARDUINO_USB_CDC_EVENTS); + +typedef enum { + ARDUINO_USB_CDC_ANY_EVENT = ESP_EVENT_ANY_ID, + ARDUINO_USB_CDC_CONNECTED_EVENT = 0, + ARDUINO_USB_CDC_DISCONNECTED_EVENT, + ARDUINO_USB_CDC_LINE_STATE_EVENT, + ARDUINO_USB_CDC_LINE_CODING_EVENT, + ARDUINO_USB_CDC_RX_EVENT, + ARDUINO_USB_CDC_MAX_EVENT, +} arduino_usb_cdc_event_t; + +typedef union { + struct { + bool dtr; + bool rts; + } line_state; + struct { + uint32_t bit_rate; + uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits + uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space + uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16 + } line_coding; + struct { + size_t len; + } rx; +} arduino_usb_cdc_event_data_t; + +class USBCDC: public Stream +{ +public: + USBCDC(uint8_t itf=0); + + void onEvent(esp_event_handler_t callback); + void onEvent(arduino_usb_cdc_event_t event, esp_event_handler_t callback); + + void begin(size_t rx_queue_len=256); + void end(); + + int available(void); + int availableForWrite(void); + int peek(void); + int read(void); + size_t read(uint8_t *buffer, size_t size); + size_t write(uint8_t); + size_t write(const uint8_t *buffer, size_t size); + void flush(void); + + inline size_t read(char * buffer, size_t size) + { + return read((uint8_t*) buffer, size); + } + inline size_t write(const char * buffer, size_t size) + { + return write((uint8_t*) buffer, size); + } + inline size_t write(const char * s) + { + return write((uint8_t*) s, strlen(s)); + } + inline size_t write(unsigned long n) + { + return write((uint8_t) n); + } + inline size_t write(long n) + { + return write((uint8_t) n); + } + inline size_t write(unsigned int n) + { + return write((uint8_t) n); + } + inline size_t write(int n) + { + return write((uint8_t) n); + } + uint32_t baudRate(); + operator bool() const; + + void enableReboot(bool enable); + bool rebootEnabled(void); + + //internal methods + void _onDFU(void); + void _onLineState(bool _dtr, bool _rts); + void _onLineCoding(uint32_t _bit_rate, uint8_t _stop_bits, uint8_t _parity, uint8_t _data_bits); + void _onRX(void); + +protected: + uint8_t itf; + uint32_t bit_rate; + uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits + uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space + uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16 + bool dtr; + bool rts; + bool connected; + bool reboot_enable; + xQueueHandle rx_queue; + +}; + +extern void serialEventRun(void) __attribute__((weak)); + +#endif /* CONFIG_USB_CDC_ENABLED */