资讯动态

esp-iot-solution 实战:使用 iot_usbh_cdc 组件实现 USB Host CDC 通信

发布时间:2026/9/19 7:48:02 来源:尧图企业网站定制
esp-iot-solution 实战使用 iot_usbh_cdc 组件实现 USB Host CDC 通信【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solutioniot_usbh_cdc是 esp-iot-solution 仓库中实现的一个简化版 USB Host CDC通信设备类驱动组件。借助它开发者只需少量配置即可让 ESP32 系列芯片作为 USB 主机与 CDC 设备串口模块、4G/5G 模组、AT 指令设备等通信组件原生支持热插拔并可通过内部环形缓冲区ring buffer简化数据收发。读完本文你将掌握该组件的完整使用流程驱动安装、设备事件注册与匹配、端口打开、数据收发与资源释放并能理解其底层实现原理与常见踩坑点。组件概览一个轻量级的 USB Host CDC 驱动iot_usbh_cdc组件位于仓库的 components/usb/iot_usbh_cdc 目录其官方定位是simple version of the USB host CDC driver。从 组件 README 可以看到它支持以下特性支持 USB CDC 设备支持 USB Vendor 类厂商自定义类设备支持 USB CDC 多接口multiple interface支持通过 USB HUB 连接多个 USB 设备。组件内部架构上它在 USB Host Driver 之上注册了一个异步 USB Host Client由独立的 FreeRTOS 任务usbh_cdc任务负责轮询并分发设备/端口事件用户业务逻辑通过回调与 API 与设备交互。核心实现见 iot_usbh_cdc.c对外接口声明在 iot_usbh_cdc.h。将组件添加到项目最简单的方式是使用 ESP-IDF 组件管理器命令idf.py add-dependency espressif/iot_usbh_cdc*使用指南六步完成 USB CDC 通信第 1 步安装驱动usbh_cdc_driver_install调用usbh_cdc_driver_install完成驱动的安装与初始化。配置结构体usbh_cdc_driver_config_t的字段含义如下声明见 iot_usbh_cdc.h字段类型说明task_stack_sizesize_t驱动任务usbh_cdc客户端任务的栈大小单位字节task_priorityunsigned驱动任务的优先级task_coreidint驱动任务绑定的核 ID设置为 -1 表示不指定核心skip_init_usb_host_driverbool是否跳过 USB Host Driver 的初始化/* Install the USB CDC driver and initialize the USB Host Driver stack inside the driver */ usbh_cdc_driver_config_t config { .task_stack_size 1024 * 4, .task_priority 5, .task_coreid 0, .skip_init_usb_host_driver false, }; usbh_cdc_driver_install(config);关于skip_init_usb_host_driverUSB Host Driverusb_host_install在应用中只能初始化一次。当你的项目里还有其他 USB 驱动如 USB HID、USB MSC 等也需要使用 USB Host Driver 栈时应将该选项置为true由其他模块完成 USB Host Driver 的初始化避免重复初始化报错反之置为false默认让组件在内部完成初始化。从源码看iot_usbh_cdc.c当skip_init_usb_host_driver false时组件会创建usb_lib任务并在其中调用usb_host_install(host_config)安装 USB Host 库随后usbh_cdc_driver_install创建usbh_cdc客户端任务、注册 USB Host Clientusb_host_client_register并初始化事件组、互斥锁等同步原语。安装流程如下校验入参与当前状态重复安装会返回ESP_ERR_INVALID_STATE按需初始化 USB Host Driver为驱动对象分配内存创建同步原语事件组 event group、互斥锁 mutex创建驱动任务注册 USB Host Client初始化驱动结构体并恢复驱动任务运行。若中间步骤失败函数会清理已分配的资源。测试用例 test_usbh_cdc.c 中也有对空配置、重复安装、未安装就卸载等边界场景的覆盖验证。第 2 步注册设备事件回调usbh_cdc_register_dev_event_cbUSB 设备接入是异步的因此需要注册一个设备事件回调并配置设备匹配列表dev_match_id_list。当新 USB 设备连接时驱动会将该设备与匹配列表逐条比对只要命中任意一条就会调用事件回调在回调中用户可以按应用需求调用usbh_cdc_port_open打开该设备的一个或多个端口进行通信。static void device_event_cb(usbh_cdc_device_event_t event, usbh_cdc_device_event_data_t *event_data, void *user_ctx) { switch (event) { case CDC_HOST_DEVICE_EVENT_CONNECTED: { ESP_LOGI(TAG, Device connected: dev_addr%d, matched_intf_num%d, event_data-new_dev.dev_addr, event_data-new_dev.matched_intf_num); usbh_cdc_port_handle_t cdc_port NULL; usbh_cdc_port_config_t cdc_port_config { .dev_addr event_data-new_dev.dev_addr, .itf_num 0, .in_transfer_buffer_size 512, .out_transfer_buffer_size 512, .cbs { .notif_cb NULL, .recv_data NULL, .closed NULL, .user_data NULL, }, }; usbh_cdc_port_open(cdc_port_config, cdc_port); } break; case CDC_HOST_DEVICE_EVENT_DISCONNECTED: ESP_LOGI(TAG, Device disconnected: dev_addr%d, dev_hdl%p, event_data-dev_gone.dev_addr, event_data-dev_gone.dev_hdl); break; default: break; } } const static usb_device_match_id_t match_id_list[] { { .match_flags USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT, .idVendor USB_DEVICE_VENDOR_ANY, .idProduct USB_DEVICE_PRODUCT_ANY, }, { 0 } }; usbh_cdc_register_dev_event_cb(match_id_list, device_event_cb, NULL);事件枚举usbh_cdc_device_event_t只包含两类事件CDC_HOST_DEVICE_EVENT_CONNECTED设备连接与CDC_HOST_DEVICE_EVENT_DISCONNECTED设备断开。事件数据usbh_cdc_device_event_data_t是一个联合体连接事件new_dev携带dev_addr设备地址、matched_intf_num命中的接口数量-1 表示无效、device_desc设备描述符指针、active_config_desc活动配置描述符指针断开事件dev_gone携带dev_addr与dev_hdl设备句柄。设备匹配列表的语义dev_match_id_list参数要求传入一个不能位于栈上、且必须以空条目全零结尾的数组。数组中的每一项可以指定匹配一个或多个设备的条件例如上例使用USB_DEVICE_VENDOR_ANY和USB_DEVICE_PRODUCT_ANY匹配所有 USB 设备也可以填写具体的idVendor/idProduct精确匹配特定设备。匹配标志match_flagsusb_dev_match_flags_t定义见 usbh_helper.h支持更细粒度的匹配条件常见取值包括标志值含义USB_DEVICE_ID_MATCH_VENDOR0x0001匹配 VIDUSB_DEVICE_ID_MATCH_PRODUCT0x0002匹配 PIDUSB_DEVICE_ID_MATCH_DEV_BCD0x0004匹配设备 BCD 版本号USB_DEVICE_ID_MATCH_DEV_CLASS0x0010匹配设备类USB_DEVICE_ID_MATCH_DEV_SUBCLASS0x0020匹配设备子类USB_DEVICE_ID_MATCH_DEV_PROTOCOL0x0040匹配设备协议USB_DEVICE_ID_MATCH_INT_CLASS0x0080匹配接口类USB_DEVICE_ID_MATCH_INT_SUBCLASS0x0100匹配接口子类USB_DEVICE_ID_MATCH_INT_PROTOCOL0x0200匹配接口协议USB_DEVICE_ID_MATCH_INT_NUMBER0x0400匹配接口编号此外还提供组合宏USB_DEVICE_ID_MATCH_DEV匹配设备描述符全字段、USB_DEVICE_ID_MATCH_INTF匹配接口描述符、USB_DEVICE_ID_MATCH_VID_PID、USB_DEVICE_ID_MATCH_ALL以及便捷宏ESP_USB_DEVICE_MATCH_ID_ANY匹配任意设备。配套的匹配函数usbh_match_id_from_list、usbh_match_interface_in_configuration等在 usbh_helper.c 中实现。回调上下文与死锁警告重要不要在device_event_cb回调中调用usbh_cdc_write_bytes等通信函数。因为该回调运行在 USB 任务上下文中调用这些函数会造成死锁deadlock。正确做法是在回调中新建一个任务或向应用任务发送事件然后在新建任务/应用任务中执行设备通信。示例代码 usb_cdc_basic_main.c 正是这样做的在连接回调中通过xTaskCreate(usb_demo_task, ...)创建独立任务负责持续发送数据。第 3 步打开端口usbh_cdc_port_open端口配置结构体usbh_cdc_port_config_t见 iot_usbh_cdc.h关键字段字段说明dev_addrCDC 设备的设备地址itf_num要打开的 CDC 接口编号in_ringbuf_size接收环形缓冲区大小设为 0 表示不使用环形缓冲区out_ringbuf_size发送环形缓冲区大小设为 0 表示不使用环形缓冲区in_transfer_buffer_sizeIN 传输缓冲区大小不能为 0out_transfer_buffer_sizeOUT 传输缓冲区大小不能为 0cbs端口事件回调集合见下文flags端口配置标志位端口事件回调集合usbh_cdc_port_event_callbacks_t包含三个可选回调置 NULL 则禁用recv_data端口收到数据时被调用user_data作为参数传入notif_cb收到 CDC 通知notification时被调用携带iot_cdc_notification_t通知结构指针可用于解析串口状态、网络连接变化、速率变化等closed端口被关闭时调用。从实现看iot_usbh_cdc.cusbh_cdc_port_open的完整流程为校验参数 → 检查端口是否已打开同一dev_addritf_num不允许重复打开→ 首次打开该设备时调用usb_host_device_open并解析设备/配置描述符 → 按需创建收发环形缓冲区 → 分配并初始化 USB transfer →usb_host_interface_claim声明数据接口必要时还要声明通知接口→ 提交 IN 轮询传输与 INTR 通知轮询传输 → 返回端口句柄。第 4 步接收数据端口打开后主机自动将来自 CDC 设备的 USB 数据接收到 USB 缓冲区中。用户可以通过两种方式取数据轮询方式调用usbh_cdc_get_rx_buffer_size查询缓冲数据长度再调用usbh_cdc_read_bytes读取回调方式注册recv_data回调数据就绪时被通知再调用usbh_cdc_read_bytes。当in_ringbuf_size 0时驱动会启用内部接收环形缓冲区缓存数据usbh_cdc_read_bytes会先尝试一次性取出请求的字节数若不足则再从环形缓冲区补齐对应_ringbuf_pop的实现逻辑见 iot_usbh_cdc.c。当in_ringbuf_size 0时usbh_cdc_read_bytes直接从 IN transfer 缓冲拷贝数据此时传入的length必须等于内部传输缓冲区的实际数据长度actual_num_bytes否则返回ESP_ERR_INVALID_ARG源码第 1116-1122 行的校验逻辑。示例中接收回调的典型写法static void _usbh_cdc_recv_data_cb(usbh_cdc_port_handle_t cdc_port_handle, void *arg) { uint8_t buf[64]; size_t data_len 0; /* Polling USB receive buffer to get data */ usbh_cdc_get_rx_buffer_size(cdc_port_handle, data_len); if (data_len 0) { data_len data_len sizeof(buf) ? sizeof(buf) : data_len; usbh_cdc_read_bytes(cdc_port_handle, buf, data_len, 0); ESP_LOG_BUFFER_HEXDUMP(data_recv, buf, data_len, ESP_LOG_INFO); } }另外usbh_cdc_flush_rx_buffer可以清空接收缓冲区仅环形缓冲区模式支持。第 5 步发送数据usbh_cdc_write_bytesusbh_cdc_write_bytes用于向 USB 设备发送数据当out_ringbuf_size 0时数据先写入内部发送环形缓冲区_ringbuf_push再由发送任务异步提交 OUT transfer缓冲区满或设备断开时写入失败可通过ticks_to_wait设置阻塞等待时间当out_ringbuf_size 0时数据直接写入 OUT transfer 缓冲并提交到设备此时要求length out_transfer_buffer_size否则返回ESP_ERR_INVALID_SIZE见 iot_usbh_cdc.c。发送路径上还使用了out_mux互斥锁与out_xfer_free_sem二值信号量串行化传输确保多任务并发写时不会互相干扰。usbh_cdc_flush_tx_buffer可以清空发送缓冲区同样仅环形缓冲区模式支持。第 6 步关闭端口与卸载驱动usbh_cdc_port_close(port_handle)关闭指定 CDC 端口释放该端口占用的资源transfer、环形缓冲区、接口声明等usbh_cdc_driver_uninstall()彻底卸载 USB CDC 驱动并释放全部资源。注意如果设备仍处于连接状态驱动无法直接卸载——源码在卸载前会检查设备链表非空时返回ESP_ERR_INVALID_STATE并提示 Cannot uninstall usbh cdc driver, there are still exist devices见 iot_usbh_cdc.c。卸载流程会向 CDC 任务发送CDC_TEARDOWN事件、注销 USB Host Client并在全部设备释放后由usb_lib任务卸载 USB Host 库。热插拔的注意事项由于 USB 设备热插拔是异步的设备可能在运行过程中的任何时刻被拔出。用户在调用usbh_cdc_write_bytes或usbh_cdc_read_bytes等函数前必须确保设备仍然连接且端口处于打开状态否则这些函数会返回错误ESP_ERR_INVALID_STATE等。当设备被拔出时驱动内部会遍历端口链表自动关闭该设备对应的所有端口USB_HOST_CLIENT_EVENT_DEV_GONE处理分支见 iot_usbh_cdc.c。支持的 CDC 设备描述符驱动支持以下标准 CDC 设备描述符结构典型的 CDC-ACM 设备含 IAD 关联 通知接口 数据接口------------------- IAD Descriptor -------------------- bLength : 0x08 (8 bytes) bDescriptorType : 0x0B (Interface Association Descriptor) bFirstInterface : 0x00 (Interface 0) bInterfaceCount : 0x02 (2 Interfaces) bFunctionClass : 0x02 (Communications and CDC Control) bFunctionSubClass : 0x06 bFunctionProtocol : 0x00 iFunction : 0x07 (String Descriptor 7) Language 0x0409 : ---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x00 (Interface 0) bAlternateSetting : 0x00 bNumEndpoints : 0x01 (1 Endpoint) bInterfaceClass : 0x02 (Communications and CDC Control) bInterfaceSubClass : 0x06 (Ethernet Networking Control Model) bInterfaceProtocol : 0x00 (No class specific protocol required) iInterface : 0x07 (String Descriptor 7) Language 0x0409 : ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x87 (DirectionIN EndpointID7) bmAttributes : 0x03 (TransferTypeInterrupt) wMaxPacketSize : 0x0020 Bits 15..13 : 0x00 (reserved, must be zero) Bits 12..11 : 0x00 (0 additional transactions per microframe - allows 1..1024 bytes per packet) Bits 10..0 : 0x20 (32 bytes per packet) bInterval : 0x09 (256 microframes - 32 ms) ---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x01 (Interface 1) bAlternateSetting : 0x00 bNumEndpoints : 0x00 (Default Control Pipe only) bInterfaceClass : 0x0A (CDC-Data) bInterfaceSubClass : 0x00 bInterfaceProtocol : 0x00 iInterface : 0x00 (No String Descriptor) ---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x01 (Interface 1) bAlternateSetting : 0x01 bNumEndpoints : 0x02 (2 Endpoints) bInterfaceClass : 0x0A (CDC-Data) bInterfaceSubClass : 0x00 bInterfaceProtocol : 0x00 iInterface : 0x00 (No String Descriptor) ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x0C (DirectionOUT EndpointID12) bmAttributes : 0x02 (TransferTypeBulk) wMaxPacketSize : 0x0200 (max 512 bytes) bInterval : 0x00 (never NAKs) ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x83 (DirectionIN EndpointID3) bmAttributes : 0x02 (TransferTypeBulk) wMaxPacketSize : 0x0200 (max 512 bytes) bInterval : 0x00 (never NAKs)驱动也支持一些厂商自定义类Vendor SpecificbInterfaceClass 0xFF的 CDC 设备描述符例如带Mobile AT Interface移动 AT 指令接口的 4G 模组---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x03 (Interface 3) bAlternateSetting : 0x00 bNumEndpoints : 0x03 (3 Endpoints) bInterfaceClass : 0xFF (Vendor Specific) bInterfaceSubClass : 0x00 bInterfaceProtocol : 0x00 iInterface : 0x09 (String Descriptor 9) Language 0x0409 : Mobile AT Interface ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x85 (DirectionIN EndpointID5) bmAttributes : 0x03 (TransferTypeInterrupt) wMaxPacketSize : 0x0010 Bits 15..13 : 0x00 (reserved, must be zero) Bits 12..11 : 0x00 (0 additional transactions per microframe - allows 1..1024 bytes per packet) Bits 10..0 : 0x10 (16 bytes per packet) bInterval : 0x09 (256 microframes - 32 ms) ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x82 (DirectionIN EndpointID2) bmAttributes : 0x02 (TransferTypeBulk) wMaxPacketSize : 0x0200 (max 512 bytes) bInterval : 0x00 (never NAKs) ----------------- Endpoint Descriptor ----------------- bLength : 0x07 (7 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x0B (DirectionOUT EndpointID11) bmAttributes : 0x02 (TransferTypeBulk) wMaxPacketSize : 0x0200 (max 512 bytes) bInterval : 0x00 (never NAKs)驱动对描述符的解析在 iot_usbh_descriptor.c 中实现配套的类型定义IAD 描述符、Header/Union/以太网功能描述符、CDC 请求码、通知类型、串口状态位等集中在 iot_usbh_cdc_type.h。例如通知回调中常用的usb_cdc_serial_state_t联合体按位定义了 DCD、DSR、break、ring、framing/parity/overrun 错误等串口状态而iot_cdc_notification_t则承载USB_CDC_NOTIFY_NETWORK_CONNECTION、USB_CDC_NOTIFY_SPEED_CHANGE、USB_CDC_NOTIFY_SERIAL_STATE等通知宏定义见 iot_usbh_cdc_type.h示例回调_usbh_cdc_notif_cb演示了这些通知的解析方式。多设备场景HUB 连接与通道优化当通过 USB HUB 连接多个设备时如果 HOST 的通道channel不足可以尝试在usbh_cdc_port_config_t中使能USBH_CDC_FLAGS_DISABLE_NOTIFICATION标志强制禁用中断端点通知端点从而减少一个通道的占用。从源码看iot_usbh_cdc.c该标志会将解析出的通知端点强制置空cdc_info.notif_ep NULL随后端口打开流程便不会为通知端点分配 transfer、提交 INTR 轮询或声明通知接口。注意如果设备本身没有中断端点此标志不会产生额外效果。示例代码usb_cdc_basic完整的可运行示例位于 examples/usb/host/usb_cdc_basic支持 ESP32-P4、ESP32-S2、ESP32-S3、ESP32-S31 目标芯片。示例默认使能一个 Bulk 接口将usb_cdc_basic_main.c中的EXAMPLE_BULK_ITF_NUM改为2可打开两个 Bulk 接口同时通信。通过组件管理器从示例模板创建项目idf.py create-project-from-example espressif/iot_usbh_cdc*:usb_cdc_basic编译、烧录与监视示例 READMEidf.py set-target esp32s3 idf.py -p PORT build flash monitor示例演示了完整流程app_main中安装驱动 → 注册ESP_USB_DEVICE_MATCH_ID_ANY匹配回调 → 连接事件中打开端口并注册recv_data/notif_cb回调 → 创建usb_demo_task周期发送 AT\r\n 指令 → 断开事件中置位事件组通知发送任务退出。运行日志中可以看到驱动打印iot usbh cdc version、New device connected, address: 1、设备/配置描述符详情以及周期发送的 AT 指令。关键配置项Kconfig组件提供 Kconfig 菜单可通过idf.py menuconfig调整以下选项配置项默认值说明USBH_TASK_CORE_ID0CDC 任务绑定的核心 ID范围 -1~1USBH_TASK_BASE_PRIORITY5CDC 任务的基础优先级USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE256控制传输缓冲区大小字节USBH_CDC_IN_EP_RETRY_COUNT3IN 端点重试次数超过该次数将停止传输辅助 API 一览除上述核心接口外组件还提供以下实用 API均声明于 iot_usbh_cdc.husbh_cdc_send_custom_request向设备发送自定义控制请求支持 IN/OUT 双向示例中用它读取产品字符串描述符usbh_cdc_desc_print打印设备的设备描述符与配置描述符调试利器usbh_cdc_get_dev_handle获取底层usb_device_handle_t可用于调用 ESP-IDF USB Host 原生 APIusbh_cdc_port_get_intf_desc获取解析出的通知接口与数据接口描述符指针usbh_cdc_unregister_dev_event_cb注销已注册的设备事件回调usbh_cdc_driver_uninstall时也会自动清理全部回调。总而言之iot_usbh_cdc屏蔽了 USB Host 底层复杂的描述符解析、端点轮询与异步事件处理把USB CDC 设备接入 ESP32收敛为安装驱动 → 注册事件 → 打开端口 → 读写数据四步操作。理解本文介绍的匹配机制、环形缓冲区语义与回调上下文限制即可在真实项目中稳定、高效地集成串口设备、AT 模组等 USB CDC 外设。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价