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 主程序流程

CONNECTED

DISCONNECTED

QUIT

app_main 启动

创建消息队列

创建 usb_lib_task

等待 USB 任务就绪

配置 GPIO 按键中断

进入主循环处理消息

收到消息类型

打开设备并运行 run_cdc_demo

关闭设备

关闭所有设备

卸载驱动并退出

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 主机开发。如果有任何疑问或发现错误,欢迎在评论区留言讨论。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐