From c1f2c42e783b5b0cd3224b27994f23a2ed2ed644 Mon Sep 17 00:00:00 2001 From: Peter M Date: Mon, 22 Jun 2026 13:09:02 +0200 Subject: [PATCH] esp32: Fix USB CDC for esp_tinyusb 2.0.0 API The USB CDC code targeted the deprecated esp_tinyusb 1.x API, but CI pulls in esp_tinyusb ^2.0.0. Migrate to the canonical 2.0.0 names so the driver and console build against the dependency actually installed: - tusb_cdc_acm_init/deinit -> tinyusb_cdcacm_init/deinit - esp_tusb_init_console -> tinyusb_console_init - tusb_cdc_acm.h/tusb_console.h headers -> tinyusb_* headers Link esp_tinyusb in avm_builtins whenever the managed component is present instead of gating on IDF >= 5.3. esp_tinyusb 2.0.0 supports IDF >= 5.0, and the old guard left the usb_cdc_driver.c TinyUSB includes unsatisfied on IDF 5.2, breaking the build. Emit a clear FATAL_ERROR if the component is missing rather than failing later on a missing header. Stop shadowing the managed component's TINYUSB_CDC_ENABLED symbol: the AtomVM USB options now select it instead of redefining/depending on it, which also lets CI enable USB CDC with a single sdkconfig entry. Add IDF v5.2.7 usb-serial and usb-cdc jobs to the ESP32 CI matrix to cover the lowest supported esp_tinyusb 2.0.0 IDF version. Signed-off-by: Peter M --- .github/workflows/esp32-build.yaml | 14 ++++++++++---- doc/src/programmers-guide.md | 2 +- .../esp32/components/avm_builtins/CMakeLists.txt | 8 +++++++- .../esp32/components/avm_builtins/Kconfig | 10 ++++++---- .../esp32/components/avm_builtins/usb_cdc_driver.c | 12 ++++++------ src/platforms/esp32/main/Kconfig.projbuild | 13 ++++--------- src/platforms/esp32/main/main.c | 8 ++++---- 7 files changed, 38 insertions(+), 29 deletions(-) diff --git a/.github/workflows/esp32-build.yaml b/.github/workflows/esp32-build.yaml index 35e32d685e..d0a0d19662 100644 --- a/.github/workflows/esp32-build.yaml +++ b/.github/workflows/esp32-build.yaml @@ -63,10 +63,18 @@ jobs: - esp-idf-target: "esp32s3" idf-version: 'v5.5.4' jit: false + - esp-idf-target: "esp32s3" + idf-version: 'v5.2.7' + usb-serial: 'ON' + jit: false - esp-idf-target: "esp32s3" idf-version: 'v5.5.4' usb-serial: 'ON' jit: false + - esp-idf-target: "esp32s3" + idf-version: 'v5.2.7' + usb-cdc: 'ON' + jit: false - esp-idf-target: "esp32s3" idf-version: 'v5.5.4' usb-cdc: 'ON' @@ -126,10 +134,7 @@ jobs: echo 'CONFIG_USE_USB_SERIAL=y' >> sdkconfig.defaults.in fi if [[ "${USB_CDC}" == "ON" ]]; then - printf '%s\n' \ - 'CONFIG_TINYUSB_CDC_ENABLED=y' \ - 'CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER=y' \ - >> sdkconfig.defaults.in + echo 'CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER=y' >> sdkconfig.defaults.in fi export IDF_TARGET=${{matrix.esp-idf-target}} if [ "${{ matrix.jit }}" = "true" ]; then @@ -141,6 +146,7 @@ jobs: fi - name: Print size info with idf.py + # idf.py size has known failures on IDF v5.2.7, so we skip it for this version if: matrix.idf-version != 'v5.2.7' shell: bash working-directory: ./src/platforms/esp32/ diff --git a/doc/src/programmers-guide.md b/doc/src/programmers-guide.md index 7d282e1127..ceadae395b 100644 --- a/doc/src/programmers-guide.md +++ b/doc/src/programmers-guide.md @@ -2022,7 +2022,7 @@ The Erlang-side API is the platform-specific `usb_cdc` module ([`libs/avm_esp32/ | Platform | Notes | |----------|-------| -| ESP32 | Enable `CONFIG_USE_USB_SERIAL` and `CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER` in `menuconfig`. The ESP-IDF `esp_tinyusb` component must be installed. | +| ESP32 | Enable `CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER` in `menuconfig` (it pulls in TinyUSB CDC automatically). The ESP-IDF `esp_tinyusb` managed component must be installed: `idf.py add-dependency "espressif/esp_tinyusb^2.0.0"` (or list it in `main/idf_component.yml`). `CONFIG_USE_USB_SERIAL` is a separate option that routes the system console over USB CDC. | | RP2040/RP2350 | Set `-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON` in CMake. You must also disable stdio over USB (`pico_enable_stdio_usb(AtomVM 0)`) so that the CDC interface is available for the port driver. | | STM32 | Set `-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON` in CMake. TinyUSB is fetched automatically by default; set the `TINYUSB_PATH` environment variable to use a local checkout. | diff --git a/src/platforms/esp32/components/avm_builtins/CMakeLists.txt b/src/platforms/esp32/components/avm_builtins/CMakeLists.txt index aad06d9767..d36cf90516 100644 --- a/src/platforms/esp32/components/avm_builtins/CMakeLists.txt +++ b/src/platforms/esp32/components/avm_builtins/CMakeLists.txt @@ -69,8 +69,14 @@ if(CONFIG_AVM_ENABLE_DAC_NIF) endif() if(CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER) - if(IDF_VERSION_MAJOR GREATER_EQUAL 6 OR (IDF_VERSION_MAJOR EQUAL 5 AND IDF_VERSION_MINOR GREATER_EQUAL 3)) + if(TARGET idf::espressif__esp_tinyusb) target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__esp_tinyusb) + else() + message(FATAL_ERROR + "CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER requires the espressif/esp_tinyusb " + "managed component. Add it with " + "'idf.py add-dependency \"espressif/esp_tinyusb^2.0.0\"' or by listing it " + "in main/idf_component.yml.") endif() endif() diff --git a/src/platforms/esp32/components/avm_builtins/Kconfig b/src/platforms/esp32/components/avm_builtins/Kconfig index 41ad63aaba..490673ccf4 100644 --- a/src/platforms/esp32/components/avm_builtins/Kconfig +++ b/src/platforms/esp32/components/avm_builtins/Kconfig @@ -114,12 +114,14 @@ config AVM_ENABLE_UART_PORT_DRIVER config AVM_ENABLE_USB_CDC_PORT_DRIVER bool "Enable USB CDC port driver" - depends on TINYUSB_CDC_ENABLED + depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + select TINYUSB_CDC_ENABLED default n help - Enable the AtomVM usb_cdc port driver. This requires native USB CDC - support through TinyUSB and should be enabled only when the firmware - is intended to expose a USB CDC data port. + Enable the AtomVM usb_cdc port driver and TinyUSB CDC support. This + requires the espressif/esp_tinyusb managed component and should be + enabled only when the firmware is intended to expose a USB CDC data + port. config AVM_ENABLE_OTP_CRYPTO_NIFS bool "Enable OTP Crypto NIFs" diff --git a/src/platforms/esp32/components/avm_builtins/usb_cdc_driver.c b/src/platforms/esp32/components/avm_builtins/usb_cdc_driver.c index af5701ad70..6edca5d697 100644 --- a/src/platforms/esp32/components/avm_builtins/usb_cdc_driver.c +++ b/src/platforms/esp32/components/avm_builtins/usb_cdc_driver.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include "atom.h" #include "bif.h" @@ -298,12 +298,12 @@ static Context *usb_cdc_driver_create_port(GlobalContext *global, term opts) .callback_line_state_changed = NULL, .callback_line_coding_changed = NULL, }; - esp_err_t err = tusb_cdc_acm_init(&acm_cfg); + esp_err_t err = tinyusb_cdcacm_init(&acm_cfg); if (err == ESP_OK) { cdc_data->owns_itf = true; } else if (err == ESP_ERR_INVALID_STATE) { // The interface was already initialized (e.g. by the console). - // tusb_cdc_acm_init ignored our acm_cfg, so register our RX + // tinyusb_cdcacm_init ignored our acm_cfg, so register our RX // callback explicitly — otherwise we'd never see incoming bytes. esp_err_t rerr = tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, &usb_cdc_rx_callback); if (rerr != ESP_OK) { @@ -332,7 +332,7 @@ static Context *usb_cdc_driver_create_port(GlobalContext *global, term opts) if (xQueueAddToSet(cdc_data->rxqueue, event_set) != pdPASS) { ESP_LOGE(TAG, "Failed to add USB CDC queue to event set."); if (cdc_data->owns_itf) { - tusb_cdc_acm_deinit(itf); + tinyusb_cdcacm_deinit(itf); } else { tinyusb_cdcacm_unregister_callback(itf, CDC_EVENT_RX); } @@ -350,7 +350,7 @@ static Context *usb_cdc_driver_create_port(GlobalContext *global, term opts) ESP_LOGE(TAG, "Failed to create context"); xQueueRemoveFromSet(cdc_data->rxqueue, event_set); if (cdc_data->owns_itf) { - tusb_cdc_acm_deinit(itf); + tinyusb_cdcacm_deinit(itf); } else { tinyusb_cdcacm_unregister_callback(itf, CDC_EVENT_RX); } @@ -562,7 +562,7 @@ static void usb_cdc_driver_do_close(Context *ctx, GenMessage gen_message) SMP_MUTEX_UNLOCK(s_open_lock); if (cdc_data->owns_itf) { - tusb_cdc_acm_deinit(cdc_data->itf); + tinyusb_cdcacm_deinit(cdc_data->itf); } else { tinyusb_cdcacm_unregister_callback(cdc_data->itf, CDC_EVENT_RX); } diff --git a/src/platforms/esp32/main/Kconfig.projbuild b/src/platforms/esp32/main/Kconfig.projbuild index 9901f2584a..7ce827f359 100755 --- a/src/platforms/esp32/main/Kconfig.projbuild +++ b/src/platforms/esp32/main/Kconfig.projbuild @@ -27,18 +27,13 @@ menu "AtomVM configuration" Reboot the ESP32 device if the start/0 entrypoint does not return the `ok` atom. config USE_USB_SERIAL - bool "Enable USB CDC Serial" + bool "Enable USB CDC serial console" + depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 default n select TINYUSB_CDC_ENABLED help - Enable USB CDC Serial functionality. - - config TINYUSB_CDC_ENABLED - bool "Enable TinyUSB CDC (requires 'idf.py add-dependency esp_tinyusb')" - default y - depends on USE_USB_SERIAL - help - Enable TinyUSB CDC functionality if USE_USB_SERIAL is enabled. + Route the AtomVM console over USB CDC. This requires the + espressif/esp_tinyusb managed component. config JIT_ENABLED bool "Enable just in time compilation" diff --git a/src/platforms/esp32/main/main.c b/src/platforms/esp32/main/main.c index a91a3755de..b9b49f38ef 100644 --- a/src/platforms/esp32/main/main.c +++ b/src/platforms/esp32/main/main.c @@ -43,9 +43,9 @@ #ifdef CONFIG_USE_USB_SERIAL void init_usb_serial(void); #include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_console.h" #include "tinyusb_default_config.h" -#include "tusb_cdc_acm.h" -#include "tusb_console.h" #endif #include "esp32_sys.h" @@ -155,9 +155,9 @@ void init_usb_serial() ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); tinyusb_config_cdcacm_t acm_cfg = { 0 }; // the configuration uses default values - ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg)); + ESP_ERROR_CHECK(tinyusb_cdcacm_init(&acm_cfg)); - esp_tusb_init_console(TINYUSB_CDC_ACM_0); // log to usb + ESP_ERROR_CHECK(tinyusb_console_init(TINYUSB_CDC_ACM_0)); // log to usb ESP_LOGI(TAG, "USB initialization: done."); }