Vendor-Specific Host Solution

Overview

The USB Vendor Class (Vendor Specific) leverages the openness of the USB specification to allow for vendor-defined private protocols or requests, supporting developers in flexibly defining configurations and transfer types. Ameba provides a reference example for designing a host-side vendor driver based on the underlying USB protocol stack, enabling the establishment of a highly flexible and exclusive data channel with customized USB devices. As a Vendor host, developers can develop private command sets and specific format data based on this to interact with Vendor devices, achieving customized control and functional expansion beyond the limitations of standard Host classes.

Ameba USB Vendor Host

Features

  • Supports hot-plugging

  • Automatically parse descriptors and adapt to speed modes

  • Supports three transfer types: bulk, interrupt, and isochronous

  • Support data integrity verification testing and data loopback testing

  • Adapt to the Vendor device application example. For details, refer to Custom Device Solution

Application scenarios

As a USB host with high customizability, Ameba can establish a dedicated control and communication channel between the host and the device through the Vendor interface, breaking through the functional limitations of standard classes and enabling the following typical applications, such as:

  • Custom protocol communication: Define a private command set and data frame format for dedicated communication between devices and hosts (such as parameter configuration, fault diagnosis, and status inquiry).

  • Secure firmware upgrade: Utilize a customized Vendor protocol to transmit firmware, enabling a private and secure upgrade mechanism.

  • Special peripheral control: Controls non-standard USB devices (such as specific sensors, dongles, or custom IO expansion boards).

Protocol Introduction

Please refer to the corresponding section of the vendor device solution: Protocol Introduction .

The Vendor class does not have an officially unified USB class standard specification. Its descriptor structure, command requests, and endpoint usage are all defined by the vendor and require a host-side driver or application for parsing.

Descriptor Structure

The Vendor descriptor structure can contain standard device descriptors, configuration descriptors, device qualifier descriptors, other speed configuration descriptors, and string descriptors. For detailed descriptor customization items, please refer to Device Descriptor Customization .

For Vendor devices, there are several key points to note regarding descriptors:

Device Descriptor

The class code bDeviceClass for Vendor devices is set to 0xFF/0x00. Hosts usually do not automatically load generic drivers (such as HID or MSC) but instead look for a dedicated driver matching the 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)
      |  ...

Interface Descriptor

The interface class code bInterfaceClass can be set to 0xFF to indicate a custom function class. Functional differentiation in composite devices typically occurs at the interface level.

  Interface Descriptor
  |  bInterfaceClass: 0xFF      (0xFF: Vendor Specific Class)
  |  bInterfaceSubClass         (Vendor Specific SubClass)
  |  bInterfaceProtocol         (Vendor Specific Protocol)
  |  ...

For example: A composite device has both standard MSC functionality and Vendor custom functionality.

  • Interface 0: bInterfaceClass = 0x08 (MSC)

  • Interface 1: bInterfaceClass = 0xFF (Vendor Specific)

String Descriptor

Although string descriptors are not unique to the Vendor specification, in Vendor requests, string descriptors are often used to convey serial numbers (iSerialNumber) or specific firmware version information.

For example: The three fields iManufacturer/iProduct/iSerialNumber in the device descriptor are set to 1, 2, 3 respectively.

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: For Vendor devices, it is strongly recommended to implement this string and ensure the string value is unique.

    • Importance: For Vendor devices, if there is no serial number string, Windows hosts may consider it a new device and reload the driver every time the device is plugged into a different USB port.

    • Uniqueness: Ensure that the serial number of each chip is different, otherwise two devices of the same model plugged in simultaneously may conflict.

    For Vendor devices, besides the conventional Manufacturer/Product names, using iSerialNumber for device identification is a high-frequency and critical usage.

  • Custom Strings: For Vendor devices, because there are no standard class restrictions, vendors often utilize unused string indices to pass firmware information or configurations.

    • Scenario A: Firmware Version Number

      • Index 4 can be defined as the firmware version string (e.g., “FW_Ver_1.0”).

      • Host mass production tools do not need to send complex Vendor requests; they can directly send the standard request GET_DESCRIPTOR (String, Index=4) to read the version number. This is very efficient during factory testing.

    • Scenario B: MAC Address (Commonly used in Network Cards)

      • For USB network cards, the MAC address is usually stored in eFuse or Flash.

      • Some firmware implementations will convert the MAC address into an ASCII string and place it in iSerialNumber, or in a dedicated custom index for upper-layer applications to read.

Control Requests

The USB protocol divides control request types into three categories:

  • Standard: Universal commands that all USB devices must support (e.g., GET_DESCRIPTOR, SET_ADDRESS).

  • Class: Commands defined by specific device classes (e.g., GET_REPORT for HID class, Bulk-Only Reset for MSC class).

  • Vendor: This is the space left by USB-IF for vendors to use freely. As long as the host and device agree, any command can be defined (e.g., read/write registers, download firmware, enter factory mode, etc.).

All control transfers start with an 8-byte SETUP packet. Bits[6:5] of the first byte bmRequestType in the SETUP packet determine whether it is a Vendor request:

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

When developing Vendor requests, special attention should be paid to the following key points to ensure the device responds correctly to control requests from the host:

  • Request Type Check: When processing the SETUP packet, the device must parse the bmRequestType field and confirm that its Type field is 10 (i.e., Vendor type).

  • Avoid Request Value Conflict: To prevent confusion between custom bRequest values and USB standard requests (e.g., 0x06: GET_DESCRIPTOR), it is recommended to maintain a clear request value definition table to ensure all custom requests have unique and clear identifiers.

  • Data Direction Control: Strictly adhere to Bit 7 (i.e., the Direction bit) of bmRequestType. If the host sends an OUT request but wLength > 0 and Bit 7 is IN, it may cause a device STALL or bus error; therefore, ensure direction consistency.

  • Endpoint Usage Specification: All Vendor requests are transmitted via the control endpoint by default, so the implementation should ensure that the control request processing logic is correctly bound to that endpoint.

Application Scenarios for Vendor Requests

Vendor requests allow users to flexibly implement proprietary configuration, state management, and data flow control for devices via custom control transfer commands.

For example, the following three application scenarios:

High-Performance Streaming:

High-Performance Streaming

This scenario is suitable for high-bandwidth, continuous data transmission applications, such as high-speed data acquisition instruments.

Working Mechanism:

  • Control Channel (Vendor Request): Used for sending low-frequency commands such as start/stop controls and parameter configuration.

  • Data Channel (BULK IN): Continuously and efficiently reports data to the host via bulk endpoints.

../../../_images/usb_vendor_app_perform.svg

Class Driver

Driver Architecture

The protocol stack provides a design reference example for a Vendor Class (vendor-defined class) host driver. Unlike standard class drivers such as HID or MSC, it does not bind to specific industry protocols, but provides a flexible basic framework, granting developers great freedom to customize private USB communication protocols.

The driver architecture adopts a layered design, consisting of three core layers from top to bottom:

  • User Application Layer: Implements specific business logic. Users can interact with the driver by simply registering a callback structure, and send or receive data using encapsulated interfaces, without needing to concern themselves with the underlying USB protocol details.

  • Vendor Class Driver Layer: (The core implementation of this driver example) built on top of the USB host core stack, serving as an intermediary layer connecting the underlying hardware with the upper-level applications. It is responsible for managing channel resources, maintaining state machines, and handling various data transfers. It serves as the primary foundation for developers to conduct secondary development.

  • Core Driver Layer: Responsible for low-level USB hardware interrupt handling, bus enumeration, transfer request (URB) scheduling, and standard protocol stack management.

Class Driver Implementation

The Vendor Class Driver plays a pivotal role in the system architecture, connecting the upper and lower layers. Its core interaction logic mainly revolves around the following three interfaces:

  • Host Class Driver Callback API: The class driver interacts with the underlying USB Core by registering the standard usbh_class_driver_t structure.

  • Application-Facing Callback API: Provides asynchronous event notifications (such as connection, disconnection, data reception) to the upper-layer application via the usbh_vendor_cb_t registered by the application layer during initialization.

  • Application-Facing API: Functional interfaces provided for the application layer to call. Upon calling, the driver switches its internal state machine status and initiates data transfer scheduling.

Driver Callback Mechanism

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

Note

The figure above illustrates the execution flow of callback functions at different levels and does not list all calling scenarios.

Class Driver Callback Functions

The class driver needs to define a standard usbh_class_driver_t structure. This serves as a unified entry point registered with the USB Core and is the primary method for the Core layer to notify the Class layer that “an event has occurred.”

  • id_table: Supported device ID list. The core layer uses this table to match with the inserted device to determine whether to load this driver.

  • attach: Called after device connection and successful matching.

  • detach: Called when the device is disconnected.

  • setup: Called when enumeration is complete and the class request phase begins. Used to send class-specific standard control requests to complete necessary configuration before the device enters the data transfer state.

  • process: The state machine processing function executed after the class driver is ready.

  • sof: Called during SOF interrupts. Used to handle logic with strict timing requirements, primarily for isochronous transfers.

  • completed: Called when a transfer on a pipe completes.

Application Layer Callback Functions

The Vendor class driver application layer callback structure usbh_vendor_cb_t is implemented by the user layer. Generally, the following APIs can be implemented:

API

Description

init

Called during class driver initialization to initialize application-related resources.

deinit

Called during class driver de-initialization to release application-related resources.

attach

Called when the class driver executes the attach callback to handle device connection events at the application layer.

detach

Called when the class driver executes the detach callback to handle device disconnection events at the application layer.

setup

Called when the class driver executes the setup callback, indicating that the application layer class driver is ready for data transfer.

receive

Called when the class driver receives IN data, used by the application layer to process data reported by the device.

transmit

Called when the class driver OUT data transfer completes, used by the application layer to obtain the OUT transfer status.

Loading and Unloading the Class Driver

Init:

usbh_vendor_init() is the top-level function used to load the Vendor Class Driver.

  • Execute User Callback Initialization: If a user init callback is registered, it is called first, giving the upper-layer application an opportunity to execute application-specific initialization logic.

  • Register Class Driver: Calls usbh_register_class() to register the defined usbh_class_driver_t class driver instance with the Host Core Driver, making the host ready to respond to enumeration.

Example:

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;
}

Connection and Disconnection Handling

The connection and disconnection of devices are automatically detected by the USB Core, which then calls the corresponding callbacks of the class driver for resource management.

Attach:

Device Connection

When the Core layer enumerates a Vendor device matching the usbh_dev_id_t, the usbh_vendor_attach callback is invoked. The main responsibilities of this phase are:

  • Obtain and parse the corresponding interface descriptor.

  • Open the corresponding pipe via usbh_open_pipe() based on the endpoint descriptor.

  • Initialize the transfer management structure usbh_vendor_xfer_t corresponding to each pipe.

  • Call the user attach callback to inform the application layer.

Example:

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;
    }
}

Device and Driver Matching Mechanism

In the USB host driver, the attach callback function collaborates with the usbh_dev_id_t structure to complete the critical process from “generic device connection” to “specific interface binding”.

The matching process is as follows:

  • Define Matching Rules: By configuring the usbh_dev_id_t structure, specify target device characteristics (such as Class Code, Protocol, etc.).

    • Match Flags: Use mMatchFlags for precise control over the matching strategy (e.g., matching only VID/PID, or specific Class/SubClass).

    • Flexibility: Precisely control whether to match the entire device or a specific interface within the device (this is especially important for composite devices).

    • Developers adapt according to the actual connected Vendor devices.

  • Search Matching: Inside the attach callback, call the core API usbh_get_interface_descriptor().

    • The USB Core iterates through all interface descriptors of the currently connected device and compares them with the passed usbh_dev_id_t rules. If a match is successful, it returns the pointer to the corresponding interface descriptor.

Class Driver State Machine

The usbh_vendor_process callback function is the core state machine handler for the Vendor Class on the host side. Unlike the passive response on the device side, the host-side driver needs to actively maintain device status. The USB host class driver uses a state machine to manage data transfer processing, with the driver state updated by API calls and underlying interrupt feedback.

The state machine generally contains three core states:

  • VENDOR_STATE_IDLE (Idle State): Standby state after system initialization or disconnection, waiting for new transfer requests or event triggers.

  • VENDOR_STATE_XFER (Data Transfer State): Core data processing state; dispatches and processes corresponding transfer events based on the Pipe Number in the event message.

  • VENDOR_STATE_ERROR (Error Handling State): Exception handling state; attempts error recovery via standard requests (Clear Feature).

Example:

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;
}

Data Transfer Processing

This section provides a detailed introduction to the data transfer processing flow in the Vendor class driver. Host-side transfers are mainly divided into three stages: TRX API -> Process -> Callback:

  • Initiation of Transfer Request: The application layer proactively initiates OUT (transmit) or IN (receive) transfer requests by calling APIs.

  • State Machine Management: The driver manages data interaction with the device by maintaining a transfer state machine.

  • Callback Notification: Notifies the application layer via usbh_vendor_cb_t callbacks upon transfer completion or error. This callback mechanism decouples the application layer from the low-level driver, eliminating the need for the application to poll and wait.

Transmission Flow (TX):
  • Start Transmission: The application layer calls the TX API, such as usbh_vendor_bulk_transmit().

  • Internal API Implementation:

    • Sets the transfer buffer and length for the pipe.

    • Sets the pipe to the transfer state USBH_EP_XFER_START.

    • Calls usbh_notify_class_state_change() to notify the core of state changes, which will trigger state machine scheduling.

  • Core Processing: The usbh_vendor_process callback detects the event and dispatches it to the corresponding handler function based on the pipe number.

  • Low-level Scheduling: Calls usbh_transfer_process() to starts the underlying transfer and processes the transfer results.

  • Completion Notification: Calls the user transmit callback function to notify the upper layer of the transmission result.

Note

USB DMA requires the data buffer address to be aligned with the Cache line, so the transmit/receive buffer addresses passed down by the application layer must be address-aligned.

Special Processing Logic

  • BULK ZLP Handling

    In Bulk transfers, if the data length is exactly an integer multiple of the MPS (Maximum Packet Size), the driver automatically flags trx_zlp to automatically receive/send a ZLP after the data to terminate the transfer.

    Example:

    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;
    }
    
  • Isochronous Transfer and Frame Synchronization

    Isochronous transfers are time-sensitive and rely on SOF interrupts to handle timing control for isochronous transfers.

    • SOF Callback: The usbh_vendor_sof callback function is triggered at the beginning of each frame to track the current frame number.

    • Transmission Scheduling: Ensure that data packets are submitted strictly within the correct (micro)frames at regular intervals.
      • When the TX processing function determines that there is still transmission to be done, it will set the channel status to USBH_EP_XFER_WAIT_SOF.

      • Verify the frame interval during the SOF callback, and call usbh_notify_class_state_change() to trigger the core scheduling for actual data transmission when the conditions are met.

    Example:

    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;
    }
    

Pipe Configuration

During the device enumeration phase (in the usbh_vendor_attach callback function), the Vendor Host Driver parses the configuration descriptor and automatically searches for and requests corresponding transfer resources based on the interface descriptor, establishing complete data and control pipes.

The pipe configuration in this example is as follows:

Quantity

Description

2

Default Control Transfer 0

1

Bulk IN Transfer

1

Bulk OUT Transfer

1

Interrupt IN Transfer

1

Interrupt OUT Transfer

1

Isochronous IN Transfer

1

Isochronous OUT Transfer

API Reference

For detailed function prototypes and usage, please refer to the Driver API

Application Example

Application Design

This section provides a detailed introduction to the complete development process of the Vendor application, covering core aspects such as driver loading, hotplug handling, how to establish transmission, and how to process data. However, the data content transmitted (such as Loopback test data) carries no specific business meaning. Developers can base on this framework:

  • Define private command set: Replace the test data in the example and implement specific private protocol parsing logic.

  • Adjust resource allocation: Adjust the FIFO size and buffer size based on the actual business throughput.

Driver Initialization

Before using the Vendor driver, configuration structures must be defined and callback functions registered. Subsequently, initialization interfaces are called to load the USB core driver and the Vendor class driver.

Step Description:

  • Hardware Configuration: Configure USB speed modes (High Speed/Full Speed), interrupt priorities, etc.

  • Callback Registration: Define the user callback structure usbh_vendor_cb_t and usbh_user_cb_t mount processing functions for various stages.

  • Core Initialization: Call usbh_init() to initialize the USB core driver.

  • Class Driver Initialization: Call usbh_vendor_init() to initialize the Vendor class driver.

Example:

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;
}

Hot-Plug Event Handling

As a host, the system must robustly handle the dynamic insertion and removal of USB devices. The SDK supports the hotplug mechanism by default.

Processing Logic:

  • Attach: The USB core detects the device and re-executes the enumeration and driver loading process.

  • Detach: Triggers a callback to release semaphores. The application thread captures this to execute de-initialization and free heap memory.

Example:

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);
}

Data Transfer Processing

Once the Vendor host enumeration is successful, APIs can be called to start data transfer. The following process applies to BULK, INTR, and ISOC transfer. BULK transfer is used as an example here.

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
}

The handling methods in the receive callback differ for the three transfer types upon successful reception:

Bulk IN Transfer:

Processing Logic:

Data received via bulk transfer is accumulated until specific conditions are met before releasing the semaphore. This is primarily because the protocol mandates that when a bulk transfer length is a multiple of the MPS (Max Packet Size), a Zero Length Packet (ZLP) must be appended to mark the end of the transfer. The user layer needs to distinguish this situation, so it waits for a complete packet sequence or specific termination conditions (such as a ZLP, a short packet, or a full buffer) to confirm the completion of a full transfer. This mechanism ensures data integrity and correctness, preventing subsequent processing before data is fully received.

Example:

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;
}

Note

  • Buffers allocated for transfer and reception must be Cache-line aligned.

  • For the complete data transfer logic, please refer to the SDK example code: {SDK}/example/usb/usbh_vendor/example_usbh_vendor.c.

Driver Deinitialization

When USB functionality is no longer needed or the system is shutting down, release resources in the reverse order of loading.

Example:

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

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

Operation method

This section introduces a complete Vendor host loopback example, demonstrating how to implement custom bidirectional data communication with the device via the Vendor protocol stack.

The example code path is: {SDK}/example/usb/usbh_vendor. It provides provides a complete reference solution for developers to design custom vendor products.

Configuration and Compilation

  • Compilation and Flashing

    Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.

    # 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
    
  • Confirmation of Menuconfig configuration

    If compilation fails, please execute ameba.py menuconfig and confirm that USBH VENDOR has been selected.

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

Verification

  • Host Startup

    Reset the development board and observe the serial log; it should display the following startup message:

    [VND-I] USBH vendor demo start
    
  • Connect to Device

    Connect the vendor-specific USB device (e.g. another Ameba board running usbd_vendor application) to the USB port of the board with USB cable.

  • Data Communication Test

    The Ameba development board will identify the vendor device and perform transceiving tests.

    ```
    [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