From 25d4a27cc702104e786c84e16fdacee56a6ad64b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 23 May 2024 17:23:41 +1000 Subject: [PATCH] ports/esp32: Use shared/tinyusb integration. Uses newer libusb synopsys/dwc2 driver rather than the IDF tinyusb component. Signed-off-by: Andrew Leech --- lib/tinyusb | 2 +- ports/esp32/esp32_common.cmake | 48 +++++++++++ ports/esp32/main.c | 3 +- ports/esp32/main_esp32s2/CMakeLists.txt | 2 + ports/esp32/main_esp32s3/CMakeLists.txt | 2 + ports/esp32/mpconfigport.h | 56 +++++++++++++ ports/esp32/mphalport.c | 35 ++++++-- ports/esp32/usb.c | 107 +++++++++++------------- ports/esp32/usb.h | 1 - pyproject.toml | 2 +- tools/ci.sh | 2 +- 11 files changed, 187 insertions(+), 73 deletions(-) diff --git a/lib/tinyusb b/lib/tinyusb index d10b65ada4be7..906c84d2b6a38 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit d10b65ada4be7d5754b3128e80a9b4db72bdb23f +Subproject commit 906c84d2b6a389aa811a74b3f4051b2d0d90c5dc diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index e928fb439da5e..e66e8e9ba2fe8 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -52,6 +52,45 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) +if(MICROPY_PY_TINYUSB) + string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) + + if(ECHO_SUBMODULES) + # No-op, we're just doing submodule/variant discovery. + # Cannot run the add_library/target_include_directories rules (even though + # the build won't run) because IDF will attempt verify the files exist. + else() + + set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") + string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) + + list(APPEND MICROPY_DEF_TINYUSB + CFG_TUSB_MCU=${tusb_mcu} + ) + + list(APPEND MICROPY_SOURCE_TINYUSB + ${TINYUSB_SRC}/tusb.c + ${TINYUSB_SRC}/common/tusb_fifo.c + ${TINYUSB_SRC}/device/usbd.c + ${TINYUSB_SRC}/device/usbd_control.c + ${TINYUSB_SRC}/class/cdc/cdc_device.c + ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ) + + list(APPEND MICROPY_INC_TINYUSB + ${TINYUSB_SRC} + ${MICROPY_DIR}/shared/tinyusb/ + ) + + list(APPEND MICROPY_LINK_TINYUSB + -Wl,--wrap=dcd_event_handler + ) + endif() +endif() + list(APPEND MICROPY_SOURCE_PORT panichandler.c adc.c @@ -99,6 +138,7 @@ list(APPEND MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} ) list(APPEND IDF_COMPONENTS @@ -133,6 +173,7 @@ list(APPEND IDF_COMPONENTS soc spi_flash ulp + usb vfs ) @@ -146,9 +187,11 @@ idf_component_register( ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} INCLUDE_DIRS ${MICROPY_INC_CORE} ${MICROPY_INC_USERMOD} + ${MICROPY_INC_TINYUSB} ${MICROPY_PORT_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} @@ -170,6 +213,7 @@ endif() target_compile_definitions(${MICROPY_TARGET} PUBLIC ${MICROPY_DEF_CORE} ${MICROPY_DEF_BOARD} + ${MICROPY_DEF_TINYUSB} MICROPY_ESP_IDF_4=1 MICROPY_VFS_FAT=1 MICROPY_VFS_LFS2=1 @@ -185,6 +229,10 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-missing-field-initializers ) +target_link_options(${MICROPY_TARGET} PUBLIC + ${MICROPY_LINK_TINYUSB} +) + # Additional include directories needed for private NimBLE headers. target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 330ba64b467c7..c05d6d522e692 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -52,6 +52,7 @@ #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "shared/timeutils/timeutils.h" +#include "shared/tinyusb/mp_usbd.h" #include "mbedtls/platform_time.h" #include "uart.h" @@ -101,7 +102,7 @@ void mp_task(void *pvParameter) { #endif #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED usb_serial_jtag_init(); - #elif CONFIG_USB_OTG_SUPPORTED + #elif MICROPY_HW_ENABLE_USBDEV usb_init(); #endif #if MICROPY_HW_ENABLE_UART_REPL diff --git a/ports/esp32/main_esp32s2/CMakeLists.txt b/ports/esp32/main_esp32s2/CMakeLists.txt index 40188abff8e57..bc5ab939c3ce3 100644 --- a/ports/esp32/main_esp32s2/CMakeLists.txt +++ b/ports/esp32/main_esp32s2/CMakeLists.txt @@ -8,4 +8,6 @@ if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) endif() +set(MICROPY_PY_TINYUSB ON) + include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/main_esp32s3/CMakeLists.txt b/ports/esp32/main_esp32s3/CMakeLists.txt index 40188abff8e57..bc5ab939c3ce3 100644 --- a/ports/esp32/main_esp32s3/CMakeLists.txt +++ b/ports/esp32/main_esp32s3/CMakeLists.txt @@ -8,4 +8,6 @@ if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) endif() +set(MICROPY_PY_TINYUSB ON) + include(${MICROPY_PORT_DIR}/esp32_common.cmake) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 5051afb799ead..dc7c8882c8802 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -200,6 +200,62 @@ #define MP_STATE_PORT MP_STATE_VM +#if CONFIG_USB_OTG_SUPPORTED +#ifndef MICROPY_HW_ENABLE_USBDEV +#define MICROPY_HW_ENABLE_USBDEV (1) +#endif +#endif + +#if MICROPY_HW_ENABLE_USBDEV +#define MICROPY_SCHEDULER_STATIC_NODES (1) + +// Enable USB-CDC serial port +#ifndef MICROPY_HW_USB_CDC +#define MICROPY_HW_USB_CDC (1) +#endif + +#ifndef MICROPY_HW_USB_VID +#define USB_ESPRESSIF_VID 0x303A +#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID +#define MICROPY_HW_USB_VID (USB_ESPRESSIF_VID) +#else +#define MICROPY_HW_USB_VID (CONFIG_TINYUSB_DESC_CUSTOM_VID) +#endif +#endif + +#ifndef MICROPY_HW_USB_PID +#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID +#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) +// A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. +// Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. +// Auto ProductID layout's Bitmap: +// [MSB] HID | MSC | CDC [LSB] +#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3)) // | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) +#define MICROPY_HW_USB_PID (USB_TUSB_PID) +#else +#define MICROPY_HW_USB_PID (CONFIG_TINYUSB_DESC_CUSTOM_PID) +#endif +#endif + +#ifndef MICROPY_HW_USB_MANUFACTURER_STRING +#ifdef CONFIG_TINYUSB_DESC_MANUFACTURER_STRING +#define MICROPY_HW_USB_MANUFACTURER_STRING CONFIG_TINYUSB_DESC_MANUFACTURER_STRING +#else +#define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython" +#endif +#endif + +#ifndef MICROPY_HW_USB_PRODUCT_FS_STRING +#ifdef CONFIG_TINYUSB_DESC_PRODUCT_STRING +#define MICROPY_HW_USB_PRODUCT_FS_STRING CONFIG_TINYUSB_DESC_PRODUCT_STRING +#else +#define MICROPY_HW_USB_PRODUCT_FS_STRING "Board in FS mode" +#endif +#endif +#endif + + // type definitions for the specific machine #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index cc0e2ee7b4999..39e90e905a82a 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -42,6 +42,8 @@ #include "extmod/misc.h" #include "shared/timeutils/timeutils.h" #include "shared/runtime/pyexec.h" +#include "shared/tinyusb/mp_usbd.h" +#include "shared/tinyusb/mp_usbd_cdc.h" #include "mphalport.h" #include "usb.h" #include "usb_serial_jtag.h" @@ -106,13 +108,19 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED usb_serial_jtag_poll_rx(); - #endif - if ((poll_flags & MP_STREAM_POLL_RD) && stdin_ringbuf.iget != stdin_ringbuf.iput) { + if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) { ret |= MP_STREAM_POLL_RD; } if (poll_flags & MP_STREAM_POLL_WR) { ret |= MP_STREAM_POLL_WR; } + #endif + #if MICROPY_HW_USB_CDC + ret |= mp_usbd_cdc_poll_interfaces(poll_flags); + #endif + #if MICROPY_PY_OS_DUPTERM + ret |= mp_os_dupterm_poll(poll_flags); + #endif return ret; } @@ -121,6 +129,9 @@ int mp_hal_stdin_rx_chr(void) { #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED usb_serial_jtag_poll_rx(); #endif + #if MICROPY_HW_USB_CDC + mp_usbd_cdc_poll_interfaces(0); + #endif int c = ringbuf_get(&stdin_ringbuf); if (c != -1) { return c; @@ -133,6 +144,7 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { // Only release the GIL if many characters are being sent mp_uint_t ret = len; bool did_write = false; + #if MICROPY_HW_ENABLE_UART_REPL || CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED bool release_gil = len > MICROPY_PY_STRING_TX_GIL_THRESHOLD; #if MICROPY_DEBUG_PRINTERS && MICROPY_DEBUG_VERBOSE && MICROPY_PY_THREAD_GIL // If verbose debug output is enabled some strings are printed before the @@ -143,20 +155,25 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { if (release_gil) { MP_THREAD_GIL_EXIT(); } - #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED - usb_serial_jtag_tx_strn(str, len); - did_write = true; - #elif CONFIG_USB_OTG_SUPPORTED - usb_tx_strn(str, len); - did_write = true; - #endif #if MICROPY_HW_ENABLE_UART_REPL uart_stdout_tx_strn(str, len); did_write = true; #endif + #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED + usb_serial_jtag_tx_strn(str, len); + did_write = true; + #endif if (release_gil) { MP_THREAD_GIL_ENTER(); } + #endif + #if MICROPY_HW_USB_CDC + mp_uint_t cdc_res = mp_usbd_cdc_tx_strn(str, len); + if (cdc_res > 0) { + did_write = true; + ret = MIN(cdc_res, ret); + } + #endif int dupterm_res = mp_os_dupterm_tx_strn(str, len); if (dupterm_res >= 0) { did_write = true; diff --git a/ports/esp32/usb.c b/ports/esp32/usb.c index 5a0e6b8a92091..f3d8b3e2e372d 100644 --- a/ports/esp32/usb.c +++ b/ports/esp32/usb.c @@ -28,76 +28,65 @@ #include "py/mphal.h" #include "usb.h" -#if CONFIG_USB_OTG_SUPPORTED && !CONFIG_ESP_CONSOLE_USB_CDC && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED +#if CONFIG_USB_OTG_SUPPORTED && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED +#include "esp_rom_gpio.h" +#include "esp_mac.h" +#include "esp_private/usb_phy.h" -#include "esp_timer.h" -#ifndef NO_QSTR -#include "tinyusb.h" -#include "tusb_cdc_acm.h" -#endif +#include "shared/tinyusb/mp_usbd.h" -#define CDC_ITF TINYUSB_CDC_ACM_0 +#define EXTERNAL_PHY 0 +#define SELF_POWERED 0 -static uint8_t usb_rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE]; +static usb_phy_handle_t phy_hdl; -// This is called from FreeRTOS task "tusb_tsk" in espressif__esp_tinyusb (not an ISR). -static void usb_callback_rx(int itf, cdcacm_event_t *event) { - // espressif__esp_tinyusb places tinyusb rx data onto freertos ringbuffer which - // this function forwards onto our stdin_ringbuf. - for (;;) { - size_t len = 0; - esp_err_t ret = tinyusb_cdcacm_read(itf, usb_rx_buf, sizeof(usb_rx_buf), &len); - if (ret != ESP_OK) { - break; - } - if (len == 0) { - break; - } - for (size_t i = 0; i < len; ++i) { - if (usb_rx_buf[i] == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - } else { - ringbuf_put(&stdin_ringbuf, usb_rx_buf[i]); - } - } - mp_hal_wake_main_task(); - } -} void usb_init(void) { - // Initialise the USB with defaults. - tinyusb_config_t tusb_cfg = {0}; - ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + // ref: https://github.com/espressif/esp-usb/blob/4b6a798d0bed444fff48147c8dcdbbd038e92892/device/esp_tinyusb/tinyusb.c - // Initialise the USB serial interface. - tinyusb_config_cdcacm_t acm_cfg = { - .usb_dev = TINYUSB_USBDEV_0, - .cdc_port = CDC_ITF, - .rx_unread_buf_sz = 256, - .callback_rx = &usb_callback_rx, - #ifdef MICROPY_HW_USB_CUSTOM_RX_WANTED_CHAR_CB - .callback_rx_wanted_char = &MICROPY_HW_USB_CUSTOM_RX_WANTED_CHAR_CB, - #endif - #ifdef MICROPY_HW_USB_CUSTOM_LINE_STATE_CB - .callback_line_state_changed = (tusb_cdcacm_callback_t)&MICROPY_HW_USB_CUSTOM_LINE_STATE_CB, - #endif - #ifdef MICROPY_HW_USB_CUSTOM_LINE_CODING_CB - .callback_line_coding_changed = &MICROPY_HW_USB_CUSTOM_LINE_CODING_CB, - #endif + // Configure USB PHY + usb_phy_config_t phy_conf = { + .controller = USB_PHY_CTRL_OTG, + .otg_mode = USB_OTG_MODE_DEVICE, }; - ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg)); -} + #if EXTERNAL_PHY + // External PHY IOs config + usb_phy_ext_io_conf_t ext_io_conf = { + .vp_io_num = USBPHY_VP_NUM, + .vm_io_num = USBPHY_VM_NUM, + .rcv_io_num = USBPHY_RCV_NUM, + .oen_io_num = USBPHY_OEN_NUM, + .vpo_io_num = USBPHY_VPO_NUM, + .vmo_io_num = USBPHY_VMO_NUM, + }; + phy_conf.target = USB_PHY_TARGET_EXT; + phy_conf.ext_io_conf = &ext_io_conf; + #else + phy_conf.target = USB_PHY_TARGET_INT; + #endif -void usb_tx_strn(const char *str, size_t len) { - // Write out the data to the CDC interface, but only while the USB host is connected. - uint64_t timeout = esp_timer_get_time() + (uint64_t)(MICROPY_HW_USB_CDC_TX_TIMEOUT_MS * 1000); - while (tud_cdc_n_connected(CDC_ITF) && len && esp_timer_get_time() < timeout) { - size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len); - str += l; - len -= l; - tud_cdc_n_write_flush(CDC_ITF); + #if SELF_POWERED + // OTG IOs config + const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->vbus_monitor_io); + if (config->self_powered) { + phy_conf.otg_io_conf = &otg_io_conf; } + #endif + // Init ESP USB Phy + usb_new_phy(&phy_conf, &phy_hdl); + + // Init MicroPython / TinyUSB + mp_usbd_init(); + +} + +void mp_usbd_port_get_serial_number(char *serial_buf) { + // use factory default MAC as serial ID + uint8_t mac[8]; + esp_efuse_mac_get_default(mac); + MP_STATIC_ASSERT(sizeof(mac) * 2 <= MICROPY_HW_USB_DESC_STR_MAX); + mp_usbd_hex_str(serial_buf, mac, sizeof(mac)); } #endif // CONFIG_USB_OTG_SUPPORTED && !CONFIG_ESP_CONSOLE_USB_CDC && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED diff --git a/ports/esp32/usb.h b/ports/esp32/usb.h index a4c7d40701dfc..5e5eea34e5694 100644 --- a/ports/esp32/usb.h +++ b/ports/esp32/usb.h @@ -29,6 +29,5 @@ #define MICROPY_HW_USB_CDC_TX_TIMEOUT_MS (500) void usb_init(void); -void usb_tx_strn(const char *str, size_t len); #endif // MICROPY_INCLUDED_ESP32_USB_H diff --git a/pyproject.toml b/pyproject.toml index e44afe37e0ebb..1650bd088ea22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.codespell] count = "" ignore-regex = '\b[A-Z]{3}\b' -ignore-words-list = "ans,asend,deques,dout,extint,hsi,iput,mis,numer,shft,technic,ure" +ignore-words-list = "ans,asend,deques,dout,extint,hsi,iput,mis,numer,shft,synopsys,technic,ure" quiet-level = 3 skip = """ */build*,\ diff --git a/tools/ci.sh b/tools/ci.sh index 03b6bf59aed4a..1f6a6fcc12977 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -132,7 +132,7 @@ function ci_esp32_idf_setup { function ci_esp32_build_common { source esp-idf/export.sh make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/esp32 submodules + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_S2 submodules } function ci_esp32_build_cmod_spiram_s2 {