自定义设备方案

概述

USB Vendor Class (Vendor Specific) 利用 USB 规范的开放性,允许厂商自定义的私有协议或请求,支持开发者灵活定义端点配置与传输类型。 Ameba 基于底层 USB 协议栈实现 Vendor 设备功能,建立高自由度的专属数据通道。 主机可通过专用驱动或通用库(如 WinUSB)与 Ameba 进行私有命令集与特定格式数据的交互,实现了超越标准设备类限制的定制化控制与功能扩展。

Ameba USB Vendor 设备

特性

  • 支持热插拔

  • 支持描述符全定制

  • 支持配置速度模式等参数

  • 支持批量/中断/等时传输数据的同步或异步回传操作

  • 支持与 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 第一个字节 bmRequestTypebit[6:5] 决定是否为 Vendor 请求:

Bit Position

Field Name

Description & Values

Bit 7

Direction

  • 0: Host to Device (OUT)

  • 1: Device to Host (IN)

Bit 6..5

Request Type

  • 00: Standard

  • 01: Class

  • 10: Vendor

  • 11: Reserved

Bit 4..0

Recipient

  • 00000: Device

  • 00001: Interface

  • 00010: Endpoint

  • 00011: Other

在开发 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): 通过批量端点持续高效地向主机上报数据。

../../../_images/usb_vendor_app_perform_zh.svg

类驱动

驱动架构

本驱动实现了一个通用的 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 字段的物理时间单位取决于当前速度,设置错误会导致轮询频率异常。

bInterval 差异对比与计算公式

USB 模式

时间单位

取值范围

实际时间计算公式

全速 (Full-Speed)

1ms (帧)

1 – 255

Interval = 1ms * bInterval

高速 (High-Speed)

125μs (微帧)

1 – 16

Interval = 125us * 2^(bInterval-1)

备注

示例:设定 1ms 轮询间隔

  • 全速模式: bInterval1 (即 1ms * 1 = 1ms)

  • 高速模式: bInterval4 (即 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)

设备限定描述符 & 其他速度配置描述符

  • 若设备设计为仅支持一种速度模式,则无需实现这两个描述符。

  • 若设备设计为支持全速和高速双速模式时,必须实现这两个描述符以告知主机“如果在另一种速度下,设备会具备什么能力”:

    • 设备限定描述符:结构与设备描述符类似,但不包含 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)。

../../../_images/usb_device_vendor_callback.png

备注

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

类驱动回调函数

类驱动需要定义一个标准的 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_INTERFACEUSB_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 回调函数中处理 bmRequestTypeVendor 类型的请求,这是 Vendor 驱动的核心业务逻辑。

处理流程主要依赖于两个关键参数: 数据传输方向 (USB_REQ_DIR_MASK) 和 数据阶段长度 (wLength)。

  • 无数据阶段 (No Data Stage)

    wLength 为 0,表示该请求仅为一个控制命令,不包含后续的数据传输。直接在用户 setup 回调函数中根据 bRequest 执行命令逻辑,完成后返回状态即可。

  • 有数据阶段 (Data Stage)

    wLength > 0,则需进一步根据传输方向字段 (USB_REQ_DIR_MASK) 分流处理。

    Device-to-Host (IN):

    数据发送流程:

    • 调用用户 setup 回调函数,准备数据并填充至发送缓存

    • 调用 usbd_ep_transmit() 将数据发送给主机。

以下是 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 说明

驱动 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 传输为例。

Sync Mode:
/* 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);
}

备注

完整的数据收发逻辑请参考 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 脚本或编写特定的上位机软件进行测试。