透传主机方案
概述
USB 通信设备类 (CDC) 下的抽象控制模型 (ACM) 基于 USB 批量传输 (Bulk Transfer) 机制,定义了一套通用的数据交互标准。 在主机 (Host) 模式下,系统利用该协议建立与外部设备的高速数据通道,实现原始数据流 (Raw Data Stream) 的双向透传。
Ameba 基于 USB-IF 官方发布的 CDC ACM 协议标准,实现了完备的 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 (通信类基础协议) |
|
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)
负责承载应用层的业务数据流。该接口通常配置为一对批量端点,负责透传数据流,不涉及控制指令的解析与处理。
协议交互示例:
描述符结构
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 后,驱动会切换内部状态机的状态,开启数据传输的调度。
驱动回调机制
备注
上图仅为说明不同层级的回调函数执行的流程,并未列出所有调用场景。
类驱动回调函数
类驱动需要定义一个标准的 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 核心的注册与注销。
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;
}
usbh_cdc_acm_deinit() 是用于卸载 CDC ACM 主机类驱动的顶层函数,负责清理资源:
调用用户
deinit回调,告知应用层。如果设备处于连接状态,强制关闭所有打开的管道(Interrupt IN, Bulk IN, Bulk OUT)。
释放之前分配的内存。
注销类驱动。
示例:
int usbh_cdc_acm_deinit(void)
{
/* 1. Call the user's ``deinit`` callback */
if ((cdc->cb != NULL) && (cdc->cb->deinit != NULL)) {
cdc->cb->deinit();
}
/* 2. If the device is connected, forcefully close all open pipes */
if ((host != NULL) && (host->connect_state == USBH_STATE_SETUP)) {
usbh_close_pipe(host, &cdc->intr_in);
/* Close other pipes */
}
/* 3. Free memory */
if (cdc->line_coding != NULL) {
usb_os_mfree(cdc->line_coding);
}
/* ... */
/* 4. Unregister class driver*/
usbh_unregister_class(&usbh_cdc_acm_driver);
return ret;
}
连接与断连处理
当 USB 核心检测到匹配 CDC ACM 类的设备插入或拔出时,会调用以下回调函数。
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_detach 在设备拔出时被调用。它负责通知上层应用设备已移除,并关闭所有相关管道以释放硬件通道。
示例:
static int usbh_cdc_acm_detach(usb_host_t *host)
{
/* 1. Notify the user layer */
if ((cdc->cb != NULL) && (cdc->cb->detach != NULL)) {
cdc->cb->detach();
}
/* 2. Close pipes */
if (intr_in->pipe_num) {
usbh_close_pipe(host, intr_in);
}
/* Close bulk_in / bulk_out pipes */
return HAL_OK;
}
类驱动状态机
usbh_cdc_acm_process 回调函数是主机端 CDC ACM 类的核心状态机处理函数。
与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。
它的核心职责是维护类驱动的生命周期状态(如 IDLE, TRANSFER, ERROR),处理线路编码(Line Coding)的设置与校验,以及分发数据传输任务。
状态机管理与调度
usbh_cdc_acm_process 通过当前类驱动状态管理控制传输(如波特率配置)和批量/中断数据传输的调度。
状态枚举 |
描述 |
关键动作 |
|---|---|---|
IDLE |
空闲状态 |
等待用户指令或数据传输请求。 |
SET_LINE_CODING |
发送波特率设置请求 |
发送 |
GET_LINE_CODING |
获取/校验波特率 |
发送 |
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
提供给上层应用的接口,用于触发配置或数据传输。
设置控制信号
usbh_cdc_acm_set_control_line_state():将状态切换为SET_CONTROL_LINE_STATE,并在process中调度发起控制请求。
设置/获取线路编码
usbh_cdc_acm_set_line_coding():组装 Setup 包,驱动将状态切换为SET_LINE_CODING,并在process中调度发起控制请求。usbh_cdc_acm_get_line_coding():返回在SET_LINE_CODING状态之后回读校验的设备参数。
数据传输处理
下面三个面向应用的 API 作为 USB 驱动与应用层的接口层,驱动将状态切换为 TRANSFER,并在 process 回调中对数据传输进行调度分发。
CDC ACM 数据接口:
usbh_cdc_acm_transmit():应用层传入数据开启发送,处理零长度包 (ZLP) 逻辑,并通过用户transmit回调函数将传输结果告知应用层。usbh_cdc_acm_receive():开启接收,并通过用户receive回调函数将数据透传至应用层。
CDC ACM 通信接口:
usbh_cdc_acm_notify_receive():开启中断传输接收,并通过用户notify回调函数将数据透传至应用层。
发送逻辑与零长包 (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 说明
应用示例
应用设计
本节详细介绍 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 中的 attach 和 detach 回调函数来监听 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 设备枚举成功后,主机可进行控制传输、数据传输及中断通知的接收。
控制传输 (Control Request)
主机通过
usbh_cdc_acm_get_line_coding()和usbh_cdc_acm_set_line_coding()获取或设置波特率、停止位等串口参数。示例:将波特率设置为 38400 并回读验证的完整流程。
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 ```