ESP32-S31 USB Host CDC 示例源码与运行日志分析
1. 引言
ESP32-S41芯片不仅支持 Wi-Fi 和蓝牙,还内置了 USB-OTG 控制器,可以作为 USB 主机(Host)与各种 USB 设备通信。其中 CDC(Communication Device Class)类设备,尤其是 USB 转串口适配器,是最常见的应用场景之一。
本文将详细解析 ESP-IDF 官方示例 peripherals/usb/host/cdc 的程序源码,并结合实际运行日志,一步步展示 ESP32 如何:
- 初始化 USB 主机协议栈
- 安装 CDC-ACM 驱动
- 检测并打开 CDC 设备(以 CH340 为例)
- 执行数据收发、控制线设置、波特率修改等操作
- 持续接收外部串口数据并打印
无论你是想学习 USB 主机开发,还是需要调试 USB 转串口设备,这篇文章都能提供清晰的参考。
2. 程序源码解析
2.1 整体架构
程序由三个主要层次构成:
| 层级 | 组件 | 职责 |
|---|---|---|
| USB 主机库 | usb_host |
管理 USB 总线,检测设备连接和断开 |
| CDC-ACM 驱动 | cdc_acm_host + 厂商 VCP 驱动 |
对 CDC 设备和专用芯片(FTDI、CP210x、CH34x)提供统一接口 |
| 应用程序 | app_main + 消息队列 |
协调 USB 事件和用户按键,管理多个设备 |
2.2 关键数据结构
设备表 cdc_devices
static cdc_acm_dev_hdl_t cdc_devices[MAX_CDC_DEVICES] = {0};
- 存储最多 5 个已打开的设备句柄。
- 空指针表示槽位空闲,每个槽位号会作为
user_arg传递给回调函数。
消息队列 app_message_t
typedef struct {
enum {
APP_QUIT, // 退出程序
APP_DEVICE_CONNECTED, // 设备插入
APP_DEVICE_DISCONNECTED, // 设备拔出
} id;
union {
struct { uint16_t vid; uint16_t pid; } new_dev;
int device_slot;
} data;
} app_message_t;
该结构用于在 USB 任务、GPIO 中断和主任务之间安全传递事件。
2.3 核心函数说明
| 函数 | 作用 |
|---|---|
usb_lib_task |
USB 主机任务:安装主机库和 CDC 驱动,处理主机事件 |
new_dev_cb |
USB 主机回调:提取 VID/PID,发送 APP_DEVICE_CONNECTED |
handle_event |
CDC 设备事件回调:处理断开等事件,发送消息 |
handle_rx |
数据接收回调:打印收到的原始数据 |
example_cdc_open |
根据 VID 选择标准或厂商驱动打开设备 |
run_cdc_demo |
对单个设备执行演示:发送数据、控制线、行编码 |
gpio_cb |
按键中断 ISR:发送 APP_QUIT 消息 |
2.4 主程序流程
2.5 关键代码片段解析
2.5.1 USB 主机库安装
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));
2.5.2 CDC 驱动安装并注册新设备回调
const cdc_acm_host_driver_config_t driver_config = {
.driver_task_stack_size = 4096,
.driver_task_priority = EXAMPLE_USB_HOST_PRIORITY + 1,
.new_dev_cb = new_dev_cb,
};
ESP_ERROR_CHECK(cdc_acm_host_install(&driver_config));
2.5.3 根据 VID 打开设备
switch (vid) {
case FTDI_VID:
err = ftdi_vcp_open(pid, 0, dev_config, &cdc_dev);
break;
case NANJING_QINHENG_MICROE_VID: // CH340/CH341
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;
default:
err = cdc_acm_host_open(vid, pid, 0, dev_config, &cdc_dev);
}
2.5.4 接收数据回调
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;
}
3. 运行日志实战分析
以下日志是在 ESP32-S3 开发板上运行该程序,并插入一个 CH340 USB 转串口模块后抓取的。下面逐段解读。

3.1 监控启动与芯片复位
--- esp-idf-monitor 1.9.0 on \\.\COM4 115200
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
ESP-ROM:esp32s31-20251218
rst:0x1 (POWERON),boot:0x79 (SPI_FAST_FLASH_BOOT)
idf.py monitor通过串口 COM4 连接 ESP32。- 芯片上电复位,引导加载程序启动。
3.2 引导加载程序与分区表
I (26) boot: ESP-IDF 2e290950 2nd stage bootloader
I (33) boot.esp32s31: SPI Speed : 80MHz
I (40) boot.esp32s31: SPI Flash Size : 2MB
I (49) boot: Partition Table:
I (51) boot: ## Label Usage Type ST Offset Length
I (58) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (71) boot: 2 factory factory app 00 00 00010000 00100000
- 打印闪存配置和分区表。
- 应用程序位于
factory分区,偏移地址为0x10000。
3.3 应用程序启动
I (146) boot: Loaded app from partition at offset 0x10000
I (157) cpu_start: Multicore app
I (172) app_init: Project name: cdc_host
I (185) app_init: ESP-IDF: 2e290950
I (259) main_task: Calling app_main()
- 应用程序
cdc_host启动。 app_main()被执行。
3.4 USB 主机任务初始化
I (269) usb_task: Running USB task
I (269) usb_task: - Installing USB Host
I (299) usb_task: - Installing CDC-ACM driver
I (299) app_main: Waiting for CDC devices. Press Boot button to quit.
- USB 任务运行。
- 程序依次安装 USB 主机库和 CDC-ACM 驱动。
- 主循环进入等待设备插入状态。
3.5 检测到 CDC 设备
I (36659) USB-CDC: New CDC device connected VID=0x1A86 PID=0x7523
- 检测到 VID 为
0x1A86、PID 为0x7523的设备。 - 该 VID/PID 对应南京沁恒微电子的 CH340 芯片。
3.6 打印设备描述符
程序调用 cdc_acm_host_desc_print() 输出设备描述符:
*** Device descriptor ***
idVendor 0x1a86
idProduct 0x7523
bcdUSB 1.10
...
*** Endpoint descriptor ***
bEndpointAddress 0x82 EP 2 IN BULK
bEndpointAddress 0x2 EP 2 OUT BULK
bEndpointAddress 0x81 EP 1 IN INT
- CH340 使用 BULK IN/OUT 端点进行数据传输。
- INTERRUPT IN 端点用于串口状态通知。
3.7 CDC 功能测试
I (36859) USB-CDC: Testing data transmission
I (36959) USB-CDC: Testing control line state command
I (36969) USB-CDC: - Data received (slot 0)
I (36979) USB-CDC: - Control line state set to DTR=false, RTS=true
I (36999) USB-CDC: Testing line coding commands
W (36999) USB-CDC: - Line coding get not supported
I (36999) USB-CDC: Example finished for device in slot 0.
- 数据发送:程序发送了
"CDC test string!"。日志未显示发送内容,但没有报错,说明发送成功。 - 控制线设置:成功将 RTS 设为
true。 - 行编码:CH340 不支持
GET_LINE_CODING请求,因此打印警告,但不影响正常通信。 - 演示结束后,设备保持打开状态。
3.8 持续接收外部数据
从此时开始,程序每隔约 500 ms 打印一次接收到的数据:
I (36969) USB-CDC: - Data received (slot 0)
I (36969) USB-CDC: 0x2f07e804 08 08 00 04 90 |.....|
I (37469) USB-CDC: - Data received (slot 0)
I (37469) USB-CDC: 0x2f07e804 21 63 21 00 ec |!c!..|
I (37969) USB-CDC: - Data received (slot 0)
I (37969) USB-CDC: 0x2f07e804 33 69 23 21 21 fc |3i#!!.|
handle_rx()回调被触发,打印从 CH340 的 BULK IN 端点读到的数据。- 每次收到约 5 到 7 字节,内容看起来是随机十六进制值。
这些数据可能来自以下来源:
- 如果 CH340 的 TXD/RXD 引脚悬空,可能收到噪声。
- 如果短接 TX 和 RX,则发送什么就会收到什么,形成回环。
- 更常见的情况是 CH340 连接了外部串口设备,例如 GPS 模块或传感器,该设备持续输出调试信息或数据。
本例中,数据模式不规则,很可能是外部设备发送的真实数据。
3.9 退出程序
程序中可以通过 Boot 按键发送 APP_QUIT 消息,执行清理并退出。本段日志未按下按键,因此没有出现退出流程。
4. 功能总结与对比
| 功能点 | 程序是否实现 | 日志中是否体现 |
|---|---|---|
| USB 主机初始化 | 是 | 是 |
| CDC 驱动安装 | 是 | 是 |
| 检测设备插入 | 是 | 是 |
| 打开标准/厂商设备 | 是,CH340 走 ch34x_vcp_open() |
是 |
| 打印描述符 | 是 | 是 |
| 发送数据 | 是 | 未打印发送内容,但无错误 |
| 设置控制线 DTR/RTS | 是 | 是 |
| 获取/设置行编码 | 是,获取失败因芯片限制 | 有警告提示 |
| 异步接收数据 | 是 | 是,大量日志 |
| 多设备支持 | 是,最多 5 个 | 未测试 |
| 设备断开处理 | 是 | 未触发 |
| 按键退出 | 是 | 未触发 |
5. 常见问题与注意事项
5.1 为什么我的设备不被识别?
- 确保 ESP32 的 USB D+、D- 引脚连接正确,通常为 GPIO 19/20。
- 设备必须是 USB 全速(12 Mbps)或低速设备,ESP32 主机不支持高速(480 Mbps)。
- 部分设备需要外部供电,注意电流限制。
5.2 cdc_acm_host_line_coding_get() 返回 ESP_ERR_NOT_SUPPORTED 怎么办?
这是芯片固件限制,常见于 CH340、CP2102 的某些版本,不影响数据传输。可以忽略该错误,或在程序中跳过该步骤。
5.3 如何区分接收到的数据来自哪个设备?
打开设备时,将槽位号 slot 作为 user_arg 传入。在 handle_rx() 中通过 (int)arg 获取槽位号,从而区分不同设备。
5.4 如何修改接收数据的处理方式?
在 handle_rx() 中实现自定义解析,而不是仅打印。注意回调应尽快返回,避免阻塞 USB 任务。
5.5 为什么有时会收到乱码或空数据?
检查 CH340 与外部设备的波特率、数据位、停止位、校验位是否匹配。本示例未修改波特率,可通过 cdc_acm_host_line_coding_set() 修改。
6. 总结与扩展
通过本文的程序解析和日志分析,我们完整了解了 ESP32 作为 USB 主机与 CDC 设备通信的全过程:
- 软件架构:USB 主机库 + CDC-ACM 驱动 + 应用程序三层设计。
- 事件驱动:通过消息队列协调 USB 设备热插拔、数据接收和用户按键。
- 多设备支持:通过表管理多个 CDC 设备,每个设备独立运行。
- 厂商兼容:自动识别 FTDI、CP210x、CH34x 并使用专用驱动。
- 实际运行:成功驱动 CH340 模块,发送数据、控制信号,并持续接收外部串口数据。
基于这个示例,可以进一步扩展出:
- USB 转串口网关:将多个 USB 串口设备的数据转发到网络或另一个串口。
- 数据采集器:从 USB 连接的传感器,例如 GPS 或心率带,读取数据并存储。
- 调试工具:实现自定义的 USB 串口监视器。
希望本文能帮助你快速上手 ESP32 的 USB 主机开发。如果有任何疑问或发现错误,欢迎在评论区留言讨论。
更多推荐



所有评论(0)