透传主机方案

概述

USB 通信设备类 (CDC) 下的抽象控制模型 (ACM) 基于 USB 批量传输 (Bulk Transfer) 机制,定义了一套通用的数据交互标准。 在主机 (Host) 模式下,系统利用该协议建立与外部设备的高速数据通道,实现原始数据流 (Raw Data Stream) 的双向透传。

Ameba 基于 USB-IF 官方发布的 CDC ACM 协议标准,实现了完备的 USB CDC ACM 主机协议栈,提供高效的数据透传功能。

Ameba USB CDC ACM 主机

特性

  • 支持热插拔

  • 自动解析描述符,自适应速度模式

  • 批量传输长度等参数可配置

  • 支持原始数据(文本、二进制及自定义协议等)的透传

  • 支持独立识别并驱动复合设备 (如 ACM + HID) 中的串口功能

应用场景

作为 USB CDC ACM 主机,Ameba 可通过 USB 接口与外部备建立点对点通信链路,并结合无线能力拓展多种数据透传应用,例如:

  • 无线数据透传网桥:Ameba 作为网关挂载外部专有协议 Dongle 或采集模块,将 USB 侧接收的原始数据流透传至 Wi-Fi 或蓝牙网络,实现有线数据流向无线网络的无缝桥接与协议转换。

  • 传统工业设备赋能:Ameba 通过 USB 转串口适配器连接 PLC、数控机床或精密仪表,实现对传统 RS-232/485 设备的协议转换与云端接入,低成本完成旧式产线的数字化与智能化升级。

  • 高速数据采集:Ameba 连接具备 USB 接口的高频传感器或采集卡,利用批量传输机制实时吞吐大流量原始数据,突破传统低速接口的带宽瓶颈,确保高频采样场景下的数据完整性与实时性。

协议简介

CDC (Communication Device Class) 是 USB 规范定义的通用通信设备类标准。在其 PSTN 子类下,最常用的是 ‌ACM‌ (Abstract Control Model)。它定义了一套标准化的命令集,用于控制通信参数,例如:

  • 设置波特率(如 9600、115200)

  • 配置数据位、停止位、校验位

  • 控制 DTR/RTS 等线路状态信号

正是通过 ACM,USB CDC 设备才能被主机识别为一个标准的“虚拟串口”。

协议文档

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

规范类型

文档

CDC (通信类基础协议)

Class definitions for Communication Devices

PSTN (PSTN 子类)

协议文档包含在上述 CDC 规范的压缩包中

协议框架

CDC ACM 协议规定设备必须使用 双接口 机制分离控制流与数据流,并通过 联合功能描述符 (Union Functional Descriptor) 将二者逻辑绑定为单一功能单元。

  • 通信类接口 (Communication Class Interface, CCI)

    负责设备的管理控制与信令交互。

    • 控制传输:通过默认控制端点传输类特定请求。

      • 主机主要发送 PSTN 控制命令,核心指令包括配置波特率/数据位的 SetLineCoding 以及控制 RTS/DTR 握手信号的 SetControlLineState。

    • 中断传输:利用中断输入端点实现设备向主机的异步状态通报 (Notification)。

      • 典型应用是通过 SERIAL_STATE 实时报告 DCD、DSR 或 Ring 等硬件信号状态的变化。

  • 数据类接口 (Data Class Interface, DCI)

    负责承载应用层的业务数据流。该接口通常配置为一对批量端点,负责透传数据流,不涉及控制指令的解析与处理。

协议交互示例:

../../../_images/usb_cdc_acm_class.svg

描述符结构

CDC ACM 设备除遵循标准的 USB 描述符(如设备描述符、配置描述符、端点描述符)外,还定义了 类特定功能描述符 (Class-Specific Functional Descriptors)来定义抽象控制模型的能力。

CDC ACM 描述符拓扑 (Descriptor Topology)

下面以高速配置为例,展示描述符的拓扑结构:

Device Descriptor
└── Identifies basic device information (Class: 0x02 CDC, SubClass: 0x02 ACM)

Configuration Descriptor
├── Includes total length, power attributes (Self-powered), etc.
│
├── Communication Class Interface Descriptor (Interface 0)
│   ├── Standard Interface Descriptor (Class: 0x02, SubClass: 0x02, Protocol: 0x01 AT Commands)
│   │
│   ├── Class-Specific Functional Descriptors (Defines ACM capabilities)
│   │   ├── Header Functional Descriptor (Declares CDC Spec version)
│   │   ├── Call Management Functional Descriptor (Call handling capabilities)
│   │   ├── ACM Functional Descriptor (Line coding & state capabilities)
│   │   └── Union Functional Descriptor (Binds Interface 0 and Interface 1)
│   │
│   └── Standard Endpoint Descriptor (Interrupt IN)
│       └── Used for Serial State notifications
│
├── Data Class Interface Descriptor (Interface 1)
│   ├── Standard Interface Descriptor (Class: 0x0A Data, SubClass: 0x00, Protocol: 0x00)
│   │
│   ├── Standard Endpoint Descriptor (Bulk OUT)
│   │   └── Host -> Device Data Stream
│   │
│   └── Standard Endpoint Descriptor (Bulk IN)
│       └── Device -> Host Data Stream
│
├── Device Qualifier Descriptor
│   └── Device information for other speed modes
│
└── Other Speed Configuration Descriptor
    └── Configuration information for Full Speed mode

功能描述符 (Functional Descriptor)

在通信接口中,CDC 必须包含以下特殊的“功能描述符”头部:

  • Header Functional Descriptor: 指明 CDC 版本。

  • Call Management Functional Descriptor: 指明设备如何处理呼叫管理。

  • Abstract Control Management Functional Descriptor: 指明支持哪些命令(如 Set_Line_Coding)。

  • Union Functional Descriptor: 指定哪个是 Master 接口,哪个是 Slave 接口。

  • Header Functional Descriptor

Header Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 5 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE: Class-Specific Interface)
├── bDescriptorSubtype : 1 byte   → 0x00 (HEADER)
└── bcdCDC             : 2 bytes  → USB Class Definitions for Communication Devices Specification Release Number
                                    • 0x0110 = Release 1.10 (Common for ACM)
  • Call Management Functional Descriptor

Call Management Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 5 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x01 (CALL_MANAGEMENT)
├── bmCapabilities     : 1 byte   → The capabilities that this configuration supports:
│                                   • Bit 0 = 0: Device does not handle call management itself
│                                   • Bit 0 = 1: Device handles call management itself
│                                   • Bit 1 = 0: Call management commands does not sent over Data Class Interface
│                                   • Bit 1 = 1: Call management commands can be sent over Data Class Interface
│                                   • Bits 2-7: Reserved (Reset to zero)
└── bDataInterface     : 1 byte   → Interface number of Data Class interface optionally used for call management
                                    (Zero if no data interface is used)
  • ACM Functional Descriptor

Abstract Control Management (ACM) Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 4 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x02 (ABSTRACT_CONTROL_MANAGEMENT)
└── bmCapabilities     : 1 byte   → The capabilities that this configuration supports:
                                    • Bit 0: Comm_Feature (Supports Set_Comm_Feature, Clear_Comm_Feature, Get_Comm_Feature)
                                    • Bit 1: Line_Coding (Supports Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, Serial_State)
                                    • Bit 2: Send_Break (Supports Send_Break)
                                    • Bit 3: Network_Connection (Supports Network_Connection)
                                    • Bits 4-7: Reserved (Reset to zero)
  • Union Functional Descriptor

Union Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (3 + Number of slave interfaces)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x06 (UNION)
├── bMasterInterface   : 1 byte   → The interface number of the Communication or Data Class interface
│                                   (Designated as the controlling interface for the union)
└── bSlaveInterface0   : 1 byte   → Interface number of the first subordinate interface in the union
│    ⋮
└── bSlaveInterface(N) : 1 byte   → Interface number of the last subordinate interface in the union

备注

关于 CDC ACM 传输协议规范,请参考 USB CDC ACM Specification

类特定请求

CDC ACM 设备的控制请求分为 标准请求(Standard Requests)类特定请求 (Class-Specific Requests)

本节主要介绍 CDC ACM 特有的 类特定请求,用于实现虚拟串口的串口参数配置、流控信号管理等核心功能。

请求名称

规范要求

描述

SEND_ENCAPSULATED_COMMAND

必须

主机向设备发送封装后的命令,数据格式需遵循设备支持的 控制协议(例如:AT 指令集)。

GET_ENCAPSULATED_RESPONSE

必须

主机从设备获取封装命令的响应数据,响应格式遵循设备支 持的协议。

SET_COMM_FEATURE

可选

设置特定通信特性的状态。具体目标特性由功能选择器指定。

GET_COMM_FEATURE

可选

查询特定通信特性的当前设置状态。

CLEAR_COMM_FEATURE

可选

清除特定通信特性的设置,将其重置为默认状态。

SET_LINE_CODING

可选 (+)

主机配置异步串行通信的线路编码属性(如波特率、停止位、 校验位、数据位)。

GET_LINE_CODING

可选 (+)

主机查询当前配置的异步串行通信线路编码属性。

SET_CONTROL_LINE_STATE

可选

主机控制 RS-232/V.24 标准的控制信号状态(如 DTR 和 RTS 信号电平)。用于流控或复位。

SEND_BREAK

可选

主机请求设备在发送端生成一段持续时间的“断开”(Break) 信号,模拟 RS-232 风格的线路中断。

备注

  • 上述请求均属于 通信类(Communications Class) 特定请求。

  • 对于 模拟调制解调器(Analog Modem) 应用,虽然规范将标记为 (+) 的请求列为可选,但 强烈建议 实现这些请求以保证兼容性。

类驱动

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

驱动框架

CDC ACM 主机协议栈采用分层架构设计,实现了 USB 传输层与上层数据流的解耦。从上至下,驱动架构依次划分为以下层次:

  • 应用层

    包含业务逻辑与数据处理回调。

    • 业务实现:根据实际场景(如无线网桥、数据采集)调用接口并处理收发的数据。

    • 数据缓冲:通过注册用户回调函数,应用层直接获取底层上传的数据包。

  • CDC ACM 类驱动

    严格遵循 USB CDC ACM 协议规范,实现了主机与 ACM 设备交互的核心业务逻辑。其主要职责包括:

    • 枚举与接口绑定:负责识别通信接口类 (0x02, CDC_COMM) 与数据接口类 (0x0A, CDC_DATA),自动解析接口描述符并申请对应的管道资源。

    • 传输参数协商:在连接建立初期,负责发送 SetLineCoding 等请求以初始化链路参数,确保主机与设备端的配置同步。

    • 通知事件处理:通过中断传输监听设备上报的状态通知(如 SerialState),并触发相应的事件回调。

    • 数据收发:将上层下发的数据流封装为 USB 批量传输请求 (URB),并将接收到的 USB 数据包通过回调函数将数据透传至应用层。

  • USB Core 驱动

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

类驱动实现

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

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

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

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

驱动回调机制

../../../_images/usb_host_cdc_acm_callback.png

备注

上图仅为说明不同层级的回调函数执行的流程,并未列出所有调用场景。

类驱动回调函数

类驱动需要定义一个标准的 usbh_class_driver_t 结构体,作为统一的入口注册到 USB Core 中,是 Core 层通知 Class 层“发生了某事”的主要手段。

  • id_table: 支持的设备 ID 列表,核心层使用此表与插入的设备进行匹配,以决定是否加载此驱动。

  • attach: 设备连接并匹配成功后

  • detach: 设备断开时调用。

  • setup: 枚举完成进入类请求阶段,用于发送类特定的标准控制请求,完成设备进入数据传输状态前的必要配置。

  • process: 类驱动驱动就绪后的状态机处理函数。

  • sof: SOF 中断时调用,用于处理对时序要求严格的逻辑,主要用于 同步传输

  • completed: 当通道上的传输完成时调用。

面向应用层的回调函数

CDC ACM 类驱动面向应用层的回调结构体 usbh_cdc_acm_cb_t,由用户层实现。一般可选择实现:

API

描述

init

在类驱动初始化时被调用,用于初始化应用相关的资源

deinit

在类驱动注销时被调用,用于注销应用相关的资源

attach

在类驱动执行attach回调时被调用,用于应用层处理设备连接事件

detach

在类驱动执行detach回调时被调用,用于应用层处理设备断开事件

setup

在类驱动执行setup回调时被调用,用于指示应用层类驱动已准备好进行数据传输

receive

在类驱动收到BULK IN数据时被调用,用于应用层处理设备上报的数据

transmit

在类驱动BULK OUT数据传输完成时被调用,用于应用层获取OUT传输状态

notify

在类驱动收到INTR IN数据时被调用,用于应用层处理设备上报的数据

加载与卸载类驱动

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

Init:

usbh_cdc_acm_init() 用于加载 CDC ACM 主机类驱动的顶层函数,主要完成以下任务:

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

  • 为 line_coding(当前设备参数)和 user_line_coding(用户期望参数)分配内存。

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

示例:

int usbh_cdc_acm_init(usbh_cdc_acm_cb_t *cb)
{
    /* 1. 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;
        }
      }
    }
    /* 2. Allocate memory */
    cdc->line_coding = (usb_cdc_line_coding_t *)usb_os_malloc(sizeof(usb_cdc_line_coding_t));
    /* ... */

    /* 3. Register class driver*/
    usbh_register_class(&usbh_cdc_acm_driver);
    return ret;
}

连接与断连处理

当 USB 核心检测到匹配 CDC ACM 类的设备插入或拔出时,会调用以下回调函数。

Attach:

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

  • 查找通信接口 (Comm Interface):如果找到,解析其中断端点并打开 Interrupt IN 管道。

  • 查找数据接口 (Data Interface):如果找到,解析其批量端点并打开 Bulk IN 和 Bulk OUT 管道。

  • 初始化状态机为 IDLE 状态。

  • 调用用户 attach 回调,告知应用层连接状态。

示例:

static int usbh_cdc_acm_attach(usb_host_t *host)
{
  /* 1. Get the Communication interface and open the interrupt pipe */
  dev_id.bInterfaceClass = USB_CDC_COMM_INTERFACE_CLASS_CODE;
  itf_data = usbh_get_interface_descriptor(host, &dev_id);
  if (itf_data) {
    usbh_open_pipe(host, intr_in, ep_desc);
  }
  /* 2. Get the Data interface and open the Bulk IN & Bulk OUT pipe */
  dev_id.bInterfaceClass = USB_CDC_DATA_INTERFACE_CLASS_CODE;
  itf_data = usbh_get_interface_descriptor(host, &dev_id);
  /* Open bulk_in / bulk_out pipes */

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

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

类驱动状态机

usbh_cdc_acm_process 回调函数是主机端 CDC ACM 类的核心状态机处理函数。 与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。 它的核心职责是维护类驱动的生命周期状态(如 IDLE, TRANSFER, ERROR),处理线路编码(Line Coding)的设置与校验,以及分发数据传输任务。

状态机管理与调度

usbh_cdc_acm_process 通过当前类驱动状态管理控制传输(如波特率配置)和批量/中断数据传输的调度。

状态枚举

描述

关键动作

IDLE

空闲状态

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

SET_LINE_CODING

发送波特率设置请求

发送 SET_LINE_CODING 请求 (EP0)。成功后自动转入 GET_LINE_CODING 以进行回读校验。

GET_LINE_CODING

获取/校验波特率

发送 GET_LINE_CODING 请求。获取后比对用户设定值,若一致则触发用户 line_coding_changed 回调。

SET_CONTROL_LINE_STATE

控制握手信号

配置 RTS/DTR 电平状态。

TRANSFER

数据传输中

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

ERROR

错误状态

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

线路编码配置 (Set & Verify)

CDC ACM 主机驱动实现了一个“设置-回读-校验”的闭环流程,以确保设备正确接受了波特率等参数。

  • 设置 (Set): 发送 SET_LINE_CODING 请求。成功后,状态自动流转到 GET_LINE_CODING。

  • 回读 (Get): 发送 GET_LINE_CODING 请求,读取设备当前的配置,确保串口参数同步。

  • 校验 (Verify): 对比用户请求的配置和设备实际返回的配置。如果一致,说明配置成功。调用 line_coding_changed 回调通知应用层已经成功修改。

示例:

case USBH_CDC_ACM_STATE_SET_LINE_CODING:
    /* 1. Send a setting request */
    req_status = usbh_cdc_acm_process_set_line_coding(host, cdc->user_line_coding);
    if (req_status == HAL_OK) {
      /* 2. After successful setup, immediately goto to get the setting for verification */
      cdc->state = USBH_CDC_ACM_STATE_GET_LINE_CODING;
    }
    /* ... error handling ... */
    break;

case USBH_CDC_ACM_STATE_GET_LINE_CODING:
    req_status = usbh_cdc_acm_process_get_line_coding(host, cdc->line_coding);
    if (req_status == HAL_OK) {
      cdc->state = USBH_CDC_ACM_STATE_IDLE;
      /* 3. Verify whether the readback data is consistent with the user settings */
      if ((cdc->line_coding->b.dwDteRate == cdc->user_line_coding->b.dwDteRate) &&
          /* ... compare other fields ... */ ) {
          /* 4. Config Match, notify the application layer that the setting has been changed. */
          if ((cdc->cb != NULL) && (cdc->cb->line_coding_changed != NULL)) {
            cdc->cb->line_coding_changed(cdc->line_coding);
          }
      }
    }
    break;

传输处理分发

当处于传输状态时,根据触发事件的管道号(Pipe ID),将处理分发给具体的 TX(发送)、RX(接收)或 INTR(中断)处理函数。

示例:

case USBH_CDC_ACM_STATE_TRANSFER:
    /* Distribute transmission tasks according to pipe numbers */
    if (event.msg.pipe_num == cdc->bulk_out.pipe_num) {
        usbh_cdc_acm_process_tx(host);      // Handle BULK OUT transfer
    } else if (event.msg.pipe_num == cdc->bulk_in.pipe_num) {
        usbh_cdc_acm_process_rx(host);      // Handle BULK IN transfer
    } else if (event.msg.pipe_num == cdc->intr_in.pipe_num) {
        usbh_cdc_acm_process_intr_rx(host); // Handle Interrupt IN transfer(e.g. Serial State)
    }
    break;

错误恢复

在其他状态处理发生错误时尝试清除端点特性(Clear Feature)并恢复到 IDLE 状态。

示例:

switch (cdc->state) {
/* ... IDLE state ... */

case USBH_CDC_ACM_STATE_ERROR:
    /* Error recovery mechanism */
    req_status = usbh_ctrl_clear_feature(host, 0x00U);
    if (req_status == HAL_OK) {
        cdc->state = USBH_CDC_ACM_STATE_IDLE;
    }
break;
}

类特定请求处理

Setup 阶段处理

usbh_cdc_acm_setup 回调函数在枚举基本完成后被调用。 会发起第一个类特定请求:获取当前设备的线路编码(Get Line Coding),以同步主机和设备的状态。

示例:

static int usbh_cdc_acm_setup(usb_host_t *host)
{
    /* Initiate a Get Line Coding request */
    status = usbh_cdc_acm_process_get_line_coding(host, cdc->line_coding);
    return status;
}

面向应用的 API

提供给上层应用的接口,用于触发配置或数据传输。

数据传输处理

下面三个面向应用的 API 作为 USB 驱动与应用层的接口层,驱动将状态切换为 TRANSFER,并在 process 回调中对数据传输进行调度分发。

  • CDC ACM 数据接口

    • usbh_cdc_acm_transmit():应用层传入数据开启发送,处理零长度包 (ZLP) 逻辑,并通过用户 transmit 回调函数将传输结果告知应用层。

    • usbh_cdc_acm_receive() :开启接收,并通过用户 receive 回调函数将数据透传至应用层。

  • CDC ACM 通信接口

发送逻辑与零长包 (ZLP) 处理

在批量数据发送函数 usbh_cdc_acm_transmit(),驱动需要处理 USB 协议中的 Zero Length Packet (ZLP) 问题。

如果发送的数据长度正好是端点最大包长 (MPS) 的整数倍,主机必须额外发送一个 ZLP 以告知设备传输结束。这是 USB 批量传输的标准要求。

示例:

int usbh_cdc_acm_transmit(u8 *buf, u32 len)
{
    usbh_cdc_acm_host_t *cdc = &usbh_cdc_acm_host;
    usb_host_t *host = cdc->host;
    usbh_pipe_t *pipe = &cdc->bulk_out;

    if (pipe->xfer_state == USBH_EP_XFER_IDLE) {
        pipe->xfer_buf = buf;
        pipe->xfer_len = len;

        /* If the data length is greater than 0 and an integer multiple of MPS, a ZLP needs to be sent */
        if ((pipe->xfer_len > 0) && (pipe->xfer_len % pipe->ep_mps) == 0) {
            pipe->trx_zlp = 1;
        } else {
            pipe->trx_zlp = 0;
        }

        cdc->state = USBH_CDC_ACM_STATE_TRANSFER;
        pipe->xfer_state = USBH_EP_XFER_START;
        /* ... notify state change ... */
    }
}

类特定请求实现

本驱动栈遵循 USB CDC ACM 规范,封装了核心 类特定请求 (Class-Specific Requests) 的实现与发送流程。 虽然规范将部分请求标记为可选,但为了支持标准的虚拟串口应用,主机驱动实现了以下请求。 开发者可参考源码路径 {SDK}/component/usb/host/cdc_acm 进行扩展。

类特定请求

描述

SetLineCoding

配置线路编码。向设备发送波特率、停止位、校验位等参数。在纯透传模式下,此请求主要用于完成协议握手流程。

GetLineCoding

获取线路编码。读取设备当前的线路配置参数,用于校验配置是否生效或同步设备默认状态。

SetControlLineState

设置控制信号状态。用于控制 RTS (Request To Send) 和 DTR (Data Terminal Ready) 信号电平,常用于流控握手或通知设备主机侧已就绪。

通道配置

CDC ACM 主机驱动在设备枚举阶段 (usbh_cdc_acm_attach 回调函数) 中解析配置描述符,根据接口子类自动查找并申请相应的传输资源,实现完整的数据与控制通道。

数量

描述

2

默认控制传输 0。 用于发送标准 USB 请求及 CDC 类特定控制请求(如 SetLineCoding)。

1

中断输入传输 (Interrupt IN)。 归属于通信接口 (CCI),用于接收设备主动上报的通知事件(Notify),最大超时时间设为 1000 tick。

1

批量 IN 传输,归属于数据接口 (DCI),用于接收设备上传的原始数据流 (RX Data),支持大数据包传输与 ZLP 处理。

1

批量 OUT 传输,归属于数据接口 (DCI),用于向设备发送原始数据流 (TX Data),驱动层优化了 DMA 调度以确保高带宽传输。

API 说明

驱动 API

应用示例

应用设计

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

加载驱动

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

步骤说明:

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

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

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

  • 加载类驱动:调用 usbh_cdc_acm_init() 加载 CDC ACM 类驱动。

/* 1. Configure USB speed (High Speed or Full Speed), isr priority and main task priority. */
static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_HIGH,
    .ext_intr_enable = USBH_SOF_INTR,
    .isr_priority = INT_PRI_MIDDLE,
    //...
};

/* 2. Define USB user-level callbacks. */
static usbh_user_cb_t usbh_usr_cb = {
    .process = cdc_acm_cb_process    /* USB callback to handle class-independent events in the application */
};

/* 3. Define user callbacks for CDC ACM events. */
static usbh_cdc_acm_cb_t cdc_acm_usr_cb = {
    .init   = cdc_acm_cb_init,        /* Class init callback */
    .deinit = cdc_acm_cb_deinit,      /* Class deinit callback */
    .attach = cdc_acm_cb_attach,      /* Device attach callback */
    .detach = cdc_acm_cb_detach,      /* Device detach callback */
    .setup  = cdc_acm_cb_setup,       /* Class setup callback */
    .transmit = cdc_acm_cb_transmit,  /* Data transmit callback */
    .receive  = cdc_acm_cb_receive,   /* Data receive callback */
    .notify   = cdc_acm_cb_notify,    /* Interrupt notify callback */
};

int status = 0;

/*4. Initialize USB host core driver with configuration. */
status = usbh_init(&usbh_cfg, &usbh_usr_cb);
if (status != HAL_OK) {
   return;
}

/* 5. Initialize CDC ACM class driver.  */
status = usbh_cdc_acm_init(&cdc_acm_usr_cb);
if (status != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbh_deinit();
    return;
}

热插拔事件处理

通过注册 usbh_cdc_acm_cb_t 中的 attachdetach 回调函数来监听 CDC ACM 设备的连接与断开。

在示例代码中,利用信号量(Semaphore)机制来同步状态:

  • Attach: 当设备插入并枚举成功后,触发 attach 回调,释放 cdc_acm_attach_sema,通知主线程开始数据测试。

  • Detach: 当设备拔出时,触发 detach 回调,释放 cdc_acm_detach_sema,触发热插拔管理线程进行资源清理与重新加载。

/* 1. Configure USB status change callback */
static usbd_cdc_acm_cb_t cdc_acm_cb = {
    .status_changed = cdc_acm_cb_status_changed
};

/* 2. Configure Callback executed in ISR context */
static void cdc_acm_cb_status_changed(u8 old_status, u8 status)
{
    RTK_LOGS(TAG, RTK_LOG_INFO, "Status change: %d -> %d \n", old_status, status);

    cdc_acm_attach_status = status;
    rtos_sema_give(cdc_acm_attach_status_changed_sema);
}

/* 3. Create thread handling the state machine */
static void cdc_acm_hotplug_thread(void *param)
{
    int ret = 0;
    UNUSED(param);

    for (;;) {
        if (rtos_sema_take(cdc_acm_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            if (cdc_acm_attach_status == USBD_ATTACH_STATUS_DETACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");

                /* 1.Clean up resources */
                usbd_cdc_acm_deinit();
                ret = usbd_deinit();
                if (ret != 0) {
                    break;
                }

                /* 2.Re-initialize for next connection */
                ret = usbd_init(&cdc_acm_cfg);
                if (ret != 0) {
                    break;
                }
                ret = usbd_cdc_acm_init(CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE, CONFIG_CDC_ACM_BULK_IN_XFER_SIZE, &cdc_acm_cb);
                if (ret != 0) {
                    usbd_deinit();
                    break;
                }
            } else if (cdc_acm_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
            } else {
                RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
            }
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Hotplug thread fail\n");
    rtos_task_delete(NULL);
}

数据收发处理

当 CDC ACM 设备枚举成功后,主机可进行控制传输、数据传输及中断通知的接收。

static void cdc_acm_request_test(void)
{
    int ret;
    usbh_cdc_acm_line_coding_t line_coding;
    usbh_cdc_acm_line_coding_t new_line_coding;

    /* Wait for device attach */
    ......

    /* 1. Get current line coding from device */
    ret = usbh_cdc_acm_get_line_coding(&line_coding);
    if (ret != HAL_OK) {
        /* Get failed */
    }

    /* 2. Modify configuration parameters */
    /* Baudrate: 38400, Stop bits: 1, Parity: None, Data bits: 8*/
    line_coding.b.dwDteRate = 38400;
    line_coding.b.bCharFormat = CDC_ACM_LINE_CODING_CHAR_FORMAT_1_STOP_BITS;
    line_coding.b.bParityType = CDC_ACM_LINE_CODING_PARITY_NO;
    line_coding.b.bDataBits = 8;

    /* 3. Set new line coding to device */
    ret = usbh_cdc_acm_set_line_coding(&line_coding);
    if (ret != HAL_OK) {
        /* Set failed */
    }

    /* Wait for the setting to take effect */
    rtos_time_delay_ms(10);

    /* 4. Get line coding again to verify */
    ret = usbh_cdc_acm_get_line_coding(&new_line_coding);

    /* 5. Compare set values with read-back values */
    ......
}
  • 数据传输 (Bulk Transfer)

    驱动采用异步传输机制,配合信号量实现同步逻辑:

    • 发送: 调用 usbh_cdc_acm_transmit() 发送数据。发送完成后,驱动回调 transmit 函数,释放 cdc_acm_send_sema

    • 接收: 调用 usbh_cdc_acm_receive() 准备接收数据。接收到数据后,驱动回调 receive 函数,释放 cdc_acm_receive_sema

/* RX Callback: Handle Reception Completion */
static int cdc_acm_cb_receive(u8 *buf, u32 len, u8 status)
{
    if (status == HAL_OK) {
    u16 cdc_acm_bulk_in_mps = usbh_cdc_acm_get_bulk_ep_mps();

    /* 1. Copy data to internal buffer (Logic for buffer overflow check included) */
    if ((len > 0) && ((cdc_acm_total_rx_len + len) <= USBH_CDC_ACM_LOOPBACK_BUF_SIZE)) {
        /* memcpy(cdc_acm_loopback_rx_buf + cdc_acm_total_rx_len, buf, len); */
    }
    cdc_acm_total_rx_len += len;

    /* 2. Check for ZLP, short packet, or buffer full to complete the transfer */
    if ((len == 0) || (len % cdc_acm_bulk_in_mps)
        || ((len % cdc_acm_bulk_in_mps == 0) && (len < USBH_CDC_ACM_LOOPBACK_BUF_SIZE))
        || (cdc_acm_total_rx_len > USBH_CDC_ACM_LOOPBACK_BUF_SIZE)) {

        cdc_acm_total_rx_len = 0;

        /* 3. Signal the receive semaphore */
        rtos_sema_give(cdc_acm_receive_sema);
    }
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "RX fail: %d\n", status);
    }
    return HAL_OK;
}

/* TX Callback: Handle Transmission Completion */
static int cdc_acm_cb_transmit(u8 status)
{
    if (status == HAL_OK) {
        /* TX done, signal semaphore */
        rtos_sema_give(cdc_acm_send_sema);
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "TX fail: %d\n", status);
    }

    return HAL_OK;
}

/* Task thread: Handle Loopback Test Logic */
static void cdc_acm_loopback_test(void)
{
    int i;
    int ret;

    /* Initialize TX buffer */
    //...

    for (i = 0; i < USBH_CDC_ACM_LOOPBACK_CNT; i++) {

        /* Prepare RX buffer and check device status */
        //...

        /* 1. Transmit Data */
        ret = usbh_cdc_acm_transmit(cdc_acm_loopback_tx_buf, USBH_CDC_ACM_LOOPBACK_BUF_SIZE);
        if (ret < 0) {
            RTK_LOGS(TAG, RTK_LOG_ERROR, "TX fail: %d\n", ret);
            return;
        }

        /* 2. Wait for TX Complete signal */
        if (rtos_sema_take(cdc_acm_send_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* 3. Prepare to Receive Data */
            usbh_cdc_acm_receive(cdc_acm_loopback_rx_buf, USBH_CDC_ACM_LOOPBACK_BUF_SIZE);
        }

        /* 4. Wait for RX Complete signal and Verify Data */
        if (rtos_sema_take(cdc_acm_receive_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* Check rx data integrity */
            //...
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Bulk loopback test PASS\n");
}
  • 通知处理 (Interrupt Notify)

    通过 usbh_cdc_acm_notify_receive() 监听来自设备的中断传输(如 Serial State 变化)。当收到通知时,触发 notify 回调。

/* Callback: Handle Interrupt Notify */
static int cdc_acm_cb_notify(u8 *buf, u32 len, u8 status)
{
    /* Handle buf and len */
    //...

    if (status == HAL_OK) {
        /* Notify received, signal semaphore to notify the task */
        rtos_sema_give(cdc_acm_notify_sema);
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Notify fail: %d\n", status);
    }
    return HAL_OK;
}

/* Task Thread: Execute Notify Test Logic */
static void cdc_acm_notify_test_thread(void *param)
{
    /* Wait for device ready */
    //...

    /* 1. Optional: Change control line state to trigger device notification */
    usbh_cdc_acm_set_control_line_state();

    rtos_time_delay_ms(2000);

    for (int i = 0; i < USBH_CDC_ACM_LOOPBACK_CNT; i++) {
        /* 2. Submit Interrupt IN Request to listen for notification */
        usbh_cdc_acm_notify_receive(cdc_acm_notify_rx_buf, USBH_CDC_ACM_NOTIFY_BUF_SIZE);

        /* 3. Wait for Notify Callback */
        if (rtos_sema_take(cdc_acm_notify_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* Notification received. Check data (e.g. Serial State at offset 8) */
            RTK_LOGS(TAG, RTK_LOG_INFO, "Intr rx success(0x%02x 0x%02x)\n",
                cdc_acm_notify_rx_buf[9], cdc_acm_notify_rx_buf[8]);
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Intr notify test PASS\n");
    rtos_task_delete(NULL);
}

备注

完整的数据测试逻辑请参考 SDK 示例代码: {SDK}/example/usb/usbh_cdc_acm/example_usbh_cdc_acm.c

卸载驱动

当设备断开或需要关闭 USB 主机功能时,需按顺序卸载类驱动和主机核心驱动,并释放相关系统资源。

/* 1. Deinitialize CDC ACM class driver. */
usbh_cdc_acm_deinit();

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

运行方式

本节介绍了一个完整的 USB CDC ACM 主机示例,演示了 Ameba 作为主机如何识别 CDC 设备,并进行波特率设置、数据回环测试(Loopback)以及中断通知测试。

该示例代码路径: {SDK}/example/usb/usbh_cdc_acm,可为开发者设计 CDC ACM 主机提供完整的设计参考。

配置与编译

  • 编译与烧录

    在 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_acm -p
    
  • Menuconfig 配置确认

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

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

结果验证

  • 启动主机

    重启开发板,观察串口日志,应显示如下启动信息:

    [ACM-I] USBD CDC ACM demo start
    
  • 连接设备

    使用 USB 线缆将的 CDC ACM 设备(例如另一块运行本协议栈透传设备方案示例的 Ameba 开发板)连接至本开发板的 USB 端口。

  • 虚拟串口通信测试

    如果连接后运行正常,将依次进行控制传输测试和批量传输回环测试(Loopback Test),串口日志显示如下成功信息:

    ```
    [ACM-I] Ctrl test PASS
    [ACM-I] Bulk loopback test start, loopback times:100, size: 1024
    [ACM-I] Bulk loopback test PASS
    ```