From 3ae9a7eacf2d61529b920ef29a7418b0ec60798e Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 7 Jan 2025 14:09:04 +0100 Subject: [PATCH 1/4] refactor(vbus_monitor_test): Added tests for Bvalid signal and GOTGCTL override driving for VBUS monitoring - Added test cases for USB OTG 1.1 (esp32s2, esp32s3, esp32p4) - Added test cases for USB OTG 2.0 (esp32p4) - Enabled CI tests for esp32s2 --- .../main/test_dconn_detection.c | 151 ----- .../dconn_detection/pytest_dconn_detection.py | 13 - .../CMakeLists.txt | 2 +- .../main/CMakeLists.txt | 0 .../main/idf_component.yml | 0 .../main/test_app_main.c | 12 +- .../vbus_monitor/main/test_vbus_monitor.c | 637 ++++++++++++++++++ .../vbus_monitor/main/test_vbus_monitor.h | 25 + .../vbus_monitor/pytest_vbus_monitor.py | 12 + .../sdkconfig.defaults | 6 +- 10 files changed, 688 insertions(+), 170 deletions(-) delete mode 100644 device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c delete mode 100644 device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py rename device/esp_tinyusb/test_apps/{dconn_detection => vbus_monitor}/CMakeLists.txt (90%) rename device/esp_tinyusb/test_apps/{dconn_detection => vbus_monitor}/main/CMakeLists.txt (100%) rename device/esp_tinyusb/test_apps/{dconn_detection => vbus_monitor}/main/idf_component.yml (100%) rename device/esp_tinyusb/test_apps/{dconn_detection => vbus_monitor}/main/test_app_main.c (84%) create mode 100644 device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c create mode 100644 device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h create mode 100644 device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py rename device/esp_tinyusb/test_apps/{dconn_detection => vbus_monitor}/sdkconfig.defaults (77%) diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c b/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c deleted file mode 100644 index 27da67d1..00000000 --- a/device/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "soc/soc_caps.h" - -#if SOC_USB_OTG_SUPPORTED - -#include -#include -#include "esp_system.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "esp_err.h" -#include "driver/gpio.h" -#include "esp_rom_gpio.h" -#include "soc/gpio_sig_map.h" -#include "unity.h" -#include "tinyusb.h" -#include "tinyusb_default_config.h" - -#define DEVICE_DETACH_TEST_ROUNDS 10 -#define DEVICE_DETACH_ROUND_DELAY_MS 1000 - -#if (CONFIG_IDF_TARGET_ESP32P4) -#define USB_SRP_BVALID_IN_IDX USB_SRP_BVALID_PAD_IN_IDX -#endif // CONFIG_IDF_TARGET_ESP32P4 - -/* TinyUSB descriptors - ********************************************************************* */ -#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN) - -static unsigned int dev_mounted = 0; -static unsigned int dev_umounted = 0; - -static uint8_t const test_configuration_descriptor[] = { - // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), -}; - -static const tusb_desc_device_t test_device_descriptor = { - .bLength = sizeof(test_device_descriptor), - .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers - .idProduct = 0x4002, - .bcdDevice = 0x100, - .iManufacturer = 0x01, - .iProduct = 0x02, - .iSerialNumber = 0x03, - .bNumConfigurations = 0x01 -}; - -#if (TUD_OPT_HIGH_SPEED) -static const tusb_desc_device_qualifier_t device_qualifier = { - .bLength = sizeof(tusb_desc_device_qualifier_t), - .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, - .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .bNumConfigurations = 0x01, - .bReserved = 0 -}; -#endif // TUD_OPT_HIGH_SPEED - -/** - * @brief TinyUSB callback for device event - * - * @note - * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. - * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. - */ -void test_dconn_event_handler(tinyusb_event_t *event, void *arg) -{ - switch (event->id) { - case TINYUSB_EVENT_ATTACHED: - printf("%s\n", __FUNCTION__); - dev_mounted++; - break; - case TINYUSB_EVENT_DETACHED: - printf("%s\n", __FUNCTION__); - dev_umounted++; - break; - default: - break; - } -} - -/** - * @brief TinyUSB Disconnect Detection test case - * - * This is specific artificial test for verifying the disconnection detection event. - * Normally, this event comes as a result of detaching USB device from the port and disappearing the VBUS voltage. - * In this test case, we use GPIO matrix and connect the signal to the ZERO or ONE constant inputs. - * Connection to constant ONE input emulates the attachment to the USB Host port (appearing VBUS). - * Connection to constant ZERO input emulates the detachment from the USB Host port (removing VBUS). - * - * Test logic: - * - Install TinyUSB Device stack without any class - * - In cycle: - * - Emulate the detachment, get the tud_umount_cb(), increase the dev_umounted value - * - Emulate the attachment, get the tud_mount_cb(), increase the dev_mounted value - * - Verify that dev_umounted == dev_mounted - * - Verify that dev_mounted == DEVICE_DETACH_TEST_ROUNDS, where DEVICE_DETACH_TEST_ROUNDS - amount of rounds - * - Uninstall TinyUSB Device stack - * - */ -TEST_CASE("dconn_detection", "[esp_tinyusb][dconn]") -{ - unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; - - // Install TinyUSB driver - tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); - tusb_cfg.descriptor.device = &test_device_descriptor; - tusb_cfg.descriptor.full_speed_config = test_configuration_descriptor; -#if (TUD_OPT_HIGH_SPEED) - tusb_cfg.descriptor.qualifier = &device_qualifier; - tusb_cfg.descriptor.high_speed_config = test_configuration_descriptor; -#endif // TUD_OPT_HIGH_SPEED - - TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); - - dev_mounted = 0; - dev_umounted = 0; - - while (rounds--) { - // LOW to emulate disconnect USB device - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); - vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); - // HIGH to emulate connect USB device - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false); - vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); - } - - // Verify - TEST_ASSERT_EQUAL(dev_umounted, dev_mounted); - TEST_ASSERT_EQUAL(DEVICE_DETACH_TEST_ROUNDS, dev_mounted); - - // Cleanup - TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); -} -#endif // SOC_USB_OTG_SUPPORTED diff --git a/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py b/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py deleted file mode 100644 index b7a4e7a3..00000000 --- a/device/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: Apache-2.0 - -import pytest -from pytest_embedded_idf.dut import IdfDut - - -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.esp32p4 -#@pytest.mark.usb_device Disable in CI: unavailable teardown for P4 -def test_usb_device_dconn_detection(dut: IdfDut) -> None: - dut.run_all_single_board_cases(group='dconn') diff --git a/device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt b/device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt similarity index 90% rename from device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt rename to device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt index ff14e204..b0b2f41b 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt +++ b/device/esp_tinyusb/test_apps/vbus_monitor/CMakeLists.txt @@ -6,4 +6,4 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. set(COMPONENTS main) -project(test_app_dconn_detection) +project(test_app_vbus_monitor) diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt b/device/esp_tinyusb/test_apps/vbus_monitor/main/CMakeLists.txt similarity index 100% rename from device/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt rename to device/esp_tinyusb/test_apps/vbus_monitor/main/CMakeLists.txt diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml b/device/esp_tinyusb/test_apps/vbus_monitor/main/idf_component.yml similarity index 100% rename from device/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml rename to device/esp_tinyusb/test_apps/vbus_monitor/main/idf_component.yml diff --git a/device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c similarity index 84% rename from device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c rename to device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c index b1321edd..62813305 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_app_main.c @@ -1,15 +1,19 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "unity.h" #include "unity_test_runner.h" #include "unity_test_utils_memory.h" +#include "test_vbus_monitor.h" + void app_main(void) { /* @@ -53,10 +57,16 @@ void app_main(void) void setUp(void) { unity_utils_record_free_mem(); + test_device_event_queue_setup(); + test_vbus_monitor_control_setup(); } /* tearDown runs after every test */ void tearDown(void) { + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_vbus_monitor_control_teardown(); + test_device_event_queue_teardown(); unity_utils_evaluate_leaks(); } diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c new file mode 100644 index 00000000..a1196af6 --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.c @@ -0,0 +1,637 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" +#include "driver/gpio.h" +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_default_config.h" + +// For GPIO matrix +#include "esp_rom_gpio.h" +#include "soc/gpio_sig_map.h" +// For DWC USB registers access +#include "soc/usb_dwc_struct.h" + +/* BVALID via signal matrix is supported only on these targets */ +#if CONFIG_IDF_TARGET_ESP32S2 || \ + CONFIG_IDF_TARGET_ESP32S3 || \ + CONFIG_IDF_TARGET_ESP32P4 || \ + CONFIG_IDF_TARGET_ESP32H4 + +#define TEST_BVALID_VIA_SIGNAL_MUX_ENABLED 1 + +#else +#define TEST_BVALID_VIA_SIGNAL_MUX_ENABLED 0 +#endif + +/* Select correct PAD index macro */ +#if CONFIG_IDF_TARGET_ESP32P4 +// For ESP32-P4, testing via BVALID signal uses the *_PAD_IN_IDX variant +#define USB_BVALID_PAD_IN_IDX USB_SRP_BVALID_PAD_IN_IDX +#else +// For all other supported targets, use the *_IN_IDX variant +#define USB_BVALID_PAD_IN_IDX USB_SRP_BVALID_IN_IDX +#endif + +#define DEVICE_DETACH_TEST_ROUNDS 3 +#define DEVICE_DETACH_ROUND_DELAY_MS 1000 +#define VBUS_TRIGGER_GPIO_NUM 5 // GPIO to be used to software VBUS signal triggering +#define VBUS_MONITOR_GPIO_NUM 4 // GPIO connected to VBUS via resistor divider, used for VBUS monitoring + +#define TEST_DEVICE_EVENT_QUEUE_LEN 4 // Length of the queue for device events +#define TEST_DEVICE_EVENT_TIMEOUT_MS 5000 // Timeout for waiting device events +// Device event queue +typedef struct { + tinyusb_event_id_t id; /*!< Event ID */ +} test_device_event_t; + +static QueueHandle_t _test_device_event_queue = NULL; + +void test_device_event_queue_setup(void) +{ + _test_device_event_queue = xQueueCreate(TEST_DEVICE_EVENT_QUEUE_LEN, sizeof(test_device_event_t)); + TEST_ASSERT_NOT_NULL(_test_device_event_queue); +} + +void test_device_event_queue_teardown(void) +{ + if (_test_device_event_queue) { + vQueueDelete(_test_device_event_queue); + _test_device_event_queue = NULL; + } +} + +/** + * @brief TinyUSB callback for device event + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +static void test_dconn_event_handler(tinyusb_event_t *event, void *arg) +{ + printf("\t Device event: \n"); + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + printf("\t\t -> ATTACHED\n"); + break; + case TINYUSB_EVENT_DETACHED: + printf("\t\t <- DETACHED\n"); + break; + default: + break; + } + + test_device_event_t dev_evt = { + .id = event->id, + }; + if (_test_device_event_queue) { + xQueueSend(_test_device_event_queue, &dev_evt, portMAX_DELAY); + } +} + +/** + * @brief Wait for a specific device event to be received in the event queue + * + * @param event_id The expected event ID to wait for + */ +static void test_device_wait_event(tinyusb_event_id_t event_id) +{ + TEST_ASSERT_NOT_NULL(_test_device_event_queue); + // Wait for port callback to send an event message + test_device_event_t dev_evt; + BaseType_t ret = xQueueReceive(_test_device_event_queue, &dev_evt, pdMS_TO_TICKS(TEST_DEVICE_EVENT_TIMEOUT_MS)); + TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "Device event not received on time"); + // Check the contents of that event message + TEST_ASSERT_EQUAL_MESSAGE(event_id, dev_evt.id, "Unexpected device event type received"); +} + +void test_vbus_monitor_control_setup(void) +{ + printf("VBUS monitor control setup:\n"); + +#if (TEST_BVALID_VIA_SIGNAL_MUX_ENABLED) +#if (CONFIG_IDF_TARGET_ESP32P4) + printf("\t - might be triggered by signal multiplexing (only for USB OTG 1.1).\n"); +#else + printf("\t - might be triggered by signal multiplexing.\n"); +#endif // CONFIG_IDF_TARGET_ESP32P4 +#endif // TEST_BVALID_VIA_SIGNAL_MUX_ENABLED + + // Driving the GOTGCTL register directly is for all targets with DWC OTG controller, but we + // verify it only for USB OTG 2.0 + // Note: + // On ESP32P4 OTG signals from USB-DWC are not wired to GPIO matrix, we need to toggle the USB OTG GOTGCTL value in application + printf("\t - might be triggered by manipulating the GOTGCTL register.\n"); + + // Triggering is done by external GPIO, connected to the VBUS monitor GPIO + printf("\t - might be triggered by GPIO%d. \n", VBUS_TRIGGER_GPIO_NUM); + printf("\t\t To use this, connect GPIO%d to VBUS monitor GPIO%d. \n", VBUS_TRIGGER_GPIO_NUM, VBUS_MONITOR_GPIO_NUM); + // Configure the GPIO to output + const gpio_config_t vbus_pin = { + .pin_bit_mask = BIT64(VBUS_TRIGGER_GPIO_NUM), + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, gpio_config(&vbus_pin), "Failed to configure the GPIO for VBUS triggering"); + // Set initial level to HIGH, as we assume that the device is connected at the start + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 1); +} + +void test_vbus_monitor_control_teardown(void) +{ + // Disable the GPIO + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, gpio_reset_pin(VBUS_TRIGGER_GPIO_NUM), "Failed to reset the GPIO for VBUS triggering"); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Connect the GPIO signal 1 to Bvalid input with GPIO matrix + */ +static void test_vbus_monitor_control_signal_connect(void) +{ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_BVALID_PAD_IN_IDX, false); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Connect the GPIO signal 0 to Bvalid input with GPIO matrix + */ +static void test_vbus_monitor_control_signal_disconnect(void) +{ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_BVALID_PAD_IN_IDX, false); +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief Control routines for VBUS monitoring + * + * Connect by manipulating the GOTGCTL register + */ +static void test_vbus_monitor_control_gotgctl_connect(usb_dwc_dev_t *dwc_otg) +{ + // Hardware should allow to override the BVALIDOVVAL register value + TEST_ASSERT_EQUAL_MESSAGE(1, dwc_otg->gotgctl_reg.bvalidoven, "Bvalid overriding is not enabled"); + // Set Bvalid signal to 1 + dwc_otg->gotgctl_reg.bvalidovval = 1; + + // For USB OTG2.0 we need to drive the soft-disconnect bit + if (dwc_otg == &USB_DWC_HS) { + // Clear the soft-disconnect bit + dwc_otg->dctl_reg.sftdiscon = 0; + } +} + +/** + * @brief Control routines for VBUS monitoring + * + * Disconnect by manipulating the GOTGCTL register + */ +static void test_vbus_monitor_control_gotgctl_disconnect(usb_dwc_dev_t *dwc_otg) +{ + // Hardware should allow to override the BVALIDOVVAL register value + TEST_ASSERT_EQUAL_MESSAGE(1, dwc_otg->gotgctl_reg.bvalidoven, "Bvalid overriding is not enabled"); + // Set Bvalid signal to 0 + dwc_otg->gotgctl_reg.bvalidovval = 0; + + // For USB OTG2.0 we need to drive the soft-disconnect bit + if (dwc_otg == &USB_DWC_HS) { + // Set the soft-disconnect bit + dwc_otg->dctl_reg.sftdiscon = 1; + } +} +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + +/** + * @brief Control routines for VBUS monitoring + * + * Set the GPIO, which represent VBUS triggering to HIGH level to emulate the VBUS presence + */ +static void test_vbus_monitor_control_trigger_connect(void) +{ + // Set the GPIO to HIGH to emulate the VBUS presence + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 1); +} + +/** + * @brief Control routines for VBUS monitoring + * + * Set the GPIO, which represent VBUS triggering to LOW level to emulate the VBUS absence + */ +static void test_vbus_monitor_control_trigger_disconnect(void) +{ + // Set the GPIO to LOW to emulate the VBUS absence + gpio_set_level(VBUS_TRIGGER_GPIO_NUM, 0); +} + +/** + * @brief Test routine to emulate device attach/detach by manipulating the Bvalid signal directly + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_emulated_via_bvalid_signal(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Connect Bvalid signal to LOW level (0) to emulate the detachment + test_vbus_monitor_control_signal_disconnect(); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Connect Bvalid signal to HIGH level (1) to emulate the attachment + test_vbus_monitor_control_signal_connect(); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the mount_cnt value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief Test routine to emulate device attach/detach by toggling the GOTGCTL register Bvalid value + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_emulated_via_gotgctl_bvalid(usb_dwc_dev_t *dwc_otg) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the detachment by setting the VBUS to Lo state + test_vbus_monitor_control_gotgctl_disconnect(dwc_otg); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the attachment by setting the VBUS to Hi state + test_vbus_monitor_control_gotgctl_connect(dwc_otg); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + +/** + * @brief Test routine to emulate device attach/detach by toggling the VBUS monitoring GPIO + * + * Note: + * To run this test, connect VBUS monitor GPIO (VBUS_MONITOR_GPIO_NUM) to GPIO (VBUS_TRIGGER_GPIO_NUM) + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_controlled_by_gpio(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the detachment by setting the VBUS to Lo state + test_vbus_monitor_control_trigger_disconnect(); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + // Allow some time for the device to be removed from the USB Host driver + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Emulate the attachment by setting the VBUS to Hi state + test_vbus_monitor_control_trigger_connect(); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +/** + * @brief Test routine to emulate device attach/detach by manually connecting/disconnecting the device + * + * Note: this test requires external hardware (resistor divider or similar) to connect VBUS to the VBUS monitor GPIO + * and an operator, who will manually connect/disconnect the device from the USB Host port according the prompts + * + * @return + * Number of times the device was mounted + */ +static uint8_t test_vbus_manual_attach_detach(void) +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + uint8_t mount_cnt = 0; + + while (rounds--) { + printf("[ACTION REQUIRED] Detach device from the Host port ...\n"); + // Allow some time for the operator to remove the device + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Wait for the TINYUSB_EVENT_DETACHED event + test_device_wait_event(TINYUSB_EVENT_DETACHED); + printf("[ EVENT OK]\n"); + // + printf("[ACTION REQUIRED] Attach device to the Host port ...\n"); + // Allow some time for the operator to connect the device + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // Wait for the TINYUSB_EVENT_ATTACHED event + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + printf("[ EVENT OK]\n"); + // Increase the dev_mounted value + mount_cnt++; + } + // Allow some time for the device class to be recognized + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + return mount_cnt; +} + +#if ((SOC_USB_OTG_PERIPH_NUM == 1) && (TEST_BVALID_VIA_SIGNAL_MUX_ENABLED == 1)) + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS, verify attach/detach events callback (via Bvalid signal)", "[ci][dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_bvalid_signal(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_controlled_by_gpio(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // In this test we use VBUS monitoring GPIO, we need to enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_manual_attach_detach(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} +#endif // ((SOC_USB_OTG_PERIPH_NUM == 1) && (TEST_BVALID_VIA_SIGNAL_MUX_ENABLED == 1)) + +#if ((SOC_USB_OTG_PERIPH_NUM > 1) && CONFIG_IDF_TARGET_ESP32P4) +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS USB OTG 1.1, verify attach/detach events callback (via Bvalid signal)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_bvalid_signal(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated directly + */ +TEST_CASE("Emulated VBUS USB OTG 1.1, verify attach/detach events callback (via GOTGCTL register)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + + // When we do not set self_powered mode, the Bvalid override should be not enabled + TEST_ASSERT_EQUAL_MESSAGE(0, USB_DWC_FS.gotgctl_reg.bvalidoven, "Bvalid override value is already enabled"); + // Set Bvalid signal to 1 initially + USB_DWC_FS.gotgctl_reg.bvalidovval = 1; + // Wait 5 PHY clocks + esp_rom_delay_us(1); + // Enable to override the signal from PHY + USB_DWC_FS.gotgctl_reg.bvalidoven = 1; + + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_emulated_via_gotgctl_bvalid(&USB_DWC_FS); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when Bvalid value is manipulated via GOTGCTL register + */ +TEST_CASE("Emulated VBUS USB OTG 2.0, verify attach/detach events callback (via GOTGCTL register)", "[ci][dconn][ignore]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we do not use VBUS monitoring GPIO, because we manipulate Bvalid value directly + tusb_cfg.phy.self_powered = false; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + + // When we do not set self_powered mode, the Bvalid override should be not enabled + TEST_ASSERT_EQUAL_MESSAGE(0, USB_DWC_HS.gotgctl_reg.bvalidoven, "Bvalid override value is already enabled"); + // Set Bvalid signal to 1 initially + USB_DWC_HS.gotgctl_reg.bvalidovval = 1; + // Wait 5 PHY clocks + esp_rom_delay_us(1); + // Enable to override the signal from PHY + USB_DWC_HS.gotgctl_reg.bvalidoven = 1; + + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */ + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS USB OTG 1.1, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_controlled_by_gpio(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to GPIO + */ +TEST_CASE("Controlled VBUS USB OTG 2.0, verify attach/detach events callback", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */ + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS USB OTG 1.1, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 1.1 peripheral + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = test_vbus_manual_attach_detach(); + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +/** + * @brief TinyUSB TinyUSB Attach/Detach events test, when VBUS monitoring is enabled and connected to real VBUS + */ +TEST_CASE("Real VBUS USB OTG 2.0, verify attach/detach events callback (requires manual handling)", "[dconn]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + // Use USB OTG 2.0 + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; + // In this test we use VBUS monitoring GPIO, so enable it + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = VBUS_MONITOR_GPIO_NUM; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Failed to install TinyUSB driver"); + test_device_wait_event(TINYUSB_EVENT_ATTACHED); + + uint8_t dev_mounted = 0; /* TODO: Expect to fail on run. Enable in VBUS monitor part2 */ + + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Failed to uninstall TinyUSB driver"); + // Verify test results + TEST_ASSERT_EQUAL_MESSAGE(DEVICE_DETACH_TEST_ROUNDS, dev_mounted, "Mount events count mismatch with rounds number"); +} + +#endif // (SOC_USB_OTG_PERIPH_NUM > 1 && CONFIG_IDF_TARGET_ESP32P4) + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h new file mode 100644 index 00000000..9b5101de --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/main/test_vbus_monitor.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Setup the device event queue + */ +void test_device_event_queue_setup(void); + +/** + * @brief Teardown the device event queue + */ +void test_device_event_queue_teardown(void); + +/** + * @brief Setup the VBUS monitor control + */ +void test_vbus_monitor_control_setup(void); + +/** + * @brief Teardown the VBUS monitor control + */ +void test_vbus_monitor_control_teardown(void); diff --git a/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py b/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py new file mode 100644 index 00000000..5dd743ce --- /dev/null +++ b/device/esp_tinyusb/test_apps/vbus_monitor/pytest_vbus_monitor.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32p4 +@pytest.mark.usb_device +def test_usb_device_vbus_monitor(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group='ci') diff --git a/device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults b/device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults similarity index 77% rename from device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults rename to device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults index 0a6271fd..21bf3a74 100644 --- a/device/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults +++ b/device/esp_tinyusb/test_apps/vbus_monitor/sdkconfig.defaults @@ -1,8 +1,6 @@ # Configure TinyUSB, it will be used to mock USB devices -CONFIG_TINYUSB_MSC_ENABLED=n -CONFIG_TINYUSB_CDC_ENABLED=n -CONFIG_TINYUSB_CDC_COUNT=0 -CONFIG_TINYUSB_HID_COUNT=0 +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 # Disable watchdogs, they'd get triggered during unity interactive menu # CONFIG_ESP_TASK_WDT_INIT is not set From e77ff0b9836de7f09b53dcacd75048dc101bdf2e Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 30 Oct 2025 03:17:44 +0100 Subject: [PATCH 2/4] fix(esp_tinyusb): Pin the tinyusb to ^0.18.0 --- device/esp_tinyusb/idf_component.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/device/esp_tinyusb/idf_component.yml b/device/esp_tinyusb/idf_component.yml index 408fc428..ca10ebcb 100644 --- a/device/esp_tinyusb/idf_component.yml +++ b/device/esp_tinyusb/idf_component.yml @@ -11,5 +11,7 @@ targets: dependencies: idf: '>=5.0' # IDF 4.x contains TinyUSB as submodule tinyusb: - version: '>=0.17.0~2' # 0.17.0~2 is the first version that supports deinit + # TODO: Temporary pin to 0.18.0, revert after IEC-403 + # version: '>=0.17.0~2' # 0.17.0~2 is the first version that supports deinit + version: '^0.18.0' public: true From 849e0aec8025eba18583f8e09808274b7adf54bb Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 2 Oct 2025 14:21:30 +0200 Subject: [PATCH 3/4] feature(tinyusb): Added VBUS monitor api and structs --- device/esp_tinyusb/CMakeLists.txt | 9 ++ device/esp_tinyusb/include/tinyusb.h | 1 + .../include/tinyusb_default_config.h | 90 ++++++++++--------- .../include_private/tinyusb_task.h | 6 +- .../include_private/tinyusb_vbus_monitor.h | 44 +++++++++ device/esp_tinyusb/tinyusb.c | 54 +++++++++-- device/esp_tinyusb/tinyusb_task.c | 49 ++++++++-- device/esp_tinyusb/tinyusb_vbus_monitor.c | 38 ++++++++ 8 files changed, 233 insertions(+), 58 deletions(-) create mode 100644 device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h create mode 100644 device/esp_tinyusb/tinyusb_vbus_monitor.c diff --git a/device/esp_tinyusb/CMakeLists.txt b/device/esp_tinyusb/CMakeLists.txt index 04ab9b8b..b5ca4152 100644 --- a/device/esp_tinyusb/CMakeLists.txt +++ b/device/esp_tinyusb/CMakeLists.txt @@ -1,3 +1,5 @@ +idf_build_get_property(target IDF_TARGET) + set(srcs "descriptors_control.c" "tinyusb.c" @@ -43,6 +45,13 @@ if(CONFIG_TINYUSB_NET_MODE_NCM) ) endif() # CONFIG_TINYUSB_NET_MODE_NCM +# Software VBUS monitoring is available only on esp32p4 +if(${target} STREQUAL "esp32p4") + list(APPEND srcs + "tinyusb_vbus_monitor.c" + ) +endif() # esp32p4 + idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "include_private" diff --git a/device/esp_tinyusb/include/tinyusb.h b/device/esp_tinyusb/include/tinyusb.h index cb302263..cab99e47 100644 --- a/device/esp_tinyusb/include/tinyusb.h +++ b/device/esp_tinyusb/include/tinyusb.h @@ -51,6 +51,7 @@ typedef struct { The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port). The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge). */ int vbus_monitor_io; /*!< GPIO for VBUS monitoring, 3.3 V tolerant (use a comparator or a resistior divider to detect the VBUS valid condition). Ignored if not self_powered. */ + uint32_t vbus_monitor_debounce_ms; /*!< Debounce delay for VBUS monitoring in milliseconds. Default is 250 ms. Relevant only for ESP32P4 and ignored if not self_powered. */ } tinyusb_phy_config_t; /** diff --git a/device/esp_tinyusb/include/tinyusb_default_config.h b/device/esp_tinyusb/include/tinyusb_default_config.h index a1cb9714..e61043ad 100644 --- a/device/esp_tinyusb/include/tinyusb_default_config.h +++ b/device/esp_tinyusb/include/tinyusb_default_config.h @@ -63,54 +63,58 @@ extern "C" { #define TINYUSB_DEFAULT_TASK_SIZE 4096 // Default priority for task used in TinyUSB task creation #define TINYUSB_DEFAULT_TASK_PRIO 5 +// Default VBUS debounce time in milliseconds +#define TINYUSB_DEFAULT_DEBOUNCE_MS 250 -#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ - (tinyusb_config_t) { \ - .port = TINYUSB_PORT_FULL_SPEED_0, \ - .phy = { \ - .skip_setup = false, \ - .self_powered = false, \ - .vbus_monitor_io = -1, \ - }, \ - .task = TINYUSB_TASK_DEFAULT(), \ - .descriptor = { \ - .device = NULL, \ - .qualifier = NULL, \ - .string = NULL, \ - .string_count = 0, \ - .full_speed_config = NULL, \ - .high_speed_config = NULL, \ - }, \ - .event_cb = (event_hdl), \ - .event_arg = (arg), \ +#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_FULL_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + .vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ } -#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \ - (tinyusb_config_t) { \ - .port = TINYUSB_PORT_HIGH_SPEED_0, \ - .phy = { \ - .skip_setup = false, \ - .self_powered = false, \ - .vbus_monitor_io = -1, \ - }, \ - .task = TINYUSB_TASK_DEFAULT(), \ - .descriptor = { \ - .device = NULL, \ - .qualifier = NULL, \ - .string = NULL, \ - .string_count = 0, \ - .full_speed_config = NULL, \ - .high_speed_config = NULL, \ - }, \ - .event_cb = (event_hdl), \ - .event_arg = (arg), \ +#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_HIGH_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + .vbus_monitor_debounce_ms = TINYUSB_DEFAULT_DEBOUNCE_MS, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ } -#define TINYUSB_TASK_DEFAULT() \ - (tinyusb_task_config_t) { \ - .size = TINYUSB_DEFAULT_TASK_SIZE, \ - .priority = TINYUSB_DEFAULT_TASK_PRIO, \ - .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ +#define TINYUSB_TASK_DEFAULT() \ + (tinyusb_task_config_t) { \ + .size = TINYUSB_DEFAULT_TASK_SIZE, \ + .priority = TINYUSB_DEFAULT_TASK_PRIO, \ + .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ } /** diff --git a/device/esp_tinyusb/include_private/tinyusb_task.h b/device/esp_tinyusb/include_private/tinyusb_task.h index f0302638..23244d67 100644 --- a/device/esp_tinyusb/include_private/tinyusb_task.h +++ b/device/esp_tinyusb/include_private/tinyusb_task.h @@ -8,6 +8,7 @@ #include "esp_err.h" #include "tinyusb.h" +#include "tinyusb_vbus_monitor.h" #ifdef __cplusplus extern "C" { @@ -42,7 +43,10 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *task_cfg); * - ESP_ERR_NO_MEM if memory allocation failed * - ESP_OK if TinyUSB Task initialized successfully */ -esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *task_cfg, const tinyusb_desc_config_t *desc_cfg); +esp_err_t tinyusb_task_start(tinyusb_port_t port, + const tinyusb_task_config_t *task_cfg, + const tinyusb_desc_config_t *desc_cfg, + const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg); /** * @brief Stops TinyUSB Task diff --git a/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h b/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h new file mode 100644 index 00000000..cee11eec --- /dev/null +++ b/device/esp_tinyusb/include_private/tinyusb_vbus_monitor.h @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "tinyusb.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + gpio_num_t gpio_num; /*!< GPIO number used for VBUS monitoring, 3.3 V tolerant */ + uint32_t debounce_delay_ms; /*!< Debounce delay in milliseconds */ +} tinyusb_vbus_monitor_config_t; + +/** + * @brief Initialize VBUS monitoring on the specified GPIO + * + * Note: + * - This function should be called after tusb_init() when GOTGCTL register is initialized + * - This is a single-threaded implementation, so only one instance of VBUS monitoring is supported + * + * @param config VBUS monitoring configuration + * + * @return + * - ESP_ERR_INVALID_ARG if config is NULL + * - ESP_OK if VBUS monitoring was initialized successfully + */ +esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config); + +/** + * @brief Deinitialize VBUS monitoring + */ +void tinyusb_vbus_monitor_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/device/esp_tinyusb/tinyusb.c b/device/esp_tinyusb/tinyusb.c index a2be1a63..3abf1a03 100644 --- a/device/esp_tinyusb/tinyusb.c +++ b/device/esp_tinyusb/tinyusb.c @@ -100,6 +100,17 @@ static esp_err_t tinyusb_check_config(const tinyusb_config_t *config) ESP_RETURN_ON_FALSE(config->port != TINYUSB_PORT_0, ESP_ERR_INVALID_ARG, TAG, "USB PHY support for OTG1.1 has not been implemented, please update your esp-idf"); #endif // ESP-IDF supports OTG1.1 peripheral #endif // CONFIG_IDF_TARGET_ESP32P4 + + // Device is self powered + if (config->phy.self_powered) { + // VBUS monitoring GPIO must be valid + ESP_RETURN_ON_FALSE(config->phy.vbus_monitor_io >= GPIO_NUM_0 && config->phy.vbus_monitor_io < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, TAG, "VBUS monitor GPIO must be a valid GPIO number when self_powered is true"); +#if (CONFIG_IDF_TARGET_ESP32P4) + if (config->port == TINYUSB_PORT_HIGH_SPEED_0) { + ESP_RETURN_ON_FALSE(config->phy.vbus_monitor_debounce_ms > 0, ESP_ERR_INVALID_ARG, TAG, "VBUS monitor debounce time must be greater than 0 when self_powered is true"); + } +#endif // CONFIG_IDF_TARGET_ESP32P4 + } return ESP_OK; } @@ -110,32 +121,55 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) esp_err_t ret; usb_phy_handle_t phy_hdl = NULL; + + tinyusb_vbus_monitor_config_t vbus_cfg = { + .gpio_num = GPIO_NUM_NC, /*!< Default: Not connected */ + .debounce_delay_ms = 0, /*!< Default: No debounce */ + }; + if (!config->phy.skip_setup) { // Configure USB PHY usb_phy_config_t phy_conf = { - .controller = USB_PHY_CTRL_OTG, - .target = USB_PHY_TARGET_INT, - .otg_mode = USB_OTG_MODE_DEVICE, - .otg_speed = USB_PHY_SPEED_FULL, + .controller = USB_PHY_CTRL_OTG, /*!< OTG controller */ + .target = USB_PHY_TARGET_INT, /*!< Internal PHY */ + .otg_mode = USB_OTG_MODE_DEVICE, /*!< OTG mode: Device */ + .otg_speed = USB_PHY_SPEED_FULL, /*!< Default: Full speed */ + .otg_io_conf = NULL, /*!< Default: No OTG IOs configuration */ }; + // Prepare the OTG IOs configuration + usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io); #if (SOC_USB_OTG_PERIPH_NUM > 1) if (config->port == TINYUSB_PORT_HIGH_SPEED_0) { - // Default PHY for OTG2.0 is UTMI + // Change the PHY parameters for USB OTG 2.0 High-speed phy_conf.target = USB_PHY_TARGET_UTMI; phy_conf.otg_speed = USB_PHY_SPEED_HIGH; } #endif // (SOC_USB_OTG_PERIPH_NUM > 1) - // OTG IOs config - const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io); if (config->phy.self_powered) { - phy_conf.otg_io_conf = &otg_io_conf; + if (config->port == TINYUSB_PORT_FULL_SPEED_0) { + // For USB OTG 1.1, mux VBUS monitor GPIO to BVALID signal + phy_conf.otg_io_conf = &otg_io_conf; + } +#if (SOC_USB_OTG_PERIPH_NUM > 1) + else if (config->port == TINYUSB_PORT_HIGH_SPEED_0) { + // For USB OTG 2.0, use VBUS GPIO + debounce to drive BVALID via GOTGCTL +#if CONFIG_IDF_TARGET_ESP32P4 + vbus_cfg.gpio_num = config->phy.vbus_monitor_io; + vbus_cfg.debounce_delay_ms = config->phy.vbus_monitor_debounce_ms; +#else + ESP_LOGW(TAG, "VBUS monitoring GPIO for OTG 2.0 is not supported on this target yet"); +#endif + } +#endif // SOC_USB_OTG_PERIPH_NUM > 1 } + // Install PHY ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed"); } + // Init TinyUSB stack in task - ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor), del_phy, TAG, "Init TinyUSB task failed"); + ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor, &vbus_cfg), del_phy, TAG, "Init TinyUSB task failed"); s_ctx.port = config->port; // Save the port number s_ctx.phy_hdl = phy_hdl; // Save the PHY handle for uninstallation @@ -155,9 +189,11 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) esp_err_t tinyusb_driver_uninstall(void) { ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed"); + if (s_ctx.phy_hdl) { ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY"); s_ctx.phy_hdl = NULL; } + return ESP_OK; } diff --git a/device/esp_tinyusb/tinyusb_task.c b/device/esp_tinyusb/tinyusb_task.c index 79d4acaa..8e1cd5e0 100644 --- a/device/esp_tinyusb/tinyusb_task.c +++ b/device/esp_tinyusb/tinyusb_task.c @@ -11,8 +11,11 @@ #include "esp_log.h" #include "esp_check.h" #include "tinyusb.h" +#include "tinyusb_task.h" +#include "tinyusb_vbus_monitor.h" #include "sdkconfig.h" #include "descriptors_control.h" +#include "soc/usb_dwc_struct.h" #if TUSB_VERSION_NUMBER < 1900 // < 0.19.0 #define tusb_deinit(x) tusb_teardown(x) // For compatibility with tinyusb component versions from 0.17.0~2 to 0.18.0~5 @@ -45,7 +48,10 @@ typedef struct { const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */ // Task related TaskHandle_t handle; /*!< Task handle */ - volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ + volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ +#if (CONFIG_IDF_TARGET_ESP32P4) + tinyusb_vbus_monitor_config_t vbus_monitor_cfg; /*!< VBUS monitoring configuration */ +#endif // CONFIG_IDF_TARGET_ESP32P4 } tinyusb_task_ctx_t; static bool _task_is_running = false; // Locking flag for the task, access only from the critical section @@ -77,6 +83,15 @@ static void tinyusb_device_task(void *arg) goto desc_free; } +#if (CONFIG_IDF_TARGET_ESP32P4) + if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) { + if (tinyusb_vbus_monitor_init(&task_ctx->vbus_monitor_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Init VBUS monitoring failed"); + goto desc_free; + } + } +#endif // CONFIG_IDF_TARGET_ESP32P4 + TINYUSB_TASK_ENTER_CRITICAL(); task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle p_tusb_task_ctx = task_ctx; // Save global task context pointer @@ -100,9 +115,9 @@ static void tinyusb_device_task(void *arg) esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config) { - ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Task configuration can't be NULL"); - ESP_RETURN_ON_FALSE(config->size != 0, ESP_ERR_INVALID_ARG, TAG, "Task size can't be 0"); - ESP_RETURN_ON_FALSE(config->priority != 0, ESP_ERR_INVALID_ARG, TAG, "Task priority can't be 0"); + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Task configuration cannot be NULL"); + ESP_RETURN_ON_FALSE(config->size != 0, ESP_ERR_INVALID_ARG, TAG, "Task size cannot be 0"); + ESP_RETURN_ON_FALSE(config->priority != 0, ESP_ERR_INVALID_ARG, TAG, "Task priority cannot be 0"); #if CONFIG_FREERTOS_UNICORE ESP_RETURN_ON_FALSE(config->xCoreID == 0, ESP_ERR_INVALID_ARG, TAG, "Task affinity must be 0 only in uniprocessor mode"); #else @@ -111,9 +126,13 @@ esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config) return ESP_OK; } -esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *config, const tinyusb_desc_config_t *desc_cfg) +esp_err_t tinyusb_task_start(tinyusb_port_t port, + const tinyusb_task_config_t *config, + const tinyusb_desc_config_t *desc_cfg, + const tinyusb_vbus_monitor_config_t *vbus_monitor_cfg) { ESP_RETURN_ON_ERROR(tinyusb_descriptors_check(port, desc_cfg), TAG, "TinyUSB descriptors check failed"); + ESP_RETURN_ON_FALSE(vbus_monitor_cfg != NULL, ESP_ERR_INVALID_ARG, TAG, "VBUS configuration cannot be NULL"); TINYUSB_TASK_ENTER_CRITICAL(); TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); // Task shouldn't started @@ -133,6 +152,11 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection task_ctx->desc_cfg = desc_cfg; +#if (CONFIG_IDF_TARGET_ESP32P4) + // VBUS monitor config + task_ctx->vbus_monitor_cfg.gpio_num = vbus_monitor_cfg->gpio_num; + task_ctx->vbus_monitor_cfg.debounce_delay_ms = vbus_monitor_cfg->debounce_delay_ms; +#endif // CONFIG_IDF_TARGET_ESP32P4 TaskHandle_t task_hdl = NULL; ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID); @@ -177,8 +201,23 @@ esp_err_t tinyusb_task_stop(void) vTaskDelete(task_ctx->handle); task_ctx->handle = NULL; } + + /* TODO: Free descriptors and disable the VBUS monitor should be in the task itself + * but currently we don't have a way to signal the task to do it and exit. + * So we do it here for now. + * Refer to https://github.com/espressif/esp-usb/pull/272 + */ + // Free descriptors tinyusb_descriptors_free(); + +#if (CONFIG_IDF_TARGET_ESP32P4) + if (task_ctx->vbus_monitor_cfg.gpio_num != GPIO_NUM_NC) { + // Deinit VBUS monitoring if it was enabled + tinyusb_vbus_monitor_deinit(); + } +#endif // CONFIG_IDF_TARGET_ESP32P4 + // Stop TinyUSB stack ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack"); // Cleanup diff --git a/device/esp_tinyusb/tinyusb_vbus_monitor.c b/device/esp_tinyusb/tinyusb_vbus_monitor.c new file mode 100644 index 00000000..c3e9f9e6 --- /dev/null +++ b/device/esp_tinyusb/tinyusb_vbus_monitor.c @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "tinyusb_vbus_monitor.h" + +const static char *TAG = "VBUS mon"; + +/** + * @brief VBUS monitoring context + * + * Note: This is a single-threaded implementation, so only one instance of VBUS monitoring is supported + */ +typedef struct { + gpio_num_t gpio_num; /*!< GPIO number used for VBUS monitoring */ + bool prev_state; /*!< Previous VBUS IO state: true - HIGH, false - LOW */ + TimerHandle_t debounce_timer; /*!< Debounce timer handle */ +} vbus_monitor_context_t; + +// -------------- Public API ------------------ + +esp_err_t tinyusb_vbus_monitor_init(tinyusb_vbus_monitor_config_t *config) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid argument: config is NULL"); + ESP_LOGD(TAG, "Init GPIO%d, debounce delay: %"PRIu32" ms", config->gpio_num, config->debounce_delay_ms); + return ESP_OK; // Return success to unblock the usb_host_msc test, where the vbus monitor is used +} + +void tinyusb_vbus_monitor_deinit(void) +{ + ESP_LOGD(TAG, "Deinit"); +} From 15fb575994d1b50491e92291e2c25a734d5addd6 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 30 Oct 2025 04:59:53 +0100 Subject: [PATCH 4/4] docs(esp_tinyusb): Updated CHANGELOG.md and README.md --- device/esp_tinyusb/CHANGELOG.md | 1 + device/esp_tinyusb/README.md | 84 +++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/device/esp_tinyusb/CHANGELOG.md b/device/esp_tinyusb/CHANGELOG.md index 589f2630..50c5d1f6 100644 --- a/device/esp_tinyusb/CHANGELOG.md +++ b/device/esp_tinyusb/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased] - Fixed forward compatibility with TinyUSB 0.19 +- Added VBUS monitoring feature for ESP32P4 USB OTG 2.0 (HS) ## 2.0.1 diff --git a/device/esp_tinyusb/README.md b/device/esp_tinyusb/README.md index 1ccfe67b..6f9fb31f 100644 --- a/device/esp_tinyusb/README.md +++ b/device/esp_tinyusb/README.md @@ -81,7 +81,7 @@ The default installation automatically configures the port (High-speed if suppor Default descriptors are provided for the following USB classes: CDC, MSC, and NCM. -> **⚠️ Important:** For demonstration purposes, all error handling logic has been removed from the code examples. Do not ignore proper error handling in actual development. +> ⚠️ For demonstration purposes, all error handling logic has been removed from the code examples. Do not ignore proper error handling in actual development. ```c #include "tinyusb_default_config.h" @@ -208,10 +208,12 @@ Values of default descriptors could be configured via `menuconfig`. ### USB PHY configuration & Self-Powered Device -For self-powered devices, monitoring the VBUS voltage is required. To do this: +For self-powered USB devices, the peripheral must be able to detect the VBUS voltage to know when it is connected to, or disconnected from, a USB host. -- Configure a GPIO pin as an input, using an external voltage divider or comparator to detect the VBUS state. -- Set `self_powered = true` and assign the VBUS monitor GPIO in the `tinyusb_config_t` structure. +To enable this: + +- Connect VBUS to a GPIO input (typically through a voltage divider or external comparator). +- Set `self_powered = true` and assign the VBUS monitor GPIO in `tinyusb_config_t`. ```c #include "tinyusb_default_config.h" @@ -226,7 +228,79 @@ For self-powered devices, monitoring the VBUS voltage is required. To do this: tinyusb_driver_install(&tusb_cfg); } ``` -If external PHY is used: + +#### ESP32-P4 (USB OTG 2.0, High-Speed) + +> **⚠️ Important:** +> +> On **USB OTG 1.1** (`TINYUSB_PORT_FULL_SPEED_0`), the VBUS **BVALID** signal is handled in hardware. +> +> On **USB OTG 2.0** (`TINYUSB_PORT_HIGH_SPEED_0`), there is no hardware **BVALID** detection – it is implemented in the driver. + +For the high-speed port, the driver uses a combination of ISR-driven GPIO state detection plus a software debounce timer on the VBUS monitor GPIO. This filters glitches and cable plug/unplug noise. + +- The debounce interval is controlled by `vbus_monitor_debounce_ms` in `tinyusb_config_t`. +- If not set explicitly, the default debounce time is 250 ms. + +> **Note:** +> +> The `vbus_monitor_debounce_ms` member also exists in `tinyusb_config_t` when using `TINYUSB_PORT_FULL_SPEED_0`, but it is not used by the driver in that mode because VBUS is already handled in hardware. + +You can override the debounce interval in the driver configuration: + +```c + #include "tinyusb_default_config.h" + + void app_main(void) + { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = GPIO_NUM_0; + tusb_cfg.phy.vbus_monitor_debounce_ms = 350; // new debounce value + + tinyusb_driver_install(&tusb_cfg); + } +``` + +> **🔧 GPIO ISR requirement**: +> +> When VBUS monitoring is enabled with `TINYUSB_PORT_HIGH_SPEED_0` (ESP32-P4 USB OTG 2.0), the GPIO driver ISR service **must be installed before** calling `tinyusb_driver_install()`. + +To install GPIO's driver's ISR service: + +```c + #include "tinyusb_default_config.h" + #include "driver/gpio.h" + #include "sdkconfig.h" + + void app_main(void) + { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = GPIO_NUM_0; + + #if (CONFIG_IDF_TARGET_ESP32P4) + // TINYUSB_PORT_HIGH_SPEED_0 is used by default + gpio_install_isr_service(ESP_INTR_FLAG_LOWMED); + #endif // CONFIG_IDF_TARGET_ESP32P4 + + tinyusb_driver_install(&tusb_cfg); + + // application code + + tinyusb_driver_uninstall(); + + #if (CONFIG_IDF_TARGET_ESP32P4) + gpio_uninstall_isr_service(); + #endif // CONFIG_IDF_TARGET_ESP32P4 + } +``` + +The driver can also **skip PHY configuration** if you need to initialize and manage the USB PHY externally (for example, when using a custom or external PHY). + +To do this, set `skip_setup = true`: ```c #include "tinyusb_default_config.h"