资讯详情

ESP32 USB Host RNDIS 驱动实战:基于 `iot_usbh_rndis` 连接 4G 模组实现 USB 上网

📅 2026/9/19 6:25:54 | 华诺云谱 👁 阅读
ESP32 USB Host RNDIS 驱动实战:基于 `iot_usbh_rndis` 连接 4G 模组实现 USB 上网
ESP32 USB Host RNDIS 驱动实战基于iot_usbh_rndis连接 4G 模组实现 USB 上网【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solutioniot_usbh_rndis是 esp-iot-solution 仓库中面向 ESP32-S2 / ESP32-S3 / ESP32-P4 的 USB Host 端 RNDISRemote NDIS以太网驱动它让 ESP32 作为 USB 主机自动识别并连接 RNDIS 协议设备典型场景是各类 4G 模组从而把蜂窝网络转换成 ESP32 的以太网接口。读完本文你将掌握该组件的添加方式、核心 API、RNDIS 底层通信流程INITIALIZE / QUERY / SET / Keepalive、帧格式与数据收发实现并能基于仓库自带的usb_rndis_4g_module示例在真实硬件上搭建4G 模组 → ESP32 → Wi-Fi 热点的上网链路。RNDIS 协议与组件定位RNDISRemote Network Driver Interface Specification本质上是一种把 TCP/IP 数据包封装进 USB 报文的协议主机通过控制端点发送初始化、查询、设置等消息通过批量端点Bulk传输以太网帧。对 ESP32 而言接入一个 RNDIS 4G 模组后系统里会出现一块虚拟以太网网卡上层 lwIP / esp_netif 无需感知底层是 USB 还是 SPI 以太网。iot_usbh_rndis在仓库中的定位是 iot_eth 生态的一员其 idf_component.yml 声明了三个关键依赖依赖版本/要求作用ESP-IDF 5.2底层 USB Host 库与系统接口espressif/iot_usbh_cdc^3.1公开依赖复用 CDC 驱动的端口抽象、批量收发与通知回调espressif/iot_eth*公开依赖提供iot_eth_driver_t等以太网抽象与事件机制组件支持的目标芯片为 esp32s2 / esp32s3 / esp32s31 / esp32p4见 idf_component.yml。README 中明确的核心特性有两点支持 RNDIS 设备热插拔Hot-plug支持 RNDIS 设备的自动检测与自动连接。这两点分别由 CDC 驱动的设备事件回调usbh_cdc_register_dev_event_cb和驱动内部的任务状态机保证下文会逐一展开。快速集成添加组件依赖README 给出的标准接入方式是使用组件管理器命令add-dependencyCMake 构建阶段会自动下载依赖idf.py add-dependency espressif/iot_usbh_rndis*该命令会在工程的main/idf_component.yml中写入依赖声明。若你更习惯手动管理也可以直接编辑该文件dependencies: espressif/iot_usbh_rndis: * # 依赖管理器会自动拉取 iot_usbh_cdc、iot_eth、cmake_utilities 等传递依赖添加完成后即可在源码中引用公开头文件 include/iot_usbh_rndis.h。注意RNDIS 控制消息复用了 CDC 的自定义请求通道SEND/GET_ENCAPSULATED_COMMAND因此要求CONFIG_USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE至少为 1025 字节——源码 iot_usbh_rndis.c 中对此有编译期断言#define RNDIS_CONTROL_BUFFER_SIZE 1025 #if CONFIG_USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE RNDIS_CONTROL_BUFFER_SIZE #error CONFIG_USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE must be at least RNDIS_CONTROL_BUFFER_SIZE for RNDIS control responses #endif示例工程 sdkconfig.defaults 中配置为CONFIG_USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE1025可直接参考。核心 API 与配置结构组件对外只暴露两个接口设计非常精简详见 include/iot_usbh_rndis.h。配置结构iot_usbh_rndis_config_ttypedef struct { const usb_device_match_id_t *match_id_list; /*! USB device match ID for RNDIS */ } iot_usbh_rndis_config_t;match_id_list是 USB 设备匹配列表用于过滤哪些 VID/PID 的 USB 设备被当作 RNDIS 设备处理列表以全 0 结构体结尾。最常用的做法是匹配任意厂商与产品与 测试用例 一致static const usb_device_match_id_t dev_match_id[] { { .match_flags USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT, .idVendor USB_DEVICE_VENDOR_ANY, .idProduct USB_DEVICE_PRODUCT_ANY, }, {0}, // Null-terminated };也可以精确指定 4G 模组的 VID/PID示例工程中即采用USB_DEVICE_ID_MATCH_VID_PID方式见 usb_rndis_4g_module.c。创建驱动iot_eth_new_usb_rndisesp_err_t iot_eth_new_usb_rndis(const iot_usbh_rndis_config_t *config, iot_eth_driver_t **ret_handle);返回ESP_OK表示驱动创建成功ESP_ERR_INVALID_ARG表示 config 或 ret_handle 为 NULLESP_ERR_NO_MEM表示内存分配失败。该函数只负责分配并初始化usbh_rndis_t结构体内部嵌入iot_eth_driver_t base并挂接init / deinit / set_mediator / transmit / get_addr五个驱动回调见 iot_usbh_rndis.c真正的资源初始化发生在之后调用rndis_eth_driver-init()时。获取 CDC 端口句柄usb_rndis_get_cdc_port_handleusbh_cdc_port_handle_t usb_rndis_get_cdc_port_handle(const iot_eth_driver_t *rndis_drv);RNDIS 4G 模组通常是复合设备RNDIS 网卡 多个串口本接口返回 RNDIS 驱动占用的 CDC 端口句柄供上层复用usbh_cdc_port_open打开模组的 AT 串口、发送 AT 指令拨号或查询信号质量详见下文示例部分。USB 描述符要求与设备识别RNDIS 设备在 USB 描述符层面有明确约定官方文档 docs/zh_CN/usb/usb_host/usb_rndis.rst 给出了三条约束RNDIS 设备默认把自己声明为 USB 配置中的唯一功能对于复合设备RNDIS 希望自己是第一个 USB 配置即bInterfaceNumber为 0 的接口RNDIS 接口由一个Interface Association DescriptorIAD 两个接口描述符组成。驱动在 iot_usbh_rndis_descriptor.c 中通过usb_parse_next_descriptor_of_type扫描配置描述符匹配以下两类 IAD源码中宏USB_CLASS_WIRELESS_CONTROLLER即 0xe0USB_CLASS_MISC即 0xef匹配条件bFunctionClassbFunctionSubClassbFunctionProtocol方式一无线控制器类0xe00x01RF Controller0x03RNDIS方式二杂项类0xef0x04Common Class0x01IAD RNDIS匹配成功要求bInterfaceCount 2并把bFirstInterface作为 RNDIS 数据接口号itf_num保存未找到则返回ESP_ERR_NOT_FOUND日志打印No RNDIS interface found for device VID: ...。文档 usb_rndis.rst 给出了典型 RNDIS 描述符示例第一个接口bInterfaceNumber 0class 0xe0带 1 个中断 IN 端点作为通知端点第二个接口bInterfaceNumber 1class 0x0a即 CDC Data带 1 个批量 IN 1 个批量 OUT 端点承载以太网帧。运行日志中cdc_descriptor: Found NOTIF endpoint / OUT endpoint / IN endpoint即对应这三个端点的解析结果。RNDIS 通信流程控制通道上的三次握手RNDIS 控制消息全部走 CDC 的自定义类请求SEND_ENCAPSULATED_COMMANDOUT与GET_ENCAPSULATED_RESPONSEIN。协议消息体定义在 priv_include/usbh_rndis_protocol.hOIDObject ID对象标识符宏定义在 priv_include/ndis.h。步骤一初始化REMOTE_NDIS_INITIALIZE_MSG驱动上电后_usbh_rndis_task等待事件组中的RNDIS_CONNECTED位由设备事件回调在检测到 RNDIS 接口时置位随后调用usbh_rndis_connect()→usbh_rndis_init_msg_request()见 iot_usbh_rndis.ccmd-MessageType REMOTE_NDIS_INITIALIZE_MSG; cmd-MessageLength sizeof(rndis_initialize_msg_t); cmd-RequestId usbh_rndis_next_request_id(rndis); cmd-MajorVersion 0x1; cmd-MinorVersion 0x0; cmd-MaxTransferSize 0x4000;设备回复REMOTE_NDIS_INITIALIZE_CMPLT驱动从中解析出MaxPacketsPerTransfer单次传输最大包数与MaxTransferSize单次传输最大字节数日志打印Max transfer packets: 10、Max transfer size: 3200即来自该响应。步骤二OID 查询REMOTE_NDIS_QUERY_MSG初始化成功后驱动先查询OID_GEN_SUPPORTED_LIST0x00010101拿到设备支持的 OID 列表然后逐个查询见 iot_usbh_rndis.cOID取值用途OID_GEN_PHYSICAL_MEDIUM0x00010202物理介质类型OID_GEN_MAXIMUM_FRAME_SIZE0x00010106最大以太网帧大小OID_GEN_LINK_SPEED0x00010107链路速率存入rndis-link_speedOID_GEN_MEDIA_CONNECT_STATUS0x00010114媒体连接状态NDIS_MEDIA_STATE_CONNECTED(0)表示已连接状态变化会触发RNDIS_LINE_CHANGE事件OID_802_3_MAXIMUM_LIST_SIZE0x01010104组播列表最大长度OID_802_3_CURRENT_ADDRESS0x01010102当前 MAC 地址拷贝到rndis-eth_mac_addrOID_802_3_PERMANENT_ADDRESS0x01010101永久 MAC 地址OID_GEN_MAXIMUM_TOTAL_SIZE0x00010111最大总包大小之后用REMOTE_NDIS_SET_MSG完成两项关键配置uint32_t packet_filter 0x0f; // 对应 NDIS_PACKET_TYPE_DIRECTED|MULTICAST|ALL_MULTICAST|BROADCAST usbh_rndis_set_msg_request(rndis, OID_GEN_CURRENT_PACKET_FILTER, (uint8_t *)packet_filter, 4); usbh_rndis_set_msg_request(rndis, OID_802_3_MULTICAST_LIST, rndis-mac_addr, 6);OID_GEN_CURRENT_PACKET_FILTER0x0001010E设置接收过滤器0x0f 表示接收单播/组播/全组播/广播帧OID_802_3_MULTICAST_LIST0x01010103上报本机 MAC。日志中rndis set OID_GEN_CURRENT_PACKET_FILTER success即该步骤完成。步骤三连接上报与 DHCP握手完成后驱动向 iot_eth 层上报链路状态RNDIS_CONNECTED→usbh_rndis_connect成功后等待RNDIS_LINE_CHANGE事件通过mediator-on_stage_changed(..., IOT_ETH_STAGE_LINK, link)把IOT_ETH_LINK_UP通知给协议栈见 iot_usbh_rndis.c。随后应用层即可发起 DHCP 获取 IP 地址示例日志中的ETHIP:192.168.43.100 / ETHGW:192.168.43.1即为模组分配的地址。Keepalive 与异步事件处理驱动对三类异步消息做了专门处理usbh_rndis_recv_ctrl_response中的消息分拣见 iot_usbh_rndis.cREMOTE_NDIS_INDICATE_STATUS_MSG0x00000007解析RNDIS_STATUS_MEDIA_CONNECT/RNDIS_STATUS_MEDIA_DISCONNECT同步更新connect_status并触发链路状态事件REMOTE_NDIS_KEEPALIVE_MSG0x00000008设备侧主动保活请求驱动立即回REMOTE_NDIS_KEEPALIVE_CMPLTCDC 通知 USB_CDC_NOTIFY_RESPONSE_AVAILABLE置位RNDIS_RESPONSE_AVAILABLE任务随后调用usbh_rndis_process_async_response()主动去 GET 响应。控制通道的健壮性由重试机制保障RNDIS_CONTROL_RETRY_COUNT 10、每次失败或消息不匹配延迟RNDIS_CONTROL_RETRY_DELAY_MS 40ms同时校验响应长度、RequestId与Status非RNDIS_STATUS_SUCCESS直接返回ESP_FAIL。帧格式数据通道上的 RNDIS 封装数据平面只涉及一种消息类型REMOTE_NDIS_PACKET_MSG0x00000001封装结构为rndis_data_packet_t定义见 usbh_rndis_protocol.h。文档 usb_rndis.rst 对帧格式的说明如下发送需要在以太网帧前添加 RNDIS 数据头rndis_data_packet_t再拼接以太网帧报文接收设备发来的数据同样以 RNDIS 数据头开头需要先解析头部再剥离出以太网帧。发送路径usbh_rndis_transmit()见 iot_usbh_rndis.c的实现与之一致动态分配头部 载荷缓冲区MessageType置为REMOTE_NDIS_PACKET_MSGMessageLength为头部与载荷长度之和DataOffset指向载荷起点DataLength为载荷长度最后通过usbh_cdc_write_bytes(..., 500ms 超时)写入批量 OUT 端点。接收路径_usbh_rndis_recv_data_cb()见 iot_usbh_rndis.c采用先读头部、再读载荷的两段式设计并利用 CDC 端口的内环缓冲区in_ringbuf_size 4096应对粘包若pmsg.MessageType ! REMOTE_NDIS_PACKET_MSG则调用usbh_cdc_flush_rx_buffer丢弃异常数据若载荷未收齐则把DataLength暂存到rx_pending_data_len等下一次回调继续读取。完整数据包通过mediator-stack_input_info/mediator-stack_input上交给协议栈。数据平面参数与 Kconfig 选项驱动在打开 CDC 端口时对传输参数做了针对性配置见 iot_usbh_rndis.cusbh_cdc_port_config_t cdc_port_config { .dev_addr event_data-new_dev.dev_addr, .itf_num itf_num, .in_transfer_buffer_size 512 * 4, // 批量 IN 传输缓冲 .out_transfer_buffer_size 512 * 3, // 批量 OUT 传输缓冲 .in_ringbuf_size 4096, // 使能环缓冲以处理粘包 .cbs { .notif_cb _usbh_rndis_notif_cb, .recv_data _usbh_rndis_recv_data_cb, .user_data rndis, }, #ifdef CONFIG_RNDIS_DISABLE_CDC_NOTIFICATION .flags USBH_CDC_FLAGS_DISABLE_NOTIFICATION, #endif };组件唯一的 Kconfig 开关位于 KconfigRNDIS_DISABLE_CDC_NOTIFICATIONbool默认n关闭 CDC 通知通道以节省 USB Host 的通道资源。注意关闭后RESPONSE_AVAILABLE通知不再触发异步响应改由轮询方式处理源码注释也说明 Linux 在发送 SEND_ENCAPSULATED_COMMAND 后直接轮询控制通道。对于通道资源紧张或不需要实时通知的场景可开启。另一个影响接收效率的是 CDC 通知回调对USB_CDC_NOTIFY_NETWORK_CONNECTION与USB_CDC_NOTIFY_SPEED_CHANGE的解析后者会把上下行速率以 kbps 打印见 iot_usbh_rndis.c。实战示例usb_rndis_4g_module 完整接入流程仓库自带的示例工程位于 examples/usb/host/usb_rndis_4g_module支持 ESP32-P4 / ESP32-S2 / ESP32-S3 / ESP32-S31见其 README.md。它的核心价值在于展示了RNDIS 网卡 AT 串口复用 Wi-Fi 热点的完整产品级组合。硬件接线┌─────────────┐ ┌─────────────────┐ │ ┼──────────┼5V │ │ 4G Module ┼──────────┼GND │ │ │ │ ESP32-xx │ │ ┼──────────┼USB D │ │ ┼──────────┼USB D- │ └─────────────┘ └─────────────────┘驱动安装与网卡创建app_main中先完成基础设施初始化NVS、esp_netif、事件循环然后安装 CDC 驱动见 usb_rndis_4g_module.cusbh_cdc_driver_config_t config { .task_stack_size 1024 * 4, .task_priority configMAX_PRIORITIES - 1, .task_coreid 0, .skip_init_usb_host_driver false, // 由本驱动一并初始化 USB Host }; ESP_ERROR_CHECK(usbh_cdc_driver_install(config));install_rndis()函数则是 RNDIS 驱动的标准接线模板创建 RNDIS 以太网驱动 → 安装到 iot_ethiot_eth_install→ 新建 esp_netif 网卡继承ESP_NETIF_INHERENT_DEFAULT_ETH()if_key/if_desc设为USB RNDIS0→ 创建 netif glue 并 attach →iot_eth_start见 usb_rndis_4g_module.c。注意dev_match_id数组由驱动接管生命周期创建成功后不能再free。事件驱动获取 IP 后开启热点示例注册了IOT_ETH_EVENT与IP_EVENT监听IOT_ETH_EVENT_CONNECTED若开启 AT 命令功能则调用at_init()打开模组 AT 串口IP_EVENT_ETH_GOT_IP置位EVENT_GOT_IP_BIT随后启动 ping 定时器验证连通性IOT_ETH_EVENT_DISCONNECTED清除 IP 事件位并停止 ping。拿到 IP 后示例启动 Wi-Fi SoftAP 分享网络app_wifi_main并把 RNDIS 网卡设为默认网卡esp_netif_set_default_netif(s_rndis_netif)。Wi-Fi 热点默认名称ESP-USB-4G、无密码可在 menuconfig 的4G Modem WiFi Config中修改。为了让数据在 SoftAP 与 RNDIS 网卡之间转发sdkconfig.defaults 开启了 lwIP 转发与 IPv4 NAPTCONFIG_LWIP_IP_FORWARDy CONFIG_LWIP_IPV4_NAPTy CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE512 CONFIG_USBH_CDC_CONTROL_TRANSFER_BUFFER_SIZE1025 CONFIG_USB_HOST_HUBS_SUPPORTEDy复用 CDC 端口收发 AT 指令RNDIS 模组一般是网卡 串口复合设备示例通过usb_rndis_get_cdc_port_handle(s_rndis_eth_driver)拿到 RNDIS 驱动已打开的 CDC 端口再用usb_host_device_info反查设备地址最后用CONFIG_EXAMPLE_AT_INTERFACE_NUM指定的接口号打开 AT 串口见 usb_rndis_4g_module.c配合modem_at解析器周期性打印信号质量ATCSQ、模组厂商/型号/版本与 PDP 上下文。menuconfig 中Example Configuration下的开关可启用 AT 命令与下载速度测试。典型运行输出I (884) USBH_CDC: New device connected, address: 1 I (884) usbh_rndis: RNDIS device found: VID: 2C7C, PID: 0903, IFNUM: 0 I (902) usbh_rndis: Max transfer packets: 10 I (905) usbh_rndis: Max transfer size: 3200 I (918) usbh_rndis: rndis set OID_GEN_CURRENT_PACKET_FILTER success I (931) usbh_rndis: RNDIS connected success I (936) iot_eth: Ethernet link up I (9454) RNDIS_4G_MODULE: GOT_IP I (9457) iot_eth.netif_glue: ETHIP:192.168.43.100 I (9468) iot_eth.netif_glue: ETHGW:192.168.43.1已测试 4G 模组清单官方文档 usb_rndis.rst 的已测试 4G 模组一节给出了在 ESP32-S2 / ESP32-S3 / ESP32-P4 上验证通过的产品及其 USB 功能切换 AT 指令仅供参考以实际模组手册为准型号固件版本USB 功能切换 AT 指令RNDIS / ECM 示意ML307RML307R-DC-MBRH0S00ATMDIALUPCFGmode,00:RNDIS 1:ECMML302ML302-CNLM_MBRH0S00ATSYSNV1,usbmode,33:RNDISserials 5:ECMserialsAIR720 SLAirM2M_720SL_V545_LTE_ATATSETUSB11:RNDIS 2:ECMEC800E-CNEC800ECNLCR01A21M04ATQCFGusbnet,11:ECM 3:RNDISEC801E-CNEC801ECNCGR03A01M04_BETA_0521AATQCFGusbnet,11:ECM 3:RNDISYM310YM310.X09S_AT.A60_R2.1.3.241121ATECPCFGusbNet,00:RNDIS 1:ECMMC610-EU16000.1000.00.97.20.10ATGTUSBMODE3333:RNDIS 32:ECM需ATGTRNDIS1,1激活拨号Lierda NT26NT26FCNB30WNA-Q01010950ATECPCFGusbNet,00:RNDIS 1:ECM需ATECNETDEVCTL3,1激活自动拨号需要注意部分 4G 模组必须先用 AT 指令把 USB 模式切到 RNDIS 并设置自动拨号RNDIS 链路建立后 ESP32 才能拿到 IP具体请参考对应模组的文档手册。性能参考文档 usb_rndis.rst 记录的实测速率如下ESP32-S3 连接 4G 网卡手机连接 SoftAP 测速芯片上行 (Mbps)下行 (Mbps)ESP32-S35.87.9该数据来自官方文档在特定硬件与网络环境下的实测实际速率取决于运营商网络、模组与 WiFi 环境。测试与验证组件在 test_apps/main/test_usbh_rndis.c 中提供了基于 Unity 框架的测试用例覆盖两类场景usbh rndis inini device memory leak安装 CDC 驱动 → 创建并初始化 RNDIS 驱动 → 等待 10 秒 → deinit → 卸载驱动验证热插拔场景下无内存泄漏usbh rndis device memory leak同样流程并设置UPDATE_LEAK_THRESHOLD(-40)进一步校验完整连接/断开周期后的内存水位。测试代码同时给出了 RNDIS 驱动的最小初始化模板usbh_cdc_driver_install→iot_eth_new_usb_rndis→init→deinit可当作接入 RNDIS 组件时的最小可用参考。小结从组件依赖iot_usbh_cdciot_eth、USB 描述符识别两类 IAD、控制通道三次握手INITIALIZE → OID QUERY/SET → LINK UP到数据平面的 RNDIS 帧封装、热插拔事件处理与 Keepalive 保活iot_usbh_rndis把 RNDIS 协议栈完整收敛到了一个即插即用的 iot_eth 驱动中。接入时只需三步idf.py add-dependency添加依赖、按iot_usbh_rndis_config_t配置 VID/PID 匹配列表、创建驱动并挂到 esp_netif。配合仓库自带的usb_rndis_4g_module示例即可快速构建4G 模组 USB 上网 Wi-Fi 热点共享的物联网网关方案。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。