自定义主机方案

概述

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

Ameba USB Vendor 主机

特性

  • 支持热插拔

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

  • 支持批量/中断/等时三种传输类型

  • 支持数据完整性校验测试和数据回传测试

  • 与 Vendor 设备应用示例适配,具体参考 自定义设备方案

应用场景

作为具备高度可定制性的 USB 主机,Ameba 可通过 USB 接口建立主机与设备间专属的控制与通信通道,突破标准类的功能限制,实现以下典型应用,例如:

  • 自定义协议通信:定义私有命令集与数据帧格式,用于设备与主机间的专用通信(如参数配置、故障诊断、状态查询)。

  • 安全固件升级:利用自定义 Vendor 协议传输固件,实现私有的安全升级机制。

  • 特殊外设控制:控制非标准 USB 设备(如特定传感器、加密狗或自定义 IO 扩展板)。

协议简介

请参考自定义设备方案的对应章节: 协议简介

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

类驱动

驱动架构

协议栈提供了一个 Vendor Class(厂商自定义类)主机驱动的设计参考示例。 不同于 HID 或 MSC 等标准类驱动,它不绑定特定的行业协议,而是提供了一个灵活的基础框架,赋予开发者极大的自由度来定制私有的 USB 通信协议。

驱动架构采用分层设计,自上而下包含三个核心层次:

  • 用户应用层:实现具体的业务逻辑。用户仅需通过注册回调结构体即可与驱动交互,利用封装好的接口发送或接收数据,无需关心底层 USB 协议细节。

  • Vendor 类驱动层:(本驱动示例核心实现)建立在 USB 主机核心栈之上,作为中间层连接底层硬件与上层应用。负责管理通道资源、维护状态机以及处理各类数据传输等。是开发者进行二次开发的主要基础。

  • 核心驱动层: 负责底层 USB 硬件中断处理、总线枚举、传输请求 (URB) 调度及标准协议栈管理。

类驱动实现

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

  • 主机类驱动回调 API:类驱动通过注册标准的 usbh_class_driver_t 结构体,实现与底层 USB Core 进行交互。

  • 面向应用的回调 API:通过应用层在初始化时注册的 usbh_vendor_cb_t ,向上层应用提供异步事件通知(如连接、断开、数据接收)。

  • 面向应用的 API:提供给应用层调用的功能接口。调用后驱动会切换内部状态机的状态,开启数据传输的调度。

驱动回调机制

../../../_images/usb_host_vendor_callback.png

备注

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

类驱动回调函数

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

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

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

  • detach: 设备断开时调用。

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

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

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

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

面向应用层的回调函数

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

API

描述

init

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

deinit

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

attach

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

detach

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

setup

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

receive

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

transmit

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

加载与卸载类驱动

Init:

usbh_vendor_init() 是用于加载 Vendor 类驱动的顶层函数。

  • 执行用户回调初始化:如果注册了用户 init 回调函数,则优先调用它,这给了上层应用一个机会执行应用层特有的初始化逻辑。

  • 注册类驱动:调用 usbh_register_class(),将定义好的 usbh_class_driver_t 类驱动实例注册到向主机核心驱动,使主机准备好响应枚举。

示例:

typedef struct {
    usbh_vendor_xfer_t bulk_in_xfer; //BULK IN pipe xfer.
    // Other pipe xfer if exit.
    usbh_vendor_cb_t *cb;           //User callback.
    usb_host_t *host;               //USB host core.
    usbh_vendor_state_t state;      //Class Driver state.
} usbh_vendor_host_t;

/* Define Vendor host */
static usbh_vendor_host_t usbh_vendor_host;
/* Define Vendor Class Driver */
static usbh_class_driver_t usbh_vendor_driver = {
    .id_table = vendor_devs,
    .attach = usbh_vendor_attach,
    .detach = usbh_vendor_detach,
    .setup = usbh_vendor_setup,
    .process = usbh_vendor_process,
    .sof = usbh_vendor_sof,
};

int usbh_vendor_init(usbh_vendor_cb_t *cb)
{
    usbh_vendor_host_t *vendor = &usbh_vendor_host;
    /* 1. Execute User Init Callback */
    if (cb != NULL) {
        vendor->cb = cb;
        if (cb->init != NULL) {
            ret = cb->init();
            if (ret != HAL_OK) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "User init err %d\n", ret);
                return ret;
            }
        }
    }
    /* 2. Register Class Driver */
    usbh_register_class(&usbh_vendor_driver);
    return HAL_OK;
}

连接与断连处理

设备的连接与断开由 USB Core 自动检测,并调用类驱动的对应回调进行资源管理。

Attach:

设备连接

当 Core 层枚举到匹配 usbh_dev_id_t 的 Vendor 设备时, usbh_vendor_attach 回调被调用。该阶段的主要职责是:

  • 获得并解析对应的接口描述符。

  • 根据端点描述符,通过 usbh_open_pipe() 打开对应通道。

  • 初始化每个通道对应的传输管理结构体 usbh_vendor_xfer_t

  • 调用用户 attach 回调,告知应用层。

示例:

static int usbh_vendor_attach(usb_host_t *host)
{
    usbh_vendor_host_t *vendor = &usbh_vendor_host;
    usbh_itf_data_t *itf_data;
    usbh_dev_id_t dev_id = {0,};
    /* 1. Sets up device identification parameters to match a vendor class interface */
    dev_id.bInterfaceClass = VENDOR_CLASS_CODE;
    dev_id.bInterfaceSubClass = VENDOR_SUBCLASS_CODE;
    dev_id.bInterfaceProtocol = VENDOR_PROTOCOL;
    dev_id.mMatchFlags = USBH_DEV_ID_MATCH_ITF_INFO;  //Core only needs to match interface information (ignore VID/PID)
    /* 2. Search for interfaces that match the dev_id rule in the devices associated with the host */
    itf_data = usbh_get_interface_descriptor(host, &dev_id);

    if (itf_data == NULL) {
       // Match failure: Although the device is connected, there is no driver supporting the Interface
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Get itf desc fail\n");
        return HAL_ERR_PARA;
    } else {
        /* 3. If found, initializes the vendor host with the host pointer and sets the state to transfer */
        vendor->host = host;
        vendor->state = VENDOR_STATE_XFER;

        /* 4. Get data in/out endpoints */
        usbh_vendor_get_endpoints(host, itf_data->itf_desc_array);

        /* 5. Calls the `attach` callback function if it exists */
        if ((vendor->cb != NULL) && (vendor->cb->attach != NULL)) {
            vendor->cb->attach();
        }
        return HAL_OK;
    }
}

设备和驱动匹配机制

在 USB 主机驱动中, attach 回调函数与 usbh_dev_id_t 结构体共同协作,完成了从“通用设备连接”到“特定接口绑定”的关键过程。

匹配过程如下:

  • 定义匹配规则:过配置 usbh_dev_id_t 结构体,指定目标设备特征(如类代码、协议等)。

    • Match Flags: 使用 mMatchFlags 精确控制匹配策略(如仅匹配 VID/PID,或匹配特定 Class/SubClass)。

    • 灵活性: 精确控制是匹配整个设备还是设备中的某个特定接口(这对复合设备尤为重要)。

    • 开发者根据实际连接的 Vendor 设备进行适配。

  • 搜索匹配:在 attach 回调中,调用核心 API usbh_get_interface_descriptor()

    • USB Core 会遍历当前连接设备的所有接口描述符,与传入的 usbh_dev_id_t 规则进行比对,若匹配成功,则返回对应的接口描述符指针。

类驱动状态机

usbh_vendor_process 回调函数是主机端 Vendor 类的核心状态机处理函数。与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。 USB 主机类驱动采用状态机来管理数据传输处理,驱动由 API 调用和底层中断反馈更新状态。

状态机一般包含三种核心状态:

  • VENDOR_STATE_IDLE(空闲状态):系统初始化或连接断开后的待机状态,等待新的传输请求或事件触发。

  • VENDOR_STATE_XFER(数据传输状态):核心数据处理状态,根据事件消息中的 通道号 (pipe_num) 分发处理相应的传输事件。

  • VENDOR_STATE_ERROR(错误处理状态):异常处理状态,尝试通过标准请求 (Clear Feature) 实现错误恢复。

示例:

switch (vendor->state) {
case VENDOR_STATE_IDLE:
    break
case VENDOR_STATE_XFER:
    /* Distribute and process according to the pipe number */
    if (event.msg.pipe_num == vendor->bulk_in_xfer.pipe.pipe_num) {
        usbh_vendor_bulk_process_rx(host);
    } else if (event.msg.pipe_num == vendor->isoc_out_xfer.pipe.pipe_num) {
        usbh_vendor_isoc_process_tx(host);
    }
    /* Deal with other pipe */
    break;
case VENDOR_STATE_ERROR:
    /* Try to recover */
    if (usbh_ctrl_clear_feature(host, 0x00U) == HAL_OK) {
        vendor->state = VENDOR_STATE_IDLE;// Restoration successful, reset to IDLE
    }
    break;
}

数据传输处理

本小节详细介绍了 Vendor 类驱动中的数据传输处理流程。主机端的传输主要分为 TRX API -> Process -> Callback 三个阶段:

  • 传输请求发起:由应用层调用 API 主动发起 OUT(发送)或 IN(接收)传输请求。

  • ‌状态机管理:驱动程序通过维护传输状态机,实现与设备端的数据交互。

  • 回调通知:传输完成或出错时,通过 usbh_vendor_cb_t 回调通知应用层。该回调机制实现了应用层与底层驱动的解耦,应用层无需轮询等待。

发送流程 (TX):
  • 启动发送: 应用层调用 TX API ,如 usbh_vendor_bulk_transmit()

  • API 内部实现:

    • 设置通道的传输缓存和传输长度。

    • 设置通道为传输状态 USBH_EP_XFER_START

    • 调用 usbh_notify_class_state_change() 通知核心状态发生变化,触发状态机调度处理。

  • 核心处理: usbh_vendor_process 回调中检测到事件,根据通道号分发给对应的处理函数。

  • 底层调度: 调用 usbh_transfer_process() 开启底层传输和处理传输结果。

  • 完成通知: 调用用户 transmit 回调函数通知上层发送结果。

备注

USB DMA 要求数据缓冲区地址和 Cache line 对齐,所以应用层传下的收发缓存区地址必须地址对齐。

特殊处理逻辑

  • BULK ZLP 处理

    在批量传输中,如果数据长度正好是 MPS(最大包长)的整数倍,驱动会自动标记 trx_zlp 以便在数据后自动接收/发送一个 ZLP 结束传输。

    示例:

    if ((pipe->xfer_len > 0) && ((pipe->xfer_len % pipe->ep_mps) == 0)
        && (pipe->ep_type == USB_CH_EP_TYPE_BULK)) { //ZLP
        pipe->trx_zlp = 1;
    }
    
  • 同步传输和帧同步

    同步传输对时间敏感,利用 SOF 中断处理同步传输的时序控制。

    • SOF 回调: usbh_vendor_sof 回调函数在每帧起始触发,用于追踪当前帧号。

    • 发送调度: 确保数据包严格按照帧间隔在正确的(微)帧内传输。

      • 发送处理函数中判断后续仍有传输时,会将通道状态置为 USBH_EP_XFER_WAIT_SOF

      • 在 SOF 回调中检查帧间隔,满足条件时调用 usbh_notify_class_state_change() 触发核心调度进行实际的数据发送。

    示例:

    static int usbh_vendor_sof(usb_host_t *host)
    {
        usbh_vendor_host_t *vendor = &usbh_vendor_host;
    
        /* 1.Obtain the current frame number for ISOC scheduling */
        int cur_frame = usbh_get_current_frame_number(host);
    
        /* 2. Waiting for the correct SOF interval to start next transfer
            - if cur_frame - last frame_num >= interval, means we should trigger a xfer ASAP.
            - if xfer_state = USBH_EP_XFER_WAIT_SOF, it means that last xfer has been done, so in sof intr, we should check whether the next frame will be the xfer frame
        */
        if ((usbh_get_elapsed_frame_cnt(host, pipe->frame_num) >= pipe->ep_interval) ||
            ((pipe->xfer_state == USBH_EP_XFER_WAIT_SOF) && (cur_frame - out_xfer->cur_frame) % pipe->ep_interval == 0)){
            //3. Set next transfer parameters: e.g. buffer and length
    
            //4. Start next transfer
            usbh_notify_class_state_change(host, pipe->pipe_num);
        }
    
        return HAL_OK;
    }
    

通道配置

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

该示例中的通道配置如下:

数量

描述

2

默认控制传输 0

1

批量 IN 传输

1

批量 OUT 传输

1

中断 IN 传输

1

中断 OUT 传输

1

同步 IN 传输

1

同步 OUT 传输

API 说明

驱动 API

应用示例

应用设计

本节详细介绍 Vendor 应用的完整开发流程,涵盖驱动加载、热插拔处理、如何建立传输以及如何处理数据等核心环节。 但其传输的数据内容(如 Loopback 测试数据)是无具体业务含义的。开发者可以基于此框架:

  • 定义私有命令集:替换示例中的测试数据,实现具体的私有协议解析逻辑。

  • 调整资源配置:根据实际业务吞吐量,调整 FIFO 大小和缓冲区大小。

加载驱动

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

步骤说明:

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

  • 回调注册:定义用户回调结构体 usbh_vendor_cb_t,和 usbh_user_cb_t 挂载各个阶段的处理函数。

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

  • 加载类驱动:调用 usbh_vendor_init() 加载 Vendor 类驱动。

示例:

static usbh_config_t usbh_cfg = {
  .speed = USB_SPEED_HIGH,
  .ext_intr_enable = USBH_SOF_INTR,
  .isr_priority = INT_PRI_MIDDLE,
  .main_task_priority = 3U,
  .tick_source = USBH_SOF_TICK,
};

static usbh_vendor_cb_t vendor_usr_cb = {
  .attach = vendor_cb_attach,
  .detach = vendor_cb_detach,
  .setup = vendor_cb_setup,
  .transmit = vendor_cb_transmit,
  .receive  = vendor_cb_receive,
};

static usbh_user_cb_t usbh_usr_cb = {
  .process = vendor_cb_process
};

int ret = 0;

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

/* Initialize class driver with application callback handler. */
ret = usbh_vendor_init(&vendor_usr_cb);
if (ret != HAL_OK) {
  /* If class driver init fails, clean up the core driver */
  usbh_deinit();
  return;
}

热插拔事件处理

作为主机,系统必须能够健壮地处理 USB 设备的动态插入与移除,SDK 默认支持热插拔机制。

处理逻辑:

  • 设备插入 (Attach):USB 核心检测到设备,重新执行枚举和驱动加载流程。

  • 设备拔出 (Detach):触发回调释放信号量,应用线程捕获后执行反初始化,并释放堆内存。

示例:

static rtos_sema_t vendor_detach_sema;

/* USB detach callback */
static usbh_vendor_cb_t vendor_usr_cb = {
  .detach = vendor_cb_detach,
};

/* Callback executed in ISR context */
static int vendor_cb_detach(void)
{
  RTK_LOGS(TAG, RTK_LOG_INFO, "DETACH\n");
  rtos_sema_give(vendor_detach_sema);
  return HAL_OK;
}

/* Thread handling the state machine */
static void vendor_hotplug_thread(void *param)
{
  int ret = 0;

  UNUSED(param);

  for (;;) {
    if (rtos_sema_take(vendor_detach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
      rtos_time_delay_ms(100);//make sure disconnect handle finish before deinit.

      /* 1. Clean up resources */
      usbh_vendor_deinit();
      /* 2. De-initialize USB core */
      usbh_deinit();

      rtos_time_delay_ms(10);

      RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%08x\n", rtos_mem_get_free_heap_size());
      /* 3. Re-initialize for next connection */
      ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
      if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init USBH fail: %d\n", ret);
        break;
      }

      ret = usbh_vendor_init(&vendor_usr_cb);
      if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init vendor fail: %d\n", ret);
        usbh_deinit();
        break;
      }
    }
  }

  rtos_task_delete(NULL);
}

数据收发处理

当 Vendor 主机枚举成功后,可以调用 API 开启数据收发。 以下流程适用于 BULK、INTR 和 ISOC 类型的传输,此处以 BULK 传输为例。

static __IO int vendor_is_ready = 0;
static rtos_sema_t vendor_bulk_send_sema;
static rtos_sema_t vendor_bulk_receive_sema;
static u8 vendor_bulk_loopback_tx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE] __attribute__((aligned(CACHE_LINE_SIZE)));
static u8 vendor_bulk_loopback_rx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE] __attribute__((aligned(CACHE_LINE_SIZE)));

static usbh_vendor_cb_t vendor_usr_cb = {
   .setup = vendor_cb_setup,
   .transmit = vendor_cb_transmit,
   .receive  = vendor_cb_receive,
};

static int vendor_cb_setup(void)
{
   vendor_is_ready = 1;
   return HAL_OK;
}

/**
* @brief USB Vendor transmit callback function
* @param ep_type transmission type (BULK, INTERRUPT, ISOC)
* @return HAL status
*/
static int vendor_cb_transmit(u8 ep_type)
{
   //Release the different semaphore according to different transmission, e.g. transmission type
   switch (ep_type) {
   case USB_CH_EP_TYPE_BULK:
      rtos_sema_give(vendor_bulk_send_sema);
      break;
      //Other transmission
   default:
      break;
   }

   return HAL_OK;
}

/**
* @brief USB Vendor receive callback function
* @param ep_type transmission type (BULK, INTERRUPT, ISOC)
* @param buf Received data buffer
* @param len Length of received data
* @param status Transfer status
* @return HAL status
*/
static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
   // Get endpoint maximum packet size for each type
   u16 vendor_bulk_in_mps = usbh_vendor_get_bulk_ep_mps();

   switch (ep_type) {
   case USB_CH_EP_TYPE_BULK:
         // 1. Check if transfer transfer is complete successfully
         // 2. Reset total length and signal completion
         rtos_sema_give(vendor_bulk_receive_sema);
      } else {
         RTK_LOGS(TAG, RTK_LOG_ERROR, "%d RX fail: %d\n", ep_type, status);
      }

      break;
   }
   return HAL_OK;
}

/*---------------- BULK TRX Test --------------------*/

/*1. Wait for device to be ready for data transfer*/
while (1) {
   if (vendor_is_ready) { /* Check if device is ready */
      rtos_time_delay_ms(10);
      break;
   }
}

/* 2. BULK TX test */
/* Initiate transfer with TX configuration: transfer length and times */
usbh_vendor_bulk_transmit(vendor_bulk_loopback_tx_buf, USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE, USBH_VENDOR_BULK_LOOPBACK_CNT);

/* 3. Wait for transfer completion semaphore */
if (rtos_sema_take(vendor_bulk_send_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
   // Success indicates transfer completion
}

/* 4. BULK RX test */
/* Initiate transfer with RX configuration: transfer length and times */
usbh_vendor_bulk_receive(vendor_bulk_loopback_rx_buf, USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE, USBH_VENDOR_BULK_LOOPBACK_CNT);

/* 5. Wait for transfer completion semaphore */
if (rtos_sema_take(vendor_bulk_receive_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
   // Success indicates transfer completion
}

三种不同传输类型在接收成功后 receive 回调中的处理方式不同:

批量 IN 传输:

处理逻辑:‌

批量传输接收后累积数据直到满足特定条件后才释放信号量。 这主要是因为协议规定当批量传输长度为 MPS 整数倍时,需要追加一个 ZLP 零长度包标识传输结束。 用户层需要区别这种情况,所以需要等待完整的数据包序列或特定的结束条件(如 ZLP 零长度包、短包或缓冲区满)才能确认一次完整的传输完成。 这种机制确保了数据的完整性和正确性,避免了在数据未完全接收时就进行后续处理。

示例:

static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
   /* 1. Get endpoint maximum packet size of BULK IN endpoint */
   u16 vendor_bulk_in_mps = usbh_vendor_get_bulk_ep_mps();

   switch (ep_type) {
   case USB_CH_EP_TYPE_BULK:
      /* 2. Check if transfer was successful */
      if (status == HAL_OK) {
         /* 3 Update total received length */
         vendor_bulk_total_rx_len += len;

         /* 4. Determine if transfer is complete based on: Zero-length packet (ZLP)/Short packet (not multiple of MPS)/ Buffer full condition */
         if ((len == 0) || (len % vendor_bulk_in_mps)
            || ((len % vendor_bulk_in_mps == 0) && (len < USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE))
            || (vendor_bulk_total_rx_len > USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE)) {
             /* 5. Reset total length and signal completion */
            vendor_bulk_total_rx_len = 0;
            rtos_sema_give(vendor_bulk_receive_sema);
         }
      } else {
         RTK_LOGS(TAG, RTK_LOG_ERROR, "%d RX fail: %d\n", ep_type, status);
      }
      break;
   }
   return HAL_OK;
}

备注

  • 分配的用于收发的缓存必须要 Cache-line 地址对齐。

  • 完整的数据收发逻辑请参考 SDK 示例代码: {SDK}/example/usb/usbh_vendor/example_usbh_vendor.c

卸载驱动

在不再需要 USB 功能或系统关机时,按加载的反序释放资源。

示例:

/* Deinitialize Vendor class driver. */
usbh_vendor_deinit();

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

运行方式

本节介绍了一个完整的 Vendor 主机示例,该示例演示了如何通过 Vendor 协议栈实现与设备之间的自定义数据双向通信。

该示例代码路径: {SDK}/example/usb/usbh_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 usbh_vendor -p
    
  • Menuconfig 配置确认

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

    - Choose `CONFIG USB --->`:
    
      [*] Enable USB
          USB Mode (host)  --->
      [*] Vendor
    

结果验证

  • 启动设备

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

    [VND-I] USBH vendor demo start
    
  • 连接主机

    使用 USB 线缆将开发板连接特定厂商的 USB 设备(例如,另一块运行本 USB 协议栈自定义设备方案的开发板,详见 自定义设备方案

  • 数据通信测试

    Ameba 开发板将识别 Vendor 设备并自动进行收发测试。

    ```
    [VND-I] ISOC test start, times:100, size: 1024
    [VEN-I] ISOC OUT test finish 100/100:
    [VEN-I]   0   1   2   3   4   5   6   7   8   9
    
    ```
    [VEN-I]  90  91  92  93  94  95  96  97  98  99
    [VEN-I] ISOC IN test finish 100/100:
    [VEN-I]   0   1   2   3   4   5   6   7   8   9
    
    ```
    [VEN-I]  90  91  92  93  94  95  96  97  98  99
    [VND-I] ISOC test PASS
    [VND-I] BULK loopback test start, times:100, size: 512
    [VND-I] INTR loopback test start, times:100, size: 1024
    [VND-I] BULK loopback test PASS 100/100
    [VND-I] INTR loopback test PASS 100/100
    [VND-I] USBH vendor demo stop