Merge branch 'fix/usb_cdc_example' into 'master'

fix(usb/host): Update CDC example so it opens device with any VID/PID

Closes IEC-404

See merge request espressif/esp-idf!44117
This commit is contained in:
Tomas Rezucha
2025-12-16 20:27:37 +01:00
3 changed files with 119 additions and 56 deletions
@@ -5,22 +5,46 @@
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to use the CDC-ACM Host Driver to allow an ESP chip to communicate with a USB CDC-ACM device.
## 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.
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
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
## How to use example
### Hardware Required
Two development boards with USB-OTG support. One will act as USB host and the other as USB device.
* Development board with USB-OTG support that will act as USB host
* USB CDC-ACM 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 connected with USB-Serial-JTAG
- Any other USB device that implements the CDC-ACM class
#### Pin Assignment
Follow instruction 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. Build and flash [tusb_serial_device example](../../../device/tusb_serial_device) to USB device board.
2. Build this project and flash it to the USB host board, then run monitor tool to view serial output:
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
2. Connect the CDC device to the USB Host port
3. 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
@@ -34,24 +58,50 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output
After the flashing you should see the output at idf monitor:
After flashing and connecting a CDC-ACM device, you should see output similar to the following in the idf monitor:
```
...
I (256) USB-CDC: USB Host installed
I (256) USB-CDC: Opening CDC ACM device 0x303A:0x4001
...
Device descriptor is printed here
...
I (1666) USB-CDC: Data received
I (1666) USB-CDC: 0x3ffc4c20 41 54 0d |AT.|
I (2666) USB-CDC: Data received
I (2666) USB-CDC: 0x3ffc4c20 41 54 2b 47 53 4e 0d |AT+GSN.|
I (3666) USB-CDC: Setting up line coding
I (3666) USB-CDC: Line Get: Rate: 115200, Stop bits: 0, Parity: 0, Databits: 8
I (3666) USB-CDC: Line Set: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
I (3666) USB-CDC: Line Get: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
I (3676) Example finished successfully!
...
I (256) USB-CDC: Installing USB Host
I (256) USB-CDC: Installing CDC-ACM driver
I (256) USB-CDC: Opening CDC ACM device...
...
I (1174) USB-CDC: Data received
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.
```
## Troubleshooting
### Device Not Detected
If the CDC-ACM 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
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)
### No Data Received
If data is sent but not received:
1. **Check device connection:** Ensure the device is still connected and enumerated
2. **Verify device functionality:** Test the device with another host to confirm it's working
3. **Check line coding:** Some devices may require specific line coding settings before they start transmitting data
4. **Increase log level:** Set the log level to debug via `idf.py menuconfig` to get more detailed information
### Line Coding Commands Not Supported
If you see warnings like:
```
W (xxx) USB-CDC: Line coding get not supported
W (xxx) USB-CDC: Control line state set not supported
```
This is normal for some CDC-ACM devices that don't support these optional commands. The example will continue to work for basic data transfer operations.
@@ -1,4 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
usb_host_cdc_acm: "2.*"
idf: ">=4.4"
usb_host_cdc_acm: "^2.1.1"
@@ -7,7 +7,6 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
@@ -19,9 +18,6 @@
#include "usb/cdc_acm_host.h"
#define EXAMPLE_USB_HOST_PRIORITY (20)
#define EXAMPLE_USB_DEVICE_VID (0x303A)
#define EXAMPLE_USB_DEVICE_PID (0x4001) // 0x303A:0x4001 (TinyUSB CDC device)
#define EXAMPLE_USB_DEVICE_DUAL_PID (0x4002) // 0x303A:0x4002 (TinyUSB Dual CDC device)
#define EXAMPLE_TX_STRING ("CDC test string!")
#define EXAMPLE_TX_TIMEOUT_MS (1000)
@@ -113,7 +109,7 @@ void app_main(void)
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, xTaskGetCurrentTaskHandle(), EXAMPLE_USB_HOST_PRIORITY, NULL);
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");
@@ -131,16 +127,14 @@ void app_main(void)
while (true) {
cdc_acm_dev_hdl_t cdc_dev = NULL;
// Open USB device from tusb_serial_device example example. Either single or dual port configuration.
ESP_LOGI(TAG, "Opening CDC ACM device 0x%04X:0x%04X...", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID);
esp_err_t err = cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID, 0, &dev_config, &cdc_dev);
// 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, "Opening CDC ACM device 0x%04X:0x%04X...", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_DUAL_PID);
err = cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_DUAL_PID, 0, &dev_config, &cdc_dev);
if (ESP_OK != err) {
ESP_LOGI(TAG, "Failed to open device");
continue;
}
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));
@@ -149,27 +143,47 @@ void app_main(void)
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));
// Test Line Coding commands: Get current line coding, change it 9600 7N1 and read again
ESP_LOGI(TAG, "Setting up line coding");
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;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
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);
line_coding.dwDTERate = 9600;
line_coding.bDataBits = 7;
line_coding.bParityType = 1;
line_coding.bCharFormat = 1;
ESP_ERROR_CHECK(cdc_acm_host_line_coding_set(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Set: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
ESP_LOGI(TAG, "Line Get: Rate: %"PRIu32", Stop bits: %"PRIu8", Parity: %"PRIu8", Databits: %"PRIu8"",
line_coding.dwDTERate, line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
ESP_ERROR_CHECK(cdc_acm_host_set_control_line_state(cdc_dev, true, false));
// 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.");