diff --git a/docs/en/api-reference/peripherals/usb_host.rst b/docs/en/api-reference/peripherals/usb_host.rst index e800a311032..480bd954631 100644 --- a/docs/en/api-reference/peripherals/usb_host.rst +++ b/docs/en/api-reference/peripherals/usb_host.rst @@ -507,8 +507,7 @@ CDC-ACM """"""" * A host class driver for the Communication Device Class (Abstract Control Model) is distributed as a managed component via the `ESP Component Registry `__. -* :example:`peripherals/usb/host/cdc/cdc_acm_host` demonstrates how to use the CDC-ACM Host Driver to enable communication between {IDF_TARGET_NAME} and a USB CDC-ACM device. -* :example:`peripherals/usb/host/cdc/cdc_acm_vcp` demonstrates how to extend the CDC-ACM driver for Virtual Communication Port (VCP) devices like CP210x, FTDI FT23x or CH34x devices, and how to control the device and send data using the CDC-ACM API. +* :example:`peripherals/usb/host/cdc` demonstrates how to use the CDC-ACM Host Driver to enable communication between {IDF_TARGET_NAME} and a USB CDC-ACM device, including vendor-specific devices such as CP210x, FTDI FT23x or CH34x. * The CDC-ACM driver is also used in `esp_modem examples `__, where it is used for communication with cellular modems. MSC diff --git a/docs/zh_CN/api-reference/peripherals/usb_host.rst b/docs/zh_CN/api-reference/peripherals/usb_host.rst index ef6b20c664f..8755995d333 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host.rst @@ -425,8 +425,8 @@ USB 主机库事件 自动挂起定时器可以配置为以下几种模式: -- **单次** (:cpp:enumerator:`USB_HOST_LIB_PM_SUSPEND_ONE_SHOT`):定时器超时一次后停止。 -- **周期性** (:cpp:enumerator:`USB_HOST_LIB_PM_SUSPEND_PERIODIC`):定时器在每次超时后自动重启,无限重复。 +- **单次** (:cpp:enumerator:`USB_HOST_LIB_AUTO_SUSPEND_ONE_SHOT`):定时器超时一次后停止。 +- **周期性** (:cpp:enumerator:`USB_HOST_LIB_AUTO_SUSPEND_PERIODIC`):定时器在每次超时后自动重启,无限重复。 由传输提交触发自动恢复 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -469,7 +469,7 @@ USB 主机库事件 // 将自动挂起定时器设置为周期模式,周期为 1 秒, // 用于在无操作 1 秒后自动挂起设备,并在超时后自动重启 - usb_host_lib_set_auto_suspend(USB_HOST_LIB_PM_SUSPEND_PERIODIC, 1000); + usb_host_lib_set_auto_suspend(USB_HOST_LIB_AUTO_SUSPEND_PERIODIC, 1000); while (1) { uint32_t event_flags; @@ -507,8 +507,7 @@ CDC-ACM """"""" * 通信设备 Class(抽象控制模型)的主机 Class 驱动程序通过 `乐鑫组件注册表 `__ 作为受管理的组件分发。 -* 示例 :example:`peripherals/usb/host/cdc/cdc_acm_host` 演示了使用 CDC-ACM 主机驱动程序组件,实现 {IDF_TARGET_NAME} 与 USB CDC-ACM 设备的通信。 -* 示例 :example:`peripherals/usb/host/cdc/cdc_acm_vcp` 演示了如何扩展 CDC-ACM 的主机驱动程序,以支持 VCP 设备(即虚拟通信端口设备,如 CP210x、FTDI FT23x 或 CH34x),以及如何使用 CDC-ACM API 控制设备并发送数据。 +* 示例 :example:`peripherals/usb/host/cdc` 演示了如何使用 CDC-ACM 主机驱动程序,让 {IDF_TARGET_NAME} 与 USB CDC-ACM 设备通信,包括 CP210x、FTDI FT23x 或 CH34x 等厂商设备。 * 示例 `esp_modem `__ 中也使用了 CDC-ACM 驱动程序,该程序在这些示例中与蜂窝模块通信。 MSC diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt b/examples/peripherals/usb/host/cdc/CMakeLists.txt similarity index 95% rename from examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt rename to examples/peripherals/usb/host/cdc/CMakeLists.txt index b3352276ee7..8e6cba0f185 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt +++ b/examples/peripherals/usb/host/cdc/CMakeLists.txt @@ -7,4 +7,4 @@ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) -project(cdc_acm_vcp) +project(cdc_host) diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/README.md b/examples/peripherals/usb/host/cdc/README.md similarity index 63% rename from examples/peripherals/usb/host/cdc/cdc_acm_host/README.md rename to examples/peripherals/usb/host/cdc/README.md index bbd5836c767..0f9ebde98bd 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_host/README.md +++ b/examples/peripherals/usb/host/cdc/README.md @@ -1,46 +1,46 @@ | Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | -------- | -------- | -------- | -------- | -# USB CDC-ACM Host Driver Example +# USB CDC Host Driver Example (See the README.md file in the upper level 'examples' directory for more information about examples.) ## Overview -This example demonstrates how to use the [CDC-ACM Host Driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) to enable an ESP chip to communicate with a USB CDC-ACM (Communication Device Class - Abstract Control Model) device. CDC-ACM is a USB device class specification that allows USB devices to appear as serial ports, commonly used by USB-to-UART converters and virtual COM port devices. +This example demonstrates how to use the [CDC-ACM Host Driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) together with VCP drivers to enable an ESP chip to communicate with USB CDC devices, including vendor-specific USB-to-UART converters (e.g., [CP210x](https://components.espressif.com/components/espressif/usb_host_cp210x_vcp), [FTDI FT23x](https://components.espressif.com/components/espressif/usb_host_ftdi_vcp), [CH34x](https://components.espressif.com/components/espressif/usb_host_ch34x_vcp)). CDC-ACM is a USB device class specification that allows USB devices to appear as serial ports, commonly used by USB-to-UART converters and virtual COM port devices. The example performs the following operations: 1. Installs the USB Host Library and CDC-ACM driver -2. Waits for a CDC-ACM device to be connected -3. Opens the first available CDC-ACM device found +2. Waits for CDC devices to be connected +3. Opens the device using the appropriate driver based on VID/PID 4. Prints the device descriptor information 5. Sends test data to the device 6. Receives data from the device (handled via callback) 7. Demonstrates line coding commands (get/set baud rate, data bits, stop bits, parity) 8. Demonstrates control line state commands (DTR/RTS) -9. Waits for device disconnection and repeats the process +9. Handles device disconnection and allows connecting another device ## How to use example ### Hardware Required * Development board with USB-OTG support that will act as USB host -* USB CDC-ACM device. This can be: +* USB CDC device. This can be: - A USB-to-UART converter (e.g., CP210x, FTDI FT23x, CH34x) - - Another ESP development board configured as a USB serial device (see [tusb_serial_device example](../../../device/tusb_serial_device)) + - Another ESP development board configured as a USB serial device (see [tusb_serial_device example](../../device/tusb_serial_device)) - Another ESP development board connected with USB-Serial-JTAG - Any other USB device that implements the CDC-ACM class #### Pin Assignment -Follow instructions in [examples/usb/README.md](../../../README.md) for specific hardware setup. +Follow instructions in [examples/usb/README.md](../../README.md) for specific hardware setup. ### Build and Flash 1. Prepare the USB CDC device: - If using a USB-to-UART converter, no preparation is needed - - If using another ESP board as a USB device, build and flash the [tusb_serial_device example](../../../device/tusb_serial_device) to that board first + - If using another ESP board as a USB device, build and flash the [tusb_serial_device example](../../device/tusb_serial_device) to that board first 2. Connect the CDC device to the USB Host port @@ -52,38 +52,42 @@ idf.py -p PORT flash monitor (Replace PORT with the name of the serial port to use.) -(To exit the serial monitor, type ``Ctrl-]``.) +(To exit the serial monitor, type ``Ctrl-]``. To stop the example, press the Boot button on the board.) See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. ## Example Output -After flashing and connecting a CDC-ACM device, you should see output similar to the following in the idf monitor: +After flashing and connecting a CDC device, you should see output similar to the following in the idf monitor: ``` -I (256) USB-CDC: Installing USB Host -I (256) USB-CDC: Installing CDC-ACM driver -I (256) USB-CDC: Opening CDC ACM device... +I (256) usb_task: Running USB task +I (256) usb_task: - Installing USB Host +I (266) usb_task: - Installing CDC-ACM driver +I (276) app_main: Waiting for CDC devices. Press Boot button to quit. +I (584) USB-CDC: New CDC device connected VID=0x10C4 PID=0xEA60 +I (594) USB-CDC: CDC device opened (slot 0). Descriptor: ... -I (1174) USB-CDC: Data received +I (1154) USB-CDC: Testing data transmission +I (1174) USB-CDC: - Data received (slot 0) I (1174) USB-CDC: 0x4ffbf640 43 44 43 20 74 65 73 74 20 73 74 72 69 6e 67 21 |CDC test string!| I (1274) USB-CDC: Testing control line state command I (1294) USB-CDC: - Control line state set to DTR=false, RTS=true I (1314) USB-CDC: Testing line coding commands I (1314) USB-CDC: - Line Coding Get: Rate: 115200, Stop bits: 0, Parity: 0, Databits: 8 I (1314) USB-CDC: - Line Set: Rate: 115200, Stop bits: 0, Parity: 0, Databits: 8 -I (1314) USB-CDC: Example finished successfully! You can reconnect the device to run again. +I (1314) USB-CDC: Example finished for device in slot 0. You can disconnect or connect another device. ``` ## Troubleshooting ### Device Not Detected -If the CDC-ACM device is not detected, check: +If the CDC device is not detected, check: 1. **USB connection:** Ensure the device is properly connected to the USB host port -2. **Device compatibility:** Verify that the device implements the CDC-ACM class. Some USB-to-UART converters may use proprietary drivers +2. **Device compatibility:** Verify that the device implements the CDC-ACM class or is a supported VCP device (CP210x/FTDI/CH34x). Some USB-to-UART converters may use proprietary drivers 3. **Power supply:** Ensure the USB host port provides adequate power for the connected device 4. **Cable quality:** Use a quality USB cable that supports data transfer (not charge-only cables) diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/CMakeLists.txt b/examples/peripherals/usb/host/cdc/cdc_acm_host/CMakeLists.txt deleted file mode 100644 index 6cba5855ac2..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_host/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# For more information about build system see -# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html -# The following five lines of boilerplate have to be in your project's -# CMakeLists in this exact order for cmake to work correctly -cmake_minimum_required(VERSION 3.22) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -idf_build_set_property(MINIMAL_BUILD ON) -project(cdc_acm_host) diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/idf_component.yml b/examples/peripherals/usb/host/cdc/cdc_acm_host/main/idf_component.yml deleted file mode 100644 index 395a39e7efb..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/idf_component.yml +++ /dev/null @@ -1,3 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - usb_host_cdc_acm: "^2.1.1" diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb_cdc_example_main.c b/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb_cdc_example_main.c deleted file mode 100644 index ff91f636d3d..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb_cdc_example_main.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include -#include -#include -#include "esp_log.h" -#include "esp_err.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" - -#include "usb/usb_host.h" -#include "usb/cdc_acm_host.h" - -#define EXAMPLE_USB_HOST_PRIORITY (20) -#define EXAMPLE_TX_STRING ("CDC test string!") -#define EXAMPLE_TX_TIMEOUT_MS (1000) - -static const char *TAG = "USB-CDC"; -static SemaphoreHandle_t device_disconnected_sem; - -/** - * @brief Data received callback - * - * @param[in] data Pointer to received data - * @param[in] data_len Length of received data in bytes - * @param[in] arg Argument we passed to the device open function - * @return - * true: We have processed the received data - * false: We expect more data - */ -static bool handle_rx(const uint8_t *data, size_t data_len, void *arg) -{ - ESP_LOGI(TAG, "Data received"); - ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_INFO); - return true; -} - -/** - * @brief Device event callback - * - * Apart from handling device disconnection it doesn't do anything useful - * - * @param[in] event Device event type and data - * @param[in] user_ctx Argument we passed to the device open function - */ -static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) -{ - switch (event->type) { - case CDC_ACM_HOST_ERROR: - ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %i", event->data.error); - break; - case CDC_ACM_HOST_DEVICE_DISCONNECTED: - ESP_LOGI(TAG, "Device suddenly disconnected"); - ESP_ERROR_CHECK(cdc_acm_host_close(event->data.cdc_hdl)); - xSemaphoreGive(device_disconnected_sem); - break; - case CDC_ACM_HOST_SERIAL_STATE: - ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val); - break; - default: - ESP_LOGW(TAG, "Unsupported CDC event: %d (possibly suspend/resume)", event->type); - break; - } -} - -/** - * @brief USB Host library handling task - * - * @param arg Unused - */ -static void usb_lib_task(void *arg) -{ - while (1) { - // Start handling system events - uint32_t event_flags; - usb_host_lib_handle_events(portMAX_DELAY, &event_flags); - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_ERROR_CHECK(usb_host_device_free_all()); - } - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - ESP_LOGI(TAG, "USB: All devices freed"); - // Continue handling USB events to allow device reconnection - } - } -} - -/** - * @brief Main application - * - * Here we open a USB CDC device and send some data to it - */ -void app_main(void) -{ - device_disconnected_sem = xSemaphoreCreateBinary(); - assert(device_disconnected_sem); - - // Install USB Host driver. Should only be called once in entire application - ESP_LOGI(TAG, "Installing USB Host"); - const usb_host_config_t host_config = { - .skip_phy_setup = false, - .intr_flags = ESP_INTR_FLAG_LOWMED, - }; - ESP_ERROR_CHECK(usb_host_install(&host_config)); - - // Create a task that will handle USB library events - BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, EXAMPLE_USB_HOST_PRIORITY, NULL); - assert(task_created == pdTRUE); - - ESP_LOGI(TAG, "Installing CDC-ACM driver"); - ESP_ERROR_CHECK(cdc_acm_host_install(NULL)); - - const cdc_acm_host_device_config_t dev_config = { - .connection_timeout_ms = 1000, - .out_buffer_size = 512, - .in_buffer_size = 512, - .user_arg = NULL, - .event_cb = handle_event, - .data_cb = handle_rx - }; - - while (true) { - cdc_acm_dev_hdl_t cdc_dev = NULL; - - // Open any CDC-ACM device - // This call always opens the first CDC-ACM device and first interface it finds - ESP_LOGI(TAG, "Opening CDC ACM device..."); - esp_err_t err = cdc_acm_host_open(CDC_HOST_ANY_VID, CDC_HOST_ANY_PID, 0, &dev_config, &cdc_dev); - if (ESP_OK != err) { - ESP_LOGI(TAG, "Failed to open device"); - vTaskDelay(pdMS_TO_TICKS(1000)); // To prevent infinite loop of failed attempts to open the device - continue; - } - cdc_acm_host_desc_print(cdc_dev); - vTaskDelay(pdMS_TO_TICKS(100)); - - // Test sending and receiving: responses are handled in handle_rx callback - ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (const uint8_t *)EXAMPLE_TX_STRING, strlen(EXAMPLE_TX_STRING), EXAMPLE_TX_TIMEOUT_MS)); - vTaskDelay(pdMS_TO_TICKS(100)); - - ESP_LOGI(TAG, "Testing control line state command"); - err = cdc_acm_host_set_control_line_state(cdc_dev, false, false); - if (ESP_ERR_NOT_SUPPORTED == err) { - ESP_LOGW(TAG, "\t- Control line state set not supported"); - } else if (ESP_OK != err) { - ESP_LOGE(TAG, "\t- Failed to set control line state"); - } else { - // In case you connected ESP board with USB-Serial-JTAG, this procedure will reset the board - vTaskDelay(pdMS_TO_TICKS(20)); - cdc_acm_host_set_control_line_state(cdc_dev, false, true); - ESP_LOGI(TAG, "\t- Control line state set to DTR=false, RTS=true"); - vTaskDelay(pdMS_TO_TICKS(20)); - cdc_acm_host_set_control_line_state(cdc_dev, false, false); - } - - // Test Line Coding commands: Get current line coding and change it to 115200 8N1 - ESP_LOGI(TAG, "Testing line coding commands"); - - cdc_acm_line_coding_t line_coding; - err = cdc_acm_host_line_coding_get(cdc_dev, &line_coding); - if (ESP_ERR_NOT_SUPPORTED == err) { - ESP_LOGW(TAG, "\t- Line coding get not supported"); - } else if (ESP_OK != err) { - ESP_LOGE(TAG, "\t- Failed to get line coding"); - } else { - ESP_LOGI(TAG, "\t- Line Coding Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"", - line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits); - - // Set new line coding - line_coding.dwDTERate = 115200; - line_coding.bDataBits = 8; - line_coding.bParityType = 0; - line_coding.bCharFormat = 0; - err = cdc_acm_host_line_coding_set(cdc_dev, &line_coding); - if (ESP_OK != err) { - ESP_LOGE(TAG, "\t- Failed to set line coding"); - } else { - ESP_LOGI(TAG, "\t- Line Set: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"", - line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits); - } - } - - // We are done. Wait for device disconnection and start over - ESP_LOGI(TAG, "Example finished successfully! You can reconnect the device to run again."); - xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); - } -} diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md deleted file mode 100644 index 9bd7229b5f2..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md +++ /dev/null @@ -1,41 +0,0 @@ -| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | -------- | -------- | -------- | -------- | - -# USB CDC-ACM Virtual COM Port example - -(See the README.md file in the upper level 'examples' directory for more information about examples.) - -This example shows how to extend CDC-ACM driver for Virtual Communication Port (VCP) devices, such as CP210x, FTDI FT23x or CH34x devices. - -The drivers are fetched from [ESP Component Registry](https://components.espressif.com/) together with VCP service that automatically loads correct driver for plugged-in device. - -## How to use example - -1. Connect your USB<->UART converter to ESP board, the device will be automatically enumerated and correct driver will be loaded -2. Change baudrate and other line coding parameters in [cdc_acm_vcp_example_main.cpp](main/cdc_acm_vcp_example_main.cpp) to match your needs -3. Now you can use the usual CDC-ACM API to control the device and send data. Data are received in `handle_rx` callback -4. Try disconnecting and then reconnecting of the USB device to experiment with USB hotplugging - -### Hardware Required - -* Development board with USB-OTG support -* A USB cable for Power supply and programming -* Silicon Labs CP210x, FTDI FT23x or CP34x USB to UART converter - -#### Pin Assignment - -Follow instruction in [examples/usb/README.md](../../../README.md) for specific hardware setup. - -### Build and Flash - -Build this project and flash it to the USB host board, then run monitor tool to view serial output: - -```bash -idf.py -p PORT flash monitor -``` - -(Replace PORT with the name of the serial port to use.) - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt deleted file mode 100644 index 7fefe3ecbdb..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -idf_component_register( - SRCS "cdc_acm_vcp_example_main.cpp" - INCLUDE_DIRS "." - ) diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp_example_main.cpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp_example_main.cpp deleted file mode 100644 index b21a4f77587..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp_example_main.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include -#include - -#include "esp_log.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" - -#include "usb/cdc_acm_host.h" -#include "usb/vcp_ch34x.hpp" -#include "usb/vcp_cp210x.hpp" -#include "usb/vcp_ftdi.hpp" -#include "usb/vcp.hpp" -#include "usb/usb_host.h" - -using namespace esp_usb; - -// Change these values to match your needs -#define EXAMPLE_BAUDRATE (115200) -#define EXAMPLE_STOP_BITS (0) // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits -#define EXAMPLE_PARITY (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space -#define EXAMPLE_DATA_BITS (8) - -namespace { -static const char *TAG = "VCP example"; -static SemaphoreHandle_t device_disconnected_sem; - -/** - * @brief Data received callback - * - * Just pass received data to stdout - * - * @param[in] data Pointer to received data - * @param[in] data_len Length of received data in bytes - * @param[in] arg Argument we passed to the device open function - * @return - * true: We have processed the received data - * false: We expect more data - */ -static bool handle_rx(const uint8_t *data, size_t data_len, void *arg) -{ - printf("%.*s", data_len, data); - return true; -} - -/** - * @brief Device event callback - * - * Apart from handling device disconnection it doesn't do anything useful - * - * @param[in] event Device event type and data - * @param[in] user_ctx Argument we passed to the device open function - */ -static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) -{ - switch (event->type) { - case CDC_ACM_HOST_ERROR: - ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %d", event->data.error); - break; - case CDC_ACM_HOST_DEVICE_DISCONNECTED: - ESP_LOGI(TAG, "Device suddenly disconnected"); - xSemaphoreGive(device_disconnected_sem); - break; - case CDC_ACM_HOST_SERIAL_STATE: - ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val); - break; - case CDC_ACM_HOST_NETWORK_CONNECTION: - default: break; - } -} - -/** - * @brief USB Host library handling task - * - * @param arg Unused - */ -static void usb_lib_task(void *arg) -{ - while (1) { - // Start handling system events - uint32_t event_flags; - usb_host_lib_handle_events(portMAX_DELAY, &event_flags); - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_ERROR_CHECK(usb_host_device_free_all()); - } - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - ESP_LOGI(TAG, "USB: All devices freed"); - // Continue handling USB events to allow device reconnection - } - } -} -} - -/** - * @brief Main application - * - * This function shows how you can use Virtual COM Port drivers - */ -extern "C" void app_main(void) -{ - device_disconnected_sem = xSemaphoreCreateBinary(); - assert(device_disconnected_sem); - - // Install USB Host driver. Should only be called once in entire application - ESP_LOGI(TAG, "Installing USB Host"); - usb_host_config_t host_config = {}; - host_config.skip_phy_setup = false; - host_config.intr_flags = ESP_INTR_FLAG_LOWMED; - ESP_ERROR_CHECK(usb_host_install(&host_config)); - - // Create a task that will handle USB library events - BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, NULL); - assert(task_created == pdTRUE); - - ESP_LOGI(TAG, "Installing CDC-ACM driver"); - ESP_ERROR_CHECK(cdc_acm_host_install(NULL)); - - // Register VCP drivers to VCP service - VCP::register_driver(); - VCP::register_driver(); - VCP::register_driver(); - - // Do everything else in a loop, so we can demonstrate USB device reconnections - while (true) { - const cdc_acm_host_device_config_t dev_config = { - .connection_timeout_ms = 5000, // 5 seconds, enough time to plug the device in or experiment with timeout - .out_buffer_size = 512, - .in_buffer_size = 512, - .event_cb = handle_event, - .data_cb = handle_rx, - .user_arg = NULL, - }; - - // You don't need to know the device's VID and PID. Just plug in any device and the VCP service will load correct (already registered) driver for the device - ESP_LOGI(TAG, "Opening any VCP device..."); - auto vcp = std::unique_ptr(VCP::open(&dev_config)); - - if (vcp == nullptr) { - ESP_LOGI(TAG, "Failed to open VCP device"); - continue; - } - vTaskDelay(10); - - ESP_LOGI(TAG, "Setting up line coding"); - cdc_acm_line_coding_t line_coding = { - .dwDTERate = EXAMPLE_BAUDRATE, - .bCharFormat = EXAMPLE_STOP_BITS, - .bParityType = EXAMPLE_PARITY, - .bDataBits = EXAMPLE_DATA_BITS, - }; - ESP_ERROR_CHECK(vcp->line_coding_set(&line_coding)); - - /* - Now the USB-to-UART converter is configured and receiving data. - You can use standard CDC-ACM API to interact with it. E.g. - - ESP_ERROR_CHECK(vcp->set_control_line_state(false, true)); - ESP_ERROR_CHECK(vcp->tx_blocking((uint8_t *)"Test string", 12)); - */ - - // Send some dummy data - ESP_LOGI(TAG, "Sending data through CdcAcmDevice"); - uint8_t data[] = "test_string"; - ESP_ERROR_CHECK(vcp->tx_blocking(data, sizeof(data))); - ESP_ERROR_CHECK(vcp->set_control_line_state(true, true)); - - // We are done. Wait for device disconnection and start over - ESP_LOGI(TAG, "Done. You can reconnect the VCP device to run again."); - xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); - } -} diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/idf_component.yml b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/idf_component.yml deleted file mode 100644 index d8f3149282a..00000000000 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/idf_component.yml +++ /dev/null @@ -1,7 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - usb_host_ch34x_vcp: "^2" - usb_host_cp210x_vcp: "^2" - usb_host_ftdi_vcp: "^2" - usb_host_vcp: "^1" - idf: ">=5.1.0" diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/CMakeLists.txt b/examples/peripherals/usb/host/cdc/main/CMakeLists.txt similarity index 100% rename from examples/peripherals/usb/host/cdc/cdc_acm_host/main/CMakeLists.txt rename to examples/peripherals/usb/host/cdc/main/CMakeLists.txt diff --git a/examples/peripherals/usb/host/cdc/main/Kconfig.projbuild b/examples/peripherals/usb/host/cdc/main/Kconfig.projbuild new file mode 100644 index 00000000000..7ec87391f93 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/main/Kconfig.projbuild @@ -0,0 +1,13 @@ +menu "Example Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + config APP_QUIT_PIN + int "APP Quit button GPIO pin" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 35 if IDF_TARGET_ESP32P4 + default 0 + help + GPIO pin number to be used as APP_QUIT button. + +endmenu diff --git a/examples/peripherals/usb/host/cdc/main/idf_component.yml b/examples/peripherals/usb/host/cdc/main/idf_component.yml new file mode 100644 index 00000000000..93dfb8331ec --- /dev/null +++ b/examples/peripherals/usb/host/cdc/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +dependencies: + usb_host_cdc_acm: "^2.3" + usb_host_ch34x_vcp: "^2.2" + usb_host_cp210x_vcp: "^2.2" + usb_host_ftdi_vcp: "^2.1" diff --git a/examples/peripherals/usb/host/cdc/main/usb_cdc_example_main.c b/examples/peripherals/usb/host/cdc/main/usb_cdc_example_main.c new file mode 100644 index 00000000000..283664957ab --- /dev/null +++ b/examples/peripherals/usb/host/cdc/main/usb_cdc_example_main.c @@ -0,0 +1,407 @@ +/* + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "driver/gpio.h" +#include "usb/cdc_host_types.h" +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include "usb/vcp_ch34x.h" +#include "usb/vcp_cp210x.h" +#include "usb/vcp_ftdi.h" + +#define EXAMPLE_USB_HOST_PRIORITY (20) +#define EXAMPLE_TX_STRING ("CDC test string!") +#define EXAMPLE_TX_TIMEOUT_MS (1000) +#define MAX_CDC_DEVICES (5) +#define ESPRESSIF_VID (0x303A) // 0x303A Espressif VID, used in TinyUSB devices or in USB-Serial-JTAG devices +#define APP_QUIT_PIN CONFIG_APP_QUIT_PIN + +static const char *TAG = "USB-CDC"; + +/** Open CDC device handles; NULL means slot is free. Needed for APP_QUIT (close all) and to clear slot on disconnect. */ +static cdc_acm_dev_hdl_t cdc_devices[MAX_CDC_DEVICES] = {0}; + +/** + * @brief Application queue and its message IDs + */ +static QueueHandle_t app_queue; +typedef struct { + enum { + APP_QUIT, /**< Request to exit: uninstall CDC driver and USB host lib */ + APP_DEVICE_CONNECTED, /**< New USB CDC device connected */ + APP_DEVICE_DISCONNECTED, /**< CDC device disconnected */ + } id; + union { + struct { + uint16_t vid; + uint16_t pid; + } new_dev; /**< VID/PID for APP_DEVICE_CONNECTED */ + int device_slot; /**< Slot for APP_DEVICE_DISCONNECTED */ + } data; +} app_message_t; + +/** + * @brief Find a free slot in the device table. + */ +static inline int find_free_slot(void) +{ + for (int i = 0; i < MAX_CDC_DEVICES; i++) { + if (cdc_devices[i] == NULL) { + return i; + } + } + return -1; +} + +/** + * @brief Data received callback + * + * @param[in] data Pointer to received data + * @param[in] data_len Length of received data in bytes + * @param[in] arg Argument we passed to the device open function + * @return + * true: We have processed the received data + * false: We expect more data + */ +static bool handle_rx(const uint8_t *data, size_t data_len, void *arg) +{ + int slot = (int)arg; + ESP_LOGI(TAG, "\t- Data received (slot %d)", slot); + ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_INFO); + return true; +} + +/** + * @brief Device event callback + * + * Forwards disconnection to app queue + * + * @param[in] event Device event type and data + * @param[in] user_ctx Argument we passed to the device open function + */ +static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + switch (event->type) { + case CDC_ACM_HOST_ERROR: + ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %i", event->data.error); + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + if (app_queue) { + app_message_t msg = { + .id = APP_DEVICE_DISCONNECTED, + .data.device_slot = (int)(intptr_t)user_ctx, + }; + xQueueSend(app_queue, &msg, 0); + } else { + ESP_ERROR_CHECK(cdc_acm_host_close(event->data.cdc_hdl)); + } + break; + case CDC_ACM_HOST_SERIAL_STATE: + ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val); + break; + default: + ESP_LOGW(TAG, "Unsupported CDC event: %d (possibly suspend/resume)", event->type); + break; + } +} + +/** + * @brief New USB device callback + * + * Gets VID/PID and posts APP_DEVICE_CONNECTED to app queue. + * Called from USB Host context; device is closed after this callback returns. + * + * @param[in] usb_dev USB device handle + */ +static void new_dev_cb(usb_device_handle_t usb_dev) +{ + const usb_device_desc_t *device_desc; + esp_err_t err = usb_host_get_device_descriptor(usb_dev, &device_desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "usb_host_get_device_descriptor failed: %s", esp_err_to_name(err)); + return; + } + + uint16_t vid = device_desc->idVendor; + uint16_t pid = device_desc->idProduct; + ESP_LOGI(TAG, "New CDC device connected VID=0x%04X PID=0x%04X", vid, pid); + + if (app_queue) { + app_message_t msg = { + .id = APP_DEVICE_CONNECTED, + .data.new_dev.vid = vid, + .data.new_dev.pid = pid, + }; + xQueueSend(app_queue, &msg, 0); + } +} + +/** + * @brief Open CDC device by VID/PID using the appropriate driver. + */ +static cdc_acm_dev_hdl_t example_cdc_open(uint16_t vid, uint16_t pid, + const cdc_acm_host_device_config_t *dev_config) +{ + cdc_acm_dev_hdl_t cdc_dev = NULL; + esp_err_t err; + + switch (vid) { + case FTDI_VID: + err = ftdi_vcp_open(pid, 0, dev_config, &cdc_dev); + break; + case NANJING_QINHENG_MICROE_VID: + err = ch34x_vcp_open(pid, 0, dev_config, &cdc_dev); + break; + case SILICON_LABS_VID: + err = cp210x_vcp_open(pid, 0, dev_config, &cdc_dev); + break; + case ESPRESSIF_VID: + default: + err = cdc_acm_host_open(vid, pid, 0, dev_config, &cdc_dev); + break; + } + + if (err == ESP_OK) { + return cdc_dev; + } + + ESP_LOGE(TAG, "Failed to open device VID=0x%04X PID=0x%04X", vid, pid); + return NULL; +} + +static void free_cdc_device(int slot) +{ + if (slot < 0 || slot >= MAX_CDC_DEVICES || cdc_devices[slot] == NULL) { + return; + } + ESP_LOGI(TAG, "\t- Closing CDC device in slot %d", slot); + cdc_acm_host_close(cdc_devices[slot]); + cdc_devices[slot] = NULL; +} + +static void free_all_cdc_devices(void) +{ + for (int i = 0; i < MAX_CDC_DEVICES; i++) { + if (cdc_devices[i] != NULL) { + free_cdc_device(i); + } + } +} + +/** + * @brief Run CDC demo (print descriptor, tx, control line, line coding) for one device. + */ +static void run_cdc_demo(int slot) +{ + cdc_acm_dev_hdl_t cdc_dev = cdc_devices[slot]; + + ESP_LOGI(TAG, "CDC device opened (slot %d). Descriptor:", slot); + cdc_acm_host_desc_print(cdc_dev); + vTaskDelay(pdMS_TO_TICKS(100)); + + ESP_LOGI(TAG, "Testing data transmission"); + ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (const uint8_t *)EXAMPLE_TX_STRING, + strlen(EXAMPLE_TX_STRING), EXAMPLE_TX_TIMEOUT_MS)); + vTaskDelay(pdMS_TO_TICKS(100)); + + ESP_LOGI(TAG, "Testing control line state command"); + esp_err_t err = cdc_acm_host_set_control_line_state(cdc_dev, false, false); + if (err == ESP_ERR_NOT_SUPPORTED) { + ESP_LOGW(TAG, "\t- Control line state set not supported"); + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "\t- Failed to set control line state"); + } else { + vTaskDelay(pdMS_TO_TICKS(20)); + cdc_acm_host_set_control_line_state(cdc_dev, false, true); + ESP_LOGI(TAG, "\t- Control line state set to DTR=false, RTS=true"); + vTaskDelay(pdMS_TO_TICKS(20)); + cdc_acm_host_set_control_line_state(cdc_dev, false, false); + } + + ESP_LOGI(TAG, "Testing line coding commands"); + cdc_acm_line_coding_t line_coding; + err = cdc_acm_host_line_coding_get(cdc_dev, &line_coding); + if (err == ESP_ERR_NOT_SUPPORTED) { + ESP_LOGW(TAG, "\t- Line coding get not supported"); + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "\t- Failed to get line coding"); + } else { + ESP_LOGI(TAG, "\t- Line Coding Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8, + line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits); + line_coding.dwDTERate = 115200; + line_coding.bDataBits = 8; + line_coding.bParityType = 0; + line_coding.bCharFormat = 0; + err = cdc_acm_host_line_coding_set(cdc_dev, &line_coding); + if (err != ESP_OK) { + ESP_LOGE(TAG, "\t- Failed to set line coding"); + } else { + ESP_LOGI(TAG, "\t- Line Set: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8, + line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits); + } + } + + ESP_LOGI(TAG, "Example finished for device in slot %d. You can disconnect or connect another device.", slot); +} + +/** + * @brief Boot button pressed callback - send APP_QUIT to front of queue. + */ +static void gpio_cb(void *arg) +{ + BaseType_t xTaskWoken = pdFALSE; + app_message_t message = { + .id = APP_QUIT, + }; + + if (app_queue) { + xQueueSendToFrontFromISR(app_queue, &message, &xTaskWoken); + } + + if (xTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief USB Host library handling task. + */ +static void usb_lib_task(void *arg) +{ + ESP_LOGI("usb_task", "Running USB task"); + const usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LOWMED, + }; + ESP_LOGI("usb_task", "\t- Installing USB Host"); + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + const cdc_acm_host_driver_config_t driver_config = { + .driver_task_stack_size = 4096, + .driver_task_priority = EXAMPLE_USB_HOST_PRIORITY + 1, + .xCoreID = 0, + .new_dev_cb = new_dev_cb, + }; + ESP_LOGI("usb_task", "\t- Installing CDC-ACM driver"); + ESP_ERROR_CHECK(cdc_acm_host_install(&driver_config)); + + xTaskNotifyGive(arg); // Notify the main task that the USB host is installed + + bool has_clients = true; // We installed CDC-ACM driver, so we have clients + while (1) { + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + has_clients = false; + if (ESP_OK == usb_host_device_free_all()) { + break; + } + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + if (!has_clients) { + break; + } + } + } + + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_ERROR_CHECK(usb_host_uninstall()); + ESP_LOGI("usb_task", "USB Host task completed"); + vTaskDelete(NULL); +} + +void app_main(void) +{ + app_queue = xQueueCreate(10, sizeof(app_message_t)); + assert(app_queue); + + BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, xTaskGetCurrentTaskHandle(), + EXAMPLE_USB_HOST_PRIORITY, NULL); + assert(task_created == pdTRUE); + + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + const gpio_config_t input_pin = { + .pin_bit_mask = BIT64(APP_QUIT_PIN), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .intr_type = GPIO_INTR_NEGEDGE, + }; + ESP_ERROR_CHECK(gpio_config(&input_pin)); + ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_LOWMED)); + ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_cb, NULL)); + + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 0, + .out_buffer_size = 512, + .in_buffer_size = 0, + .user_arg = NULL, + .event_cb = handle_event, + .data_cb = handle_rx + }; + + ESP_LOGI("app_main", "Waiting for CDC devices. Press Boot button to quit."); + + bool running = true; + while (running) { + app_message_t msg; + xQueueReceive(app_queue, &msg, portMAX_DELAY); + + switch (msg.id) { + case APP_DEVICE_CONNECTED: { + int slot = find_free_slot(); + if (slot < 0) { + ESP_LOGW("app_main", "No free slots for new CDC device (max %d)", MAX_CDC_DEVICES); + continue; + } + + dev_config.user_arg = (void *)(intptr_t)slot; + cdc_acm_dev_hdl_t cdc_dev = example_cdc_open(msg.data.new_dev.vid, msg.data.new_dev.pid, &dev_config); + if (cdc_dev == NULL) { + continue; + } + + cdc_devices[slot] = cdc_dev; + run_cdc_demo(slot); + break; + } + + case APP_DEVICE_DISCONNECTED: { + ESP_LOGI("app_main", "Device disconnected from slot %d", msg.data.device_slot); + free_cdc_device(msg.data.device_slot); + break; + } + + case APP_QUIT: { + ESP_LOGI("app_main", "Exiting example"); + free_all_cdc_devices(); + ESP_ERROR_CHECK(cdc_acm_host_uninstall()); + running = false; + break; + } + default: + ESP_LOGW("app_main", "Unknown message ID: %d", msg.id); + break; + } + } + + ESP_LOGI("app_main", "\t- Exit completed"); + gpio_isr_handler_remove(APP_QUIT_PIN); + gpio_uninstall_isr_service(); + vQueueDelete(app_queue); +} diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults b/examples/peripherals/usb/host/cdc/sdkconfig.defaults similarity index 85% rename from examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults rename to examples/peripherals/usb/host/cdc/sdkconfig.defaults index a3e635c9d8d..bf1b553b238 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults +++ b/examples/peripherals/usb/host/cdc/sdkconfig.defaults @@ -1,5 +1,4 @@ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # -CONFIG_COMPILER_CXX_EXCEPTIONS=y CONFIG_USB_HOST_HUBS_SUPPORTED=y