网络通信主机方案

概述

USB 通信设备类 (CDC) 下的以太网控制模型 (ECM) 定义了一套通过 USB 接口传输以太网帧的标准协议。在主机 (Host) 模式下,系统通过该协议识别并驱动外部 USB 网络设备(如 USB 转以太网适配器、LTE 蜂窝模组),将其抽象为本地网络接口。

Ameba 平台集成了符合 USB-IF 标准的 CDC ECM 主机协议栈,支持与系统内置 LwIP 网络协议栈的无缝对接。该方案为设备提供了灵活的网络扩展能力,使其能够通过 USB 接口便捷地接入有线局域网或 4G 蜂窝网络。

Ameba USB CDC ECM 主机

特性

  • 支持标准 CDC ECM 设备 (如 USB 网卡、LTE 模组)

  • 与 LwIP 协议栈深度集成,抽象为标准网络接口 (Netif)

  • 支持复合设备,可并行处理多接口功能 (如 AT 命令 + 网络)

  • 支持热插拔

  • 优化批量传输,提供高性能数据吞吐

应用场景

作为 USB 主机 (Host),Ameba 负责对连接的 CDC ECM 设备进行枚举、配置及驱动加载。此方案允许用户在不修改板级硬件设计的情况下,灵活拓展网络连接能力,满足多样化的应用需求,例如:

  • 有线以太网扩展 (USB-to-Ethernet):Ameba 通过 USB 接口驱动外置的标准 USB 转以太网适配器(支持 Realtek RTL815x 等主流芯片方案)。该方案为未配备原生以太网接口的硬件平台提供了即插即用的有线接入能力。

  • 4G 蜂窝网络接入 (LTE Dongle):Ameba 通过 USB 接口连接支持 ECM 模式的 4G 模组或 USB Dongle。协议栈将 USB 数据流转换为标准网络数据包,实现通过蜂窝网络接入互联网。此场景适用于户外 IoT 网关、车载终端等需要移动网络回传或作为 Wi-Fi 链路备份的应用。

  • 双网口路由与桥接:结合 Ameba 原生的 Wi-Fi 接口,利用 USB 接口额外扩展物理网络通道。Ameba 可作为网络枢纽,在不同网络接口间执行数据转发、负载均衡或故障切换,组成简易的无线路由器或双网口桥接设备。

协议简介

CDC (Communication Device Class) 是 USB 规范定义的通用通信设备类标准。ECM (Ethernet Control Model) 作为其子类(Subclass),专门定义了如何在 USB 接口上封装和传输以太网数据帧,使 USB 设备能够在操作系统中被识别为标准的网络接口控制器 (NIC)。

协议文档

USB-IF 官方发布了 CDC 类基础协议及 ECM 子类规范。开发过程中请参考以下核心文档:

规范类型

文档

CDC 1.2 (通信类基础协议)

下载

ECM 1.2 (以太网控制模型)

包含在上述 CDC 1.2 压缩包中的 ECM120.pdf。

术语定义

本文档涉及的通用 CDC ECM 技术术语定义如下:

术语

描述

CCI (Communication Class Interface)

通信类接口。用于管理设备的连接状态、发送控制命令和接收网络状态通知(如网线插拔)。通常使用控制端点和中断端点。

DCI (Data Class Interface)

数据类接口。用于实际传输以太网数据包。通常使用批量 (Bulk) 输入和输出端点。

以太网帧 (Ethernet Frame)

CDC ECM 传输的数据载荷,通常遵循 IEEE 802.3 标准(包含目标 MAC、源 MAC、EtherType 及 Payload)。

通知 (Notification)

设备通过中断端点主动向主机报告的异步事件,例如 NETWORK_CONNECTION (连接状态改变) 或 CONNECTION_SPEED_CHANGE (速率改变)。

功能描述符 (Functional Descriptor)

CDC 特有的描述符,嵌入在标准配置描述符中,用于描述网络设备的具体能力(如 MAC 地址、最大段长度)。

协议框架

CDC ECM Host 协议栈采用分层架构设计,旨在实现 USB 传输层与 LwIP 网络协议栈的解耦。

../../../_images/usb_cdc_ecm_spec_arch_zh.svg

组件职责

  • 网络应用层

    位于架构最上层,它是一个具体的网络应用。

  • LwIP/网络协议栈 (Network Stack)

    网络协议栈不感知底层的 USB 细节,仅操作一个抽象的标准网络接口 (Netif),给上层提供网络服务。负责处理 TCP/IP、UDP、DHCP 等网络层协议逻辑。

  • CDC ECM 类驱动 (Class Driver)

    核心中间层,实现了 ECM 规范定义的行为:

    • 枚举解析:解析 CDC 功能描述符以获取 MAC 地址。

    • 控制管理:配置数据包过滤器 (Packet Filter),处理网络状态中断通知。

    • 数据收发:将网络栈下发的 pbuf 封装为 USB 传输请求 (URB/Transfer),并将接收到的 USB 数据包还原为以太网帧上交 LwIP。

  • USB Core & HCD (Host Controller Driver)

    底层驱动,负责处理 USB 标准枚举、端点管理以及底层的物理数据传输调度(如 DMA 操作),向类驱动屏蔽不同硬件控制器的差异。

通信机制

标准的 CDC ECM 设备通常由两个 USB 接口 (Interface) 组成,两者通过 联合功能描述符 (Union Functional Descriptor) 关联:

通信接口 (CCI) - 控制与通知

  • 控制传输 (Control Transfer)

    • 映射端点: 默认控制端点 0 (Endpoint 0)。

    • 枚举与配置: 传输标准的 USB 描述符以及 CDC 特有的功能描述符。

    • 类特定请求: 处理 ECM 协议控制命令,最核心的是 SetEthernetPacketFilter,用于配置设备接收广播、多播或单播包。

  • 中断传输 (Interrupt Transfer)

    • 中断输入 (Interrupt IN): 必选。用于设备向主机发送通知 (Notification)。

    • 典型应用:

      NETWORK_CONNECTION: 报告网线插入 (Link Up) 或拔出 (Link Down) 状态。 CONNECTION_SPEED_CHANGE: 报告当前的上下行连接速率。

数据接口 (DCI) - 批量管道

数据接口通常包含两个替代设置 (Alternate Settings),这是 ECM 协议设计的关键点:

  • Alt Setting 0:空闲模式。不包含任何端点。当网络未连接或驱动未加载时,主机应将接口切换至此模式以节省总线带宽。

  • Alt Setting 1:工作模式。包含一对批量端点 (Bulk IN / Bulk OUT)。

    • Bulk IN:设备 -> 主机。上传接收到的网络数据包。

    • Bulk OUT:主机 -> 设备。发送要转发到网络的以太网帧。

描述符结构

CDC ECM 设备在标准配置描述符中,通过 类特定接口描述符 (Class-Specific Interface Descriptor) 来详细定义网络能力。这些描述符通常被统称为 功能描述符 (Functional Descriptors)。

CDC ECM 描述符拓扑 (Descriptor Topology)

Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│       ├── Header Functional Descriptor
│       ├── Union Functional Descriptor
│       ├── Ethernet Networking Functional Descriptor
│       └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        ├── Endpoint Descriptor(Bulk In)
        └── Endpoint Descriptor(Bulk Out)

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│       ├── Header Functional Descriptor
│       ├── Union Functional Descriptor
│       ├── Ethernet Networking Functional Descriptor
│       └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        ├── Endpoint Descriptor(Bulk In)
        └── Endpoint Descriptor(Bulk Out)

功能描述符 (Functional Descriptor)

  • Header Functional Descriptor

Header Functional Descriptor
├── bLength (1 byte): Fixed 0x05
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x00 (HEADER)
└── bcdCDC (2 bytes): CDC specification version
  • Union Functional Descriptor

Union Functional Descriptor
├── bLength (1 byte): 0x05 + n (n = number of subordinate interfaces)
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x06 (UNION)
├── bControlInterface (1 byte): Master interface number
└── bSubordinateInterface[n] (n bytes): One or more slave interface numbers
  • Ethernet Networking Functional Descriptor

Ethernet Networking Functional Descriptor
├── bLength (1 byte): Total descriptor length
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x0F
├── iMACAddress (1 byte): MAC address string index
├── bmEthernetStatistics (4 bytes): Supported statistics counters
├── wMaxSegmentSize (2 bytes): Maximum frame size (e.g., 1518)
├── wNumberMCFilters (2 bytes): Multicast filtering capability
└── bNumberPowerFilters (1 byte): Number of wake patterns

类特定请求

CDC ECM Host 驱动通过控制端点 0 发送以下请求控制设备行为。

请求名称 (Request)

要求

描述

SendEncapsulatedCommand

可选

发送封装命令

GetEncapsulatedResponse

可选

获取封装响应

SetEthernetMulticastFilters

可选

设置组播地址列表过滤。

SetEthernetPowerManagementPatternFilter

可选

配置电源管理模式(如网络唤醒 WoL)。

GetEthernetPowerManagementPatternFilter

可选

获取电源管理模式过滤器

SetEthernetPacketFilter

必选

设置数据包过滤器。主机使用此命令通知设备需要接收哪些类型的包(单播、广播、多播、混杂模式等)。

GetEthernetStatistic

可选

获取设备的传输统计信息(如丢包数、错误帧数)。

数据传输格式

CDC ECM 的数据传输非常直接,USB 载荷 (Payload) 即为原始的 以太网帧 (Ethernet Frame),不包含额外的头部封装(这一点不同于 RNDIS 或 NCM)。下图显示了一个完整的以太网满帧(长度 1514)被分割成 3 个 USB 包传输。

../../../_images/usb_cdc_ecm_spec_split_ethernet_packet.svg

备注

ZLP (Zero Length Packet)

如果传输的以太网帧长度恰好是 USB 端点最大包长 (wMaxPacketSize, 如 HS 下为 512 字节) 的整数倍,主机或设备需要发送一个零长包 (ZLP) 来标识传输结束。

类驱动

本节详细介绍了 CDC ECM 主机驱动的内部实现细节,包括驱动框架、类特定请求的支持情况以及端点资源的分配方案。

具体实现

USB Host CDC ECM 主机驱动栈基于模块化设计,通过分层架构实现了网络协议栈与 USB 硬件控制器之间的高效交互。该架构确保了数据传输的稳定性与系统的可扩展性。

按功能职责划分为以下几个核心模块:

网络协议栈适配层 (LwIP Adapter Layer)

负责网络层数据包的封装与解封。

作为 USB 驱动与 TCP/IP 协议栈(LwIP)的接口层,它充当了数据链路层的角色:

  • 发送数据 (TX):作为数据生产者,接收来自 LwIP 的 PBUF 数据包,将其转换为 USB 传输请求。

  • 接收数据 (RX):作为数据消费者,接收 USB 底层上报的以太网帧,封装为 PBUF 并提交给 LwIP 进行上层协议处理。

ECM 驱动架构

这是 CDC ECN 类驱动的核心组件,严格遵循 USB CDC ECM 协议规范,实现了主机与 ECM 设备交互的核心业务逻辑。其主要职责包括:

  • 枚举与配置解析:负责识别 CDC 通信接口与数据接口,解析设备描述符,并提取 MAC 地址等关键参数。

  • 网络控制管理:通过控制端点管理设备行为,例如配置数据包过滤器 (Packet Filter) 和组播列表。

  • 数据收发抽象:提供标准化的数据收发 API,作为上层网络协议栈 (LwIP) 与底层 USB 传输之间的桥梁。

  • 自愈机制:一旦检测到传输异常(如超时或阻塞),立即触发恢复处理流程,实现网络连接的自动修复,确保通信不中断。

USB Core 驱动

实时响应硬件中断,负责处理 USB 标准枚举、传输管理以及底层的物理数据传输调度等。

核心交互接口

ECM Host 类驱动在系统架构中起着承上启下的作用,其实现逻辑主要围绕以下三个核心交互接口展开:

  • 主机类驱动回调 API:类驱动通过定义并注册一个标准的 usbh_class_driver_t 结构体与底层 USB 核心(USB Core)进行交互。

  • 面向应用的回调 API:类驱动通过 usbh_cdc_ecm_state_cb_t 结构体,向上层应用提供异步事件通知机制。

  • 面向应用的 API:应用层调用这些 API 后,驱动会切换内部状态机的状态,从而触发并调度数据传输。

加载与卸载类驱动

这两个函数负责内存资源的分配与释放,以及类驱动向 USB 核心的注册与注销。

Init:

usbh_cdc_ecm_init() 是加载 CDC ECM 主机类驱动的顶层函数,主要完成以下任务:

  • 保存用户提供的回调函数,并调用用户 init 回调。

  • 保存用户配置的 CDC ECM 私有数据结构信息 usbh_cdc_ecm_priv_data_t

  • 为控制传输分配必要的内存资源。分配内存,用于控制传输

  • 解析应用层传入的私有数据,并将其保存至类内部,供后续特定场景使用。

  • 调用 usbh_register_class() 注册 CDC ECM 类驱动到 USB 主机核心。

示例:

int usbh_cdc_ecm_init(usbh_cdc_ecm_state_cb_t *cb, usbh_cdc_ecm_priv_data_t *priv)
{
    /* 1. Save the frame count param */
    if (priv == NULL) {
        USBH_ECM_FREE_MEM(cdc->led_array);
        cdc->led_cnt = 0;
    } else {
        if (priv->mac_value) {
            usbh_cdc_ecm_set_dongle_mac(priv->mac_value);
        }
        if ((priv->led_array != NULL) && (priv->led_cnt > 0)) {
            usbh_cdc_ecm_set_dongle_led_array(priv->led_array, priv->led_cnt);
        }
    }

    /* 2. Allocate memory */
    cdc->dongle_ctrl_buf = (u8 *)usb_os_malloc(CDC_ECM_MAC_STRING_LEN);

    /* 3. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
        cdc->cb = cb;
        if (cb->init != NULL) {
            ret = cb->init();
            if (ret != HAL_OK) {
            RTK_LOGS(TAG, RTK_LOG_ERROR, "User init err %d\n", ret);
            return ret;
            }
        }
    }

    /* 4. Register class driver*/
    usbh_register_class(&usbh_cdc_ecm_driver);

    return HAL_OK;
}

连接与断连处理

当 USB 核心检测到与 CDC ECM 类去陪陪的设备插入或拔出时,会触发相应的回调函数。

Attach:

usbh_cdc_ecm_attach 是设备枚举的关键步骤,负责解析接口描述符并分配管道资源:

  • 查找网络控制接口 (CDC Control Interface):解析获取网卡 MAC 字符串索引及中断节点信息等。

  • 查找网络数据接口 (CDC Data Interface):解析获取网络所在的接口(Interface)索引及管道信息等。

  • 打开管道:根据获取到的描述符信息,分配并打开检测管道和数据收发管道。

  • 初始化状态机:将状态置为 CDC_ECM_STATE_IDLE 初始状态,等待接收数据

  • 通知应用层:调用用户 attach 回调,将设备连接状态告知应用层。

static int usbh_cdc_ecm_attach(usb_host_t *host)
{
    /* 1. Parse descriptors to get Ctrl and Data information */
    status = usbh_cdc_ecm_parse_interface_desc(host);
    if (status) {
        return status;
    }

    /* 2. Open the pipe for steaming transfer */
    /* control in */
    pipe_info = &(cdc->intr_rx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
        pipe_info->buf = (u8 *)usb_os_malloc(pipe_info->pipe.ep_mps);
        pipe_info->buf_len = pipe_info->pipe.ep_mps;
        pipe_info->pipe.xfer_state = USBH_EP_XFER_START;
        cdc->intr_check_tick = pipe_info->pipe.ep_interval;
    }

    /* bulk out */
    pipe_info = &(cdc->bulk_tx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
    }

    /* bulk in */
    pipe_info = &(cdc->bulk_rx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
        /* ecm use bulk, the max ethernet packet size is 1542, malloc (512*3) to rx a whole ethernet packet*/
        pipe_info->buf = (u8 *)usb_os_malloc(USBH_CDC_ECM_BULK_BUF_MAX_SIZE);
        pipe_info->buf_len = USBH_CDC_ECM_BULK_BUF_MAX_SIZE;
        pipe_info->pipe.xfer_state = USBH_EP_XFER_START;
    }

    /* 3. Initialize the state machine */
    cdc->state = CDC_ECM_STATE_IDLE;

    /* 4. Notify the user layer */
    if ((cdc->cb != NULL) && (cdc->cb->attach != NULL)) {
        cdc->cb->attach();
    }
    return HAL_OK;
}

类驱动状态机

usbh_cdc_ecm_process 回调函数是主机端 CDC ECM 类的核心状态机处理枢纽。

与设备端被动响应请求不同,主机端驱动必须主动维护设备状态。其核心职责是管理类驱动的生命周期,并进行传输任务的调度与分发。

状态机管理与调度

usbh_cdc_ecm_process 根据当前类驱动的状态,统筹管理控制传输(如获取网卡 MAC 地址)和数据传输。

状态枚举

描述

关键动作

CDC_ECM_STATE_IDLE

空闲状态

等待用户指令或数据传输请求。

CDC_ECM_STATE_PRE_SETTING

控制信息数据传输

根据不同的状态,调用不同的控制信息处理函数,如获取 MAC,设置 MAC 等。

CDC_ECM_STATE_TRANSFER

数据传输中

根据管道号分发任务到具体的 TX/RX 处理函数。

CDC_ECM_STATE_ERROR

错误状态

尝试清除端点特征 (Clear Feature) 以恢复通信。

控制信息传输处理分发示例

当处于 CDC_ECM_STATE_PRE_SETTING 状态时,状态机会根据触发事件的不同,将处理任务分发给具体的控制传输处理函数。

示例:

case CDC_ECM_STATE_PRE_SETTING:
    /* Ctrl Request */
    req_status = usbh_cdc_ecm_ctrl_setting(host);
    if (req_status == HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "ECM alt setting finish %d\n", cdc->intr_rx.pipe.pipe_num);
        cdc->state = CDC_ECM_STATE_TRANSFER;
        usbh_notify_class_state_change(host, cdc->intr_rx.pipe.pipe_num);
    } else {
        usbh_notify_class_state_change(host, 0);
    }
break;

传输处理分发示例

当处于 CDC_ECM_STATE_TRANSFER 状态时,状态机会根据触发事件的管道号,将数据处理任务分发给具体的收发处理函数。

示例:

case CDC_ECM_STATE_TRANSFER:
    /* Bulk Tx Pipe */
    if (msg == cdc->bulk_tx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_bulk_out(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->bulk_tx.pipe.pipe_num);
        }
    }
    /* Bulk Rx Pipe */
    else if (msg == cdc->bulk_rx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_bulk_in(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->bulk_rx.pipe.pipe_num);
        }
    }
    /* Intr In Pipe */
    else if (msg == cdc->intr_rx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_intr_in(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->intr_rx.pipe.pipe_num);
        }
    }
    break;

错误恢复

如果在其他状态处理过程中发生错误,驱动会尝试向设备发送 Clear Feature 请求,以期恢复通信并回到 IDLE 状态。

示例:

/* ... Error state ... */
case CDC_ECM_STATE_ERROR:
    /* Error recovery mechanism */
    ret = usbh_ctrl_clear_feature(host, 0x00U);
    if (ret == HAL_OK) {
        cdc->xfer_state = CDC_ECM_STATE_IDLE;
    }
break;

数据传输处理

CDC ECM 数据传输主要分为两类:网络控制接口数据传输网络数据流传输

网络控制传输(CDC Control Transfer)

负责管理和配置 CDC ECM 设备的功能行为。驱动内部实现了如下状态机流程来处理各类配置请求:

GetMac:
case CDC_ECM_STATE_GET_MAC_STR:
    /* get Mac String */
    state = usbh_cdc_ecm_get_mac_str(host);
    if (state == HAL_OK) {
        cdc->sub_status ++;
    } else if (state != HAL_BUSY) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "Get MAC fail error[%d]\n", state);
        usb_os_sleep_ms(10);
    }
    break;

网络数据流传输(CDC-DATA Transfer)

CDC ECM 的网络数据流传输涵盖网络状态的上报和实际网络数据的收发。其中,网络状态传输基于中断传输(INTR),而网络数据传输基于批量传输(BULK)。

获取网络状态:

代码示例(基于 INTR 传输的网络状态获取逻辑):

static int cdc_ecm_cb_intr_receive(u8 *buf, u32 length)
{
    if (buf && length >= 8) {
        if (length == 8 && buf[0] == 0xA1 && buf[1] == CDC_ECM_NOTIFY_NETWORK_CONNECTION) {
            /* parse to get the value */
        }
        else if (length == 16 && buf[0] == 0xA1 && buf[1] == CDC_ECM_NOTIFY_CONNECTION_SPEED_CHANGE) {
            /*/* parse to get the value */
        }
    }
    return HAL_OK;
}

static void usbh_cdc_ecm_process_intr_in(usb_host_t *host)
{
    usbh_pipe_t *ep = &(cdc->intr_rx->pipe);

    switch (ep->xfer_state) {
    case USBH_EP_XFER_START:
        /* 1. Do Transfer */
        ep->xfer_buf = pipe_info->buf;
        ep->xfer_len = pipe_info->buf_len;
        usbh_transfer_data(host, ep);
        ep->tick = usbh_get_tick(host);
        pipe_info->busy_tick = usbh_get_tick(host);
        ep->xfer_state = USBH_EP_XFER_BUSY;
        break;

    case USBH_EP_XFER_BUSY:
        /* 2. Check the urb state */
        urb_state = usbh_get_urb_state(host, ep);
        switch (urb_state) {
        case USBH_URB_DONE:
            /* 2.1. Xfer Complete */
            len = usbh_get_last_transfer_size(host, ep);
            if (len > 0) {
                cdc_ecm_cb_intr_receive(ep->xfer_buf, len);
            }
            break;

        case USBH_URB_BUSY:
            /* 2.2 Handle urb busy status */
            if (usbh_get_elapsed_ticks(host, pipe_info->busy_tick) >= cdc->intr_check_tick) {
                ep->xfer_state = USBH_EP_XFER_IDLE;
            }
            break;

        case USBH_URB_ERROR:
        case USBH_URB_STALL:
            /* 2.3 Handle error status */
            ep->xfer_state = USBH_EP_XFER_IDLE;
            break;

        case USBH_URB_IDLE:
            /* 2.4 Check idle time */
            if (usbh_get_elapsed_ticks(host, ep->tick) >= cdc->intr_check_tick) {
                ep->xfer_state = USBH_EP_XFER_IDLE;
            }
            break;
        }
        break;
    }
}

面向应用的 APIs

这部分接口提供给上层应用使用,用于获取设备信息、配置设备工作参数以及发起数据传输。

配置类接口

数据流类接口

类特定请求实现

本驱动栈严格遵循 USB CDC ECM 1.2 规范,已封装了核心 类特定请求 (Class-Specific Requests) 的组成与发送流程。

驱动层默认实现了 SetEthernetPacketFilter、SetEthernetMulticastFilters 以及 GetEthernetStatistic 的处理逻辑。开发者可参考源码实现扩展其他类型的请求。源码路径: {SDK}/component/usb/host/cdc_ecm

类特定请求类型

备注

SetEthernetPacketFilter

设置以太网数据包过滤器。 用于控制设备接收哪些类型的网络包(如:单播、广播、多播或混杂模式)。

SetEthernetMulticastFilters

设置组播地址过滤器。 当上层网络协议栈(如 IGMP)需要监听特定组播组时,通过此请求将组播 MAC 地址列表下发至设备

GetEthernetStatistic

获取以太网统计数据。 用于读取设备内部维护的统计计数器(如:发送/接收成功帧数、CRC 错误数、丢包数等),通常用于网络诊断。

端点配置

CDC ECM 主机驱动在设备枚举阶段,会解析配置描述符,并根据接口类型自动查找并申请相应的端点资源,以建立完整的通信管道。

管道类型

描述

控制 IN/OUT 端点

默认控制端点 0 (EP0)。 用于发送标准 USB 请求及 CDC 类特定控制请求(如 SetEthernetPacketFilter)。

中断 IN 端点

中断输入端点 (Interrupt IN)。 归属于通信接口 (CCI),用于接收设备主动上报的网络通知(如网线插拔状态 NetworkConnection)。

批量 IN 端点

归属于数据接口 (DCI),用于接收设备上传的网络数据包 (RX Data)。

批量 OUT 端点

归属于数据接口 (DCI),用于向设备发送网络数据包 (TX Data)。

API 说明

驱动 API

应用示例

应用设计

本节详细介绍 CDC ECM 主机驱动的完整开发流程,涵盖驱动加载、热插拔管理、网络数据收发机制以及资源释放。

加载驱动

使用 CDC ECM Host 驱动前,需定义配置结构体并注册回调函数,随后依次加载 USB 主机核心驱动及 ECM 类驱动。

步骤说明:

  • 硬件配置:设置 USB 速度模式(High Speed/Full Speed)及中断/任务优先级。

  • 回调注册:定义用户回调结构体,挂载各个阶段(连接、断开、数据传输)的处理函数。

  • 加载核心驱动:调用 usbh_init() 加载 USB 核心驱动。

  • 加载类驱动:调用 usbh_cdc_ecm_init() 加载 CDC ECM 类驱动。

/*
 * 1. Configure USB speed (High Speed or Full Speed) ,isr priority and main task priority.
 */
static usbh_config_t usbh_ecm_cfg = {
        .speed = USB_SPEED_HIGH,
        .isr_priority = USBH_ECM_ISR_PRIORITY,
        .main_task_priority = USBH_ECM_MAIN_THREAD_PRIORITY,
};
/*
 * Define USB user-level callbacks.
 */
static usbh_user_cb_t usbh_ecm_usr_cb = {
        .process = cdc_ecm_cb_process,            /* USB callback to handle class-independent events in the application */
        .validate = cdc_ecm_cb_device_check,      /* USB callback to validate a device when multiple devices are connected to a hub */
};

/*
 * 2. Define user callbacks for CDC ECM events.
 */
static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .init   = cdc_ecm_cb_init,                /* USB init callback */
        .deinit = cdc_ecm_cb_deinit,              /* USB deinit callback */
        .attach = cdc_ecm_cb_attach,              /* USB attach callback */
        .detach = cdc_ecm_cb_detach,              /* USB detach callback */
        .setup  = cdc_ecm_cb_setup,               /* USB setup callback */
        .bulk_send     = cdc_ecm_cb_bulk_send,    /* Data transmission complet callback */
        .bulk_received = cdc_ecm_cb_bulk_receive, /* Data transmission complet callback */
        .intr_received = cdc_ecm_cb_intr_receive, /* Data transmission complet callback */
};

int ret = 0;

/**
 * 3. Initialize USB host core driver with configuration.
 */
ret = usbh_init(&usbh_ecm_cfg, &usbh_ecm_usr_cb);
if (ret != HAL_OK) {
        return;
}

/*
 * 4. Initialize CDC ECM class driver.
 */
ret = usbh_cdc_ecm_init(&cdc_ecm_usb_cb);
if (ret != HAL_OK) {
        /* If class driver init fails, clean up the core driver */
        usbh_deinit();

        return;
}

热插拔事件处理

作为主机,系统必须能够健壮地处理 USB 网卡的动态插入与移除。SDK 默认支持热插拔机制,并联动 LwIP 协议栈管理网络接口状态(如 DHCP 重启、默认路由切换)。

处理逻辑:

  • 设备拔出 (Detach):触发回调释放信号量,应用线程捕获后执行驱动卸载,并释放堆内存。

  • 设备插入 (Attach):USB 核心检测到设备,重新执行枚举和驱动加载流程。

  • 链路状态维护:监控物理链路 (Link Up/Down) 状态,根据状态触发 DHCP 请求或释放 IP,并配置路由。

/* USB detach callback */
static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .detach = cdc_ecm_cb_detach,
};

/* Callback executed in main task */
static void cdc_ecm_cb_detach(void)
{
        cdc_ecm_is_ready = 0;
        usb_os_sema_give(cdc_ecm_detach_sema);
}

static int usbh_cdc_ecm_doinit(void)
{
        int status;

        status = usbh_init(&usbh_ecm_cfg, &usbh_ecm_usr_cb);
        if (status != HAL_OK) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "Host init fail %d\n", status);
                return USBH_CORE_INIT_FAIL;
        }

        status = usbh_cdc_ecm_init(&cdc_ecm_usb_cb, params);
        if (status != HAL_OK) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "Init driver fail %d\n", status);
                return USBH_CLASS_INIT_FAIL;
        }

        return HAL_OK;
}

/* Thread Context: Handle the state machine */
static void ecm_hotplug_thread(void *param)
{
        int ret = 0;

        UNUSED(param);

        for (;;) {
                usb_os_sema_take(cdc_ecm_detach_sema, USB_OS_SEMA_TIMEOUT);
                RTK_LOGS(TAG, RTK_LOG_INFO, "Hot plug\n");

                /* Stop transfer, release resource */
                usbh_cdc_ecm_deinit();
                usbh_deinit();
                usbh_cdc_ecm_tx_status_check();
                usbh_ecm_timer_unregister();

                RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap size: 0x%08x\n", usb_os_get_free_heap_size());

                /* Re-init */
                ret = usbh_cdc_ecm_doinit();
                if (ret != HAL_OK) {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init fail %d\n", ret);
                        break;
                }
        }
}

/**
  * Get ecm device connect status
  */
int usbh_cdc_ecm_get_connect_status(void)//1 up
{
        u8 ret1 = cdc_ecm_is_ready;
        u8 ret2 = ecm_hw_connect;
        int ret = ret1 & ret2;

        return ret;;
}

/* Link Status Task */
static void ecm_link_change_thread(void *param)
{
        u8 *mac;
        u32 dhcp_status = 0;
        u8 link_is_up = 0;
        eth_state_t ethernet_unplug = ETH_STATUS_IDLE;

        UNUSED(param);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Enter link status task!\n");

        cdc_ecm_do_init();

        while (1) {
                link_is_up = usbh_cdc_ecm_get_connect_status();

                if (1 == link_is_up && (ethernet_unplug < ETH_STATUS_INIT)) {   // unlink -> link
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Do DHCP\n");

                        ...

                        dhcp_status = LwIP_IP_Address_Request(NETIF_ETH_INDEX);
                        if (DHCP_ADDRESS_ASSIGNED == dhcp_status) {
                                netifapi_netif_set_default(pnetif_eth); //Set default gw to ether netif
                                RTK_LOGS(TAG, RTK_LOG_INFO, "Switch to link\n");
                        } else {
                                RTK_LOGS(TAG, RTK_LOG_INFO, "DHCP Fail\n");
                        }
                } else if (0 == link_is_up && (ethernet_unplug >= ETH_STATUS_INIT)) {   // link -> unlink
                        ethernet_unplug = ETH_STATUS_DEINIT;
                        netif_set_default(pnetif_sta);  //switch to other netif
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Swicth to unlink\n");
                } else {
                        rtos_time_delay_ms(1000);
                }
        }
}

以太网帧发送流程 (Ethernet Output)

当上层 LwIP 协议栈有数据包需要发送时,会调用 USB CDC ECM 类驱动提供的 usbh_cdc_ecm_send_data() 接口。

流程说明:

  • 提交请求:LwIP 调用发送函数,驱动层调用 usbh_cdc_ecm_bulk_send() 提交 Bulk OUT 传输请求。

  • 等待完成: 驱动通过 usb_os_sema_take() 阻塞等待传输完成信号量。

  • USB 传输:SB Core 处理底层 DMA 传输,将数据发送至设备。

  • 回调释放:传输完成后,触发:func:cdc_ecm_cb_bulk_send() 回调,释放信号量。

  • 返回上层:函数获取到信号量,返回 LwIP,表示发送结束。

static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .bulk_send     = cdc_ecm_cb_bulk_send,
};

/* USB transfer finish callback */
static int cdc_ecm_cb_bulk_send(usbh_urb_state_t state)
{
        usb_os_sema_give(cdc_ecm_tx_sema);
        if (state != USBH_URB_DONE) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "BULK TX fail %d\n", state);
        }

        return HAL_OK;
}

/* Tansfer APi, used for LwIP */
int usbh_cdc_ecm_send_data(u8 *buf, u32 len)
{
        int ret;
        u8 retry_cnt = 0;

        while (1) {
                ret = usbh_cdc_ecm_bulk_send(buf, len);
                if (ret == HAL_OK) {
                        //success
                        break;
                }
                if (++retry_cnt > 100) {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "TX drop(%d)\n", len);
                        ret = HAL_ERR_UNKNOWN;
                        break;
                } else {
                        usb_os_delay_ms(1);
                }
        }

        /* *wait cdc_ecm_cb_bulk_send to give the sema */
        if (ret == HAL_OK) {
                usb_os_sema_take(cdc_ecm_tx_sema, USB_OS_SEMA_TIMEOUT);
        }

        return ret;
}

以太网帧接收流程 (Ethernet Input)

当 USB 网卡接收到网络数据时,通过 Bulk IN 端点上传给主机。

流程说明:

  • 注册回调:加载驱动时将 ethernetif_mii_recv() 注册给类驱动的接收处理函数。

  • USB 接收:底层驱动完成 Bulk IN 传输后,触发 cdc_ecm_cb_bulk_receive()

  • 数据上交:回调函数调用 ethernetif_mii_recv()

  • 封装 PBUF:申请 LwIP 的 pbuf,将接收到的原始数据拷贝至 pbuf payload。

  • 递交协议栈:调用 netif->input() 数据包送入 TCP/IP 协议栈处理。

static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
    .bulk_received = cdc_ecm_cb_bulk_receive,
};

report_data = ethernetif_mii_recv;

static int cdc_ecm_cb_bulk_receive(u8 *buf, u32 length)
{
    if ((report_data != NULL) && (length > 0)) {
        report_data(buf, length);
    }

    return HAL_OK;
}

void ethernetif_mii_recv(u8 *buf, u32 frame_len)
{
    struct eth_drv_sg sg_list[MAX_ETH_DRV_SG];
    struct pbuf *p, *q;
    u32 total_len = frame_len;
    struct netif *netif = pnetif_eth;

    memcpy((u8*)RX_BUFFER, buf, frame_len);

    /* Allocate buffer to store received packet */
    p = pbuf_alloc(PBUF_RAW, total_len, PBUF_POOL);
    if (p == NULL) {
        RTK_LOGW(TAG, "\n\r[%s]Cannot allocate pbuf to receive packet(%d)\n", __func__, total_len);
        return;
    }

    /* Create scatter list */
    for (q = p; q != NULL && sg_len < MAX_ETH_DRV_SG; q = q->next) {
        sg_list[sg_len].buf = (unsigned int) q->payload;
        sg_list[sg_len++].len = q->len;
    }
    rltk_mii_recv(sg_list, sg_len);

    /* Pass received packet to the interface */
    if (ERR_OK != netif->input(p, netif)) {
        pbuf_free(p);
    }
}

驱动卸载

在系统关闭或切换模式时,应按加载的 反序 释放资源。

/* 1. Deinitialize CDC ECM class driver first */
usbh_cdc_ecm_deinit();

/* 2. Deinitialize USB host core driver */
usbh_deinit();

运行方式

本节以 USB 转以太网适配器互联 为例,演示如何将 Ameba 开发板配置为 USB CDC ECM 主机,并通过外接 USB CDC ECM Dongle 访问网络。

Ameba 识别 Dongle,通过 DHCP 获取 IP 地址,并执行 Ping 及 iPerf 测试。

该示例路径: {SDK}/example/usb/usbh_cdc_ecm,可为开发者设计网络路由等产品提供完整的设计参考。

配置与编译

  • 编译与烧录

    在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程,然后将生成的 Image 文件烧录至开发板:

    # Initialize environment (required for every new terminal)
    source env.sh or env.bat(Windows system)
    
    # Select Target SoC (replace xxx with your specific SoCs)
    ameba.py soc xxx
    
    ameba.py build -a usbh_cdc_ecm -p
    
  • Menuconfig 配置确认

    若编译失败,请执行 ameba.py menuconfig,确认已选择 USBH CDC ECM

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] CDC ECM
            Select USB Ethernet (USB Ethernet)  --->
    

结果验证

  • 启动设备

    重启开发板,串口日志应显示驱动加载成功:

[ECM-I] USBH ECM demo start
  • 连接网卡

    将 USB 网卡插入开发板,并连接网线。硬件拓扑如下:

../../../_images/usb_host_cdc_ecm_example_hw_top.svg
  • 功能测试

    • 查询网络状态(AT+WLSTATE)

      检查是否成功获取到 IP 地址,更多细节参考 查询网络状态

    AT+WLSTATE
    
    [+WLSTATE]: _AT_WLAN_INFO_
    WLAN0 Status: Running
    ==============================
    ....
    
    Interface ethernet
    ==============================
    MAC => 00:e0:4c:58:64:18
    IP  => 192.168.31.87
    GW  => 192.168.31.1
    MSK  => 255.255.255.0
    
    • Ping 测试(AT+PING)

      测试与网关的连通性,更多细节参考 Ping 测试

    AT+PING=192.168.31.1
    
    [+PING]: _AT_WLAN_PING_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2978368
    
    #
    
    [ping_test] PING 192.168.31.1 32(60) bytes of data
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=1 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=2 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=3 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=4 time=0 ms
    
    [ping_test] 4 packets transmitted, 4 received, 0% packet loss, average 0 ms
    [ping_test] min: 0 ms, max: 0 ms
    
    • 上行输入测试 (RX/Input Test)

      Ameba 作为 Server (接收),PC 作为 Client (发送),更多细节参考 网络带宽测试

    EVB:
    
    AT+IPERF=-s,-i,1
    [+IPERF]: _AT_WLAN_IPERF1_TCP_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2977792
    
    #
    Start TCP server! id = [0]
    tcp_server_func: Create socket fd = 0
    tcp_server_func: Bind socket successfully
    tcp_server_func: Listen port 5001
    tcp_server_func: Accept connection successfully
    tcp_s: id[0] Receive 9383 KBytes in 1000 ms, 76866 Kbits/sec
    tcp_s: id[0] Receive 11391 KBytes in 1000 ms, 93323 Kbits/sec
    tcp_s: id[0] Receive 11400 KBytes in 1000 ms, 93393 Kbits/sec
    tcp_s: id[0] Receive 11393 KBytes in 1000 ms, 93334 Kbits/sec
    tcp_s: id[0] Receive 11324 KBytes in 1000 ms, 92774 Kbits/sec
    tcp_s: [END] id[0] Totally receive 55040 KBytes in 5013 ms, frame_num = 38604, 89943 Kbits/sec
    TCP server stopped!
    
    PC:
    
    C:\Users\test\Desktop>iperf-2-1-9-win.exe -c 192.168.31.87 -i 1 -t 5
    ------------------------------------------------------------
    Client connecting to 192.168.31.87, TCP port 5001
    TCP window size: 64.0 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.31.234 port 53419 connected with 192.168.31.87 port 5001
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.00-1.00 sec  9.25 MBytes  77.6 Mbits/sec
    [  1] 1.00-2.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 2.00-3.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 3.00-4.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 4.00-5.00 sec  11.0 MBytes  92.3 Mbits/sec
    [  1] 0.00-5.01 sec  53.8 MBytes  89.9 Mbits/sec
    
    • 下行输出测试 (TX/Output Test)

      Ameba 作为 Client (发送),PC 作为 Server (接收),更多细节参考 网络带宽测试

    EVB:
    
    AT+IPERF=-c,192.168.31.234,-i,1,-t,5
    [+IPERF]: _AT_WLAN_IPERF1_TCP_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2977792
    
    #
    Start TCP client! id = [0]
    tcp_client_func: Server IP=192.168.31.234, port=5001
    tcp_client_func: Create socket fd = 0
    tcp_client_func: Connect to server successfully
    tcp_c: id[0] Send 11317 KBytes in 1000 ms, 92715 Kbits/sec
    tcp_c: id[0] Send 11326 KBytes in 1000 ms, 92785 Kbits/sec
    tcp_c: id[0] Send 11343 KBytes in 1000 ms, 92926 Kbits/sec
    tcp_c: id[0] Send 11379 KBytes in 1000 ms, 93218 Kbits/sec
    tcp_c: id[0] Send 11332 KBytes in 1000 ms, 92832 Kbits/sec
    tcp_c: [END] id[0] Totally send 56709 KBytes in 5001 ms, 92893 Kbits/sec
    tcp_client_func: Close client socket
    TCP client stopped!
    
    PC:
    
    Desktop>iperf-2-1-9-win.exe -s -i 1
    ------------------------------------------------------------
    Server listening on TCP port 5001
    TCP window size: 64.0 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.31.234 port 5001 connected with 192.168.31.87 port 56481
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.00-1.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 1.00-2.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 2.00-3.00 sec  11.1 MBytes  92.9 Mbits/sec
    [  1] 3.00-4.00 sec  11.1 MBytes  93.2 Mbits/sec
    [  1] 4.00-5.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 0.00-5.00 sec  55.4 MBytes  92.9 Mbits/sec