欢迎光临
我们一直在努力

ESP32-S31 USB Host CDC 示例源码与运行日志分析

文章目录

  • 1. 引言
  • 2. 程序源码解析
    • 2.1 整体架构
    • 2.2 关键数据结构
      • 设备表 `cdc_devices`
      • 消息队列 `app_message_t`
    • 2.3 核心函数说明
    • 2.4 主程序流程
    • 2.5 关键代码片段解析
      • 2.5.1 USB 主机库安装
      • 2.5.2 CDC 驱动安装并注册新设备回调
      • 2.5.3 根据 VID 打开设备
      • 2.5.4 接收数据回调
  • 3. 运行日志实战分析
    • 3.1 监控启动与芯片复位
    • 3.2 引导加载程序与分区表
    • 3.3 应用程序启动
    • 3.4 USB 主机任务初始化
    • 3.5 检测到 CDC 设备
    • 3.6 打印设备描述符
    • 3.7 CDC 功能测试
    • 3.8 持续接收外部数据
    • 3.9 退出程序
  • 4. 功能总结与对比
  • 5. 常见问题与注意事项
    • 5.1 为什么我的设备不被识别?
    • 5.2 `cdc_acm_host_line_coding_get()` 返回 `ESP_ERR_NOT_SUPPORTED` 怎么办?
    • 5.3 如何区分接收到的数据来自哪个设备?
    • 5.4 如何修改接收数据的处理方式?
    • 5.5 为什么有时会收到乱码或空数据?
  • 6. 总结与扩展

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

#mermaid-svg-BtvpIglBl4a2hL8T{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BtvpIglBl4a2hL8T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BtvpIglBl4a2hL8T .error-icon{fill:#552222;}#mermaid-svg-BtvpIglBl4a2hL8T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BtvpIglBl4a2hL8T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BtvpIglBl4a2hL8T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BtvpIglBl4a2hL8T .marker.cross{stroke:#333333;}#mermaid-svg-BtvpIglBl4a2hL8T svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BtvpIglBl4a2hL8T p{margin:0;}#mermaid-svg-BtvpIglBl4a2hL8T .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-BtvpIglBl4a2hL8T .cluster-label text{fill:#333;}#mermaid-svg-BtvpIglBl4a2hL8T .cluster-label span{color:#333;}#mermaid-svg-BtvpIglBl4a2hL8T .cluster-label span p{background-color:transparent;}#mermaid-svg-BtvpIglBl4a2hL8T .label text,#mermaid-svg-BtvpIglBl4a2hL8T span{fill:#333;color:#333;}#mermaid-svg-BtvpIglBl4a2hL8T .node rect,#mermaid-svg-BtvpIglBl4a2hL8T .node circle,#mermaid-svg-BtvpIglBl4a2hL8T .node ellipse,#mermaid-svg-BtvpIglBl4a2hL8T .node polygon,#mermaid-svg-BtvpIglBl4a2hL8T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BtvpIglBl4a2hL8T .rough-node .label text,#mermaid-svg-BtvpIglBl4a2hL8T .node .label text,#mermaid-svg-BtvpIglBl4a2hL8T .image-shape .label,#mermaid-svg-BtvpIglBl4a2hL8T .icon-shape .label{text-anchor:middle;}#mermaid-svg-BtvpIglBl4a2hL8T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BtvpIglBl4a2hL8T .rough-node .label,#mermaid-svg-BtvpIglBl4a2hL8T .node .label,#mermaid-svg-BtvpIglBl4a2hL8T .image-shape .label,#mermaid-svg-BtvpIglBl4a2hL8T .icon-shape .label{text-align:center;}#mermaid-svg-BtvpIglBl4a2hL8T .node.clickable{cursor:pointer;}#mermaid-svg-BtvpIglBl4a2hL8T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BtvpIglBl4a2hL8T .arrowheadPath{fill:#333333;}#mermaid-svg-BtvpIglBl4a2hL8T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BtvpIglBl4a2hL8T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BtvpIglBl4a2hL8T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BtvpIglBl4a2hL8T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BtvpIglBl4a2hL8T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BtvpIglBl4a2hL8T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BtvpIglBl4a2hL8T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BtvpIglBl4a2hL8T .cluster text{fill:#333;}#mermaid-svg-BtvpIglBl4a2hL8T .cluster span{color:#333;}#mermaid-svg-BtvpIglBl4a2hL8T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BtvpIglBl4a2hL8T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BtvpIglBl4a2hL8T rect.text{fill:none;stroke-width:0;}#mermaid-svg-BtvpIglBl4a2hL8T .icon-shape,#mermaid-svg-BtvpIglBl4a2hL8T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BtvpIglBl4a2hL8T .icon-shape p,#mermaid-svg-BtvpIglBl4a2hL8T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BtvpIglBl4a2hL8T .icon-shape .label rect,#mermaid-svg-BtvpIglBl4a2hL8T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BtvpIglBl4a2hL8T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BtvpIglBl4a2hL8T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BtvpIglBl4a2hL8T :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

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

赞(0)
未经允许不得转载:171主机测评 » ESP32-S31 USB Host CDC 示例源码与运行日志分析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址