自定义设备方案
概述
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 |
Interval = 1ms * bInterval |
高速 (High-Speed) |
125μs (微帧) |
1 – 16 |
Interval = 125us * 2^(bInterval-1) |
备注
示例:设定 1ms 轮询间隔
全速模式:
bInterval填 1 (即 1ms * 1 = 1ms)高速模式:
bInterval填 4 (即 125us * 2^(4-1) = 1000us = 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) 分流处理。Host-to-Device (OUT):数据接收准备流程:
在此阶段代码不应立即处理数据(因为数据尚未到达),而是需要准备接收主机即将在 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}/example/usb/usbd_vendor/example_usbd_vendor.c。
卸载驱动
在不再需要 USB 功能或系统关机时,按加载的反序释放资源。
示例:
/* Deinitialize VENDOR class driver. */
usbd_vendor_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();
运行方式
该示例代码路径: {SDK}/example/usb/usbd_vendor,可为开发者设计自定义 Vendor 产品提供完整的参考方案。
本节介绍了一个完整的 Vendor 设备回显示例,该示例演示了如何通过 Vendor 协议栈实现与主机之间的自定义数据双向通信。
配置与编译
编译与烧录
在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程,然后将生成的
Image文件烧录至开发板。# Initialize environment (required for every new terminal) source env.sh or env.bat(Windows system) # Select Target SoC (replace xxx with your specific SoCs) ameba.py soc xxx ameba.py build -a usbd_vendor -p
Menuconfig 配置确认
若编译失败,请执行
ameba.py menuconfig,确认已选择USBD VENDOR。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Device) ---> [*] Vendor
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[VND-I] USBD vendor demo start
连接主机
使用 USB 线缆将开发板连接至 PC(或 Ameba Vendor 主机)。
数据通信测试
方法 1:使用另一块开发板,运行本 USB 协议栈的自定义主机方案,连接后自动测试。详见 自定义主机方案 。
方法 2:在 PC 端使用 USB 测试工具(如 Cypress Control Center)、编写 LibUSB / PyUSB 脚本或编写特定的上位机软件进行测试。