Merge branch 'refactor/usb_host_cdc_example' into 'master'

refactor: Merge USB Host CDC-ACM and CDC-VCP examples

Closes IDF-11359

See merge request espressif/esp-idf!45589
This commit is contained in:
Tomas Rezucha
2026-02-06 12:35:05 +01:00
16 changed files with 454 additions and 461 deletions

View File

@@ -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 <https://components.espressif.com/component/espressif/usb_host_cdc_acm>`__.
* :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 <https://github.com/espressif/esp-protocols/tree/master/components/esp_modem/examples>`__, where it is used for communication with cellular modems.
MSC

View File

@@ -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 驱动程序通过 `乐鑫组件注册表 <https://components.espressif.com/component/espressif/usb_host_cdc_acm>`__ 作为受管理的组件分发。
* 示例 :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 <https://github.com/espressif/esp-protocols/tree/master/components/esp_modem/examples>`__ 中也使用了 CDC-ACM 驱动程序,该程序在这些示例中与蜂窝模块通信。
MSC

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,3 +0,0 @@
## IDF Component Manager Manifest File
dependencies:
usb_host_cdc_acm: "^2.1.1"

View File

@@ -1,192 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#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);
}
}

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
idf_component_register(
SRCS "cdc_acm_vcp_example_main.cpp"
INCLUDE_DIRS "."
)

View File

@@ -1,177 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#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<FT23x>();
VCP::register_driver<CP210x>();
VCP::register_driver<CH34x>();
// 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<CdcAcmDevice>(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);
}
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1,407 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#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);
}

View File

@@ -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