透传设备方案
概述
USB CDC ACM (Communication Device Class - Abstract Control Model) 协议采用 USB 批量传输(Bulk Transfer)机制,提供通用的高速数据透传通道,适用于高吞吐量数据流的实时传输与指令交互。
Ameba 遵循 USB-IF 标准实现 CDC ACM 功能,实现双向透传。Host 主机可通过标准 USB 接口与 Ameba 内部逻辑或后端外设直接交换原始数据(Raw Data),实现了对底层传输细节的屏蔽与抽象。
特性
可被识别为标准串口设备,支持通用的串口工具,例如 PuTTY/TeraTerm
支持 USB 热插拔
支持描述符全定制
支持配置传输缓存大小、批量传输长度、速度模式等参数
可选择性配置中断 IN 端点,方便在不需要中断 IN 的应用中节省带宽
支持双向通信, 可执行数据的同步或异步回传操作
应用场景
作为 USB 数据透传桥接设备,Ameba 可通过 USB 接口建立主设备(Host,如 PC 或嵌入式主机)与外界的高速数据链路,并结合其他外设接口实现多种典型应用,例如:
传感器采集与机器人控制:Ameba 充当高性能传感器节点,采集前端的高速环境或运动数据,通过 USB 通道实时上传至机器人控制系统,支持高频原始数据的零丢包传输及闭环控制指令的快速下发。
设备管理与固件维护:面向物联网设备的生命周期管理,管理终端可通过 CDC 通道与设备建立通信,进行参数配置、运行状态诊断,并支持通过该数据链路完成固件的升级(OTA)与维护。
无线数据网关:借助内部集成的 Wi-Fi 与蓝牙模块,Ameba 可作为无线网桥,将 USB 接收到的主机数据流透传至无线网络,为不具备无线功能的工控主机或管理终端提供即插即用的无线接入能力。
协议简介
CDC (Communication Device Class) 是 USB 规范定义的通用通信设备类标准。在其 PSTN 子类下,最常用的是 ACM (Abstract Control Model)。它定义了一套标准化的命令集,用于控制通信参数,例如:
设置波特率(如 9600、115200)
配置数据位、停止位、校验位
控制 DTR/RTS 等线路状态信号
正是通过 ACM,USB CDC 设备才能被主机识别为一个标准的“虚拟串口”。
协议文档
USB-IF 官方发布了 CDC 类基础协议及 PSTN 子类规范。开发过程中请参考以下核心文档:
规范类型 |
文档 |
|---|---|
CDC 1.2 (通信类基础协议) |
https://www.usb.org/sites/default/files/CDC1.2_WMC1.1_012011.zip |
PSTN 1.2 (PSTN 子类) |
包含在上述 CDC 1.2 压缩包中的 PSTN120.pdf。 |
协议框架
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 类特定描述符结构体。这些结构体对应于 USB 协议规范中的标准描述符定义。
Device Descriptor
└─ USB Version 2.00 (CDC ACM)
Configuration Descriptor (Interfaces 2)
│
├─ Communication Interface (IF 0, CCI)
│ ├─ Header Functional (CDC 1.10)
│ ├─ Call Management Functional (Data IF=1, Handled by Host)
│ ├─ ACM Functional (Cap=0x02: Line Coding & Serial State)
│ ├─ Union Functional (Master=0, Slave=1)
│ └─ Endpoint: INTR IN, maxpkt=HS_INTR_SIZE, Interval=HS_Interval
│
└─ Data Interface (IF 1, DCI)
├─ Endpoint: BULK OUT, maxpkt=0x0200 (512 bytes)
└─ Endpoint: BULK IN, maxpkt=0x0200 (512 bytes)
Device Qualifier Descriptor
└─ USB 2.0
Other Speed Configuration Descriptor (Interfaces 2)
│
├─ Communication Interface (IF 0, CCI)
│ ├─ Header Functional (CDC 1.10)
│ ├─ Call Management Functional (Data IF=1, Handled by Host)
│ ├─ ACM Functional (Cap=0x02: Line Coding & Serial State)
│ ├─ Union Functional (Master=0, Slave=1)
│ └─ Endpoint: INTR IN, maxpkt=FS_INTR_SIZE, Interval=FS_Interval
│
└─ Data Interface (IF 1, DCI)
├─ Endpoint: BULK OUT, maxpkt=0x0040 (64 bytes)
└─ Endpoint: BULK IN, maxpkt=0x0040 (64 bytes)
类特定请求实现
驱动已实现了 CDC ACM 规范定义的核心类特定请求(Class-Specific Requests),主要包括:
SET_LINE_CODING
GET_LINE_CODING
SET_CONTROL_LINE_STATE
备注
开发者可参考现有实现来扩展其他类型的请求。CDC ACM 驱动源码路径: {SDK}/component/usb/device/cdc_acm
端点配置
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
EP0,用于处理 USB 主机发送的标准请求及 CDC 类特定请求。 |
中断 IN 端点 |
1 |
用于向 USB 主机发送串口状态通知(SERIAL_STATE)。 |
批量 OUT 端点 |
1 |
用于接收来自 USB 主机的下行数据流。 |
批量 IN 端点 |
1 |
用于向 USB 主机发送上行数据流。 |
API 说明
应用示例
应用设计
本节概述 CDC ACM 驱动的完整使用流程,涵盖驱动加载、热插拔管理、数据回显(Echo)处理及虚拟串口参数配置等核心环节。
加载驱动
定义配置结构体并注册回调函数,随后调用初始化接口,加载 USB 设备核心及 CDC ACM 类驱动。
// Do not change the settings unless indeed necessary
#define CONFIG_CDC_ACM_BULK_IN_XFER_SIZE 2048U
#define CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE 2048U
static usbd_config_t cdc_acm_cfg = {
.speed = CONFIG_USBD_CDC_ACM_SPEED,
.isr_priority = INT_PRI_MIDDLE,
.intr_use_ptx_fifo = 0U,
};
static usbd_cdc_acm_cb_t cdc_acm_cb = {
.init = cdc_acm_cb_init, /* USB init callback */
.deinit = cdc_acm_cb_deinit, /* USB deinit callback */
.setup = cdc_acm_cb_setup, /* USB setup callback */
.received = cdc_acm_cb_received, /* USB received callback */
.status_changed = cdc_acm_cb_status_changed, /* USB status change callback */
};
int ret = 0;
/**
* Initialize USB device core driver with configuration.
* param[in] cfg: USB device configuration.
* return 0 on success, non-zero on failure.
*/
ret = usbd_init(&cdc_acm_cfg);
if (ret != HAL_OK) {
return;
}
/**
* Initializes class driver with application callback handler.
* param[in] bulk_out_xfer_size: BULK OUT xfer buffer malloc length.
* param[in] bulk_in_xfer_size: BULK IN xfer buffer malloc length.
* param[in] cb: Pointer to the user-defined callback structure.
* return 0 on success, non-zero on failure.
*/
ret = usbd_cdc_acm_init(CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE, CONFIG_CDC_ACM_BULK_IN_XFER_SIZE, &cdc_acm_cb);
if (ret != HAL_OK) {
/**
* Deinitialize USB device core driver.
* return 0 on success, non-zero on failure.
*/
usbd_deinit();
return;
}
热插拔事件处理
通过注册 status_changed 回调函数来监听 USB 连接状态变化(连接/断开)。建议使用信号量(Semaphore)通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。
参考 设备连接状态检测 获取更多细节,下面是示例代码:
/* USB status change callback */
static usbd_cdc_acm_cb_t cdc_acm_cb = {
.status_changed = cdc_acm_cb_status_changed
};
/* 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);
}
/* 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");
/* Clean up resources */
usbd_cdc_acm_deinit();
ret = usbd_deinit();
if (ret != 0) {
break;
}
/* 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 枚举成功后,主机(Host)通过虚拟串口发送数据。驱动支持同步与异步两种数据处理模式,由宏 CONFIG_USBD_CDC_ACM_ASYNC_XFER 控制。
数据接收回调
所有来自主机的数据通过 cdc_acm_cb_received 回调函数传入。
同步回显 (Sync Echo)
若未开启异步传输,在接收回调中直接调用
usbd_cdc_acm_transmit()将接收到的数据原样发回主机。异步传输 (Async Transfer)
若开启异步传输,接收到的数据将被存入 cdc_acm_async_xfer_buf 环形缓冲区,并通过信号量唤醒发送线程 cdc_acm_xfer_thread。该线程负责分包发送数据,以避免在中断上下文中进行耗时操作或阻塞。
/* In callback: Echo immediately */
static int cdc_acm_cb_received(u8 *buf, u32 len)
{
/* Directly transmit received data back to host */
return usbd_cdc_acm_transmit(buf, len);
}
/* In callback: Buffer data & Trigger Task */
static int cdc_acm_cb_received(u8 *buf, u32 len)
{
// 1. Check if transfer is currently busy
if (cdc_acm_async_xfer_busy) {
return HAL_BUSY;
}
// 2. Copy data to internal buffer
// (Logic for buffer overflow check omitted...)
memcpy(..., buf, len);
// 3. If buffer full, signal the transfer thread
if (buffer_is_full) {
rtos_sema_give(cdc_acm_async_xfer_sema);
}
return HAL_OK;
}
/* In task thread: Handle Transmission */
static void cdc_acm_xfer_thread(void *param)
{
for (;;) {
// 1. Wait for signal from callback
if (rtos_sema_take(cdc_acm_async_xfer_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
cdc_acm_async_xfer_busy = 1; // Set busy flag
// 2. Loop until all buffered data is sent
while (data_remaining > 0) {
// Try to transmit a chunk (Bulk size)
ret = usbd_cdc_acm_transmit(xfer_buf, chunk_size);
if (ret == HAL_OK) {
// Advance buffer pointer
......
} else {
// 3. If HW is busy, retry after delay
rtos_time_delay_us(200);
}
}
// 4. Transmission complete, clear flag
cdc_acm_async_xfer_busy = 0;
}
}
}
备注
完整的数据收发逻辑请参考 SDK 示例代码:{SDK}/component/example/usb/usbd_cdc_acm/example_usbd_cdc_acm.c。
卸载驱动
在不再需要 CDC ACM 功能或系统关机时,按加载驱动的反序释放资源。
/* Deinitialize CDC ACM class driver. */
usbd_cdc_acm_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();
运行方式
本节介绍了一个完整的 USB CDC ACM(虚拟串口)回显示例,该示例演示了如何通过 CDC ACM 协议栈实现 PC 与开发板之间的双向字符通信。
该示例代码路径: {SDK}/component/example/usb/usbd_cdc_acm,可作为 USB 转串口透传或命令行交互界面(CLI)的基础参考。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择USBD CDC_ACM, 保存退出。
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Device) --->
[*] CDC ACM
编译与烧录
执行编译命令,并烧录生成的
Image文件至开发板:
cd amebaxxx_gcc_project
./build.py -a usbd_cdc_acm
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[ACM-I] USBD CDC ACM demo start
连接主机
使用 USB 线缆将开发板连接至 PC(如 Windows 电脑)
串口通信测试
启动任意串口调试工具(例如 Tera Term 或 Realtek Trace Tool),打开 Ameba 开发板 USB 端口对应的虚拟串口。
尝试向开发板发送任意字符消息,观察终端界面,开发板应能将接收到的消息原样回显(Echo)至主机。
备注
针对 Windows 7/XP 主机用户,系统可能无法自动安装驱动,请手动安装 CDC ACM 驱动文件 RtkUsbCdcAcmSetup.INF。
在安装前,请务必确认 INF 文件中包含当前 CDC ACM 类所使用的 VID/PID 值,例如:
[DeviceList]
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8720
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8721
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8722
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8730
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8006
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8061
; Add support for new VID/PID
HID 设备方案
概述
USB 人机接口设备 (Human Interface Device, HID) 协议是 USB-IF 定义的一套用于连接人机交互设备(如键盘、鼠标、游戏控制器、触摸屏等)的标准接口规范。
Ameba 平台基于 USB-IF 官方标准,实现了功能完备的 USB HID 设备端协议栈。该方案能够为系统提供稳定、低延迟的人机交互接口,并支持高度灵活的自定义配置。
特性
描述符全定制
支持 USB 热插拔
模拟支持基础的键盘按键与鼠标移动事件
支持单/双向通信:
鼠标:支持 HID 数据的单向上报 (Input Report)。
键盘:支持 HID 数据的双向通信 (Input & Output Report),可处理主机下发的 LED 状态控制(如 “Caps Lock” 指示灯)。
支持配置速度模式等参数
应用场景
作为 USB HID 设备,Ameba 可通过 USB 接口向 Host 主机(如 PC、平板电脑、智能电视)发送标准输入报告。结合其内置的 Wi-Fi 和 Bluetooth® 能力,Ameba 能够实现多种跨协议、跨介质的创新应用,例如:
无线外设适配器 (Dongle):Ameba 作为 USB 接收端 (Dongle) 插入主机,通过 Wi-Fi 或蓝牙接收远端无线键盘/鼠标的数据,并将其转换为标准的 USB HID 报文透传给主机,实现无线外设的即插即用。
IoT 智能控制网关:Ameba 可作为物理输入与数字控制的桥梁。它读取本地物理按键、旋钮或传感器信号,通过 USB HID 协议将其映射为标准输入指令,或转换为网络控制指令,从而实现对智能家居及工业设备的精准控制。
游戏控制器转接器:支持将非标准接口的游戏控制器(如老式游戏机手柄、自定义街机摇杆)的信号接入 Ameba,通过内部协议栈转换为标准 HID 游戏控制器报告,确保在 PC 或现代游戏主机上的兼容性。
协议简介
HID (Human Interface Device) 协议是 USB 规范框架下,专为实现人机交互设备而定义的标准接口与数据传输规范。该协议定义了主机(Host)与设备(Device)之间的数据交互格式及控制响应机制。
遵循 HID 标准的常见设备包括:
输入设备:键盘、鼠标、游戏手柄、数字转换器(触摸屏/绘图板)。
控制设备:消费类电子控制旋钮(如音量调节)、系统休眠按钮等。
协议文档
USB-IF 官方发布了 HID 核心协议规范及用于定义设备功能的用途表(Usage Tables)。开发者可参考以下核心文档:
规范类型 |
文档 |
|---|---|
HID 1.11 (核心协议) |
|
Usage Tables 1.5 (用途表) |
术语定义
本文档涉及的通用 HID 技术术语定义如下:
术语 |
描述 |
|---|---|
报告 (Report) |
HID 设备与主机之间传输数据的基本单元。数据包的结构由报告描述符定义。 |
用途 (Usage) |
描述数据具体含义的标识符。例如,定义某个数据位代表“左键点击”还是“X 轴坐标”。 |
集合 (Collection) |
将相关的输入/输出数据项分组的逻辑结构。例如,将鼠标的 X、Y 轴和按键组合在一个“鼠标”集。 |
项目 (Item) |
报告描述符的基本组成部分,分为主项目 (Main)、全局项目 (Global) 和局部项目 (Local)。 |
协议框架
HID 协议栈采用分层架构设计,旨在将底层的 USB 传输细节与上层的应用逻辑解耦。系统架构主要包含以下核心组件:USB 核心传输驱动、HID 类驱动以及特定类型的 HID 应用。
组件职责
HID 应用程序 (HID Application)
位于架构最上层,负责处理具体的业务逻辑。例如:采集传感器数据并打包成鼠标坐标报告,或接收键盘 LED 控制指令并驱动 GPIO 点亮指示灯。
HID 类驱动 (HID Class Driver)
实现了 Device Class Definition for HID 1.11 规范。其主要职责包括:解析和组织 HID 协议特有的数据包,管理报告描述符(Report Descriptor),并处理类特定请求(Class-Specific Requests)。
USB 核心与传输驱动 (USB Core & Transfer Driver)
负责处理标准的 USB 枚举流程、端点管理及底层的数据包调度,向上层屏蔽硬件控制器的差异。
通信机制
HID 设备通过特定的端点(Endpoint)配置来实现低延迟或低带宽要求的交互。从逻辑功能上,HID 接口主要依赖以下两种传输方式:
控制管道 (Control Pipe)
映射端点: 默认控制端点 0 (Endpoint 0)。
枚举与配置: 传输标准的 USB 描述符(设备、配置、接口描述符)以及 HID 特有的 HID 描述符和报告描述符。
类特定请求: 处理 HID 协议控制命令,例如 Set Idle (设置空闲率)。
低频数据传输: 当设备未配置中断输出端点时,主机通过控制传输(Control Transfer)发送输出报告(Output Report),例如设置键盘的大小写锁定灯状态。
中断管道 (Interrupt Pipe)
功能描述: 利用 USB 的轮询(Polling)机制,保证数据传输的实时性。
中断输入 (Interrupt IN): 必选。负责将设备产生的异步数据(如按键按下、鼠标位移、触摸屏坐标)实时传输给主机。
中断输出 (Interrupt OUT): 可选。负责主机向设备发送低延迟的下行数据。常见于需要高实时性反馈的场景,如游戏手柄的力反馈震动指令。若未定义此端点,下行数据将回退至控制管道传输。
描述符结构
HID 设备除遵循标准的 USB 描述符外,引入了其独有的描述符体系。最关键的是 HID 描述符(HID Descriptor)和报告描述符 (Report Descriptor)。
HID 描述符拓扑 (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.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
├── Endpoint Descriptor(Interrupt In)
└── Endpoint Descriptor(Interrupt Out)
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
├── Endpoint Descriptor(Interrupt In)
└── Endpoint Descriptor(Interrupt Out)
HID 描述符 (HID Descriptor)
位于配置描述符集合中,用于告知主机该接口包含哪些下级描述符(通常是报告描述符)及其长度。
HID Descriptor
├── bLength : 1 byte → Total descriptor length
├── bDescriptorType : 1 byte → 0x21 (HID Descriptor)
├── bcdADC : 2 bytes → HID Version
├── bCountryCode : 1 byte → Numeric expression identifying country code of the localized hardware
├── bNumDescriptors : 1 byte → Numeric expression specifying the number of class descriptors
│ └─ (always at least one i.e. Report descriptor.)
├── bDescriptorType : 1 byte → Constant name identifying type of class descriptor.
└── wDescriptorLength : 2 byte → Numeric expression that is the total size of the Report descriptor.
报告描述符 (Report Descriptor)
这是一个由特定项目(Item)组成的字符流,而非固定的结构体。
它灵活地定义了设备能够产生或接收的数据格式、长度、用途及物理范围。主机必须解析该描述符才能理解设备发送的数据。
Report Descriptor
├── Usage Page (Generic Desktop)
├── Usage (Keyboard)
├── Collection (Application)
│ ├── Usage Page (Key Codes)
│ ├── Usage Minimum (224)
| ├── Usage Maximum (231)
│ ├── Logical Minimum (0)
│ ├── Logical Maximum (1)
│ ├── Report Count (8)
│ ├── Report Size (1)
│ ├── Input (Data, Variable, Absolute) ; Modifier keys (8 bits, 1 per modifier)
│ ├── Report Count (1)
│ ├── Report Size (8)
│ ├── Input (Constant, Variable, Absolute) ; Reserved byte
| |
│ ├── Usage Page (LEDs)
│ ├── Logical Minimum (0)
│ ├── Logical Maximum (1)
│ ├── Report Count (5)
│ ├── Report Size (1)
│ ├── Usage Minimum (Num Lock)
│ ├── Usage Maximum (Kana)
│ ├── Output (Data, Variable, Absolute) ; 5 LED control bits
│ ├── Report Count (1)
│ ├── Report Size (3)
│ ├── Output (Constant, Variable, Absolute) ; Padding 3 bits
│ │
│ └── ....... Can configure multiple different setting as needed
│
└── End Collection
备注
HID 报告描述符的编写非常灵活且复杂,建议使用 USB-IF 提供的 HID Descriptor Tool 进行生成和校验。
类特定请求
HID 设备的控制请求分为 标准请求(Standard Requests) 和 类特定请求 (Class-Specific Requests) 。
本节主要介绍 HID 特有的 类特定请求,这些 HID 类特定请求主要用于管理报告状态和协议行为。不同类型的设备对类特定请求的支持要求如下表所示:
设备类型 |
Get Report |
Set Report |
Get Idle |
Set Idle |
Get Protocol |
Set Protocol |
|---|---|---|---|---|---|---|
Boot Mouse |
必选 |
可选 |
可选 |
可选 |
必选 |
必选 |
Non-Boot Mouse |
必选 |
可选 |
可选 |
可选 |
可选 |
可选 |
Boot Keyboard |
必选 |
可选 |
必选 |
必选 |
必选 |
必选 |
Non-Boot Keyboard |
必选 |
可选 |
必选 |
必选 |
可选 |
可选 |
Other Device |
必选 |
可选 |
可选 |
可选 |
可选 |
可选 |
数据传输格式
与 UAC (USB Audio Class) 固定的 PCM 流不同,HID 的数据格式完全由 报告描述符 动态定义。
以下为标准键盘的典型数据结构示例:
键盘输入报告 (Keyboard Input Report)
用于向主机发送按键数据。以下为按下 "Left Shift" + "A" 键的数据示例:
Filed |
Length(bits) |
Offset(bits) |
Value |
|---|---|---|---|
Left Ctrl |
1 |
0 |
0 |
Left Shift |
1 |
1 |
1 |
Left Alt |
1 |
2 |
0 |
Left GUI |
1 |
3 |
0 |
Right Ctrl |
1 |
4 |
0 |
Right Shift |
1 |
5 |
0 |
Right Alt |
1 |
6 |
0 |
Right GUI |
1 |
7 |
0 |
Pading |
8 |
8 |
0 |
Key |
48 |
16 |
0x04 |
键盘输出报告 (Keyboard Output Report)
用于主机控制键盘 LED 状态。以下为点亮 "Caps Lock" 灯的数据示例:
Filed |
Length(bits) |
Offset(bits) |
Value |
|---|---|---|---|
Num Lock |
1 |
0 |
0 |
Caps Lock |
1 |
1 |
1 |
Scroll Lock |
1 |
2 |
0 |
Compose |
1 |
3 |
0 |
Kana |
1 |
4 |
0 |
备注
报告 ID (Report ID):
如果一个 HID 接口支持多种不同格式的数据包,必须在每个数据包的起始位置添加一个字节的 Report ID 进行区分。如果设备仅定义了一种报告格式,则可以省略 Report ID,直接传输数据载荷。
类驱动
本节详细介绍了 HID 类驱动的内部实现细节,包括描述符的层级结构、类特定请求的支持情况以及端点配置方案。
驱动描述符结构
下文展示了 HID 类驱动中定义的描述符拓扑结构。这些结构体严格遵循 USB 2.0 协议规范与 HID 类定义。
Device Descriptor
└── Identifies basic device information (USB Version 2.00)
Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
└── Endpoint Descriptor(Interrupt In)
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
└── Endpoint Descriptor(Interrupt In)
Device Descriptor
└── Identifies basic device information (USB Version 2.00)
Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
├── Endpoint Descriptor(Interrupt In)
└── Endpoint Descriptor(Interrupt Out)
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor (Interface 0)
├── Standard Interface Descriptor (Interface 0, Human Interface Device)
├── HID Descriptor(HID Version, bNumDescriptors, etc)
│ └── Report Descriptor()
├── Endpoint Descriptor(Interrupt In)
└── Endpoint Descriptor(Interrupt Out)
类特定请求实现
本驱动栈遵循 USB HID 1.11 规范,已实现了核心 类特定请求 (Class-Specific Requests) 的基础框架。
驱动层已完成底层消息通信、协议解析及数据传输流程。开发者无需关注 USB 协议细节,仅需参考现有源码架构,在预留的接口中实现具体的业务逻辑。源码路径: {SDK}/component/usb/device/hid
类特定请求类型 |
备注 |
|---|---|
Get Report |
驱动已完成请求响应流程。注意:默认实现返回 0 数据,需在应用层填充具体的 Report 内容。 |
Set Report |
驱动已接收数据包,开发者需实现对主机下发数据(Output/Feature Report)的具体处理逻辑。 |
Get Idle |
标准实现。 |
Set Idle |
标准实现。 |
Get Protocol |
用于查询当前协议模式(Boot/Report)。 |
Set Protocol |
驱动已解析请求,开发者需根据主机下发的协议模式(Boot 或 Report)执行相应的状态切换处理。 |
端点配置
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
EP0,用于处理 USB 主机发送的标准枚举请求、描述符获取及 HID 类特定控制请求。 |
中断 IN 端点 |
1 |
用于设备向主机实时上报数据(如鼠标位移、按键键值等)。 |
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
EP0,用于处理 USB 主机发送的标准枚举请求、描述符获取及 HID 类特定控制请求。 |
中断 IN 端点 |
1 |
用于设备向主机实时上报数据(如鼠标位移、按键键值等)。 |
中断 OUT 端点 |
1 |
用于接收来自 USB 主机的下行键盘消息数据(如主机点亮 "Caps Lock" 灯)。 |
API 说明
应用示例
应用设计
本节详细介绍 HID 应用的完整开发流程,涵盖加载驱动、热插拔管理、数据收发机制以及资源释放。
加载驱动
使用 HID 驱动前,需定义配置结构体并注册回调函数,随后调用核心接口加载 USB 设备控制器及 HID 类驱动。步骤说明:
硬件配置:配置 USB 速度模式及中断优先级。
回调注册:定义用户回调结构体
usbd_hid_usr_cb_t,挂载各个阶段的处理函数。加载核心驱动:调用
usbd_init()加载 USB 核心驱动。加载类驱动:调用
usbd_hid_init()加载 HID 类驱动。
/*
* 1. Configure USB speed (High Speed or Full Speed) and interrupt priority.
*/
static usbd_config_t hid_cfg = {
.speed = CONFIG_USBD_HID_SPEED,
.isr_priority = INT_PRI_MIDDLE,
};
/*
* 2. Define user callbacks for HID events.
*/
static usbd_hid_usr_cb_t hid_usr_cb = {
.init = hid_cb_init, /* USB init callback */
.deinit = hid_cb_deinit, /* USB deinit callback */
.setup = hid_cb_setup, /* USB setup callback */
.transmitted = hid_cb_transmitted, /* Data transmission complet callback */
.received = hid_cb_received, /* Data reception callback */
.status_changed = hid_cb_status_changed, /* Connection status change callbac */
};
int ret = 0;
/**
* Initialize USB device core driver with configuration.
*/
ret = usbd_init(&hid_cfg);
if (ret != HAL_OK) {
return;
}
/*
* 4. Initialize HID class driver. 512 is the transfer buffer size.
*/
ret = usbd_hid_init(512, &hid_usr_cb);
if (ret != HAL_OK) {
/* If class driver init fails, clean up the core driver */
usbd_deinit();
return;
}
热插拔事件处理
为了确保系统的健壮性,建议通过注册 status_changed 回调函数来监听 USB 的连接与断开事件。
备注
不要在中断回调(ISR)中直接执行耗时的资源释放或重新加载操作。建议使用信号量(Semaphore)通知专用的任务线程进行处理。
参考 设备连接状态检测 获取更多细节,下面是示例代码:
/* USB status change callback */
static usbd_hid_usr_cb_t hid_usr_cb = {
.status_changed = hid_cb_status_changed,
};
/* Callback executed in ISR context */
static void hid_cb_status_changed(u8 old_status, u8 status)
{
hid_attach_status = status;
rtos_sema_give(hid_attach_status_changed_sema);
}
/* Thread Context: Handle the state machine */
static void hid_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
/* Wait for status change signal */
if (rtos_sema_take(hid_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (hid_attach_status == USBD_ATTACH_STATUS_DETACHED) {
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
/* 1. Clean up HID class resources */
usbd_hid_deinit();
/* 2. De-initialize USB core */
ret = usbd_deinit();
if (ret != 0) {
break;
}
/* 3. Re-initialize for next connection */
ret = usbd_init(&hid_cfg);
if (ret != 0) {
break;
}
ret = usbd_hid_init(512, &hid_usr_cb);
if (ret != 0) {
usbd_deinit();
break;
}
} else if (hid_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);
}
HID 报告发送流程 (Input Report)
当设备作为数据生产者(如鼠标移动、键盘按键)时,需主动向主机发送输入报告。发送流程:
等待设备就绪:监听
hid_cb_setup()回调触发的信号量,确保主机已完成枚举。构造报告数据:根据 HID 报告描述符定义的格式填充数据缓冲区。
发送数据:使用
usbd_hid_send_data()发送数据。
/* Mouse data structure (Defined by Report Descriptor) */
typedef struct {
u8 left; //left button. 0: release, 1: press
u8 right; //right button. 0: release, 1: press
u8 middle; //wheel button. 0: release, 1: press
char x_axis; //x-axis pixels. relative value from -127 to 127, positive for right and negative for left
char y_axis; //y-axis pixels. relative value from -127 to 127, positive for up and negative for down
char wheel; //scrolling units. relative value from -127 to 127, positive for up and negative for down.
} usbd_hid_mouse_data_t;
/* Mouse moving data */
static usbd_hid_mouse_data_t mdata[] = {
{0, 0, 0, 50, 0, 0}, //move the cursor 50 pixels to the right
{0, 0, 0, 0, 50, 0}, //move the cursor down 50 pixels
{0, 0, 0, -50, 0, 0}, //move the cursor 50 pixels to the left
{0, 0, 0, 0, -50, 0}, //move the cursor up 50 pixels
{0, 0, 0, 0, 0, 5}, //scroll up for 5 units
{0, 0, 0, 0, 0, -5}, //scroll down for 5 units
{0, 0, 1, 0, 0, 0}, //middle button pressed
{0, 0, 0, 0, 0, 0}, //middle button released
{0, 1, 0, 0, 0, 0}, //right button pressed
{0, 0, 0, 0, 0, 0}, //right button released
{0, 0, 0, -5, 0, 0}, //move the cursor 5 pixels to the left
{1, 0, 0, 0, 0, 0}, //left button pressed
{0, 0, 0, 0, 0, 0}, //left button released
};
/* Callback when device is configured by Host */
static void hid_cb_setup(void)
{
rtos_sema_give(hid_connect_sema);
}
/* Send function */
static void hid_send_device_data(void *pdata)
{
u8 byte[4];
usbd_hid_mouse_data_t *data = (usbd_hid_mouse_data_t *)pdata;
memset(byte, 0, 4);
/* mouse protocol:
BYTE0
|-- bit7~bit3: RSVD
|-- bit2: middle button press
|-- bit1: right button press
|-- bit0: left button press
BYTE1: x-axis value, -128~127
BYTE2: y-axis value, -128~127
BYTE3: wheel value, -128~127
*/
if (data->left != 0) {
byte[0] |= USBD_HID_MOUSE_BUTTON_LEFT;
}
if (data->right != 0) {
byte[0] |= USBD_HID_MOUSE_BUTTON_RIGHT;
}
if (data->middle != 0) {
byte[0] |= USBD_HID_MOUSE_BUTTON_MIDDLE;
}
byte[0] |= USBD_HID_MOUSE_BUTTON_RESERVED;
byte[1] = data->x_axis;
byte[2] = data->y_axis;
byte[3] = data->wheel;
usbd_hid_send_data(byte, 4);
}
rtos_sema_give(hid_transmit_sema);
/* Wait for USB configured */
rtos_sema_take(hid_connect_sema, RTOS_SEMA_MAX_COUNT);
u32 array_len = sizeof(mdata) / sizeof(usbd_hid_mouse_data_t);
do {
for (i = 0; i < array_len; i++) {
/* Wait for previous transfer to complete */
rtos_sema_take(hid_transmit_sema, RTOS_SEMA_MAX_COUNT);
/* Send the mouse data out */
hid_send_device_data(&mdata[i]);
/* Force 1000ms of sleep to slow down movement */
rtos_time_delay_ms(1000);
}
rtos_time_delay_ms(5 * 1000); //next loop
} while (++loop < CONFIG_USBD_HID_CONSTANT_LOOP);
HID 报告接收流程 (Output Report)
当主机向设备发送控制指令(如点亮键盘 CapsLock 灯、手柄震动)时,设备端需通过回调函数处理输出报告。处理流程:
注册回调:在加载驱动时注册
hid_cb_received()。解析数据:在回调函数中读取数据长度及内容,并解析对应的 HID 报告 ID(如果存在)。
执行动作:解析协议载荷并控制相应硬件。
/* Define callbacks for HID receive. */
static usbd_hid_usr_cb_t hid_usr_cb = {
.received = hid_cb_received,
};
/* Callback for received data (executed in ISR context) */
static void hid_cb_received(u8 *buf, u32 len)
{
/* Example: Parse Keyboard LED status */
if (len == 1) {
u8 led_status = buf[0];
// Control GPIO based on led_status bits (NumLock, CapsLock, etc.)
}
}
备注
如需测试输出报告功能,请确保在 {SDK}/component/usb/device/hid/usbd_hid.h 中将 USBD_HID_DEVICE_TYPE 配置为支持双向通信的设备类型(如 USBD_HID_KEYBOARD_DEVICE)。
卸载驱动
在系统关机或不再需要 HID 功能时,应按加载的反序释放资源,以防止内存泄漏。
/* 1. Deinitialize HID class driver first */
usbd_hid_deinit();
/* 2. Deinitialize USB device core driver */
usbd_deinit();
运行方式
本节以一个 USB 鼠标模拟器 为例,演示如何将 Ameba 开发板配置为 USB HID 设备。
功能描述
设备类型:标准 USB HID 鼠标。
行为:连接 PC 后,开发板会自动模拟鼠标移动轨迹。
源码路径:
{SDK}/component/example/usb/usbd_hid,可为开发者设计自定义 USB 键鼠、游戏手柄等产品提供完整的参考方案。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择USBD HID, 保存退出。
[*] Enable USB
USB Mode (Device) --->
[*] HID
编译与烧录
执行编译命令,并烧录生成的
Image文件至开发板:
cd amebaxxx_gcc_project
./build.py -a usbd_hid
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[HID-I] USBD HID demo start
连接主机
使用 USB 线缆将开发板连接至 PC(如 Windows 电脑)
功能测试
默认示例代码将开发板配置为 USB 鼠标。
观察现象:连接成功后,PC 屏幕上的鼠标光标将自动开始移动(移动轨迹由 Example 代码中的数组定义)。
数据流向:此过程验证了 设备 -> 主机 (Device to Host) 的输入报告(Input Report)传输功能。
备注
若光标未移动,请检查:
硬件连接:确认 USB 线缆已插好,且线缆支持数据传输(非仅充电线)。
设备冲突:检查是否有其他物理鼠标或虚拟鼠标驱动干扰了光标移动。
枚举状态:检查 PC 设备管理器中是否有带黄色感叹号的 USB 设备。
若修改了示例代码配置(USBD_HID_DEVICE_TYPE)为键盘设备,可进行以下双向通信测试。
上行输入测试 (Input Report)
打开 PC 上的任意文本编辑器(如记事本)。
开发板将自动模拟键盘输入字符(如 "aA"),并循环输出。
备注
若记事本里没有"aA"输入,请检查:
硬件连接:确认 USB 线缆已插好,且线缆支持数据传输(非仅充电线)。
设备冲突:检查是否有其他物理鼠标或虚拟鼠标驱动干扰了光标移动。
枚举状态:检查 PC 设备管理器中是否有带黄色感叹号的 USB 设备。
输入模式:请切换为英文输入法。其它输入法可能会显示输入弹出框。
下行输出测试 (Output Report)
此测试验证 主机 -> 设备 (Host to Device) 的控制报文传输。
在 PC 键盘上按下 “Caps Lock”(大写锁定)键。
观察开发板的串口日志。
预期结果:日志应打印接收到的控制报文
RX 1 byte(s): 0x02,根据 键盘输出报告,解析可知,其对应的即是 “Caps Lock”,证明设备成功获取了主机的输出报告。
备注
目前仅支持鼠标和键盘两种 HID 设备,若需支持其他设备,需在代码中替换相应的报告描述符(Report Descriptor)。
存储设备方案
概述
USB Mass Storage Class (MSC) 协议定义了 USB 大容量存储设备(如 U 盘、SD 卡读卡器等)的标准接口与传输规范。
Ameba 基于 USB-IF 官方发布的 MSC 协议标准,实现了完备的 USB 存储设备功能,支持通过 SCSI(Small Computer System Interface)命令集与主机进行交互,提供对存储介质的高效读写、擦除及状态查询能力。
特性
基于 BOT(Bulk-Only Transport)传输协议
支持多种存储介质
RAM
SD 卡(SD 模式:使用 SDIO 接口)
SD 卡(SPI 模式:使用 SPI 接口)
外置 flash
支持 USB 热插拔
支持描述符全定制
支持配置速度模式等参数
应用场景
作为 USB 存储设备,Ameba 支持对多种存储介质的灵活访问与管理。可结合 Wi-Fi、蓝牙等无线连接技术,实现多样化的数据存储与交互方案,例如:
个人存储与无线扩展:Ameba 可作为标准 U 盘或 SD/TF 读卡器,用于文件传输、系统启动盘制作及车载媒体播放。结合 Wi-Fi 或蓝牙功能,可升级为“无线 U 盘”,支持手机/PC 通过无线网络共享访问存储内容,打破传统有线连接的物理限制。
多媒体设备数据桥接:在数码相机、行车记录仪或数字多媒体播放器(MP3/MP4)等应用中,Ameba 将设备内部存储或扩展卡模拟为通用 U 盘。连接 PC 后,用户可直接通过拖拽方式管理照片、视频及音乐文件。
便捷固件升级:设备连接 PC 后被识别为一个存储盘符,用户只需将新的固件文件(如 .bin 或 .uf2 格式)拖入该盘符,设备即可自动校验并完成系统更新,极大地降低终端用户的维护门槛。
智能工业数据记录仪:针对工业、农业或科研领域的数据采集需求,利用蓝牙功能进行低功耗的参数配置(如修改采样频率、文件命名规则)。当需要处理海量历史数据时,通过 USB 接口连接 PC 进行高速导出,兼顾配置的灵活性与传输的高效性。
协议简介
MSC 协议定义了在 USB 规范下实现存储设备的传输和控制功能,遵循该标准的常见设备有 U 盘、移动硬盘和读卡器等。
描述符结构
MSC 设备除了遵循标准的 USB 描述符规范(如设备描述符、配置描述符、端点描述符)外,还需通过接口描述符声明其使用的通信协议(如 SCSI),并通过批量端点封装传输指令与数据。
以下展示了基于 Bulk-Only Transport (BOT) 模式并使用 SCSI 命令集的标准 USB MSC 描述符结构:
Device Descriptor
└── Identifies basic device information
Configuration Descriptor
└── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── 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.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── Endpoint Descriptor(BULK IN)
└── Endpoint Descriptor(BULK OUT)
协议文档
USB-IF 官方发布了 MSC 类基础协议及关于 BOT 传输协议规范。开发过程中请参考以下核心文档:
MSC BOT 传输协议规范,请参考 https://www.usb.org/sites/default/files/usbmassbulk_10.pdf
BOT 传输过程中使用的 SCSI 命令集,请参考 https://www.t10.org/, 其中主要关注下面两个规范:
规范
说明
文档
SCSI Primary Commands (SPC)
所有 SCSI 设备通用的基本命令
SCSI Block Commands (SBC)
针对块存储设备(硬盘、U盘、SSD)的特定命令
软硬件层级结构
下图展示了命令和数据在主机和设备之间所经过的软硬件层级。
以读操作为例,当用户从 U 盘读取一个文件时:
主机端:
应用请求:用户在应用程序(如文件管理器)中发起读文件请求。
文件系统转换:文件系统将文件名与偏移量转换为逻辑块地址(LBA)读取请求,并生成标准的 SCSI READ 命令。
协议封装:主机 MSC 类驱动将 SCSI 命令封装为 MSC 协议规定的命令包格式。
硬件发送:USB 主机控制器驱动通过 USB 物理端口将数据包发送至总线。
设备端:
硬件接收:USB 设备控制器从物理端口接收数据包。
协议解析:MSC 设备类驱动校验数据包完整性,并解析出内部封装的 SCSI 命令。
介质访问:根据解析出的命令参数(如 LBA 地址和长度),从底层存储介质(如 SD 卡)读取对应数据。
返回数据和状态:将读取的数据返回给主机,并回应命令执行状态。
BOT 传输流程
当 MSC 设备连接至主机并完成枚举后,若识别为支持 BOT 模式的大容量存储设备,后续的数据通信将仅通过批量端点进行。批量传输不受严格的时间限制,能够最大程度保证数据的完整性。
根据 MSC BOT 传输协议规范,所有的传输均遵循 "命令 (Command) -> 数据 (Data) -> 状态 (Status)" 的三段式流程:
CBW (Command Block Wrapper):由主机发往设备,包内封装了具体的 SCSI 命令(如 READ, WRITE, INQUIRY 等)。
Data (数据阶段):传输实际的文件或控制数据(传输方向取决于 SCSI 命令类型,对于某些无数据交互的命令,此阶段可省略)。
CSW (Command Status Wrapper):由设备发往主机,用于报告上一条 CBW 命令的执行结果(成功、失败或阶段错误)。
数据传输流程如下:
主机发起请求:主机 MSC 类驱动将 SCSI 命令封装进 CBW 包,通过批量 OUT 端点发送给设备。
设备解析与执行:设备接收 CBW 包并进行合法性检查,解析出 SCSI 命令。若 CBW 有效,设备将根据命令操作底层物理存储介质:
写操作 (如 WRITE):通过批量 OUT 端点接收主机发送的数据流,并写入存储介质。
读操作 (如 READ):从存储介质读取数据,通过批量 IN 端点回传给主机。
无数据命令 (如 TEST UNIT READY):跳过数据阶段,直接进入状态阶段。
设备反馈状态:数据传输完成后(或无需传输数据),设备通过批量 IN 端点发送 CSW 包,向主机报告命令执行结果。
主机确认完成:主机解析接收到的 CSW 包,检查 bCSWStatus 字段以确认命令是否执行成功,从而结束本次操作。
类特定请求
MSC 设备的控制请求分为 标准请求(Standard Requests) 和 类特定请求 (Class-Specific Requests) 。
本节主要介绍 MSC BOT 规范特有的 类特定请求,这些请求用于实现特有的存储功能,只有下面两个:
MSC 类特定请求 |
要求 |
描述 |
|---|---|---|
Bulk-Only Mass Storage Reset |
必选 |
重置设备接口及其相关的端点 |
Get Max LUN |
必选 |
查询该设备支持的最大逻辑单元数量 |
SCSI 命令
MSC BOT 规范下主要的 SCSI 命令如下:
SCSI 命令 |
要求 |
描述 |
|---|---|---|
INQUIRY |
必选 |
主机发送的第一个命令,用于查询设备信息 |
REQUEST_SENSE |
必选 |
当任何命令执行失败时,主机会发送此命令来获取详细的错误信息 |
TEST_UNIT_READY |
必选 |
检查设备是否就绪 |
READ_CAPACITY(10) |
必选 |
查询存储介质的容量 |
READ(10) |
必选 |
核心的读数据命令 |
WRITE(10) |
必选 |
核心的写数据命令 |
MODE_SENSE(6) |
可选 |
查询设备的特定参数,如缓存策略、写保护状态等 |
ALLOW_MEDIUM_REMOVAL |
可选 |
用来允许或禁止移除介质,实现“安全删除硬件”功能 |
START_STOP_UNIT |
可选 |
用于加载/弹出介质 |
VERIFY(10) |
可选 |
请求设备验证指定块的数据是否可读,但不传输数据 |
READ_FORMAT_CAPACITIES |
可选 |
提供比 READ_CAPACITY 更详细的容量和格式信息 |
备注
详细 SCSI 标准可参考官方文档。
类驱动
驱动描述符结构
本节展示了驱动层定义的 MSC 类描述符结构体。这些结构体对应于 USB 协议规范中的标准描述符定义。
Device Descriptor
└── Identifies basic device information
Configuration Descriptor
└── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── 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.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
├── bInterfaceClass: 0x08 (Mass Storage)
├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
└── bNumEndpoints: 2 (2 Endpoints)
├── Endpoint Descriptor(BULK IN)
└── Endpoint Descriptor(BULK OUT)
支持设备描述符全定制,参考 设备描述符定制
端点配置
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
处理 USB 主机发送的控制请求 |
批量 IN 端点 |
1 |
向 USB 主机发送数据 |
批量 OUT 端点 |
1 |
从 USB 主机接收数据 |
类特定请求实现
驱动已实现了 MSC 的两个类特定请求(Class-Specific Requests)
MSC 类特定请求 |
说明 |
|---|---|
Bulk-Only Mass Storage Reset |
重置 BOT 传输状态,准备接收下一个 CSW |
Get Max LUN |
驱动当前只支持一个逻辑单元 |
SCSI 命令实现
驱动已实现的 MSC BOT 规范下的 SCSI 命令如下,开发者可参考现有实现来扩展其他命令。
SCSI 命令 |
说明 |
|---|---|
INQUIRY |
返回常量 MSC 设备信息 |
REQUEST_SENSE |
当任何命令执行失败时,主机会发送此命令来获取详细的错误信息 |
TEST_UNIT_READY |
指示设备是否准备好接收下一个命令 |
READ_CAPACITY(10) |
返回存储介质的容量 |
READ(10) |
读存储介质 |
WRITE(10) |
写数据到存储介质 |
MODE_SENSE(6) |
返回常量 MSC 设备参数 |
ALLOW_MEDIUM_REMOVAL |
MSC 驱动不支持移除介质,直接返回成功 |
START_STOP_UNIT |
MSC 驱动不支持加载/弹出介质,介质一旦初始化即可使用 |
VERIFY(10) |
检查指定块的地址是否在合理范围 |
READ_FORMAT_CAPACITIES |
提供比 READ_CAPACITY 更详细的容量和格式信息 |
MODE_SENSE(10) |
返回比 MODE_SENSE 更多的常量 MSC 设备参数 |
MODE_SELECT(6) |
不允许主机修改其内部参数,驱动直接返回成功 |
MODE_SELECT(10) |
同 MODE_SELECT(6) 实现 |
READ(12) |
同 READ(10) 实现 |
WRITE(12) |
同 WRITE(10) 实现 |
API 说明
应用示例
应用设计
本节概述 MSC 驱动的完整使用流程,涵盖驱动加载、热插拔管理和驱动卸载等核心环节。
加载驱动
定义配置结构体和回调函数,随后调用初始化接口,对底层存储初始化并加载 USB 设备核心驱动及 MSC 类驱动。
static usbd_config_t usbd_msc_cfg = {
.speed = CONFIG_USBD_MSC_SPEED,
.isr_priority = INT_PRI_MIDDLE,
};
static usbd_msc_cb_t usbd_msc_cb = {
.status_changed = usbd_msc_cb_status_changed /* USB status change callback. */
};
int ret = 0;
ret = usbd_msc_disk_init(); /* Initializes the underlying storage disk. */
if (ret != HAL_OK) {
return;
}
ret = usbd_init(&usbd_msc_cfg); /* Initialize USB device core driver with configuration. */
if (ret != HAL_OK) {
usbd_msc_disk_deinit();
return;
}
ret = usbd_msc_init(&usbd_msc_cb); /* Initializes the MSC device class. */
if (ret != HAL_OK) {
usbd_msc_disk_deinit();
usbd_deinit();
return;
}
USB 热插拔事件处理
通过注册 status_changed 回调函数来检测 USB 连接状态变化(连接/断开)。
参考 设备连接状态检测, 获取更多细节。
备注
建议使用信号量通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。
static u8 msc_usb_attach_status;
static rtos_sema_t msc_usb_status_changed_sema;
static usbd_msc_cb_t usbd_msc_cb = {
.status_changed = usbd_msc_cb_status_changed /* USB status change callback. */
};
/* Callback executed in ISR context */
static void usbd_msc_cb_status_changed(u8 old_status, u8 status)
{
RTK_LOGS(TAG, RTK_LOG_INFO, "USB status change: %d -> %d\n", old_status, status);
msc_usb_attach_status = status;
rtos_sema_give(msc_usb_status_changed_sema);
}
/* Thread handling the state machine */
static void msc_usb_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
if (rtos_sema_take(msc_usb_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (msc_usb_attach_status == USBD_ATTACH_STATUS_DETACHED) {
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
/* Clean up resources */
usbd_msc_deinit();
ret = usbd_deinit();
if (ret != 0) {
break;
}
usbd_msc_disk_deinit();
RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());
/* Re-initialize for next connection */
usbd_msc_disk_init();
ret = usbd_init(&msc_cfg);
if (ret != 0) {
break;
}
ret = usbd_msc_init(&msc_cb);
if (ret != 0) {
usbd_deinit();
break;
}
} else if (msc_usb_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_ERROR, "Hotplug thread fail\n");
rtos_task_delete(NULL);
}
卸载驱动
在不再需要存储功能或系统关机时,按加载的反序释放资源。
/* De-initializes the underlying storage disk. */
usbd_msc_disk_deinit();
/* Deinitialize MSC device class driver. */
usbd_msc_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();
运行方式
本节介绍了一个完整的 USB 大容量存储(MSC)应用示例,该示例演示了如何通过 MSC 协议栈,将 Ameba 开发板配置为 USB 存储设备。
当开发板连接至 USB 主机(如 PC)时,系统将其识别为可移动磁盘。主机可以通过 USB 接口直接读写开发板上的存储介质(如 SD 卡), 支持 USB 热插拔。
该示例代码路径: {SDK}/component/example/usb/usbd_msc ,可为开发者设计自定义 USB 存储产品提供完整的设计参考。
备注
慎用 SD 卡热插拔功能,在传输过程中热插拔,有造成文件系统损坏、数据丢失的风险。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择USBD MSC和存储介质,保存退出。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Device) ---> [*] MSC Select storage media (RAM) --->编译与烧录
执行编译命令,并烧录生成的
Image文件至开发板:cd amebaxxx_gcc_project ./build.py -a usbd_msc
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[MSC-I] USBD MSC demo start
连接主机
用 USB 线缆将开发板连接到 PC(或其他支持 USB MSC 的主机设备)。
系统识别
PC 端文件管理器中应自动弹出一个新的可移动磁盘盘符。用户可以双击打开该盘符,对其中的文件进行读写操作,验证数据传输是否正常。
备注
使用 RAM 做存储介质时,需要格式化后才能正常使用。
音频设备方案
概述
USB Audio Class (UAC) 协议定义了 USB 音频设备(如 USB 耳机、麦克风、声卡及其他音频接口设备)的标准接口与功能控制规范。
Ameba 基于 USB-IF 官方发布的 UAC 协议标准,实现了完备的 USB 音频设备功能,能够为系统提供便捷、高质量的音频数据传输能力。
特性
支持 USB 热插拔
支持描述符全定制
支持音量、静音控制
支持配置速度模式等参数
应用场景
作为 USB 音频设备,Ameba 可通过 USB 接口从主设备(Host,如电视、PC 或其他音频源)获取音频数据流,并结合 Wi-Fi、蓝牙等无线技术实现多种创新应用,例如:
Wi-Fi 无线多声道音响系统:Ameba 作为 USB 音频接收端,从主设备(如电视/PC)获取多声道音频数据,并通过 Wi-Fi 组播或单播至多个从属播放设备。每个播放设备解析并播放指定声道的音频流,共同组成无线环绕声系统。
蓝牙音频发射器(Dongle):Ameba 充当 USB 声卡插入 PC,获取系统播放的音频数据,并通过板载蓝牙协议栈将音频流转发至蓝牙耳机或蓝牙音箱。
USB 有线音响:Ameba 接收来自 PC 的 USB 音频流,通过 I2S/PCM 等接口转发给外置音频编解码器(Audio Codec)或功放模块,实现音频的有线播放功能。
支持的音频格式
UAC 不同协议版本(1.0/2.0)在音频格式支持上存在差异,具体概览如下图所示:
协议栈支持详情
Ameba USB 协议栈提供了完整的 UAC 设备类驱动,支持多种主流音频格式及采样率。具体支持的特性及参数如下:
支持高速和全速 UAC 2.0 扬声器功能,默认支持以下音频格式,可在应用层灵活配置:
采样率(kHz) |
位宽 |
声道 |
|||
|---|---|---|---|---|---|
2 |
4 |
6 |
8 |
||
44.1 |
16 |
Y |
Y |
Y |
Y |
24/32 |
Y |
Y |
Y |
Y |
|
48 |
16 |
Y |
Y |
Y |
Y |
24/32 |
Y |
Y |
Y |
Y |
|
96 |
16 |
Y |
Y |
||
24/32 |
Y |
Y |
|||
192 |
16 |
Y |
|||
24/32 |
Y |
||||
支持音量、静音控制
支持描述符全定制
支持 USB 热插拔
支持速度模式配置
备注
USB 设备核心驱动目前不支持 UAC 2.0 高带宽端点,即每个帧/微帧最多只支持一笔等时传输,传输大小不超过等时 OUT 端点的最大包长
整条音频通路支持的音频格式由 UAC 主机、UAC 设备硬件及软件框架、音频硬件及软件框架、基于 UAC 的音频应用共同决定
支持全速 UAC 1.0 扬声器功能,默认支持以下音频格式,可在应用层灵活配置:
采样率(kHz) |
位宽 |
声道 |
|||
|---|---|---|---|---|---|
2 |
4 |
6 |
8 |
||
44.1 |
16 |
Y |
Y |
Y |
Y |
24/32 |
Y |
Y |
|||
48 |
16 |
Y |
Y |
Y |
Y |
24/32 |
Y |
Y |
|||
96 |
16 |
Y |
Y |
||
24/32 |
Y |
||||
192 |
16 |
Y |
|||
24/32 |
|||||
支持音量、静音控制
支持描述符全定制
支持 USB 热插拔
支持速度模式配置
备注
整条音频通路支持的音频格式由 UAC 主机、UAC 设备硬件及软件框架、音频硬件及软件框架、基于 UAC 的音频应用共同决定
协议简介
UAC (USB Audio Class) 协议定义了在 USB 规范框架下,实现音频设备 数据传输 与 功能控制 的标准接口。遵循该标准的常见设备包括 USB 麦克风、USB 耳机及 USB 声卡等。
版本对比
USB-IF 官方已发布多个 UAC 协议版本。各主流版本的规范文档下载链接如下表所示:
版本 |
文档 |
|---|---|
1.0 |
|
2.0 |
术语定义
本文档涉及的通用音频技术术语定义如下:
术语 |
描述 |
|---|---|
PCM |
脉冲编码调制(Pulse Code Modulation),一种将模拟音频信号转换为数字信号的编码方式。 |
声道数(Channel) |
指独立的音频信号流。声道数通常对应声源或扬声器的数量(如单声道、立体声)。 |
位深(Bit Depth) |
音频采样时的量化精度,表示每个采样点所占用的位数。位深越大,动态范围越大,音质越精细。 |
采样率(Sampling Rate) |
每秒钟对音频信号进行采样的次数。采样率越高,能还原的高频信号越多,音频保真度越高。 |
协议框架
UAC 系统架构通过定义不同的接口集合来支持音频数据的传输与控制。从逻辑功能上,UAC 设备接口主要分为两大类:音频控制接口 (Audio Control Interface) 和 音频流接口 (Audio Streaming Interface)。
音频控制接口(AC Interface)
负责管理音频设备的整体功能行为,例如音量调节、静音控制、输入源选择等。
一个 AC 接口内部包含定义的拓扑结构(Topology),用于描述音频信号从输入终端(Input Terminal)到输出终端(Output Terminal)的流向及处理过程。
音频流接口 (AS Interface)
负责传输实际的音频负载数据。
一个 UAC 设备可包含多个 AS 接口,每个接口可配置为传输不同格式、不同采样率或不同位深的音频数据。
描述符结构
UAC 设备除遵循标准的 USB 描述符(如设备描述符、配置描述符、端点描述符)外,还定义了类特定描述符 (Class-Specific Descriptors)。这些描述符依据其所属接口,分为类特定控制接口描述符和类特定音频流接口描述符。
不同协议版本在描述符的定义上存在差异:
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.
│
├── Interface Association Descriptor (IAD)
│ └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (Interface 0, Control Class)
│ └── Class-Specific Descriptor Collection
│ ├── Audio Control Interface Header (declares UAC version)
│ ├── Clock Source (internal clock or external clock)
│ ├── Clock Source (internal clock or external clock)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ ├── Output Terminal (destination of audio stream)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│ ├── Alternate Setting 0: Control transfer active state (control transfer only)
│ │
│ ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│ │ ├── Format Descriptor (audio format and bit width)
│ │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│ │ └── Class-Specific Endpoint Descriptor (no special control)
│ │
│ ├── Alternate Setting 2
│ │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
├── Alternate Setting 0: Control transfer active state (control transfer only)
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
│ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│ ├── Format Descriptor (audio format and bit width)
│ ├── Standard Endpoint Descriptor (ISO IN endpoint)
│ └── Class-Specific Endpoint Descriptor (no special control)
│
├── Alternate Setting 2
│ ...... Can configure multiple different setting as needed
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── Interface Association Descriptor (IAD)
│ └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (Interface 0, Control Class)
│ └── Class-Specific Descriptor Collection
│ ├── Audio Control Interface Header (declares UAC version)
│ ├── Clock Source (internal clock or external clock)
│ ├── Clock Source (internal clock or external clock)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ ├── Output Terminal (destination of audio stream)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│ ├── Alternate Setting 0: Control transfer active state (control transfer only)
│ │
│ ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│ │ ├── Format Descriptor (audio format and bit width)
│ │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│ │ └── Class-Specific Endpoint Descriptor (no special control)
│ │
│ ├── Alternate Setting 2
│ │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
├── Alternate Setting 0: Control transfer active state (control transfer only)
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
│ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│ ├── Format Descriptor (audio format and bit width)
│ ├── Standard Endpoint Descriptor (ISO IN endpoint)
│ └── Class-Specific Endpoint Descriptor (no special control)
│
├── Alternate Setting 2
│ ...... Can configure multiple different setting as needed
UAC Audio Control (AC) Interface Descriptor
Audio Control Interface Header
Audio Control Interface Header Descriptor
├── bLength : 1 byte → Total descriptor length (fixed = 9)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (HEADER)
├── bcdADC : 2 bytes → Audio Device Class Specification Release Number
├── bCategory : 1 byte → Indicates the classification/function of the device
├── wTotalLength : 2 bytes → Total length of all AC Class-Specific descriptors, including this one
└── bmControls : 1 byte → Bitmap indicating the availability of non-addressable control functions
Clock Source Descriptor
Clock Source Descriptor
├── bLength : 1 byte → Total descriptor length (fixed = 8)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x0A (Clock Source)
├── bClockID : 1 byte → Unique Clock ID, ranging from 1 to 255
├── bmAttributes : 1 byte → Clock type (0=Internal, 1=External)
├── bmControls : 1 byte → Bitmap indicating the control attributes of the clock
└── iClockSource : 1 byte → String descriptor index
Input Terminal Descriptor
Input Terminal Descriptor
├─ bLength : 1 byte → Total descriptor length (fixed = 17)
├─ bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x02 (INPUT_TERMINAL)
├─ bTerminalID : 1 byte → Unique ID of this terminal (referenced in topology)
├─ wTerminalType : 2 bytes → Terminal type (little-endian)
│ ├─ 0x0101 = USB Streaming (Host audio stream input)
│ └─ Other values refer to UAC2.0 Appendix B (e.g., Microphone)
├─ bAssocTerminal : 1 byte → Associated Output Terminal ID (0 = no pairing)
├─ bCSourceID : 1 byte → Associated Clock Source ID
├─ bNrChannels : 1 byte → Number of logical output channels (e.g., 2 = stereo)
├─ bmChannelConfig : 4 bytes → Spatial location bitmap for channels
├─ iChannelNames : 1 byte → String index for channel names
├─ bmControls : 2 byte → Control bitmap
└─ iTerminal : 1 byte → String index for describing this terminal
Feature Unit Descriptor
Feature Unit Descriptor
├─ bLength : 1 byte → otal descriptor length in bytes
│ = 6 + (1 + bNrChannels) × 4
├─ bDescriptorType : 1 byte → = 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → = 0x06 (FEATURE_UNIT)
├─ bUnitID : 1 byte → Unique ID of this Feature Unit
├─ bSourceID : 1 byte → ID of the connected Source Unit or Terminal
├─ bmaControls[0] : 4 bytes → Master Channel Control Bitmap
├─ bmaControls[1] : 4 bytes → Logical Channel 1 Control Bitmap
├─ bmaControls[2] : 4 bytes → Logical Channel 2 Control Bitmap
│ ⋮
├─ bmaControls[N] : 4 bytes → Logical Channel N Control Bitmap (Total bNrChannels entries)
└─ iFeature : 1 byte → String descriptor index
Output Terminal Descriptor
Output Terminal Descriptor
├─ bLength : 1 byte → Total descriptor length (fixed = 12 bytes)
├─ bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x03 (OUTPUT_TERMINAL)
├─ bTerminalID : 1 byte → Unique ID of this terminal
├─ wTerminalType : 2 bytes → Terminal type
│ ├─ 0x0301 = Speaker
│ ├─ 0x0302 = Headphones
│ ├─ 0x0603 = SPDIF
│ └─ Other values refer to Appendix B
├─ bAssocTerminal : 1 byte → Associated Input Terminal ID
├─ bSourceID : 1 byte → ID of the connected Source Unit or Terminal
├─ bCSourceID : 1 byte → Associated Clock Source ID
└─ iTerminal : 1 byte → String index for describing this terminal
Audio Streaming Interface Descriptor
Class-Specific AS Interface Descriptor
Class-Specific AS Interface Descriptor
├─ bLength : 1 byte → Fixed as 0x10 (16 bytes)
├─ bDescriptorType : 1 byte → 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x01(AS_GENERAL)
├─ bTerminalLink : 1 byte → Associated Terminal ID (Input or Output Terminal)
├─ bmControls : 1 bytes → Bitmap of endpoint control capabilities
├─ bFormatType : 1 byte → Format type
│ └─ 0x01 = FORMAT_TYPE_I(PCM)
├─ bmFormats : 4 bytes → Bitmap of supported audio formats
├─ bNrChannels : 1 bytes → Number of supported audio channels
├─ bmChannelConfig : 4 bytes → Supported audio channel configuration bitmap
└─ iChannelNames : 1 byte → String index for channel names
Audio Streaming Format Type Descriptor
Audio Streaming Format Type Descriptor
├─ bLength : 1 byte → Total length of descriptor in bytes (6 bytes)
├─ bDescriptorType : 1 byte → = 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → = 0x02(FORMAT_TYPE)
├─ bFormatType : 1 byte → = 0x01(FORMAT_TYPE_I)
├─ bSubslotSize : 1 byte → Container size for each audio sample (in bytes)
│ • Typical values: 1, 2, 3, 4
├─ bBitResolution : 1 byte → Number of valid bits in each sample(≤ bSubslotSize × 8)
└─ • Example: 16 represents 16-bit PCM
Descriptor Topology
Device Descriptor
└── Identifies basic device information (USB Version 1.10)
Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│ └── Class-Specific Descriptor Collection
│ ├── Audio Control Interface Header (declares UAC version)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ ├── Output Terminal (destination of audio stream)
│ ├── Input Terminal (source of audio stream)
│ ├── Feature Unit (volume/mute controls, etc.)
│ └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│ ├── Alternate Setting 0: Control transfer active state (control transfer only)
│ │
│ ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ │ ├── Class-Specific AS Interface (associated USB streaming terminal)
│ │ ├── Format Descriptor (audio format:channel, bit width and frequency)
│ │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│ │ └── Class-Specific Endpoint Descriptor (no special control)
│ │
│ ├── Alternate Setting 2
│ │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
├── Alternate Setting 0: Control transfer active state (control transfer only)
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
│ ├── Class-Specific AS Interface (associated USB streaming terminal)
│ ├── Format Descriptor (audio format:channel, bit width and frequency)
│ ├── Standard Endpoint Descriptor (ISO IN endpoint)
│ └── Class-Specific Endpoint Descriptor (no special control)
│
├── Alternate Setting 2
│ ...... Can configure multiple different setting as needed
UAC Audio Control (AC) Interface Descriptor
Audio Control Interface Header
Audio Control Interface Header Descriptor
├── bLength : 1 byte → Total descriptor length (typically 9 + bInCollection × 1)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (HEADER)
├── bcdADC : 2 bytes → Audio Device Class Specification Release Number (0x0100)
├── wTotalLength : 2 byte → Total number of bytes for all AC descriptors (including this header and all Unit/Terminal descriptors)
├── baInterfaceNr(1) : 1 byte → Interface number of the first AudioStreaming or MIDIStreaming interface in the Collection.
│ ⋮
└── baInterfaceNr(N) : 1 byte → Interface number of the last AudioStreaming or MIDIStreaming interface in the Collection.
Input Terminal Descriptor
Clock Source Descriptor
├── bLength : 1 byte → Total descriptor length (fixed = 12)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x0A (Clock Source)
├── bTerminalID : 1 byte → Constant uniquely identifying the Terminal within the audio function.
├── wTerminalType : 2 bytes → Constant characterizing the type of Terminal.
├── bAssocTerminal : 1 byte → D of the Output Terminal to which this Input Terminal is associated.
├── bNrChannels : 1 byte → Number of logical output channels in the Terminal’s output audio channel cluster.
├── wChannelConfig : 2 bytes → Describes the spatial location of the logical channels.
├── iChannelNames : 1 byte → Index of a string descriptor, d
└── iTerminal : 1 byte → String descriptor index
Feature Unit Descriptor
Feature Unit Descriptor
├─ bLength : 1 byte → otal descriptor length in bytes = 7+(ch+1)*n
├─ bDescriptorType : 1 byte → = 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → = 0x06 (FEATURE_UNIT)
├─ bUnitID : 1 byte → Unique ID of this Feature Unit
├─ bSourceID : 1 byte → ID of the connected Source Unit or Terminal
├─ bControlSize : 1 byte → Size in bytes of an element of the bmaControls() array: n
├─ bmaControls[0] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for master channel
├─ bmaControls[1] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for logical channel1
│ ⋮
├─ bmaControls[N] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for logical channel ch
└─ iFeature : 1 byte → String descriptor index
Output Terminal Descriptor
Output Terminal Descriptor
├─ bLength : 1 byte → Total descriptor length (fixed = 9 bytes)
├─ bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x03 (OUTPUT_TERMINAL)
├─ bTerminalID : 1 byte → Unique ID of this terminal
├─ wTerminalType : 2 bytes → Terminal type
│ ├─ 0x0301 = Speaker
│ ├─ 0x0302 = Headphones
│ ├─ 0x0603 = SPDIF
│ └─ Other values refer to Appendix B
├─ bAssocTerminal : 1 byte → Associated Input Terminal ID
├─ bSourceID : 1 byte → ID of the connected Source Unit or Terminal
└─ iTerminal : 1 byte → String index for describing this terminal
Audio Streaming Interface Descriptor
Class-Specific AS Interface Descriptor
Class-Specific AS Interface Descriptor
├─ bLength : 1 byte → Fixed as 0x07 (7 bytes)
├─ bDescriptorType : 1 byte → 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x01(AS_GENERAL)
├─ bTerminalLink : 1 byte → Associated Terminal ID (Input or Output Terminal)
├─ bDelay : 1 bytes → Delay introduced by this interface (in number of frames)
└─ wFormatTag : 2 byte → Audio data format (e.g., 0x0001 = PCM)
Audio Streaming Format Type Descriptor
Audio Streaming Format Type Descriptor
├─ bLength : 1 byte → Total length of descriptor in bytes (8 + (num_freq × 3))
├─ bDescriptorType : 1 byte → = 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → = 0x02(FORMAT_TYPE)
├─ bFormatType : 1 byte → = 0x01(FORMAT_TYPE_I)
├─ bNrChannels : 1 byte → Number of channels (e.g., 2)
├─ bSubslotSize : 1 byte → Container size for each audio sample (in bytes)
│ • Typical values: 1, 2, 3, 4
├─ bBitResolution : 1 byte → Number of valid bits in each sample(≤ bSubslotSize × 8)
├─ • Example: 16 represents 16-bit PCM
├─ bSamFreqType : 1 byte → sampling frequency count
└─ tSamFreq(n) : 3×n byte → Sample rates in little-endian 3-byte format
备注
详细的字段定义请参考 USB-IF 官方 UAC 协议文档。
类特定请求
UAC 设备的控制请求分为 标准请求(Standard Requests) 和 类特定请求 (Class-Specific Requests) 。
本节主要介绍 UAC 特有的 类特定请求,这些请求用于实现音频设备的特有功能,主要包括音频控制请求(针对 AC 接口)和音频流请求(针对 AS 接口)
音频控制请求 (AC 请求)
音频控制请求是主机通过端点 0 发送的类专用控制传输,用于在 USB 音频设备内动态配置和管理音频功能。
音频控制类型 |
要求 |
描述 |
|---|---|---|
Mute Control Request |
可选 |
操作音频功能中功能单元的静音控制 |
Volume Control Request |
可选 |
操作音频功能中功能单元的音量控制 |
Sampling Frequency Control |
可选 |
操作时钟信号的实际采样频率 |
Mixer Unit Control Request |
可选 |
操作音频功能中混音单元的内部控制 |
Terminal Control Request |
可选 |
操作音频功能中混音单元的内部控制 |
Selector Unit Control Request |
可选 |
操作音频功能中选择器单元的内部控制 |
Effect Unit Control Request |
可选 |
操作音频功能中效果单元的内部控制 |
Processing Unit Control Request |
可选 |
操作音频功能中处理单元的内部控制 |
Extension Unit Control Requests |
可选 |
操作音频功能中扩展单元的内部控制 |
音频流请求(AS 请求)
音频流请求是主机通过端点 0 发起的类专用控制传输,用于配置和管理音频数据流相关参数的核心类请求,其作用聚焦于音频流的建立、参数配置、状态管理等。
音频控制类型 |
要求 |
描述 |
|---|---|---|
Interface Control Request |
可选 |
操作音频功能中音频流接口内的控制项 |
Encoder Control Request |
可选 |
操作音频功能中音频流接口内的编码器控制项 |
Decoder Control Request |
可选 |
操作音频功能中音频流接口内的解码器控制项 |
Endpoint Control Request |
可选 |
操作音频功能中音频流端点内的控制项 |
音频控制请求 (AC 请求)
音频控制请求是主机通过端点 0 发送的类专用控制传输,用于在 USB 音频设备内动态配置和管理音频功能。
音频控制类型 |
要求 |
描述 |
|---|---|---|
Mute Control Request |
可选 |
操作音频功能中功能单元内的静音控制项 |
Volume Control Request |
可选 |
操作音频功能中功能单元内的音量控制项 |
Mixer Unit Control Request |
可选 |
操作音频功能中混音单元内的控制项 |
Selector Unit Control Request |
可选 |
操作音频功能中选择器单元内的控制项 |
Processing Unit Control Request |
可选 |
操作音频功能中处理单元内的控制项 |
Extension Unit Control Requests |
可选 |
操作音频功能中扩展单元内的控制项 |
音频流请求(AS 请求)
音频流请求是主机通过端点 0 发起的类专用控制传输,用于配置和管理音频数据流相关参数的核心类请求,其作用聚焦于音频流的建立、参数配置、状态管理等。
音频控制类型 |
要求 |
描述 |
|---|---|---|
Interface Control Request |
可选 |
操作音频功能中音频流接口内的控制项 |
Endpoint Control Request |
可选 |
描述音频功能可为其音频流端点支持的请求 |
数据传输格式
本节说明 UAC 音频数据流的传输格式。UAC 通常采用多声道交错 (Interleaved)的方式传输 PCM 数据。具体的数据排列方式取决于通道数量和位深。
更多支持格式细节请参考 支持的音频格式 了解更多细节。
两声道 (2-Channel) 交错数据示例如下图:
四声道 (4-Channel) 交错数据示例:
N 声道 (N-Channel) 交错数据示例:
备注
声道对齐规则:
UAC 协议要求传输的声道数通常应为 2 的幂次(如 2, 4, 8, 16 等)。如果实际物理声道数(如 10 声道)不符合此规则,则必须向上取整至最近的 2 的幂次(配置为 16 声道)进行传输,多余的通道位置填充无效数据。
类驱动
驱动描述符结构
本节展示了驱动层定义的 UAC 类特定描述符结构体。这些结构体对应于 USB 协议规范中的标准描述符定义。
Device Descriptor
└─ USB Version 2.00
Configuration Descriptor(Interfaces 2)
│
├─ Interface Association Descriptor (IAD)
├── Binds audio control and streaming interfaces into a single functional unit
│
├─ AudioControl Interface (IF 0, Alt 0)
│ ├─ Audio Control Interface Header (Header 2.0, desktop speaker)
│ ├─ Clock Source (ID=0x15, Internal, Sync to SOF)
│ ├─ Input Terminal (ID=0x01, USB Streaming)
│ ├─ Feature Unit (ID=0x05, Mute+Volume, 8 ch)
│ └─ Output Terminal (ID=0x09, Speaker, clocked=0x15)
│
└─ AudioStreaming Interface (IF 1)
├─ Alt 0: Inactive (no endpoints)
│
├─ Alt 1: 16-bit | 2 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(2)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x001C (28 bytes)
│
├─ Alt 2: 16-bit | 4 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(4)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x0038 (56 bytes)
│
├─ Alt 3: 16-bit | 6 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(6)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x0054 (84 bytes)
│
├─ Alt 4: 16-bit | 8 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(8)
│ AS Format: BitResolution(16)
└ Endpoint: ISO OUT, maxpkt=0x0070 (112 bytes)
Device Qualifier Descriptor
└─ USB 2.0
Other Speed Configuration Descriptor(Interfaces 2)
│
├─ Interface Association Descriptor (IAD)
├── Binds audio control and streaming interfaces into a single functional unit
│
├─ AudioControl Interface (IF 0, Alt 0)
│ ├─ Audio Control Interface Header (Header 2.0, desktop speaker)
│ ├─ Clock Source (ID=0x15, Internal, Sync to SOF)
│ ├─ Input Terminal (ID=0x01, USB Streaming)
│ ├─ Feature Unit (ID=0x05, Mute+Volume, 8 ch)
│ └─ Output Terminal (ID=0x09, Speaker, clocked=0x15)
│
└─ AudioStreaming Interface (IF 1)
├─ Alt 0: Inactive (no endpoints)
│
├─ Alt 1: 16-bit | 2 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(2)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x00C4 (196 bytes)
│
├─ Alt 2: 16-bit | 4 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(4)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x0188 (392 bytes)
│
├─ Alt 3: 16-bit | 6 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(6)
│ AS Format: BitResolution(16)
│ Endpoint: ISO OUT, maxpkt=0x024C (588 bytes)
│
├─ Alt 4: 16-bit | 8 ch | PCM
│ AS Interface: TerminalLink(0x01) channels(8)
│ AS Format: BitResolution(16)
└ Endpoint: ISO OUT, maxpkt=0x0310 (784 bytes)
Device Descriptor
└─ USB Version 1.10
Configuration Descriptor(Interfaces 2)
│
├─ AudioControl Interface (Interfaces 0, Alt 0)
│ ├─ Audio Control Interface Header (bcdADC 0x0110)
│ ├─ Input Terminal (ID=0x01, USB Streaming)
│ ├─ Feature Unit (ID=0x05, Mute+Volume, 8 ch)
│ └─ Output Terminal (ID=0x09, Speaker)
│
└─ AudioStreaming Interface (Interfaces 1)
├─ Alt 0: Inactive (no endpoints)
│
├─ Alt 1: 16-bit | 2 ch | PCM
│ AS Interface: TerminalLink(0x01)
│ AS Format: Channels(2), BBitResolution(16), SamFreq(44100,48000)
│ Endpoint: ISO OUT, maxpkt=0x00C4 (196 bytes)
│
├─ Alt 2: 16-bit | 4 ch | PCM
│ AS Interface: TerminalLink(0x01)
│ AS Format: Channels(4), BitResolution(16), SamFreq(44100,48000)
│ Endpoint: ISO OUT, maxpkt=0x0188 (392 bytes)
│
├─ Alt 3: 16-bit | 6 ch | PCM
│ AS Interface: TerminalLink(0x01)
│ AS Format: Channels(6), BitResolution(16), SamFreq(44100,48000)
│ Endpoint: ISO OUT, maxpkt=0x024C (588 bytes)
│
├─ Alt 4: 16-bit | 8 ch | PCM
│ AS Interface: TerminalLink(0x01)
│ AS Format: Channels(8), BitResolution(16), SamFreq(44100,48000)
└ Endpoint: ISO OUT, maxpkt=0x0310 (784 bytes)
类特定请求实现
驱动已实现了 UAC 规范定义的核心类特定请求(Class-Specific Requests),主要包括 采样率控制(Sampling Rate)、音量控制(Volume) 和 静音控制(Mute)。
开发者可参考现有实现来扩展其他类型的请求。UAC 驱动源码路径: {SDK}/component/usb/device/uac
音频控制请求 (AC 请求)
音频控制类型 |
要求 |
备注 |
|---|---|---|
Mute Control Request |
可选 |
控制静音功能 |
Volume Control Request |
可选 |
控制音量大小 |
Sampling Frequency Control Request |
可选 |
控制音频采样率 |
音频流请求(AS 请求)
音频控制类型 |
要求 |
备注 |
|---|---|---|
Interface Control Request |
可选 |
切换接口改变音频格式 |
音频控制请求 (AC 请求)
音频控制类型 |
要求 |
备注 |
|---|---|---|
Mute Control Request |
可选 |
控制静音功能 |
Volume Control Request |
可选 |
控制音量大小 |
Sampling Frequency Control Request |
可选 |
控制音频采样率 |
音频流请求(AS 请求)
音频控制类型 |
要求 |
备注 |
|---|---|---|
Interface Control Request |
可选 |
切换接口改变音频格式 |
端点配置
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
EP0,用于处理 USB 主机发送的标准请求及类特定控制请求。 |
等时 OUT 端点 |
1 |
用于接收来自 USB 主机的下行音频流数据。 |
API 说明
应用示例
应用设计
本节概述 UAC 驱动的完整使用流程,涵盖驱动加载、热插拔管理、音频数据处理及配置变更等核心环节。
加载驱动
定义配置结构体并注册回调函数,随后调用初始化接口加载 USB 设备核心及 UAC 类驱动。
/*
Configure different USB speeds according to different UAC applications
UAC 1.0 only support full speed, while UAC 2.0 can support both full speed and high speed
*/
static usbd_config_t uac_cfg = {
.speed = CONFIG_USBD_UAC_SPEED,
.isr_priority = INT_PRI_MIDDLE,
};
static usbd_uac_cb_t uac_cb = {
.in = {.enable = 1,}, /* Audio out params */
.out = {.enable = 0,}, /* Audio in params */
.init = uac_cb_init, /* USB init callback */
.deinit = uac_cb_deinit, /* USB deinit callback */
.setup = uac_cb_setup, /* USB setup callback */
.set_config = uac_cb_set_config, /* USB set_config callback */
.status_changed = uac_cb_status_changed, /* USB status change callback */
.mute_changed = uac_cb_mute_changed, /* Audio mute change callback */
.volume_changed = uac_cb_volume_changed, /* Audio volume change callback */
.format_changed = uac_cb_format_changed, /* Audio format change callback */
};
int ret = 0;
/**
* Initialize USB device core driver with configuration.
* param[in] cfg: USB device configuration.
* return 0 on success, non-zero on failure.
*/
ret = usbd_init(&uac_cfg);
if (ret != HAL_OK) {
return;
}
/**
* Initializes class driver with application callback handler.
* param[in] cb: Pointer to the user-defined callback structure.
* return 0 on success, non-zero on failure.
*/
ret = usbd_uac_init(&uac_cb);
if (ret != HAL_OK) {
/**
* Deinitialize USB device core driver.
* return 0 on success, non-zero on failure.
*/
usbd_deinit();
return;
}
热插拔事件处理
通过注册 status_changed 回调函数来监听 USB 连接状态变化(连接/断开)。建议使用信号量(Semaphore)通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。
参考 设备连接状态检测 获取更多细节,下面是示例代码:
/* USB status change callback */
static usbd_uac_cb_t uac_cb = {
.status_changed = uac_cb_status_changed,
};
/* Callback executed in ISR context */
static void uac_cb_status_changed(u8 old_status, u8 status)
{
uac_attach_status = status;
rtos_sema_give(uac_attach_status_changed_sema);
}
/* Thread handling the state machine */
static void uac_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
if (rtos_sema_take(uac_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (uac_attach_status == USBD_ATTACH_STATUS_DETACHED) {
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
/* Clean up resources */
usbd_uac_deinit();
ret = usbd_deinit();
if (ret != 0) {
break;
}
/* Re-initialize for next connection */
ret = usbd_init(&uac_cfg);
if (ret != 0) {
break;
}
ret = usbd_uac_init(&uac_cb);
if (ret != 0) {
usbd_deinit();
break;
}
} else if (uac_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);
}
音频数据接收流程
当 UAC 启动后,主机(Host)会下发配置请求并开始传输音频流。处理流程如下:
响应格式变更
主机下发配置时会触发 format_changed 回调,需在此保存采样率、位宽等参数。
static void uac_cb_format_changed(u32 sampling_freq, u8 ch_cnt, u8 byte_width)
{
if (sampling_freq != 0U) {
out.sampling_freq = sampling_freq;
}
if (ch_cnt != 0U) {
out.ch_cnt = ch_cnt;
}
if (byte_width != 0U) {
out.byte_width = byte_width;
}
...
}
配置并启动数据接收
调用
usbd_uac_config()应用新的参数,并调用usbd_uac_start_play()开启数据接收。建议在循环中检查启动状态,以应对线缆连接不稳定的情况。
usbd_uac_config(&(out), 0, 0);
do {
if (usbd_uac_start_play() == HAL_OK) {
break;
}
} while (uac_attach_status == USBD_ATTACH_STATUS_ATTACHED);
if(uac_attach_status != USBD_ATTACH_STATUS_ATTACHED ) {
RTK_LOGS(TAG, RTK_LOG_INFO, "USB Detached\n");
return;
}
读取音频数据
使用
usbd_uac_read()读取缓冲区数据。
while (!audio_task_stop) {
read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
if (read_dat_len > 0) {
total_len += read_dat_len;
} else {
rtos_time_delay_ms(1);
}
}
备注
完整的播放逻辑(包括与硬件 Codec 的交互)请参考 SDK 示例代码:{SDK}/component/example/usb/usbd_uac/example_usbd_uac.c,请确保 CONFIG_USBD_AUDIO_EN 已置为 1。
动态音频格式切换
当用户在主机端(如 Windows 声音设置)更改采样率或声道数时,设备端需执行“停止-重配-重启”流程。
停止播放:在回调函数
uac_cb_format_changed()中置位停止标志,通知循环读取数据退出。停止接口:调用
usbd_uac_stop_play()停止接收数据。重新配置:调用
usbd_uac_config()应用新的音频参数。重启播放:调用
usbd_uac_start_play()开始接收数据,并恢复循环读取数据。
/* In callback: Signal stop */
static void uac_cb_format_changed(u32 sampling_freq, u8 ch_cnt, u8 byte_width)
{
.......
audio_task_stop = 1;
}
/* In task loop: Handle restart */
// 1. Exit read loop
while (!audio_task_stop) {
read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
......
}
// 2. Stop UAC interface
usbd_uac_stop_play();
// 3. Re-configure
usbd_uac_config(&(out), 0, 0);
// 4. Restart UAC interface
do {
if (usbd_uac_start_play() == HAL_OK) {
break;
}
} while (uac_attach_status == USBD_ATTACH_STATUS_ATTACHED);
if(uac_attach_status != USBD_ATTACH_STATUS_ATTACHED ) {
RTK_LOGS(TAG, RTK_LOG_INFO, "USB Detached\n");
return;
}
// 5. Resume reading
while (!audio_task_stop) {
read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
....
}
卸载驱动
在不再需要音频功能或系统关机时,按加载的反序释放资源。
/* Deinitialize UAC class driver. */
usbd_uac_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();
运行方式
本节介绍了一个完整的 USB 音频类(UAC)扬声器应用示例,该示例演示了如何通过 UAC 协议栈,将 Ameba 开发板配置为 USB 音频输出设备。
当开发板连接至 PC 时,系统将其识别为扬声器;PC 端播放的数字音频流将通过 USB 传输至开发板,并经由板载 Codec/扬声器进行模拟输出。
该示例代码路径: {SDK}/component/example/usb/usbd_uac,可为开发者设计自定义 USB 音频产品提供完整的参考方案。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择USBD UAC和Audio Framework, 保存退出。
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Device) --->
- Choose UAC version 1.0 or 2.0 :
[*] UAC
Select UAC Version (UAC 2.0) --->
- Choose `CONFIG APPLICATION --->`
`Audio Config --->`:
[*] Enable Audio Framework
Select Audio Interfaces (PassThrough) --->
备注
如果音频播放出现卡顿或不流畅,修改 Audio Framework 的配置为 Select Audio Interfaces (Mixer),以优化缓冲处理。
编译与烧录
执行编译命令,并烧录生成的
Image文件至开发板:
cd amebaxxx_gcc_project
./build.py -a usbd_uac
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[UAC-I] USBD uac demo start
连接主机
使用 USB 线缆将开发板连接至 PC(如 Windows 电脑)
系统识别
Windows 声音设置中应出现名为"Realtek UAC Device speaker device"的音频设备。
若未自动切换,请手动将其设置为"默认设备"。
备注
如果看不到 Ameba 音频设备,请打开电脑声音控制面板,找到"Realtek UAC Device speaker device"设备
确保是使能状态(如果不是,鼠标右键选择
启用)。设置是默认设备(鼠标右键选择
设置为默认设备)。
格式切换测试
在 Windows 声音属性 -> 高级选项卡中,尝试切换不同的位深和采样率(如 16-bit 48000Hz),验证设备能否正常重新配置并播放。
FullMAC 设备方案
概述
基于 INIC(Internet Network Interface Controller),可实现基于 USB 的 FullMAC 解决方案,作为网卡通过 USB 与主机连接,为主机提供网络接入能力。
关于 FullMAC,参考 Wi-Fi 网卡模式 。
USB 协议栈提供了:
INIC 设备类驱动,特征如下:
支持 WiFi 单功能模式
支持描述符全定制
基于 INIC 设备类驱动的 FullMAC 应用示例,特征如下:
支持定制的 Linux 主机 FullMAC 驱动(联系 Realtek FAE)
支持 USB 热插拔
支持速度模式配置
端点配置
端点 |
数量 |
描述 |
|---|---|---|
控制 IN/OUT 端点 |
1 |
处理 USB 主机发送的控制请求 |
批量 IN 端点 |
1 |
向 USB 主机发送数据 |
批量 OUT 端点 |
2 |
从 USB 主机接收数据 |
类驱动
应用示例
示例路径: {SDK}/component/example/usb/usbd_inic
该示例定义了一个 INIC 设备,主机端加载对应的专用设备驱动后可与之通信。
更多信息,请参考示例路径下的 README.md 文件。
该示例可作为 USB FullMAC 解决方案的设计参考。
复合功能设备方案
概述
USB 复合设备(Composite Device)规范定义了通过单一物理 USB 接口承载多种独立功能类(如音频、HID、存储、串口等)的设备架构与枚举标准。
Ameba 基于 USB-IF 官方发布的 USB 规范,实现了灵活的复合设备功能框架,支持通过接口描述符(Interface Descriptor)或接口关联描述符(IAD)聚合多个功能接口,提供多逻辑设备在主机端的并行枚举、独立驱动加载及协同工作能力。
特性
支持以下功能组合
CDC ACM + HID
CDC ACM + MSC
CDC ACM + UAC
HID + UAC
支持热插拔
支持描述符全定制
支持配置传输缓存大小、速度模式等参数
应用场景
作为 USB 复合设备,Ameba 可通过单个 USB 物理接口同时枚举多种设备类,实现数据传输与控制交互的并行处理,适用于多种复杂的应用场景,例如:
带线控的 USB 音频设备 (UAC + HID):Ameba 在提供 USB 音频输入/输出功能(UAC)的同时,利用 HID 接口实现媒体控制。用户既可以获得高品质的音频流体验(如耳机听音、麦克风录音),又能通过 HID 通道实现音量调节、静音、切歌或 RGB 灯效控制等交互功能。
智能工业与 3D 打印控制 (MSC + CDC ACM):Ameba 结合了 MSC 大容量存储与 CDC 虚拟串口功能。在 3D 打印机或数据记录仪场景中,MSC 接口可模拟为 U 盘用于存储 G-code 切片文件或传感器历史数据,而 CDC 接口则同时作为控制台,供上位机实时发送 AT 指令、监控温度或校准参数。
自动化测试与辅助输入 (HID Mouse + CDC ACM):Ameba 组合了 HID 鼠标与 CDC 串口功能。在此场景下,CDC 接口负责接收来自后台脚本或传感器的原始数据(如坐标指令、头部追踪数据),HID 接口则根据这些指令模拟光标移动和点击动作,广泛应用于硬件自动化测试工具或残障辅助输入设备。
协议简介
USB 复合设备 (USB Composite Device) 规范定义了在 USB 规范框架下,通过单一物理接口同时承载多个独立功能接口的设备架构。 该机制允许主机将单一物理设备识别并枚举为多个逻辑功能的集合,实现不同功能类(如 HID、MSC、CDC 等)的并行处理与独立控制。 遵循该架构的常见设备包括无线键鼠接收器、带声卡的 USB 耳机及集成了存储与调试功能的工业设备等。
描述符结构
复合设备在遵循标准 USB 描述符(设备描述符、配置描述符)的基础上,通过在配置描述符集合中聚合多个接口描述符来实现多功能的定义。
单接口功能类
如果一个接口就代表了一个独立的功能,复合设备在聚合这种功能类的时候直接拼接使用。典型的单接口功能类有 HID 和 MSC。
以 HID 键盘 + HID 鼠标 复合设备为例,其描述符拓扑结构如下所示:
Device Descriptor
| bDeviceClass: 0xEF (Miscellaneous)
| bDeviceSubClass: 0x02 (Common Class)
| bDeviceProtocol: 0x01 (Interface Association Descriptor)
│
└── Configuration Descriptor
| bNumInterfaces: 2 (2 Interfaces)
│ ...
├── Interface Descriptor 0 (HID Keyboard)
│ bInterfaceNumber: 0
│ bInterfaceClass: 0x03 (HID)
│ bInterfaceSubClass: 0x01 (Boot Interface)
│ bInterfaceProtocol: 0x01 (Keyboard)
│
└── Interface Descriptor 1 (HID Mouse)
bInterfaceNumber: 1
bInterfaceClass: 0x03 (HID)
bInterfaceSubClass: 0x01 (Boot Interface)
bInterfaceProtocol: 0x02 (Mouse)
HID 键盘 + HID 鼠标 复合设备和单独的键盘/鼠标设备对比,描述符特征如下:
描述符层级 |
普通设备(Single Function) |
复合设备(Composite Device) |
|---|---|---|
设备描述符 |
|
|
配置描述符 |
1 个配置 |
1 个配置 |
接口描述符 |
1 个接口 |
有 2 个接口 |
端点描述符 |
归属于该接口 |
每个接口拥有独立的端点 |
备注
bDeviceClass = 0xEF:这是复合设备的标准标志,会触发主机驱动解析 IAD (可选)和配置描述符中的多个接口描述符,拆分出不同的设备功能,并创建这些逻辑子设备。
然后分别为 Interface 0 加载键盘驱动,为 Interface 1 加载鼠标驱动。
多接口功能类
如果一个逻辑功能需要占用多个接口才能完成,在使用这种功能类的时候必须加入 接口关联描述符 (Interface Association Descriptor, IAD) 将这些接口关联起来。
典型的多接口功能类
CDC (通信设备类): 通常需要 1 个控制接口 (CCI) + 1 个数据接口 (DCI)。
UVC (USB 视频类): 需要 1 个视频控制接口 (VC) + 1 个或多个视频流接口 (VS)。
UAC (USB 音频类): 需要 1 个音频控制接口 (AC) + 1 个或多个音频流接口 (AS)。
接口关联描述符(IAD)
IAD 向主机声明紧随其后的一组连续接口属于同一个功能,应由同一个驱动程序加载和管理。如果不加 IAD,主机有时候会把它们识别为两个独立的设备,或者驱动加载失败。
Interface Association Descriptor (IAD)
├── bLength : 1 byte → 0x08 (Fixed Length)
├── bDescriptorType : 1 byte → 0x0B (IAD type code)
├── bFirstInterface : 1 byte → First Interface Number
├── bInterfaceCount : 1 bytes → Count of associated interfaces
├── bFunctionClass : 1 byte → Function Class Code
├── bFunctionSubClass : 1 byte → Function SubClass Code
├── bFunctionProtocol : 1 byte → Function Protocol Code
└── iFunction : 1 byte → Function String Descriptor Index
IAD 使用示例
功能 1:设备类占用两个接口,为了让主机将其识别为单一的逻辑功能,必须使用 IAD 将这两个接口进行关联。
功能 2:设备类使用一个独立的接口,不需要 IAD 关联。
类特定请求
复合设备并没有定义专属的“复合设备类请求”,其核心逻辑在于通过 USB 标准请求中的寻址机制,将 类特定请求 (Class-Specific Requests) 精确指向指定的接口。
在 SETUP 包的 bmRequestType 字段中,低 5 位(Bits 0..4)表示 Recipient(接收者)。
普通单功能设备:控制请求通常是发给“整个设备”的,即将 Recipient 设置为
Device (00000)。复合设备:大量的控制请求必须精确地发给“特定的接口”,即将 Recipient 设置为
Interface (00001)。
以下是复合设备在控制请求处理上最显著的几个关键点:
字段 |
值 |
含义 |
复合设备中的体现 |
|---|---|---|---|
bmRequestType |
0x21 / 0xA1 |
Class Request, Recipient=Interface |
最常见。例如 CDC 设置波特率、HID 点亮键盘灯 |
wIndex |
Interface Number |
接口编号 |
当 Recipient 是接口时,wIndex 必须填入具体的接口号(如 0, 1, 2), 驱动必须根据这个值把请求分发给对应接口功能驱动处理 |
类驱动
本节详细分析如何设计并实现一个 USB 复合设备类驱动。复合类驱动充当“父类”角色,负责管理多个“子类”(如 CDC ACM、HID、MSC 等)的资源调度、请求分发和数据处理。
描述符结构
为了让主机正确识别复合设备及其包含的多种功能,驱动程序必须在运行时动态在组装一套完整、精确的描述符。在生成设备描述符的时候需要注意下面几点:
设备描述符
bDeviceClass:通常为 0xEF (Miscellaneous) 或 0x00 (由接口定义)。配置描述符
这是一个动态生成的“集合体”,包含了所有子功能的描述符。
bNumInterfaces:必须是所有子功能接口数量的总和。例如,一个 CDC 功能(占用 2 个接口)和一个 MSC 功能(占用 1 个接口)组成的复合设备,此值应为 3。wTotalLength:必须是所有描述符(配置、IAD、接口、端点)长度的总和。这个值需要在运行时精确计算。接口关联描述符 (IAD): 如果某个子功能包含多个接口(如 CDC ACM),必须使用 IAD 将这些接口“捆绑”在一起,声明它们同属于一个功能。
端点描述符
端点的配置必须根据芯片的硬件能力来配置,请参考 硬件配置。
端点选择: 必须根据功能需求(IN/OUT)和硬件支持来选择可用的端点。
最大包长 (MPS): 需考虑硬件限制。尤其在使用专用发送缓存时,要确保 IN 端点的发送缓存区大小至少能容纳一个最大数据包。
CDC ACM + MSC 示例
下面以 CDC ACM + MSC 为例介绍复合设备的描述符拓扑结构,这些结构体对应于 USB 协议规范中的标准描述符定义。
CDC ACM(虚拟串口):占用两个接口,为了让主机将其识别为单一的逻辑功能,必须使用 IAD 将这两个接口进行关联。
MSC(大容量存储):是一个独立的接口,不需要 IAD 关联。
该复合设备共使用 5 个非零端点(不包含默认控制端点 EP0):
接口编号 |
接口类 |
端点 |
说明 |
|---|---|---|---|
Interface 0 |
CDC Control (ACM) |
1x Interrupt IN |
用于通知串口状态(Serial State)与管理命令。 |
Interface 1 |
CDC Data |
1x Bulk OUT, 1x Bulk IN |
负责虚拟串口数据的发送(OUT)与接收(IN)。 |
Interface 2 |
MSC (Mass Storage) |
1x Bulk OUT, 1x Bulk IN |
负责大容量存储设备的数据读写(SCSI 命令/数据)。 |
驱动中一般将各个描述符定义为数组,在获得配置描述符时,按顺序调用每个子功能类驱动的 get_descriptor 回调函数,将它们聚合在一起。
/* USB Standard Device Descriptor */
static const u8 usbd_composite_dev_desc[USB_LEN_DEV_DESC] = {
//...
0xEF, /* bDeviceClass: Miscellaneous */
0x02, /* bDeviceSubClass: Common Class */
0x01, /* bDeviceProtocol: Interface Association Descriptor */
//...
};
/* USB Standard Configuration Descriptor */
static const u8 usbd_composite_config_desc[USB_LEN_CFG_DESC] = {
//...
0x00, 0x00, /* wTotalLength: calculated at runtime */
0x03, /* bNumInterfaces */
//...
};
/**
* @brief Get descriptor callback
* @param dev: USB device instance
* @param req: Setup request handle
* @param buf: Poniter to Buffer
* @return Descriptor length
*/
static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
{
usbd_composite_dev_t *cdev = &usbd_composite_dev;
usb_speed_type_t speed = dev->dev_speed;
u16 len = 0;
u16 desc_len;
u16 total_len = 0;
switch (USB_HIGH_BYTE(req->wValue)) {
//...
case USB_DESC_TYPE_CONFIGURATION:
case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
buf += USB_LEN_CFG_DESC;
total_len += USB_LEN_CFG_DESC;
desc_len = cdev->cdc->get_descriptor(dev, req, buf);
buf += desc_len;
total_len += desc_len;
desc_len = cdev->msc->get_descriptor(dev, req, buf);
total_len += desc_len;
buf = dev->ep0_in.xfer_buf;
if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
}
buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
len = total_len;
break;
}
return len;
}
完整的设备描述符拓扑结构如下:
Device Descriptor
| bDeviceClass: 0xEF (Miscellaneous)
| bDeviceSubClass: 0x02 (Common Class)
| bDeviceProtocol: 0x01 (Interface Association Descriptor)
|
└── Configuration descriptor
| bNumInterfaces: 3 (3 Interfaces)
|
| /* Function 1: CDC ACM */
|
├── Interface Association Descriptor (IAD)
| bFirstInterface: 0
| bInterfaceCount: 2 (Associate Interface 0 with Interface 1)
| bFunctionClass: 0x02 (CDC Control)
| bFunctionSubClass: 0x02 (ACM)
|
├── Interface Descriptor 0 (CDC Control Interface)
| | bInterfaceNumber: 0
| | bInterfaceClass: 0x02 (CDC Control)
| | bInterfaceSubClass: 0x02 (ACM)
| | ...
| ├── CDC Class Specific Descriptors (Header, Call Mgmt, ACM...)
| └── Endpoint Descriptor (Interrupt IN)
|
├── Interface Descriptor 1 (CDC Data Interface)
| | bInterfaceNumber: 1
| | bInterfaceClass: 0x0A (CDC Data)
| | ...
| ├── Endpoint Descriptor (Bulk OUT)
| └── Endpoint Descriptor (Bulk IN)
|
| /* Function 2: MSC */
|
└── Interface Descriptor 2 (MSC Interface)
| bInterfaceNumber: 2
| bInterfaceClass: 0x08 (Mass Storage)
| bInterfaceSubClass: 0x06 (SCSI Transparent Command Set)
| bInterfaceProtocol: 0x50 (Bulk-Only Transport / BBB)
| ...
├── Endpoint Descriptor (Bulk OUT)
└── Endpoint Descriptor (Bulk IN)
备注
CDC ACM 描述符详细结构请参考:CDC ACM 协议简介
MSC 描述符详细结构请参考:MSC 协议简介
类驱动实现
主要是定义复合设备和类驱动回调函数实现。
子功能类驱动
每个子功能(如 CDC, MSC, HID)都是一个独立的类驱动。
独立的驱动结构: 每个子驱动定义一个标准的
usbd_class_driver_t结构体,实现自己的业务逻辑。独立的资源管理: 每个子驱动负责管理自己的端点、数据缓冲区以及数据收发处理等。
复合类驱动
复合类驱动需要定义一个标准的
usbd_class_driver_t结构体,作为统一的入口注册到 USB Core 中,复合设备驱动会分发或者遍历子功能类驱动回调函数。复合类驱动定义一个标准的
usbd_composite_dev_t结构体,这是复合设备实例的核心,用于管理所有子功能类驱动。/* Composite Device */ static usbd_composite_dev_t usbd_composite_dev; /* Composite Class Driver */ static const usbd_class_driver_t usbd_composite_driver = { .get_descriptor = usbd_composite_get_descriptor, /* 遍历所有子功能类获取聚合后的配置描述符 */ .set_config = usbd_composite_set_config, /* 遍历初始化所有子功能类端点和资源 */ .clear_config = usbd_composite_clear_config, /* 遍历释放所有子功能类端点和资源 */ .setup = usbd_composite_setup, /* 分发类控制请求到不同接口:wIndex = Interface xx */ .sof = usbd_composite_sof, /* SOF 中断时被调用,用于时序要求严格的处理逻辑 */ .ep0_data_out = usbd_composite_handle_ep0_data_out, /* 在设备准备完成后,分发处理控制 OUT 端点的子功能类请求 */ .ep0_data_in = usbd_composite_handle_ep0_data_in, /* 在设备准备完成后,分发处理控制 IN 端点的子功能类请求结果 */ .ep_data_in = usbd_composite_handle_ep_data_in, /* 分发处理 IN 端点数据,利用 ep_addr (端点地址) 判断数据属于哪个子功能类 */ .ep_data_out = usbd_composite_handle_ep_data_out, /* 分发处理 OUT 端点数据,利用 ep_addr (端点地址) 判断数据属于哪个子功能类 */ .status_changed = usbd_composite_status_changed, /* 监控连接状态,必要时通知应用层或所有子功能类状态机 */ };
CDC ACM + MSC 示例
下面以 CDC ACM + MSC 为例介绍复合设备类驱动实例实现:
/* Composite Device structure. */
typedef struct {
usb_setup_req_t ctrl_req; /* Control setup request */
usbd_class_driver_t *cdc; /* CDC ACM class */
usbd_class_driver_t *msc; /* MSC class */
usbd_composite_cb_t *cb; /* Composite user callback */
usb_dev_t *dev; /* USB device instance */
} usbd_composite_dev_t;
/* Composite Device */
static usbd_composite_dev_t usbd_composite_dev;
/* Composite Class Driver */
static const usbd_class_driver_t usbd_composite_driver = {
.get_descriptor = usbd_composite_get_descriptor,
.set_config = usbd_composite_set_config,
.clear_config = usbd_composite_clear_config,
.setup = usbd_composite_setup,
.ep0_data_out = usbd_composite_handle_ep0_data_out,
.ep_data_in = usbd_composite_handle_ep_data_in,
.ep_data_out = usbd_composite_handle_ep_data_out,
.status_changed = usbd_composite_status_changed,
};
/********************** Function 1: CDC ACM class *********************/
/* CDC ACM device structure. */
typedef struct {
usbd_composite_dev_t *cdev; /**< Pointer to the parent composite device structure. */
usbd_composite_cdc_acm_usr_cb_t *cb; /**< Pointer to the user-registered callback structure. */
usbd_ep_t ep_bulk_in; /**< Bulk IN endpoint handler. */
usbd_ep_t ep_bulk_out; /**< Bulk OUT endpoint handler. */
#if CONFIG_COMP_CDC_ACM_NOTIFY
usbd_ep_t ep_intr_in; /**< Interrupt IN endpoint handler (for notifications). */
#endif
} usbd_composite_cdc_acm_dev_t;
/* CDC ACM Device */
static usbd_composite_cdc_acm_dev_t composite_cdc_acm_dev;
/* CDC ACM Class Driver */
const usbd_class_driver_t usbd_composite_cdc_acm_driver = {
.get_descriptor = composite_cdc_acm_get_descriptor,
.set_config = composite_cdc_acm_set_config,
.clear_config = composite_cdc_acm_clear_config,
.setup = composite_cdc_acm_setup,
.ep_data_in = composite_cdc_acm_handle_ep_data_in,
.ep_data_out = composite_cdc_acm_handle_ep_data_out,
.ep0_data_out = composite_cdc_acm_handle_ep0_data_out,
};
/********************** Function 2: MSC class *********************/
/* MSC device structure. */
typedef struct {
usbd_ep_t ep_bulk_in; /**< Bulk IN endpoint handler. */
usbd_ep_t ep_bulk_out; /**< Bulk OUT endpoint handler. */
usbd_composite_dev_t *cdev; /**< Pointer to the parent composite device structure. */
//...
} usbd_composite_msc_dev_t;
/* MSC Device */
static usbd_composite_msc_dev_t usbd_composite_msc_dev;
/* MSC Class Driver */
const usbd_class_driver_t usbd_composite_msc_driver = {
.get_descriptor = usbd_composite_msc_get_descriptor,
.set_config = usbd_composite_msc_set_config,
.clear_config = usbd_composite_msc_clear_config,
.setup = usbd_composite_msc_setup,
.ep_data_in = usbd_composite_msc_handle_ep_data_in,
.ep_data_out = usbd_composite_msc_handle_ep_data_out,
};
复合类驱动 usbd_composite_driver 的具体实现:
/**
* @brief Set composite class configuration
* @param dev: USB device instance
* @param config: USB configuration index
* @return Status
*/
static int usbd_composite_set_config(usb_dev_t *dev, u8 config)
{
int ret = HAL_OK;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
cdev->dev = dev;
cdev->cdc->set_config(dev, config);
cdev->msc->set_config(dev, config);
return ret;
}
/**
* @brief Clear composite configuration
* @param dev: USB device instance
* @param config: USB configuration index
* @return Status
*/
static int usbd_composite_clear_config(usb_dev_t *dev, u8 config)
{
int ret = 0U;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
cdev->cdc->clear_config(dev, config);
cdev->msc->clear_config(dev, config);
return ret;
}
/**
* @brief Handle class specific control requests
* @param dev: USB device instance
* @param req: USB control requests
* @return Status
*/
static int usbd_composite_setup(usb_dev_t *dev, usb_setup_req_t *req)
{
usbd_composite_dev_t *cdev = &usbd_composite_dev;
usbd_ep_t *ep0_in = &dev->ep0_in;
int ret = HAL_OK;
switch (req->bmRequestType & USB_REQ_TYPE_MASK) {
//...
case USB_REQ_TYPE_CLASS:
if ((req->wIndex == USBD_COMP_CDC_COM_ITF) || (req->wIndex == USBD_COMP_CDC_DAT_ITF)) {
ret = cdev->cdc->setup(dev, req);
} else if (req->wIndex == USBD_COMP_MSC_ITF) {
ret = cdev->msc->setup(dev, req);
} else {
RTK_LOGS(TAG, RTK_LOG_WARN, "Invalid class req\n");
}
break;
}
return ret;
}
/**
* @brief Data sent on non-control IN endpoint
* @param dev: USB device instance
* @param ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_in(usb_dev_t *dev, u8 ep_addr, u8 status)
{
int ret = HAL_OK;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
if ((ep_addr == USBD_COMP_CDC_BULK_IN_EP) || (ep_addr == USBD_COMP_CDC_INTR_IN_EP)) {
if (cdev->cdc->ep_data_in != NULL) {
ret = cdev->cdc->ep_data_in(dev, ep_addr, status);
}
} else if (ep_addr == USBD_COMP_MSC_BULK_IN_EP) {
if (cdev->msc->ep_data_in != NULL) {
ret = cdev->msc->ep_data_in(dev, ep_addr, status);
}
}
return ret;
}
/**
* @brief Data received on non-control OUT endpoint
* @param dev: USB device instance
* @param ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_out(usb_dev_t *dev, u8 ep_addr, u16 len)
{
int ret = HAL_OK;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
if (ep_addr == USBD_COMP_CDC_BULK_OUT_EP) {
if (cdev->cdc->ep_data_out != NULL) {
ret = cdev->cdc->ep_data_out(dev, ep_addr, len);
}
} else if (ep_addr == USBD_COMP_MSC_BULK_OUT_EP) {
if (cdev->msc->ep_data_out != NULL) {
ret = cdev->msc->ep_data_out(dev, ep_addr, len);
}
}
return ret;
}
/**
* @brief Handle EP0 Rx Ready event
* @param dev: USB device instance
* @return Status
*/
static int usbd_composite_handle_ep0_data_out(usb_dev_t *dev)
{
int ret = HAL_OK;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
cdev->cdc->ep0_data_out(dev);
return ret;
}
/**
* @brief USB attach status change
* @param dev: USB device instance
* @param status: USB attach status
* @return void
*/
static void usbd_composite_status_changed(usb_dev_t *dev, u8 old_status, u8 status)
{
usbd_composite_dev_t *cdev = &usbd_composite_dev;
UNUSED(dev);
if (cdev->cb->status_changed) {
cdev->cb->status_changed(old_status, status);
}
}
/**
* @brief Get descriptor callback
* @param dev: USB device instance
* @param req: Setup request handle
* @param buf: Poniter to Buffer
* @return Descriptor length
*/
static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
{
usbd_composite_dev_t *cdev = &usbd_composite_dev;
usb_speed_type_t speed = dev->dev_speed;
u16 len = 0;
u16 desc_len;
u16 total_len = 0;
switch (USB_HIGH_BYTE(req->wValue)) {
//...
case USB_DESC_TYPE_CONFIGURATION:
case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
buf += USB_LEN_CFG_DESC;
total_len += USB_LEN_CFG_DESC;
desc_len = cdev->cdc->get_descriptor(dev, req, buf);
buf += desc_len;
total_len += desc_len;
desc_len = cdev->msc->get_descriptor(dev, req, buf);
total_len += desc_len;
buf = dev->ep0_in.xfer_buf;
if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
}
buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
len = total_len;
break;
}
return len;
}
应用层回调 API
驱动为应用层提供了回调函数接口,以便应用代码能响应 USB 事件并处理业务逻辑。
Composite 应用层回调 API
usbd_composite_cb_t是针对整个复合设备状态的回调,由应用层实现。子功能类应用层回调 API
这是每个子功能自定义的回调,由应用层实现。一般可选择实现:
typedef struct { int(* init)(void); int(* deinit)(void); int(* setup)(usb_setup_req_t *req, u8 *buf); int(* set_config)(void); void (*status_changed)(u8 old_status, u8 status); int(* sof)(void); int(* received)(u8 *buf, u32 len); void(* transmitted)(u8 status); } usbd_composite_function_class_xx_usr_cb_t;
API
描述
init
在类驱动初始化时被调用,用于初始化应用相关的资源
deinit
在类驱动注销时被调用,用于注销应用相关的资源
setup
在控制传输setup或data阶段被调用,用于处理应用相关的控制请求
set_config
在类驱动set_config回调中被调用,用于通知应用层UAC类驱动已就绪
status_changed
在USB连接状态改变时被调用,用于应用层处理USB热插拔事件
sof
收到SOF中断时被调用,用于应用层处理时钟同步
transmitted
在IN传输完成时被调用,用于应用层以异步的方式获取IN传输状态
received
在OUT传输完成时被调用,用于应用层以异步的方式获取OUT传输状态
CDC ACM + MSC 示例
下面以 CDC ACM + MSC 为例介绍自定义的子功能类应用层回调(MSC 没有使用应用层回调):
/**
* @brief User callback structure for CDC ACM events.
* @details This structure allows the application layer to handle CDC ACM events.
*/
typedef struct {
int(* init)(void); /**< Called during class driver initialization for application resource setup. */
int(* deinit)(void); /**< Called during class driver deinitialization for resource cleanup. */
int(* setup)(usb_setup_req_t *req, u8 *buf); /**< Called during control transfer SETUP/DATA phases to handle application-specific control requests. */
int(* received)(u8 *buf, u32 len); /**< Called when new data is received on the Bulk OUT endpoint. */
void(* transmitted)(u8 status); /**< Called after data transmission on the Bulk IN endpoint is complete. */
} usbd_composite_cdc_acm_usr_cb_t;
面向应用层 API
应用层通过下面两个主要函数来控制整个复合设备驱动的生命周期:
usbd_composite_init(): 加载函数接收应用层传入的参数,如端点缓冲区大小和各子功能的回调函数集。
调用每个子功能的驱动加载函数。
将子功能驱动实例链接到
usbd_composite_dev结构体中。最后,调用
usbd_register_class()将复合设备驱动注册到 USB Core,使其生效。
usbd_composite_deinit(): 卸载函数调用
usbd_unregister_class()从 USB Core 注销复合设备驱动。依次调用每个子功能的卸载函数,释放所有资源。
CDC ACM + MSC 示例
下面以 CDC ACM + MSC 为例介绍复合设备类驱动面向应用层 API 实现:
/**
* @brief Init composite class
* @param cdc_bulk_out_xfer_size: CDC ACM bulk out xfer buffer size
* @param cdc_bulk_in_xfer_size: CDC ACM bulk in xfer buffer size
* @param cdc_cb: CDC ACM user callback
* @param cb: composite user callback
* @return Status
*/
int usbd_composite_init(u16 cdc_bulk_out_xfer_size, u16 cdc_bulk_in_xfer_size, usbd_composite_cdc_acm_usr_cb_t *cdc_cb, usbd_composite_cb_t *cb)
{
int ret;
usbd_composite_dev_t *cdev = &usbd_composite_dev;
if (cdc_cb == NULL) {
ret = HAL_ERR_PARA;
RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user cb\n");
return ret;
}
if (cb != NULL) {
cdev->cb = cb;
}
ret = usbd_composite_cdc_acm_init(cdev, cdc_bulk_out_xfer_size, cdc_bulk_in_xfer_size, cdc_cb);
if (ret != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init CDC ACM itf fail: %d\n", ret);
return ret;
}
ret = usbd_composite_msc_init(cdev);
if (ret != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init MSC itf fail: %d\n", ret);
usbd_composite_cdc_acm_deinit();
return ret;
}
cdev->cdc = (usbd_class_driver_t *)&usbd_composite_cdc_acm_driver;
cdev->msc = (usbd_class_driver_t *)&usbd_composite_msc_driver;
usbd_register_class(&usbd_composite_driver);
return ret;
}
/**
* @brief DeInit composite class
* @param void
* @return Status
*/
void usbd_composite_deinit(void)
{
usbd_unregister_class();
usbd_composite_msc_deinit();
usbd_composite_cdc_acm_deinit();
}
备注
详细类驱动说明请参考:自定义设备方案
API 说明
应用示例
本节以 USB Composite (CDC ACM + MSC) 为例,介绍完整的应用实现和运行测试的方式。
应用设计
本节详细介绍复合设备驱动的完整开发设计流程,涵盖加载驱动、热插拔管理以及资源释放。
加载驱动
复合设备初始化过程需要依次加载 USB 核心初始化及 Composite 类驱动。定义配置结构体并注册用户回调函数是必要步骤。
步骤说明:
硬件配置:配置 USB 速度模式及中断优先级等。
回调注册:定义用户回调结构体
usbd_composite_cb_t和usbd_composite_fucntion_xx_usr_cb,挂载各个阶段的处理函数。加载核心驱动:调用
usbd_init()加载 USB 核心驱动。加载类驱动:调用
usbd_composite_init()加载复合类驱动。
下面以 CDC ACM + MSC 为例介绍加载复合设备驱动实现:
其中 MSC 功能大部分交互由协议栈自动处理,应用层主要关注磁盘的初始化与去初始化。 在 USB 初始化之前,必须先确保存储介质(如 SD 卡或 Flash)已就绪,并调用磁盘初始化接口。
static usbd_config_t composite_cfg = {
.speed = CONFIG_USBD_COMPOSITE_SPEED,
.isr_priority = CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY,
.intr_use_ptx_fifo = 0U,
}
static usbd_composite_cdc_acm_usr_cb_t composite_cdc_acm_usr_cb = {
.init = composite_cdc_acm_cb_init,
.deinit = composite_cdc_acm_cb_deinit,
.setup = composite_cdc_acm_cb_setup,
.received = composite_cdc_acm_cb_received,
.transmitted = composite_cdc_acm_cb_transmitted
};
static usbd_composite_cb_t composite_cb = {
.status_changed = composite_cb_status_changed,
};
int ret = 0;
/* Initializes the underlying storage disk. */
ret = usbd_composite_msc_disk_init();
if (ret != HAL_OK) {
return;
}
/* Initialize USB device core driver with configuration. */
ret = usbd_init(&composite_cfg);
if (ret != HAL_OK) {
usbd_composite_msc_disk_deinit();
return;
}
/* Initialize composite class driver. */
ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
&composite_cdc_acm_usr_cb,
&composite_cb);
if (ret != HAL_OK) {
usbd_composite_msc_disk_deinit();
usbd_composite_deinit();
return;
}
热插拔事件处理
通过注册 status_changed 回调函数来监听 USB 连接状态变化(连接/断开)。
参考 设备连接状态检测, 获取更多细节。
备注
建议使用信号量(Semaphore)通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。
下面以 CDC ACM + MSC 为例介绍复合设备驱动热插拔事件处理:
static u8 composite_attach_status;
static rtos_sema_t composite_attach_status_changed_sema;
/* USB status change callback */
static usbd_composite_cb_t composite_cb = {
.status_changed = composite_cb_status_changed,
};
/* Callback executed in ISR context */
static void composite_cb_status_changed(u8 old_status, u8 status)
{
composite_attach_status = status;
rtos_sema_give(composite_attach_status_changed_sema);
}
/* Thread Context: Handle the state machine */
static void composite_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
/* Wait for status change signal */
if (rtos_sema_take(composite_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (composite_attach_status == USBD_ATTACH_STATUS_DETACHED) {
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
/* 1. Clean up composite class resources */
usbd_composite_deinit();
/* 2. De-initialize USB core */
ret = usbd_deinit();
if (ret != 0) {
break;
}
usbd_composite_msc_disk_deinit();
/* 3. Re-initialize for next connection */
usbd_composite_msc_disk_init();
ret = usbd_init(&composite_cfg);
if (ret != 0) {
break;
}
ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
&composite_cdc_acm_usr_cb,
&composite_cb);
if (ret != 0) {
usbd_deinit();
break;
}
} else if (composite_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_ERROR, "Hotplug thread fail\n");
rtos_task_delete(NULL);
}
卸载驱动
在不再需要 USB 功能或系统关机时,需按加载的反序释放资源。 下面以 CDC ACM + MSC 为例介绍复合设备驱动卸载实现:
/* De-initializes the underlying storage disk. */
usbd_composite_msc_disk_deinit();
/* Deinitialize composite class driver first */
usbd_composite_deinit();
/* Deinitialize USB device core driver */
usbd_deinit();
运行方式
该示例路径: {SDK}/component/example/usb/ ,可为开发者设计自定义 Composite 产品提供完整的参考方案。
该示例演示了如何通过复合设备协议栈,将 Ameba 开发板配置为同时具备 CDC ACM(虚拟串口) 和 MSC(大容量存储) 功能的设备。
当开发板连接至 USB 主机(如 PC)时,系统将识别出两个独立的逻辑设备。用户既可以通过串口工具与开发板进行通信,也可以像操作普通 U 盘一样读写板载 SD 卡中的文件。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择Composite Device以及具体的复合类组合(CDC ACM + MSC),保存退出。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Device) ---> [*] Composite Select Composite Class (CDC ACM + MSC) ---> (X) CDC ACM + MSC Select storage media (SD Card (SD mode)) --->编译与烧录
执行编译命令,并烧录生成的 Image 文件至开发板:
./build.py -a usbd_composite_cdc_acm_msc
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[COMP] USBD COMP demo start
连接主机
用 USB 线缆将开发板连接到 PC。
当该设备连接至 Windows 主机时,设备管理器将呈现如下层级结构:
通用串行总线控制器下出现:USB Composite Device (由 usbccgp.sys 驱动)。通用串行总线控制器下出现:USB 大容量存储设备 (对应 MSC 功能)。端口 (COM 和 LPT)下出现:USB 串行设备 (COMx) (对应 CDC ACM 功能)。
功能验证 1:CDC ACM (虚拟串口)
在 PC 端打开串口调试工具(如 Realtek Trace Tool)。
选择 Ameba 枚举出的虚拟串口号。
发送任意字符,开发板将原样回传接收到的数据,验证通信正常。
功能验证 2:MSC (大容量存储)
PC 端文件管理器中应自动弹出一个新的可移动磁盘盘符。用户可以双击打开该盘符,对插入的 SD 卡进行文件读写操作。
备注
本示例使用 SD 卡作为 MSC 的底层存储介质,请确保开发板板载 SDIOH 插槽 中已插入一张格式化好的 SD 卡。 请避免在数据读写过程中拔出 SD 卡或断开 USB 连接,以免造成文件系统损坏。
自定义设备方案
概述
USB Vendor Class (Vendor Specific) 利用 USB 规范的开放性,允许厂商自定义的私有协议或请求,支持开发者灵活定义端点配置与传输类型。 Ameba 基于底层 USB 协议栈实现 Vendor 设备功能,建立高自由度的专属数据通道。 主机可通过专用驱动或通用库(如 WinUSB)与 Ameba 进行私有命令集与特定格式数据的交互,实现了超越标准设备类限制的定制化控制与功能扩展。
特性
支持热插拔
支持描述符全定制
支持配置速度模式等参数
支持批量/中断/等时传输数据的同步或异步回传操作
支持与 Vendor 主机应用示例适配,具体参考 自定义主机方案
应用场景
作为具备高度可定制性的 USB 设备,Ameba 可通过 Vendor 接口建立主机与设备间专属的控制与通信通道,突破标准设备类的功能限制,实现以下典型应用,例如:
自定义协议通信:定义私有命令集与数据帧格式,用于设备与主机间的专用通信(如参数配置、故障诊断、状态查询)。
复合设备功能扩展:在标准类(HID/CDC/UVC)基础上,增加 Vendor 接口以实现额外的控制通道。
安全固件升级:利用自定义 Vendor 协议传输固件,实现私有的安全升级机制。
协议简介
Vendor 类没有官方统一的 USB 类标准规范。其描述符结构、命令请求及端点用途均由厂商自行定义,并需配合主机端驱动或应用程序进行解析。
描述符结构
Vendor 描述符结构可包含标准的设备描述符,配置描述符,设备质量描述符,其他速度描述符和字符串描述符。 详细描述符定制项,请参考 设备描述符定制。
对于 Vendor 设备,描述符中有几个关键点需要注意:
设备描述符
Vendor 设备的类代码 bDeviceClass 设置为 0xFF/0x00,主机通常不会自动加载通用驱动(如 HID 或 MSC),而是去寻找匹配 VID/PID 的专用驱动程序。
Device Descriptor
| bDeviceClass: 0xFF/0x00 (0xFF: Vendor Specific Class, 0x00: Interface-based Class)
| bDeviceSubClass: 0xFF/0x00 (Vendor Specific SubClass)
| bDeviceProtocol: 0xFF/0x00 (Vendor Specific Protocol)
| idVendor (Vendor ID, e.g. 0x0BDA is for Realtek)
| idProduct (Product ID)
| ...
接口描述符
可将接口的类代码 bInterfaceClass 设置为 0xFF 表明自定义功能类。复合设备的功能区分通常发生在接口层级。
Interface Descriptor
| bInterfaceClass: 0xFF (0xFF: Vendor Specific Class)
| bInterfaceSubClass (Vendor Specific SubClass)
| bInterfaceProtocol (Vendor Specific Protocol)
| ...
例如:一个复合设备既有标准 MSC 功能,又有 Vendor 自定义功能。
Interface 0: bInterfaceClass = 0x08 (MSC)
Interface 1: bInterfaceClass = 0xFF (Vendor Specific)
字符串描述符
字符串描述符虽然不是 Vendor 规范特有,但在 Vendor 请求中,字符串描述符常用于传递序列号(iSerialNumber)或特定的固件版本信息。
例如:设备描述符中三个字段 iManufacturer/iProduct/iSerialNumber 依次设为 1, 2, 3。
Device Descriptor
├── iManufacturer : 1 ──┐
├── iProduct : 2 ──┤ (Index)
├── iSerialNumber : 3 ──┘
│
└── USB String Table
│
├── String Descriptor (Index 0: Must be read first!)
│ └── LANGID Code Array (Language ID Array)
│ └── 0x0409 (English - United States)
│
├── String Descriptor (Index 1: Pointed by `iManufacturer` index)
│ └── "Realtek Semiconductor Corp." (Unicode)
│
├── String Descriptor (Index 2: Pointed by the `iProduct` index)
│ └── "Realtek USB Vendor Device" (Unicode)
│
├── String Descriptor (Index 3: Pointed by the `iSerialNumber` index)
│ └── "00E04C000001" (Unicode)
│ ├── Funtion 1: Distinguish devices (Instance ID)
│ └── Funtion 2: Function binding of USB composite device
│
└── String Descriptor (Index 4/5/6/...: Optional/Customizable)
│ ├── "FW_Ver_1.0" (Firmware version information)
│ └── "MAC_ADDR_..." (Network Configuration)
|
│ ...
iSerialNumber:对于 Vendor 设备,强烈建议实现该字符串且保证字符串值唯一。
重要性: 对于 Vendor 设备,如果没有序列号字符串,Windows 主机可能会在每次设备插入不同的 USB 端口时,都认为是一个新设备并重新加载驱动。
唯一性: 确保每颗芯片的序列号不同,否则两台同型号设备同时插入可能会冲突。
对于 Vendor 设备,除了常规的厂商/产品名,将
iSerialNumber用于设备识别是高频且最具技术含量的用法。自定义字符串:对于 Vendor 设备,因为没有标准类的限制,厂商经常利用未使用的字符串索引来传递固件信息或配置。
场景 A:固件版本号
可以定义索引 4 为固件版本字符串(例如 "FW_Ver_1.0")。
主机量产工具不需要发复杂的 Vendor 请求,直接发标准请求
GET_DESCRIPTOR(String, Index=4) 就能读到版本号。这在工厂测试环节非常高效。
场景 B:MAC 地址 (网卡常用)
对于 USB 网卡,MAC 地址通常存在 eFuse 或 Flash 里。
有些固件实现会将 MAC 地址转换成 ASCII 字符串放在
iSerialNumber中,或者放在一个专门的自定义索引中,供上层应用读取。
控制请求
USB 协议将控制请求类型分为三类:
Standard (标准请求): 所有 USB 设备都必须支持的通用命令(如
GET_DESCRIPTOR,SET_ADDRESS)。Class (类请求): 特定设备类定义的命令(如 HID 类的
GET_REPORT,MSC 类的Bulk-Only Reset)。Vendor (厂商请求): 这是 USB-IF 留给厂商自由发挥的空间。只要主机和设备约定好,可以定义任何命令(比如:读写寄存器、下载固件、进入工厂模式等)。
所有的控制传输都始于一个 8 字节的 SETUP 包,在 SETUP 第一个字节 bmRequestType 的 bit[6:5] 决定是否为 Vendor 请求:
Bit Position |
Field Name |
Description & Values |
|---|---|---|
Bit 7 |
Direction |
|
Bit 6..5 |
Request Type |
|
Bit 4..0 |
Recipient |
|
在开发 Vendor 请求时,需特别注意以下几个关键点以确保设备正确响应主机的控制请求:
请求类型检查:设备在处理 SETUP 包时,必须解析
bmRequestType字段,确认其 Type 字段为10(即 Vendor 类型)。避免请求值冲突:为防止自定义的
bRequest值与 USB 标准请求(如 0x06: GET_DESCRIPTOR)产生混淆,建议维护一份清晰的请求值定义表,确保所有自定义请求具有唯一且明确的标识。数据方向控制:必须严格遵守
bmRequestType的 Bit 7(即 Direction 位)。若主机发送 OUT 请求但 wlength > 0 且 Bit 7 为 IN,则可能导致设备 STALL 或总线错误,因此需确保方向一致。端点使用规范:所有 Vendor 请求默认通过控制端点进行传输,因此在实现中应确保控制请求的处理逻辑正确绑定到该端点。
Vendor 请求的应用场景
Vendor 请求允许用户通过自定义的控制传输指令,可以灵活地实现对设备的专有配置、状态管理及数据流控制。
例如以下三种应用模式:
高性能流式传输 (High-Performance Streaming)
此场景适用于高带宽、持续性的数据传输应用,如高速数据采集仪等。
工作机制:
控制信道 (Vendor Request): 用于发送开启/停止控制、参数配置等低频指令。
数据信道 (BULK IN): 通过批量端点持续高效地向主机上报数据。
类驱动
驱动架构
本驱动实现了一个通用的 USB Vendor Class 自定义设备。它不隶属于任何标准的 USB 类(如 HID, MSC),而是提供了一个灵活的基础框架,允许开发者自定义传输协议。
驱动架构采用分层设计,自下而上包含三个核心层次:
核心驱动层: 负责底层硬件中断和标准 USB 协议处理。
Vendor 类驱动层 (本驱动核心实现): 负责管理端点、组装描述符及传输处理等。
用户应用层: 实现具体的业务逻辑。通过注册回调结构体
usbd_vendor_cb_t与 Vendor 类驱动层交互。
需求规划与端点设计
在设计 Vendor 设备时,开发者应根据实际的应用需求(如数据吞吐量、实时性要求)以及芯片硬件资源的限制(参考 硬件配置)来进行合理规划。
端点类型选择
可根据具体数据传输场景选择端点类型:
需求场景 |
推荐端点类型 |
数据完整性 |
实时性(延迟) |
典型应用 |
|---|---|---|---|---|
命令与状态控制 |
控制 |
高 (重传) |
中 |
设置参数、获取版本号 |
大数据吞吐 |
批量 |
高 (重传) |
低 (无保证) |
固件升级、文件传输、日志 |
低延迟事件 |
中断 |
高 (重传) |
高 (固定) |
按键、报警、状态同步 |
音视频流 |
同步 |
低 (丢包) |
极高 (恒定) |
麦克风、摄像头 |
备注
避免使用中断传输大数据:尽管高速模式允许中断端点拥有较大吞吐量,但其属于保留带宽传输,过度占用会耗尽总线周期性带宽。
注意批量传输的非实时性:批量传输在 USB 总线仲裁中优先级最低。虽然总线空闲时极快,但在繁忙时可能面临数十毫秒的调度延迟。若设备要求硬实时响应(如 <2ms),请使用中断传输。
硬件限制评估
端点数量和方向:芯片支持端点数量和方向是固定的
例如: RTL8721F 芯片除 EP0 外支持 5 个 IN 和 5 个 OUT。
EP0:INOUT EP1:IN EP2:INOUT EP3:INOUT EP4:IN EP5:OUT EP6:INOUT EP7:OUT
FIFO 大小:芯片中端点的接收和发送缓存可配置,但有最大限制。要确保端点的缓存区大小至少能容纳一个 MPS 长度的数据包。
例如: RTL8721F 芯片设备模式下的硬件配置。
Device mode total DFIFO: 1024 DWORD (1 DWORD = 4 Bytes) Shared Rx FIFO max depth: 1024 DWORD 6 Dedicated Tx FIFO: - Tx FIFO 0 max depth: 32 DWORD = 128 Bytes - Tx FIFO 1 max depth: 16 DWORD = 64 Bytes - Tx FIFO 2 max depth: 256 DWORD = 1024 Bytes - Tx FIFO 3 max depth: 32 DWORD = 128 Bytes - Tx FIFO 4 max depth: 256 DWORD = 1024 Bytes - Tx FIFO 5 max depth: 128 DWORD = 512 Bytes
端点配置示例
示例中 Vendor 设备除了 EP0 还各有一组方向相反的不同类型的端点,即 BULK IN & BULK OUT, ISOC IN & ISOC OUT, INTR IN & INTR OUT。
下面以 RTL8721F 芯片为例,说明端点选择与 DFIFO 配置:
硬件资源评估:首先确认硬件层面的端点数量可以满足上述 3 组 (6 个) 非控制端点的需求。
MPS (最大包长) 设定
FIFO 大小要满足端点一次 MPS 的传输,在硬件 FIFO 空间允许的前提下,建议将 MPS 设为 USB 规范允许的最大值以提升吞吐量:
Bulk (High-Speed): 512 Bytes
Interrupt (High-Speed): 1024 Bytes
Isochronous (High-Speed): 1024 Bytes
DFIFO 分配策略
针对 RTL8721F 芯片的硬件特性:
TX FIFO (专用发送缓存):每个 IN 端点拥有独立的发送缓存区。
RX FIFO (共享接收缓存):所有 OUT 端点共用同一块接收缓存区。
在 DMA 模式下,必须为每个端点保留 1 DWORD (4 Bytes) 大小的缓存空间。
推荐采用以下分配逻辑:
发送缓存:需根据上述 MPS 值,为每个启用的 IN 端点分配足额的缓存空间。
接收缓存:将剩余的所有可用空间全部分配给 RX FIFO,以确保数据接收的稳定性。
计算公式: RX FIFO Size = 总 FIFO 空间 - Σ(已启用的 IN 端点 TX FIFO 配置值) - Σ(各端点保留的一个 DWORD)
最后确定的一组合适的端点和 DFIFO 配置为:
/* Vendor Device Endpoint Address */ #define USBD_VENDOR_BULK_IN_EP 0x86U /* EP6 for BULK IN */ #define USBD_VENDOR_BULK_OUT_EP 0x03U /* EP3 for BULK OUT */ #define USBD_VENDOR_ISOC_IN_EP 0x82U /* EP2 for ISOC IN */ #define USBD_VENDOR_ISOC_OUT_EP 0x02U /* EP2 for ISOC OUT */ #define USBD_VENDOR_INTR_IN_EP 0x84U /* EP4 for INTR IN */ #define USBD_VENDOR_INTR_OUT_EP 0x05U /* EP5 for INTR OUT */ /* Vendor Device DFIFO config */ static usbd_config_t vendor_cfg = { /*DFIFO total 1024 DWORD, resv 12 DWORD for each EP’s DMA addr and IN EP0 with fixed 32 DWORD*/ .ptx_fifo_depth = {16U, 256U, 32U, 256U, 128U, },// For IN EP: 1,2,3,4,6 .rx_fifo_depth = 292U, //All remaining fifo space is allocated to RX }
描述符结构
Vendor 类设备遵循 USB 2.0 标准描述符架构。开发者需重点关注多速率支持下的配置描述符适配,以及端点参数的合规性设置。 协议栈支持描述符定制,参考 设备描述符定制
完整的 Vendor 设备描述符集合包含:
设备描述符:根节点,定义 VID/PID 等身份信息。
配置描述符集合:包含配置、接口及端点描述符。
多速率支持描述符(可选):设备限定描述符 (Device Qualifier) 与其他速度配置描述符 (Other Speed Configuration)。
字符串描述符(可选):提供厂商、产品名称及序列号文本等文本信息。
设备描述符
无论设备运行在何种速度下,通常仅需维护一份共用的设备描述符。
关键字段设置:
bDeviceClass: 设为 0xFF (厂商自定义)/ 0x00 (类定义在接口描述符) 。bMaxPacketSize0: 控制端点 0 最大包长,通常固定设为 64。idVendor / idProduct: 厂商 ID 与产品 ID。
示例:
Standard Device Descriptor
├── bLength : 1 byte → Size of the descriptor (18 bytes)
├── bDescriptorType : 1 byte → 0x01 (DEVICE)
├── bcdUSB : 2 bytes → USB Specification Release Number (0x0200 = USB 2.0)
├── bDeviceClass : 1 byte → 0x00 (Class defined at Interface level)
├── bDeviceSubClass : 1 byte → 0x00
├── bDeviceProtocol : 1 byte → 0x00
├── bMaxPacketSize0 : 1 byte → Max Packet Size for Control Endpoint 0 (64 bytes)
├── idVendor : 2 bytes → Vendor ID (VID)
├── idProduct : 2 bytes → Product ID (PID)
├── bcdDevice : 2 bytes → Device Release Number
├── iManufacturer : 1 byte → Index of string descriptor describing manufacturer
├── iProduct : 1 byte → Index of string descriptor describing product
├── iSerialNumber : 1 byte → Index of string descriptor describing the device's serial number
└── bNumConfigurations : 1 byte → Number of possible configurations (1)
配置描述符与多速率适配
若设备设计为仅支持一种速度(如仅全速),只需定义该速度对应的配置集合即可。
若设备需同时支持 全速 和 高速,则必须根据连接速度动态适配参数。
开发者需要在代码中分别定义两套配置描述符集合,并根据枚举时的实际速度返回对应的一套:
高速配置描述符集合: 当设备枚举为高速时使用。
全速配置描述符集合: 当设备枚举为全速时使用。
备注
在编写这两套描述符时,必须针对当前速度严格调整端点描述符中的 wMaxPacketSize (最大包长) 和 bInterval (轮询间隔) 字段。 若在高速模式下使用了全速的参数(或反之),将导致枚举失败或严重的传输性能问题。
最大包长(wMaxPacketSize)
根据 USB 2.0 规范,不同速度下的端点最大包长限制不同,各端点的最大包长需结合实际应用的带宽需求进行合理配置。
端点类型 |
全速模式 (字节) |
高速模式 (字节) |
|---|---|---|
控制 |
8, 16, 32, 64 |
64 (固定) |
中断 |
≤ 64 |
≤ 1024 |
批量 |
8, 16, 32, 64 |
512 (固定) |
同步 |
≤ 1023 |
≤ 1024 |
轮询间隔(bInterval)
bInterval 字段的物理时间单位取决于当前速度,设置错误会导致轮询频率异常。
USB 模式 |
时间单位 |
取值范围 |
实际时间计算公式 |
|---|---|---|---|
全速 (Full-Speed) |
1ms (帧) |
1 – 255 |
:math:Interval = 1ms times bInterval |
高速 (High-Speed) |
125μs (微帧) |
1 – 16 |
:math:Interval = 125mu s times 2^{(bInterval-1)} |
备注
示例:设定 1ms 轮询间隔
全速模式:
bInterval填 1 (即 \(1ms \times 1 = 1ms\))高速模式:
bInterval填 4 (即 \(125\mu s \times 2^{(4-1)} = 125\mu s \times 8 = 1000\mu s = 1ms\))
下面是配置描述符示例:
High Speed Configuration ├── Configuration Descriptor │ ├── bLength : 1 byte → 9 bytes │ ├── bDescriptorType : 1 byte → 0x02 (CONFIGURATION) │ ├── wTotalLength : 2 bytes → Total length (Config + Interface + EPs) │ ├── bNumInterfaces : 1 byte → 1 │ ├── bConfigurationValue: 1 byte → 1 │ ├── iConfiguration : 1 byte → Index of string descriptor │ ├── bmAttributes : 1 byte → 0xC0 (Self-powered) or 0x80 (Bus-powered) │ └── bMaxPower : 1 byte → Max power consumption (e.g., 100mA) │ ├── Interface Descriptor │ ├── bLength : 1 byte → 9 bytes │ ├── bDescriptorType : 1 byte → 0x04 (INTERFACE) │ ├── bInterfaceNumber : 1 byte → 0 │ ├── bAlternateSetting : 1 byte → 0 │ ├── bNumEndpoints : 1 byte → 2 (Example: 1 Bulk IN + 1 Bulk OUT) │ ├── bInterfaceClass : 1 byte → 0xFF (Vendor Specific) │ ├── bInterfaceSubClass : 1 byte → 0xFF │ ├── bInterfaceProtocol : 1 byte → 0xFF │ └── iInterface : 1 byte → Index of string descriptor │ ├── Endpoint Descriptor (BULK OUT) │ ├── bLength : 1 byte → 7 bytes │ ├── bDescriptorType : 1 byte → 0x05 (ENDPOINT) │ ├── bEndpointAddress : 1 byte → 0x01 (OUT Address) │ ├── bmAttributes : 1 byte → 0x02 (Transfer Type: BULK) │ ├── wMaxPacketSize : 2 bytes → 512 bytes (High Speed Fixed) │ └── bInterval : 1 byte → 0 (Ignored for Bulk) │ └── Endpoint Descriptor (BULK IN) ├── bLength : 1 byte → 7 bytes ├── bDescriptorType : 1 byte → 0x05 (ENDPOINT) ├── bEndpointAddress : 1 byte → 0x81 (IN Address) ├── bmAttributes : 1 byte → 0x02 (Transfer Type: BULK) ├── wMaxPacketSize : 2 bytes → 512 bytes (High Speed Fixed) └── bInterval : 1 byte → 0 (Ignored for Bulk)
Full Speed Configuration ├── Configuration Descriptor │ ├── bLength : 1 byte → 9 bytes │ ├── bDescriptorType : 1 byte → 0x02 (CONFIGURATION) │ ├── wTotalLength : 2 bytes → Total length (Config + Interface + EPs) │ ├── bNumInterfaces : 1 byte → 1 │ ├── bConfigurationValue: 1 byte → 1 │ ├── iConfiguration : 1 byte → Index of string descriptor │ ├── bmAttributes : 1 byte → 0xC0 (Self-powered) or 0x80 (Bus-powered) │ └── bMaxPower : 1 byte → Max power consumption (e.g., 100mA) │ ├── Interface Descriptor │ ├── bLength : 1 byte → 9 bytes │ ├── bDescriptorType : 1 byte → 0x04 (INTERFACE) │ ├── bInterfaceNumber : 1 byte → 0 │ ├── bAlternateSetting : 1 byte → 0 │ ├── bNumEndpoints : 1 byte → 2 (Example: 1 Bulk IN + 1 Bulk OUT) │ ├── bInterfaceClass : 1 byte → 0xFF (Vendor Specific) │ ├── bInterfaceSubClass : 1 byte → 0xFF │ ├── bInterfaceProtocol : 1 byte → 0xFF │ └── iInterface : 1 byte → Index of string descriptor │ ├── Endpoint Descriptor (BULK OUT) │ ├── bLength : 1 byte → 7 bytes │ ├── bDescriptorType : 1 byte → 0x05 (ENDPOINT) │ ├── bEndpointAddress : 1 byte → 0x01 (OUT Address) │ ├── bmAttributes : 1 byte → 0x02 (Transfer Type: BULK) │ ├── wMaxPacketSize : 2 bytes → 64 bytes (Full Speed Max) │ └── bInterval : 1 byte → 0 (Ignored for Bulk) │ └── Endpoint Descriptor (BULK IN) ├── bLength : 1 byte → 7 bytes ├── bDescriptorType : 1 byte → 0x05 (ENDPOINT) ├── bEndpointAddress : 1 byte → 0x81 (IN Address) ├── bmAttributes : 1 byte → 0x02 (Transfer Type: BULK) ├── wMaxPacketSize : 2 bytes → 64 bytes (Full Speed Max) └── bInterval : 1 byte → 0 (Ignored for Bulk)
设备限定描述符 & 其他速度配置描述符
若设备设计为仅支持一种速度模式,则无需实现这两个描述符。
若设备设计为支持全速和高速双速模式时,必须实现这两个描述符以告知主机“如果在另一种速度下,设备会具备什么能力”:
设备限定描述符:结构与设备描述符类似,但不包含 VID/PID 等静态身份信息(这些信息与速度无关)。它描述了设备在“另一速度”下的基本信息(如该速度下的
bMaxPacketSize0)。其他速度配置描述符:描述了设备在“另一速度”下的完整配置树。
设备限定描述符
重点关注设备在另一种速度下的以下关键字段:
bMaxPacketSize0: 定义在另一种速度下端点 0 的最大包长。bNumConfigurations: 定义在另一种速度下支持的配置数量。
示例:
Device Qualifier Descriptor
├── bLength : 1 byte → Size of the descriptor (10 bytes)
├── bDescriptorType : 1 byte → 0x06 (DEVICE_QUALIFIER)
├── bcdUSB : 2 bytes → USB Specification Release Number (0x0200 = USB 2.0)
├── bDeviceClass : 1 byte → 0x00
├── bDeviceSubClass : 1 byte → 0x00
├── bDeviceProtocol : 1 byte → 0x00
├── bMaxPacketSize0 : 1 byte → Max Packet Size for other speed (64 bytes)
├── bNumConfigurations : 1 byte → Number of other-speed configurations (1)
└── bReserved : 1 byte → Reserved for future use (0x00)
备注
如果设备枚举为高速,但没有提供设备限定描述符,Windows 可能会报错或降级运行。
其他速度配置描述符
设备限定描述符仅是入口,主机随后会请求其他速度配置描述符。此时驱动逻辑应执行“交叉返回”策略:
若当前运行在高速模式,主机询问“另一种速度”时,应返回全速的配置描述符集合。
若当前运行在全速模式,主机询问“另一种速度”时,应返回高速的配置描述符集合。
描述符示例
本设备遵循 USB 2.0 标准描述符架构,同时支持 全速和高速,下面是设备描述符拓扑示例:
Device Descriptor
└─ USB Version 2.00 (Vendor Specific)
Configuration Descriptor
└─ Interface (IF 0, Alt 0)
├─ Endpoint: BULK OUT, maxpkt=0x0200 (512 bytes)
├─ Endpoint: BULK IN, maxpkt=0x0200 (512 bytes)
├─ Endpoint: INTR OUT, maxpkt=0x0400 (1024 bytes)
├─ Endpoint: INTR IN, maxpkt=0x0400 (1024 bytes)
├─ Endpoint: ISOC OUT, maxpkt=0x0400 (1024 bytes)
└─ Endpoint: ISOC IN, maxpkt=0x0400 (1024 bytes)
Device Qualifier Descriptor
└─ USB 2.0
Other Speed Configuration Descriptor
└─ Interface (IF 0, Alt 0)
├─ Endpoint: BULK OUT, maxpkt=0x0040 (64 bytes)
├─ Endpoint: BULK IN, maxpkt=0x0040 (64 bytes)
├─ Endpoint: INTR OUT, maxpkt=0x0040 (64 bytes)
├─ Endpoint: INTR IN, maxpkt=0x0040 (64 bytes)
├─ Endpoint: ISOC OUT, maxpkt=0x03FF (1023 bytes)
└─ Endpoint: ISOC IN, maxpkt=0x03FF (1023 bytes)
USB String Table
├── String Descriptor (Index 0: Language ID Array)
├── String Descriptor (Index 1: Manufacturer String)
├── String Descriptor (Index 2: Product String)
└── String Descriptor (Index 3: SerialNumber String)
在逻辑设计上,尽管描述符示例中仅定义了一个接口,但该接口在功能上被划分为两个模块:负责指令交互的控制模块和负责业务负载传输的数据模块。
控制模块
负责指令交互、状态管理及枚举过程。它基于默认的双向控制端点 EP0,因此不占用接口描述符中的端点资源。
枚举与状态上报:响应标准请求,完成设备识别和配置。
Vendor 请求:支持通过控制传输下发自定义指令(如进入测试模式、读写寄存器)。
数据模块
负责全速率的数据吞吐与验证。包含三组端点,分别提供 USB 协议的三种非控制传输类型。
批量传输:1 对 IN/OUT 端点,用于非周期性、对数据完整性要求高的大块数据传输与回环验证。
中断传输:1 对 IN/OUT 端点,用于周期性、低延迟的小数据量传输(如状态轮询)与回环验证。
等时传输:1 对 IN/OUT 端点,用于对实时性要求高、允许少量丢包的流数据(如音频/视频流)传输与回环验证。
类驱动实现
驱动回调机制
USB 类驱动通常分为两层: 核心层 (处理硬件中断、标准枚举、状态机)和 类驱动层 (处理具体的业务逻辑,如 HID, MSC, Vendor)。
备注
上图仅为说明不同层级的回调函数执行的流程,并未列出所有调用场景。
类驱动回调函数
类驱动需要定义一个标准的 usbd_class_driver_t 结构体,作为统一的入口注册到 USB Core 中,是 Core 层通知 Class 层“发生了某事”的主要手段。
get_descriptor: 在枚举过程中回调以获取描述符。
Setup: 收到 EP0 的 SETUP 包时调用,需判断是否为 Vendor 请求。
set_config: 遍历初始化所有端点和资源,并准备接收数据。
clear_config: 遍历释放所有开启的端点和资源。
sof: SOF 中断时被调用,用于时序要求严格的处理逻辑
ep0_data_in: 控制传输 DATA IN 阶段中数据成功发送给主机后调用。
ep0_data_out: 控制传输 DATA OUT 阶段中接收到主机发来的数据后调用。
ep_data_in: 数据成功发送给主机后调用(发送完成中断)。
ep_data_out: 收到主机发来的数据后调用(接收完成中断)。
面向应用层的回调函数
Vendor 类驱动中定义面向应用层的回调结构体 usbd_vendor_cb_t,由用户层实现,一般可选择实现:
API |
描述 |
|---|---|
init |
在类驱动初始化时被调用,用于初始化应用相关的资源 |
deinit |
在类驱动注销时被调用,用于注销应用相关的资源 |
setup |
在控制传输setup或data阶段被调用,用于处理应用相关的控制请求 |
set_config |
在类驱动set_config回调中被调用,用于通知应用层UAC类驱动已就绪 |
status_changed |
在USB连接状态改变时被调用,用于应用层处理USB热插拔事件 |
sof |
收到SOF中断时被调用,用于应用层处理时钟同步 |
transmitted |
在IN传输完成时被调用,用于应用层以异步的方式获取IN传输状态 |
received |
在OUT传输完成时被调用,用于应用层以异步的方式获取OUT传输状态 |
Vendor 类驱动面向应用层的回调示例:
typedef struct {
int(* init)(void);
int(* deinit)(void);
int(* setup)(usb_setup_req_t *req, u8 *buf);
int(* set_config)(void);
int(* bulk_received)(u8 *buf, u32 len);
int(* intr_received)(u8 *buf, u32 len);
int(* isoc_received)(u8 *buf, u32 len);
void(* bulk_transmitted)(u8 status);
void(* intr_transmitted)(u8 status);
void(* isoc_transmitted)(u8 status);
void (*status_changed)(u8 old_status, u8 status);
} usbd_vendor_cb_t;
驱动初始化与资源分配
usbd_vendor_init() 是用于初始化 Vendor 设备的顶层函数。它的核心职责是分配端点资源、配置端点参数以及注册类驱动。
定义设备实例
定义全局唯一的 Vendor 设备实例指针 usbd_vendor_dev,并分别定义指向其内部各个端点结构体和回调函数的指针。
示例:
typedef struct {
usb_setup_req_t ctrl_req; //Save control requeset for control transfer DATA OUT process.
usbd_ep_t ep_bulk_out; //BULK OUT endpoint.
usbd_ep_t ep_bulk_in; //BULK IN endpoint.
// Other endpoint if exit.
usb_dev_t *dev; //USB device core.
usbd_vendor_cb_t *cb; //User callback.
u8 alt_setting; //Record the current interface alternate setting.
} usbd_vendor_dev_t;
/*1. Vendor Device */
static usbd_vendor_dev_t usbd_vendor_dev;
端点参数初始化
设置端点的地址、类型、为其分配缓存区。
DMA 模式下,确保端点传输缓存地址和 Cache line 对齐(如 4 字节或 32 字节对齐,取决于硬件平台)。
为 OUT 端点的 接收 缓存分配至少一个 MPS (Max Packet Size) 的空间。
对于 OUT 端点,指定传输长度。
因为要提前准备接收,所以设置好传输长度,长度不应该超过接收缓存大小。
对于周期性端点(中断/同步),需设置轮询间隔
binterval。
示例:
/* 2. Initialize Endpoint,such as Bulk OUT */
usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
usbd_ep_t *ep_bulk_out = &cdev->ep_bulk_out;
ep_bulk_out->addr = USBD_VENDOR_BULK_OUT_EP;
ep_bulk_out->type = USB_CH_EP_TYPE_BULK;
ep_bulk_out->xfer_len = USBD_VENDOR_BULK_BUF_SIZE;
ep_bulk_out->xfer_buf = (u8 *)usb_os_malloc(USBD_VENDOR_BULK_BUF_SIZE);
执行用户回调初始化
如果注册了用户 init 回调函数,则调用它,这给了上层应用一个机会执行应用层特有的初始化逻辑。
示例:
/* 3. Execute User Init Callback */
if (cb != NULL) {
cdev->cb = cb;
if (cb->init != NULL) {
ret = cb->init();
if (ret != HAL_OK) {
goto exit;
}
}
}
注册 USB 类驱动
调用 usbd_register_class(),将配置好的 usbd_class_driver_t 类驱动结构体向设备核心驱动注册回调函数,使设备准备好响应枚举。
示例:
/* Vendor Class Driver */
static const usbd_class_driver_t usbd_vendor_driver = {
.get_descriptor = usbd_vendor_get_descriptor, /* 获取描述符 (Device, Config, String 等) */
.set_config = usbd_vendor_set_config, /* 主机发送 SET_CONFIGURATION 时调用,初始化端点 */
.clear_config = usbd_vendor_clear_config, /* 复位配置,去初始化端点 */
.setup = usbd_vendor_setup, /* 处理 EP0 上的 SETUP 包 (核心功能) */
.ep_data_in = usbd_vendor_handle_ep_data_in, /* 数据发送完成回调 */
.ep_data_out = usbd_vendor_handle_ep_data_out, /* 数据接收完成回调 */
.status_changed = usbd_vendor_status_changed, /* 设备状态变更 (Suspend/Resume) */
};
/* 4. Register Class Driver */
usbd_register_class(&usbd_vendor_driver);
Buffer 分配错误处理
每次调用 usb_os_malloc() 分配内存后,代码都会立即检查返回值。
如果为 NULL,则判定为内存不足,跳转至对应的错误处理标签实现倒序资源释放。
示例:
/* 5. Critical: Validate memory allocation immediately */
if (ep_bulk_out->xfer_buf == NULL) {
ret = HAL_ERR_MEM;
goto init_exit;
}
/* Reverse Resource Cleanup */
init_exit:
return ret;
描述符实现
usbd_vendor_get_descriptor 回调函数用于响应主机的 GET_DESCRIPTOR 标准请求。它根据请求的描述符类型(Device, Configuration, String 等)和当前连接速度,动态返回对应的描述符。
USB_DESC_TYPE_DEVICE: 返回设备描述符。USB_DESC_TYPE_CONFIGURATION: 根据连接速度返回对应的配置描述符。USB_DESC_TYPE_DEVICE_QUALIFIER: 支持高速设备必须的限定符。USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION: 允许主机查询另一速度下的配置信息。
在驱动代码实现中,建议定义以下核心数组结构,通过宏定义统一管理参数(如 VID/PID、端点地址)。
Device Descriptor: usbd_vendor_dev_desc[]
Config Descriptor (HS): usbd_vendor_hs_config_desc[]
Config Descriptor (FS): usbd_vendor_fs_config_desc[]
Device Qualifier Descriptor: usbd_vendor_device_qualifier_desc[]
String Descriptor: usbd_vendor_lang_id_desc[]
不同速度配置
对于配置描述符,函数会根据连接速度返回对应的描述符数组。同时,必须处理 USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION。
示例:
case USB_DESC_TYPE_CONFIGURATION:
/* Return current speed configuration */
if (speed == USB_SPEED_HIGH) {
desc = (u8 *)usbd_vendor_hs_config_desc;
len = sizeof(usbd_vendor_hs_config_desc);
} else {
desc = (u8 *)usbd_vendor_fs_config_desc;
len = sizeof(usbd_vendor_fs_config_desc);
}
break;
case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
/* Return the OPPOSITE speed configuration */
if (speed == USB_SPEED_HIGH) {
desc = (u8 *)usbd_vendor_fs_config_desc; // Give FS desc
len = sizeof(usbd_vendor_fs_config_desc);
} else {
desc = (u8 *)usbd_vendor_hs_config_desc; // Give HS desc
len = sizeof(usbd_vendor_hs_config_desc);
}
/* Update type field strictly required by spec */
// ... copy and modify logic ...
break;
字符串描述符
根据 wValue 的低字节(索引)返回不同的字符串。
特别注意,产品字符串可能需要区分高速和全速(通常在字符串后缀加上 "(HS)" 以示区别)。
示例:
case USB_DESC_TYPE_STRING:
switch (USB_LOW_BYTE(req->wValue)) {
case USBD_IDX_PRODUCT_STR:
if (speed == USB_SPEED_HIGH) {
len = usbd_get_str_desc(USBD_VENDOR_PROD_HS_STRING, buf);
} else {
len = usbd_get_str_desc(USBD_VENDOR_PROD_FS_STRING, buf);
}
break;
/* ... other strings */
}
break;
厂商请求处理
usbd_vendor_setup 回调函数负责处理控制端点上的 SETUP 数据包。它是 USB 请求分发的中心枢纽,处理标准请求和 Vendor 请求。
标准请求: 处理
SET_INTERFACE,GET_INTERFACE等。Vendor 请求: 自定义协议实现
无数据阶段: 直接在 Setup 回调中处理命令。
有数据阶段 (IN): 调用用户回调填充数据,然后
usbd_ep_transmit()发送给主机。有数据阶段 (OUT): 保存 SETUP 包,调用
usbd_ep_receive()准备接收后续数据。
标准请求处理
处理针对接口的操作。主要是 USB_REQ_SET_INTERFACE 和 USB_REQ_GET_INTERFACE,用于管理接口的 Alternate Setting。
示例:
case USB_REQ_TYPE_STANDARD:
switch (req->bRequest) {
case USB_REQ_SET_INTERFACE:
if (dev->dev_state == USBD_STATE_CONFIGURED) {
/* Update alternate Setting */
cdev->alt_setting = USB_LOW_BYTE(req->wValue);
}
break;
/* Handled other requests */
}
Vendor 请求处理 (SETUP 阶段)
在 usbd_vendor_setup 回调函数中处理 bmRequestType 为 Vendor 类型的请求,这是 Vendor 驱动的核心业务逻辑。
处理流程主要依赖于两个关键参数: 数据传输方向 (USB_REQ_DIR_MASK) 和 数据阶段长度 (wLength)。
- 无数据阶段 (No Data Stage)
若
wLength为 0,表示该请求仅为一个控制命令,不包含后续的数据传输。直接在用户setup回调函数中根据bRequest执行命令逻辑,完成后返回状态即可。
- 有数据阶段 (Data Stage)
若
wLength> 0,则需进一步根据传输方向字段 (USB_REQ_DIR_MASK) 分流处理。数据发送流程:
调用用户
setup回调函数,准备数据并填充至发送缓存调用
usbd_ep_transmit()将数据发送给主机。
数据接收准备流程:
在此阶段代码不应立即处理数据(因为数据尚未到达),而是需要准备接收主机即将在 Data OUT 阶段发送的数据包。
保存请求:将当前的 SETUP 请求完整保存下来(在数据接收完成的
ep0_data_out回调时需要知道之前的 SETUP 参数,才能正确处理收到的数据)。设置传输长度:根据
wLength设置控制 OUT 端点传输长度。启动接收:调用
usbd_ep_receive()启动控制 OUT 端点接收。在数据接收完成的
ep0_data_out回调函数中再对自定义请求进行解析处理。
以下是 usbd_vendor_setup 处理逻辑示例:
case USB_REQ_TYPE_CLASS :
case USB_REQ_TYPE_VENDOR:
/* 1. Check if there is a Data Stage (wLength > 0) */
if (req->wLength) {
/* IN transfer: Device-to-Host */
if ((req->bmRequestType & USB_REQ_DIR_MASK) == USB_D2H) {
/* Call user callback to prepare data in xfer_buf */
ret = cdev->cb->setup(req, ep0_in->xfer_buf);
/* If user callback succeeds, transmit data to Host (Data Stage) */
if (ret == HAL_OK) {
ep0_in->xfer_len = req->wLength;
usbd_ep_transmit(dev, ep0_in);
}
/* OUT transfer: Host-to-Device */
} else {
/* Save the SETUP packet because the data comes in the next stage */
usb_os_memcpy((void *)&cdev->ctrl_req, (void *)req, sizeof(usb_setup_req_t));
/* Prepare EP0 OUT to receive the data payload */
ep0_out->xfer_len = req->wLength;
/* Start receiving data (Data Stage) */
usbd_ep_receive(dev, ep0_out);
}
/* 2. No Data Stage (Control command only) */
} else {
/* Directly handle the command via user callback */
cdev->cb->setup(req, NULL);
}
break;
接口配置
usbd_vendor_set_config 回调函数是设备枚举流程中的关键步骤。当主机发送 SET_CONFIGURATION 标准请求时,设备协议栈调用此函数。标志着设备从“地址状态”进入了“配置状态”,可以开始正常传输数据了。
它的核心职责是根据协商的速度,配置非控制端点参数 (MPS/Interval),初始化端点并立即开启接收。
端点参数配置: 根据连接速度设置非控制端点参数 (MPS/Interval)。
端点初始化: 调用
usbd_ep_init()初始化非控制端点。预接收: 对于所有 OUT 端点,在初始化完成后,立即调用
usbd_ep_receive()以准备接收主机可能发送的数据,否则主机发送的第一个数据包会导致 NAK,甚至超时。
端点参数速度自适应
根据设备当前的连接速度(高速或全速),动态配置非控制端点参数: MPS 和 bInterval。然后调用 usbd_ep_init() 对端点进行初始化。
这是因为高速和全速模式下,USB 规范对包大小和传输间隔的要求不同。
最大包长 (MPS): 高速和全速下,包长通常不同(例如:批量传输最大包长在全速下通常是 64,高速下是 512)。
轮询间隔 (bInterval): 中断和同步传输的间隔时间在不同速度下定义也不同。
示例:
u8 speed = dev->dev_speed;
/* Init INTR IN EP: Select MPS/Interval based on Speed */
ep_intr_in->mps = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_INTR_MPS : USBD_VENDOR_FS_INTR_MPS;
ep_intr_in->binterval = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_INTR_IN_INTERVAL : USBD_VENDOR_FS_INTR_IN_INTERVAL;
usbd_ep_init(dev, ep_intr_in);
/* Init BULK OUT EP: Select MPS based on Speed */
ep_bulk_out->mps = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_BULK_MPS : USBD_VENDOR_FS_BULK_MPS;
usbd_ep_init(dev, ep_bulk_out);
预启动数据接收
对于所有的 OUT 类型端点(主机到设备),在 usbd_ep_init() 初始化完成后,必须立即调用 usbd_ep_receive() 确保在配置完成的那一刻,设备已经做好了接收数据的硬件准备。
这样做是因为 USB 传输是主机主导的。主机配置好设备后,可能会立即发送数据。如果设备端没有预先配置好接收缓冲区并使能接收,主机发来的数据包会被设备回复 NAK(拒绝)或丢弃,导致通信失败。
示例:
/* Critical: Prepare to receive data immediately after init */
ret = usbd_ep_receive(dev, ep_bulk_out);
if (ret != HAL_OK) {
return ret;
}
用户回调
调用用户 set_config 回调函数,这给了上层应用一个机会在 USB 底层配置完成后,执行应用层特有的配置逻辑(比如开启某个 LED,或者复位应用层的 Buffer 指针)。
示例:
cdev->alt_setting = 0U;// Default interface alternate setting.
if (cdev->cb->set_config != NULL) {
cdev->cb->set_config();
}
数据传输处理
数据处理回调
USB 协议栈在硬件传输完成时调用这些函数。它们负责通知应用层传输结果,并为下一次传输做好准备。
usbd_vendor_handle_ep_data_out当非控制端点成功接收到主机发送的数据包时调用。usbd_vendor_handle_ep_data_in当非控制端点向主机发送的数据包传输完成时调用。
数据接收流程
数据接收是异步的,可以在类驱动内部处理,而不用上层主动调用接收 API :
驱动在
usbd_vendor_set_config类驱动回调函数中开启第一次接收准备。当非控制端点成功接收到主机发送的数据包时调用
usbd_vendor_handle_ep_data_out类驱动回调函数。识别端点:检查端点地址以确定数据来自哪个端点。
上层分发:调用应用层注册的
received回调函数,将数据缓冲区指针和接收长度传递给用户。重新开启接收:这是最关键的一步。处理完数据后, 必须 立即调用
usbd_ep_receive()以接收下一包,否则后续数据将无法接收。
示例:
static int usbd_vendor_handle_ep_data_out(usb_dev_t *dev, u8 ep_addr, u32 len) { usbd_vendor_dev_t *cdev = &usbd_vendor_dev; usbd_ep_t *ep_intr_out = &cdev->ep_intr_out; int ret = HAL_OK; /* Example: Interrupt OUT handling */ if (ep_addr == USBD_VENDOR_INTR_OUT_EP) { /* 1. Notify Application Layer */ if (len > 0) { cdev->cb->intr_received(ep_intr_out->xfer_buf, len); } /* 2. CRITICAL: Receive the next packet */ ret = usbd_ep_receive(cdev->dev, ep_intr_out); if (ret != HAL_OK) { return ret; } } return ret; }
数据发送流程
应用层通过数据发送 API 主动向主机发送数据。
示例:
usbd_vendor_transmit_intr_data()发送中断数据。int usbd_vendor_transmit_intr_data(u8 *buf, u32 len) { int ret = HAL_OK; usbd_vendor_dev_t *cdev = &usbd_vendor_dev; usb_dev_t *dev = cdev->dev; usbd_ep_t *ep_intr_in = &cdev->ep_intr_in; /* Check if USB device has been enumerated. */ if (!dev->is_ready) { return HAL_ERR_HW; } /* Interrupt transmission does not support high-speed and high-bandwidth yet. */ if (len > ep_intr_in->mps) { len = ep_intr_in->mps; } /* Check if previous transfer is done. */ if (ep_intr_in->xfer_state == 0U) { /* Set Busy */ ep_intr_in->xfer_state = 1U; /* Copy data to dedicated buffer (handles DMA alignment) */ usb_os_memcpy((void *)ep_intr_in->xfer_buf, (void *)buf, len); ep_intr_in->xfer_len = len; /* Trigger Hardware Transmission */ ret = usbd_ep_transmit(dev, ep_intr_in); } else { ret = HAL_BUSY; } return ret; }
备注
长度检查:当前中断传输暂不支持高速高带宽,如果传入的 len 大于端点的最大包长,则强制将发送长度设置为 MPS。因此大块数据建议使用批量传输或在上层手动分包。
数据拷贝:将用户数据拷贝到端点的传输缓存中。USB DMA 要求数据缓冲区地址和 Cache line 对齐,这一步拷贝操作是为了防止上层缓冲区未对齐,如果传入的缓冲区地址对齐的话,该步骤可省略。
usbd_vendor_transmit_isoc_data()发送同步数据,逻辑与中断传输类似,但由于 ISOC 无重传机制,所以不需要检查传输状态。usbd_vendor_transmit_bulk_data()发送批量数据,逻辑与中断传输类似,但发送长度不会根据 MPS 拆包,只需要不大于缓存区大小即可。
当设备向主机发送的数据包传输完成时调用
usbd_vendor_handle_ep_data_in类驱动回调函数。清除忙状态:重置对应端点的传输状态,这允许应用层再次调用发送函数。
传输完成通知:如果应用层需要知道发送何时结束(例如释放传输缓存),调用用户
transmitted回调函数。
示例:
static int usbd_vendor_handle_ep_data_in(usb_dev_t *dev, u8 ep_addr, u8 status) { usbd_vendor_dev_t *cdev = &usbd_vendor_dev; usbd_vendor_cb_t *cb = cdev->cb; usbd_ep_t *ep_intr_in = &cdev->ep_intr_in; if (ep_addr == USBD_VENDOR_INTR_IN_EP) { /* 1. Clear Busy Flag */ ep_intr_in->xfer_state = 0U; /* 2. Notify Application */ if (cb->intr_transmitted != NULL) { cb->intr_transmitted(status); } } return HAL_OK; }
热插拔处理
驱动必须健壮地处理连接状态变化,一般直接将状态传递至应用层以支持热插拔。 详细请参考 设备连接状态检测。
/**
* @brief USB attach status change
* @param dev: USB device instance
* @param old_status: USB old attach status
* @param status: USB USB attach status
* @retval void
*/
static void usbd_vendor_status_changed(usb_dev_t *dev, u8 old_status, u8 status)
{
usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
UNUSED(dev);
if (cdev->cb->status_changed) {
cdev->cb->status_changed(old_status, status);
}
}
驱动卸载与资源释放
usbd_vendor_deinit() 是用于卸载 USB Vendor 设备类驱动的顶层函数,当设备断开或主机切换配置时调用。
它的核心职责是确保传输安全结束、清理用户层资源、注销类驱动并释放所有端点的缓冲区内存。
等待传输完成
在开始卸载前,首先检查关键端点(如中断端点)的忙碌状态 is_busy。如果有未完成的传输,驱动将进入等待循环,防止在数据传输过程中强制释放资源导致异常。
示例:
/* 1. Wait for Transfer Completion */
u8 is_busy = ep_intr_in->is_busy;
while (is_busy) {
usb_os_delay_us(100);
}
执行用户回调清理
如果注册了用户 deinit 回调函数,则优先调用它,允许上层应用清理其私有数据或逻辑,随后将回调指针置空。
示例:
/* 2. Execute User Deinit Callback */
if (cdev->cb != NULL) {
if (cdev->cb->deinit != NULL) {
cdev->cb->deinit();
}
cdev->cb = NULL;
}
注销 USB 类驱动
调用 usbd_unregister_class(),将 Vendor 驱动从 USB 核心栈中移除。此操作后,设备将不再响应相关的 USB 事件。
示例:
/* 3. Unregister Class Driver */
usbd_unregister_class();
释放资源
遍历所有打开的端点,调用 usb_os_mfree() 释放端点缓冲区内存,并将指针安全地重置为 NULL,防止野指针问题。
示例:
/* 4. Free Endpoint Buffers */
if (ep_bulk_in->xfer_buf != NULL) {
usb_os_mfree(ep_bulk_in->xfer_buf);
ep_bulk_in->xfer_buf = NULL;
}
/* Repeat usb_os_mfree for other endpoints... */
API 说明
应用示例
应用设计
本节详细介绍 Vendor 应用的完整开发流程,涵盖驱动加载、热插拔管理及自定义数据回显(Loopback)处理等核心环节。
加载驱动
使用 Vendor 驱动前,需定义配置结构体并注册回调函数,随后调用初始化接口加载 USB 设备核心驱动及 Vendor 类驱动。
步骤说明:
硬件配置:配置 USB 速度模式及中断优先级等。
回调注册:定义用户回调结构体
usbd_vendor_cb_t,挂载各个阶段的处理函数。加载核心驱动:调用
usbd_init()加载 USB 核心驱动。加载类驱动:调用
usbd_vendor_init()加载 vendor 类驱动。
示例:
static usbd_config_t vendor_cfg = {
.speed = CONFIG_USBD_VENDOR_SPEED,
.isr_priority = INT_PRI_MIDDLE,
};
static usbd_vendor_cb_t vendor_cb = {
.init = vendor_cb_init, /* USB init callback */
.deinit = vendor_cb_deinit, /* USB deinit callback */
.setup = vendor_cb_setup, /* USB setup callback */
.set_config = vendor_cb_set_config, /* USB set_config callback */
.bulk_received = vendor_cb_bulk_received, /* USB BULK OUT received callback */
.isoc_received = vendor_cb_isoc_received, /* USB ISOC OUT received callback */
.intr_received = vendor_cb_intr_received, /* USB INTR OUT received callback */
.status_changed = vendor_cb_status_changed, /* USB status change callback */
};
int ret = 0;
/**
* Initialize USB device core driver with configuration.
* param[in] cfg: USB device configuration.
* return 0 on success, non-zero on failure.
*/
ret = usbd_init(&vendor_cfg);
if (ret != HAL_OK) {
return;
}
/**
* Initialize class driver with application callback handler.
* param[in] cb: Pointer to the user-defined callback structure.
* return 0 on success, non-zero on failure.
*/
ret = usbd_vendor_init(&vendor_cb);
if (ret != HAL_OK) {
/**
* Deinitialize USB device core driver.
* return 0 on success, non-zero on failure.
*/
usbd_deinit();
return;
}
热插拔事件处理
通过注册 status_changed 回调函数来监听 USB 连接状态变化(连接/断开)。
参考 设备连接状态检测, 获取更多细节。
备注
建议使用信号量(Semaphore)通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。
示例:
static u8 vendor_attach_status;
static rtos_sema_t vendor_attach_status_changed_sema;
/* USB status change callback */
static usbd_vendor_cb_t vendor_cb = {
.status_changed = vendor_cb_status_changed
};
/* Callback executed in ISR context */
static void vendor_cb_status_changed(u8 old_status, u8 status)
{
RTK_LOGS(TAG, RTK_LOG_INFO, "Status change: %d -> %d \n", old_status, status);
vendor_attach_status = status;
rtos_sema_give(vendor_attach_status_changed_sema);
}
/* Thread handling the state machine */
static void vendor_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
if (rtos_sema_take(vendor_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (vendor_attach_status == USBD_ATTACH_STATUS_DETACHED) {
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
/* 1. Clean up resources */
usbd_vendor_deinit();
/* 2. De-initialize USB core */
ret = usbd_deinit();
if (ret != 0) {
break;
}
RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());
/* 3. Re-initialize for next connection */
ret = usbd_init(&vendor_cfg);
if (ret != 0) {
break;
}
ret = usbd_vendor_init(&vendor_cb);
if (ret != 0) {
usbd_deinit();
break;
}
} else if (vendor_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_ERROR, "Hotplug thread fail\n");
rtos_task_delete(NULL);
}
Vendor 请求处理
通过注册用户 setup 回调函数,处理 bRequest 对应的自定义请求。
控制传输的 DATA IN 阶段,在用户
setup回调函数,准备数据并填充至发送 Buffer。控制传输的 NO DATA/DATA OUT 阶段,在用户
setup回调函数,可解析获得主机参数/命令。
示例:
int vendor_cb_setup(usb_setup_req_t *req, u8 *buf)
{
switch (req->bRequest) {
case 0x01: /* Vendor request 1 */
/* DATA IN stage: prepare send data */
if ((bmRequestType & USB_REQ_DIR_MASK) == USB_D2H) {
prepare_send_buffer(buf);
}
/* NO DATA/DATA OUT stage: parse host parameters */
else {
parse_host_parameters(req, buf);
}
break;
case 0x02: /* Vendor request 2 */
/* Handle request */
break;
default:/* Unknown request */
return -1;
}
return 0;
}
数据收发处理
当 Vendor 设备枚举成功后,驱动支持 同步 与 异步 两种数据回传处理模式。
以下逻辑适用于 BULK、INTR 和 ISOC 类型的端点,此处以 BULK 传输为例。
/* In callback: Echo immediately */
static int vendor_cb_bulk_received(u8 *buf, u32 len)
{
/* Directly transmit received data back to host */
return usbd_vendor_transmit_bulk_data(buf, len);
}
/* In callback: Buffer data & Trigger Task */
static int vendor_cb_bulk_received(u8 *buf, u32 len)
{
// 1. Store buffer pointer and length
vendor_bulk_tx_buf = buf;
vendor_bulk_tx_len = len;
// 2. Signal the transfer thread
rtos_sema_give(vendor_bulk_async_xfer_sema);
return 0;
}
/* In task thread: Handle Transmission */
static void vendor_bulk_xfer_thread(void *param)
{
UNUSED(param);
for (;;) {
// 1. Wait for signal from callback
if (rtos_sema_take(vendor_bulk_async_xfer_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
// 2. Transmit data if buffer is valid
if ((vendor_bulk_tx_buf != NULL) && (vendor_bulk_tx_len != 0)) {
usbd_vendor_transmit_bulk_data(vendor_bulk_tx_buf, vendor_bulk_tx_len);
}
}
}
rtos_task_delete(NULL);
}
/* Task Creation (Initialization) */
void vendor_bulk_xfer_init(void)
{
/* The priority of transfer thread shall be lower than USB isr priority */
int ret = rtos_task_create(&bulk_async_xfer_task, "vendor_bulk_xfer_thread",
vendor_bulk_xfer_thread, NULL, 1024U,
CONFIG_USBD_VENDOR_XFER_THREAD_PRIORITY);
if (ret != RTK_SUCCESS) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Create bulk thread fail\n");
/* Handle error */
}
}
备注
完整的数据收发逻辑请参考 SDK 示例代码: {SDK}/component/example/usb/usbd_vendor/example_usbd_vendor.c。
卸载驱动
在不再需要 USB 功能或系统关机时,按加载的反序释放资源。
示例:
/* Deinitialize VENDOR class driver. */
usbd_vendor_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();
运行方式
该示例代码路径: {SDK}/component/example/usb/usbd_vendor,可为开发者设计自定义 Vendor 产品提供完整的参考方案。
本节介绍了一个完整的 Vendor 设备回显示例,该示例演示了如何通过 Vendor 协议栈实现与主机之间的自定义数据双向通信。
配置与编译
Menuconfig 配置
在
amebaxxx_gcc_project目录下,输入./menuconfig.py,按下面步骤选择USBD VENDOR, 保存退出。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Device) ---> [*] Vendor编译与烧录
执行编译命令,并烧录生成的
Image文件至开发板。cd amebaxxx_gcc_project ./build.py -a usbd_vendor
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[VND-I] USBD vendor demo start
连接主机
使用 USB 线缆将开发板连接至 PC(或 Ameba Vendor 主机)。
数据通信测试
方法 1:使用另一块开发板,运行本 USB 协议栈的自定义主机方案,连接后自动测试。详见 自定义主机方案。
方法 2:在 PC 端使用 USB 测试工具(如 Cypress Control Center)、编写 LibUSB / PyUSB 脚本或编写特定的上位机软件进行测试。
设备描述符定制
描述符概述
USB 设备通过描述符定义设备功能,USB 描述符的拓扑结构示例如下图所示:
其中:
一个 USB 设备只有一个设备描述符(Device Descriptor)
一个 USB 设备可以有多个(由设备描述符的 bNumConfigurations 指定)配置描述符(Configuration Descriptor),但同时只允许一个配置描述符生效
一个配置描述符下可以有多个(由配置描述符的 bNumInterfaces 指定)接口描述符(Interface Descriptor),且多个接口描述符可以同时生效
一个接口描述符下可以有多个(由接口描述符的 bNumEndPoints 指定)端点描述符(Endpoint Descriptor),且多个端点描述符可以同时生效
另外,还有其它一些常用的描述符:
设备描述符、配置描述符和接口描述符都有一些相关的功能描述,这些描述定义为字符描述符(String Descriptor)
对于需要同时支持高速和全速模式的设备,还需要同时定义设备限定描述符(Device Qualifier Descriptor)和其它速率配置描述符(Other Speed Configuration Descriptor)
对于复合设备,还需要在多个类接口描述符之前,增加一个接口关联描述符(Interface Association Descriptor,IAD)
某些 USB 设备类需要定义与设备类相关的特定描述符,比如 HID 设备需要定义报表描述符(Report Descriptor)
Device Descriptor
└─ Identifies basic device information (USB version, class, VID/PID, EP0 MPS, etc)
Configuration Descriptor
└─ Contains total length of the entire configuration(power supply information, interface count, etc)
Device Qualifier Descriptor
└─ Provides device class/EP0 MPS/config count for the other speed (HS↔FS)
Other Speed Configuration Descriptor
└─ Mirrors the configuration at the other speed (interfaces/endpoints/attributes)
String Descriptor
└─ Provides language IDs and textual identifiers (Manufacturer/Product, etc)
Device Descriptor
└─ Identifies basic device information (USB version, class, VID/PID, EP0 MPS, etc)
Configuration Descriptor
└─ Contains total length of the entire configuration(power supply information, interface count, etc)
String Descriptor
└─ Provides language IDs and textual identifiers (Manufacturer/Product, etc)
备注
具体的描述符定义,请参考 USB 规范。
描述符数据结构
一般情况下,USB 主机会按如下顺序获取 USB 设备的描述符信息:
获取设备描述符,USB 设备需要返回设备描述符信息
获取配置描述符,USB 设备需要返回描述符拓扑结构中当前有效的配置描述符及其以下节点(包含接口描述符和端点描述符)的全部描述符信息
获取配置描述符对应的字符串描述符,USB 设备需要根据主机控制请求中的字符串类型信息,返回当前有效配置描述符下对应的字符串描述符信息,包含语言 ID、串号、厂商、产品等字符串信息
另外,USB 主机可能还会尝试获取 USB 设备的以下描述符信息:
获取设备限定描述符,如果 USB 设备有支持,需要返回设备限定描述符信息
获取其它速率配置描述符,如果 USB 设备有支持,需要返回描述符拓扑结构中当前有效的配置描述符对应的其它速率配置描述符及其节点(包含接口描述符和端点描述符)的全部描述符信息
获取其它速率配置描述符对应的字符串描述符,USB 设备需要根据主机控制请求中的字符串类型信息,返回当前有效配置描述符对应的其它速率配置描述符下对应的字符串描述符信息,包含语言 ID、串号、厂商、产品等字符串信息
获取设备类相关的描述符,USB 设备需要根据设备类规范返回相关的描述符信息
获取 USB 主机相关的描述符,比如 Windows 主机会尝试获取索引值为 238(对应 16 进制数字 0xEE)的 Microsoft OS 字符串描述符(Microsoft OS String Descriptor)
备注
如果 USB 设备不支持某些描述符,在收到 USB 主机端相关请求时,需要返回 STALL 状态,复合规范的 STALL 并不会影响 USB 主机与设备间的通信,也不会影响 USB 设备的功能。
鉴于上述 USB 主机获取 USB 设备描述符的行为,建议 USB 设备定义以下几个独立数组用于存储 USB 主机需要获取的描述符信息:
设备描述符,仅包含设备描述符信息
配置描述符,每个配置描述符分别定义,包含其自身及其以下节点(包含接口描述符和端点描述符)的全部描述符信息
字符串描述符,包含语言 ID 在内的每个字符串描述符分别定义
设备限定描述符,仅包含设备限定描述符信息
其它速率配置描述符,每个其它速率配置描述符分别定义,包含其自身及其以下节点(包含接口描述符和端点描述符)的全部描述符信息;为节省存储资源,也可与配置描述符共用一个数组,运行时动态替换描述符类型数据
设备类相关的描述符,按设备类规范定义
USB 主机相关的描述符,按 USB 主机相关规范和应用需求定义
描述符定制项
USB 设备协议栈中定义的所有设备类,其全部描述符信息均开源并允许开发者根据设计需求定制。 但为兼容性,对于基于 USB 规范的标准类设备,一般仅建议对以下描述符信息进行定制:
VID 与 PID
开发者可以直接使用 USB 协议栈默认使用的 Realtek VID 和 PID,如需自定义,需要注册成为 USB-IF 成员(参考 https://www.usb.org/members),然后按照 USB-IF 的流程申请 VID 和 PID。 开发者如果需要使用 Realtek VID,并自定义 PID,请与 Realtek FAE 联系。
备注
使用 Realtek VID 和 PID, 并不意味着产品符合 USB 规范,仍需要进行 USB 认证。
字符串描述符
字符串描述符是可选的,设备描述符、配置描述符和接口描述符都有字符串描述符索引字段,如果不支持字符串描述符,描述符索引为 0。可定义非 0 的索引值并定义对应的字符串描述符:
Device Descriptor
│ iManufacturer (Manufacturer String Index)
│ iProduct (Product String Index)
│ iSerialNumber (SerialNumber String Index)
│
├── Configuration Descriptor
│ ├── iConfiguration (Configuration String Index)
│ └── Interface Descriptor
│ └── iInterface (Interface String Index)
│
└── USB String Table
├── String Descriptor (Index 0, Must be read first)
├── String Descriptor (Index 1)
├── String Descriptor (Index 2)
├── String Descriptor (Index 3)
│ ...
字符串描述符结构
主机在枚举初期读取设备描述符,其中包含了三个字段:iManufacturer/iProduct/iSerialNumber。 这些字段的值只是索引号,当对于某些设备不没有与之对应的字符串时,开发者只需要将这些字符串索引的值设为 0 即可。
当设备插入电脑时,操作系统会读取这些字符串并在设备管理器或弹窗中显示,比如依次设为 1, 2, 3。
索引 1 (iManufacturer): 厂商名称。例如: "Realtek Semiconductor Corp."
索引 2 (iProduct): 产品名称。例如: "Realtek USB Controller"
索引 3 (iSerialNumber): 序列号。例如: "00E04C000001"
USB String Table
│
├── String Descriptor (Index 0: Must be read first!)
│ └── LANGID Code Array (Language ID Array)
│ └── 0x0409 (English - United States)
│
├── String Descriptor (Index 1: Pointed by `iManufacturer` index)
│ └── "Realtek Semiconductor Corp." (Unicode)
│
├── String Descriptor (Index 2: Pointed by the `iProduct` index)
│ └── "Realtek USB Controller" (Unicode)
│
├── String Descriptor (Index 3: Pointed by the `iSerialNumber` index)
│ └── "00E04C000001" (Unicode)
│
└── String Descriptor (Index 4/5/6/...: Optional/Customizable)
字符串描述符(索引 0)
String Descriptor (Special: Language ID) │ bLength (Descriptor length) │ bDescriptorType (String descriptor type. Fixed to 0x03) └── LANGID Code Array │ wLANGUAGEID(0) (First Language ID) │ wLANGUAGEID(1) (Second Language ID) │ ...地位:它是“字符串描述符的描述符”,主机在获取任何具体字符串之前,必须优先读取 Index 0,以确认设备支持的语言类型。
特殊性:它不包含字符串,而是包含设备支持的语言 ID 列表。当主机向设备发送获取字符串索引为 0 时,这并不表示要获取索引为 0 的字符串,而是表示获取设备支持的 语言 ID (LANGID) 列表。
参数值:一般情况下,语言 ID 为通常是 0x0409 美式英语。如当为中文时,可以将语言 ID 设为 0x0804。
备注
即使开发者的设备只在某国使用,也建议支持 0x0409,因为这是 Windows/Linux 默认尝试的 ID。
字符串描述符(索引 1, 2, 3)
String Descriptor │ bLength (Descriptor length) │ bDescriptorType (String descriptor type. Fixed to 0x03) └── bString (UNICODE encoded string)
bString字符串必须是 UNICODE (UTF-16LE) 编码,每个字符占 2 字节。例如 'A' -> 0x41, 0x00。备注
USB 规范要求字符串描述符使用 UNICODE 编码,设备核心驱动提供了
usbd_get_str_desc()函数用于将 ASCII 编码的字符串转为 UNICODE 编码的字符串描述符,方便开发者用 ASCII 字符串来维护字符串信息。协议栈目前仅提供了英文字符串描述符示例,如需支持其它语言类型,请参考 USB 规范定义所需的语言 ID 描述符及对应的字符串描述符
其他自定义字符串(索引 4, 5, 6...)
只要不与标准冲突,可以自定义索引为 4, 5, 6...的字符串。
端点描述符
对于一般应用,端点描述符中的以下字段支持自定义:
bEndpointAddress:端点地址wMaxPacketSize:最大包长,一般建议按 USB 规范定义的最大值(不高于 SoC 硬件支持的最大值)来设定,除非有特别需求,不建议随意修改bInterval:传输间隔
备注
协议栈保证默认提供的端点配置对所有 SoC 可用,如需调整,请参考 硬件配置,务必熟知相关 USB 规范和 SoC 硬件限制后再做谨慎调整,否则可能导致兼容性问题甚至枚举失败。
设备连接状态检测
对于自供电 USB 设备,当 USB 连接状态改变时,设备类驱动及应用层需要及时获悉相关事件并做对应处理,从而达成支持热插拔、降低功耗等目的。
USB 设备核心驱动提供了如下 API 用于设备类驱动及应用层检测 USB 连接状态:
usbd_class_driver_t中的status_changed:用于异步获取设备连接状态改变事件的 callback。usbd_get_status():用于获取设备当前连接状态。usbd_get_bus_status():用于获取 USB 总线 DP/DM/suspend 状态。
上述 API 需要在 USB 上电之后,访问 USB 相关寄存器才能获得有效数据,因此需要在加载 USB 核心驱动之后才能调用。 如果要在 USB 未上电时就能获取 USB 状态信息,需要借助外部电路的支持,例如通过 GPIO 中断来检测 VBUS 的上电掉电事件,具体请参考硬件设计手册(Hardware Design Guide)中的 USB 插入检测电路参考设计。
借助上述软硬件检测方案,可以分辨如下 USB 设备连接状态改变事件:
事件 |
usbd_get_status |
status_changed |
VBUS GPIO中断 |
VBUS状态 |
|---|---|---|---|---|
设备复位(保持与主机连接) |
ATTACHED |
INIT -> ATTACHED |
N/A |
ON |
设备复位(保持连接断开) |
INIT |
N/A |
N/A |
OFF |
设备复位(保持与充电器连接) |
INIT |
N/A |
N/A |
ON |
连接到主机 |
ATTACHED |
DETACHED -> ATTACHED |
Y (上升沿) |
OFF -> ON |
从主机断开 |
DETACHED |
ATTACHED -> DETACHED |
Y (下降沿) |
ON -> OFF |
连接到充电器 |
INIT or DETACHED |
N/A |
Y (上升沿) |
OFF -> ON |
从充电器断开 |
INIT or DETACHED |
N/A |
Y (下降沿) |
ON -> OFF |
连接挂起(suspend) |
DETACHED |
ATTACHED -> DETACHED |
与主机设置相关 |
与主机设置相关 |
连接恢复(resume) |
ATTACHED |
DETACHED -> ATTACHED |
与主机设置相关 |
与主机设置相关 |
两个典型的应用场景:
基于软件的解决方案
如果对功耗要求不严苛,允许 USB 电源常开,可通过 status_changed,只借助软件的方式获取设备连接状态改变事件,一般用法如下:
检测到与 USB 主机断开连接时,终止通信,注销并重新注册 USB 设备,等待下次与 USB 主机连接
检测到与 USB 主机连接时,恢复通信
具体处理流程参考下图:
基于硬件辅助的解决方案
如果对功耗要求严苛,希望在 USB 设备与主机未连接时能够关闭 USB 相关电源,可通过 GPIO 中断来检测 VBUS 的上电掉电事件,一般用法如下:
检测到与 USB 主机断开连接或者连接到 USB 充电器时,注销 USB 设备,以达省电目的
检测到与 USB 主机连接时,注册 USB 设备,使能 USB 通信
具体处理流程参考下图: