Host Solution

Transparent Communication Host Solution

Overview

The Abstract Control Model (ACM) under the USB Communication Device Class (CDC) is based on the USB Bulk Transfer mechanism and defines a set of universal data interaction standards. In Host mode, the system utilizes this protocol to establish a high-speed data channel with external devices, enabling bidirectional transparent transmission of raw data streams.

The Ameba platform integrates a USB-IF compliant CDC ACM protocol stack, capable of efficient data transparent transmission capabilities.

../../_images/usb_host_cdc_acm_overview.svg

Features

  • Supports hot plugging

  • Automatically parse descriptors and adapt to speed modes

  • Batch transmission length and other parameters are configurable

  • Support transparent transmission of raw data (text, binary, custom protocols, etc.)

  • Support for independently identifying and driving the serial port functionality within composite devices (such as ACM + HID)

Application Scenarios

As a USB CDC ACM host, Ameba can establish a point-to-point communication link with devices through the USB interface, and combine its wireless capability to expand various data transparent transmission applications, such as:

  • Wireless Data Transparent Transfer Bridge: Ameba, as a gateway, can be mounted with an external proprietary protocol dongle or acquisition module to transparently forward the raw data stream received on the USB side to a Wi-Fi or Bluetooth network, achieving seamless bridging and protocol conversion of wired data streams to wireless networks.

  • Empowering Traditional Industrial Equipment: Ameba connects to PLCs, CNC machines, or precision instruments via a USB-to-serial adapter, enabling protocol conversion and cloud access for traditional RS-232/485 devices. This facilitates the digital and intelligent upgrade of old production lines at a low cost.

  • High-speed data acquisition: Ameba connects to high-frequency sensors or acquisition cards equipped with USB interfaces, utilizing a bulk transfer mechanism to process large volumes of raw data in real-time. This breaks through the bandwidth bottleneck of traditional low-speed interfaces, ensuring data integrity and real-time performance in high-frequency sampling scenarios.

Protocol Introduction

CDC (Communication Device Class) is a standard for general communication devices defined by the USB specification. Within its PSTN (Public Switched Telephone Network) subclass, the most commonly used is ACM (Abstract Control Model). It defines a standardized set of commands for controlling communication parameters, such as:

  • Set baud rate (such as 9600, 115200)

  • Configure data bits, stop bits, and parity bits

  • Control DTR/RTS and other line status signals

It is through ACM that a USB CDC device can be recognized by the host as a standard “virtual serial port”.

Protocol Document

The USB-IF has officially released the CDC (Class Driver Control) basic protocol and PSTN (Public Switched Telephone Network) sub-class specifications. During the development process, please refer to the following core documents:

Specification type

Document

CDC (Communication Device Class Basic Protocol)

Class definitions for Communication Devices

PSTN (PSTN Subclass)

PSTN specification is included in the aforementioned CDC zip file.

Protocol Framework

The CDC ACM architecture utilizes a dual-interface mechanism to separate control flow from data flow, and logically binds them into a single functional unit via the Union Functional Descriptor.

  • Communication Class Interface (CCI)

    Responsible for device management control and signaling interaction.

    • Control transmission: Transmit specific requests through the default control endpoint.

      • The host primarily sends PSTN control commands, with core instructions including SetLineCoding for configuring baud rate/data bits, and SetControlLineState for controlling RTS/DTR handshake signals.

    • Interrupt transmission: Utilizing interrupt input endpoints to achieve asynchronous status notification from the device to the host.

      • A typical application is to report changes in hardware signal status such as DCD, DSR, or Ring in real-time through SERIAL_STATE.

  • Data Class Interface (DCI)

    Responsible for carrying application-layer payload data streams. This interface is typically configured as a pair of bulk endpoints (Bulk IN/Bulk OUT), serving solely as a transparent data transmission channel without involving the parsing or processing of control commands.

Example of protocol interaction:

../../_images/usb_cdc_acm_class.svg

Descriptor Structure

In addition to adhering to standard USB descriptors (such as device descriptor, configuration descriptor, and endpoint descriptor), CDC ACM devices also define Class-Specific Functional Descriptors to specify the capabilities of the abstract control model.

CDC ACM Descriptor Topology

The following example illustrates the descriptor topology using a High Speed configuration.

Device Descriptor
└── Identifies basic device information (Class: 0x02 CDC, SubClass: 0x02 ACM)

Configuration Descriptor
├── Includes total length, power attributes (Self-powered), etc.
│
├── Communication Class Interface Descriptor (Interface 0)
│   ├── Standard Interface Descriptor (Class: 0x02, SubClass: 0x02, Protocol: 0x01 AT Commands)
│   │
│   ├── Class-Specific Functional Descriptors (Defines ACM capabilities)
│   │   ├── Header Functional Descriptor (Declares CDC Spec version)
│   │   ├── Call Management Functional Descriptor (Call handling capabilities)
│   │   ├── ACM Functional Descriptor (Line coding & state capabilities)
│   │   └── Union Functional Descriptor (Binds Interface 0 and Interface 1)
│   │
│   └── Standard Endpoint Descriptor (Interrupt IN)
│       └── Used for Serial State notifications
│
├── Data Class Interface Descriptor (Interface 1)
│   ├── Standard Interface Descriptor (Class: 0x0A Data, SubClass: 0x00, Protocol: 0x00)
│   │
│   ├── Standard Endpoint Descriptor (Bulk OUT)
│   │   └── Host -> Device Data Stream
│   │
│   └── Standard Endpoint Descriptor (Bulk IN)
│       └── Device -> Host Data Stream
│
├── Device Qualifier Descriptor
│   └── Device information for other speed modes
│
└── Other Speed Configuration Descriptor
    └── Configuration information for Full Speed mode

Functional Descriptor

In the communication interface, the CDC must include the following special “function descriptor” header:

  • Header Functional Descriptor: Indicates the CDC version.

  • Call Management Functional Descriptor: Indicates how the device handles call management.

  • Abstract Control Management Functional Descriptor: Indicates which commands (such as Set_Line_Coding) are supported.

  • Union Functional Descriptor: specifies which one is the Master interface and which one is the Slave interface.

  • Header Functional Descriptor

Header Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 5 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE: Class-Specific Interface)
├── bDescriptorSubtype : 1 byte   → 0x00 (HEADER)
└── bcdCDC             : 2 bytes  → USB Class Definitions for Communication Devices Specification Release Number
                                    • 0x0110 = Release 1.10 (Common for ACM)
  • Call Management Functional Descriptor

Call Management Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 5 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x01 (CALL_MANAGEMENT)
├── bmCapabilities     : 1 byte   → The capabilities that this configuration supports:
│                                   • Bit 0 = 0: Device does not handle call management itself
│                                   • Bit 0 = 1: Device handles call management itself
│                                   • Bit 1 = 0: Call management commands does not sent over Data Class Interface
│                                   • Bit 1 = 1: Call management commands can be sent over Data Class Interface
│                                   • Bits 2-7: Reserved (Reset to zero)
└── bDataInterface     : 1 byte   → Interface number of Data Class interface optionally used for call management
                                    (Zero if no data interface is used)
  • ACM Functional Descriptor

Abstract Control Management (ACM) Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (Fixed = 4 bytes)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x02 (ABSTRACT_CONTROL_MANAGEMENT)
└── bmCapabilities     : 1 byte   → The capabilities that this configuration supports:
                                    • Bit 0: Comm_Feature (Supports Set_Comm_Feature, Clear_Comm_Feature, Get_Comm_Feature)
                                    • Bit 1: Line_Coding (Supports Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, Serial_State)
                                    • Bit 2: Send_Break (Supports Send_Break)
                                    • Bit 3: Network_Connection (Supports Network_Connection)
                                    • Bits 4-7: Reserved (Reset to zero)
  • Union Functional Descriptor

Union Functional Descriptor
├── bLength            : 1 byte   → Total descriptor length (3 + Number of slave interfaces)
├── bDescriptorType    : 1 byte   → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte   → 0x06 (UNION)
├── bMasterInterface   : 1 byte   → The interface number of the Communication or Data Class interface
│                                   (Designated as the controlling interface for the union)
└── bSlaveInterface0   : 1 byte   → Interface number of the first subordinate interface in the union
│    ⋮
└── bSlaveInterface(N) : 1 byte   → Interface number of the last subordinate interface in the union

Note

For specifications regarding the CDC ACM transfer protocol, please refer to USB CDC ACM Specification

Class-Specific Requests

Control requests for CDC ACM devices are categorized into Standard Requests and Class-Specific Requests.

This section primarily introduces the class-specific requests unique to CDC ACM and utilized for implementing core functionalities such as serial port parameter configuration and flow control signal management for virtual serial ports.

Request Name

Requirement

Description

SEND_ENCAPSULATED_COMMAND

Required

The host sends an encapsulated command to the device. The data format must adhere to the control protocol supported by the device (e.g., AT command set).

GET_ENCAPSULATED_RESPONSE

Required

The host retrieves response data for an encapsulated command from the device. The response format adheres to the protocol supported by the device.

SET_COMM_FEATURE

Optional

Sets the status of a specific communication feature. The target feature is specified by a feature selector.

GET_COMM_FEATURE

Optional

Queries the current setting status of a specific communication feature.

CLEAR_COMM_FEATURE

Optional

Clears the setting of a specific communication feature, resetting it to the default state.

SET_LINE_CODING

Optional (+)

The host configures line coding properties for async serial communication (e.g., baud rate, stop bits, parity, data bits).

GET_LINE_CODING

Optional (+)

The host queries the currently configured line coding properties for asynchronous serial communication.

SET_CONTROL_LINE_STATE

Optional

The host controls the state of RS-232/V.24 standard control signals (e.g., DTR and RTS signal levels).

SEND_BREAK

Optional

The host requests the device to generate a “Break” signal of a specific duration on the transmission end, simulating an RS-232 style line break.

Note

  • The above requests all belong to Communications Class specific requests.

  • For Analog Modem applications, although the specification lists requests marked with (+) as optional, it is strongly recommended to implement these requests to ensure compatibility.

Class Driver

This section details the internal implementation of the CDC ACM host driver, including the driver architecture, support for class-specific requests, and the allocation scheme for transmission resources.

Driver Architecture

The CDC ACM host protocol stack adopts a layered architecture design, decoupling the USB transport layer from the upper-layer data stream. From top to bottom, the driver architecture is divided into the following layers:

  • Application Layer

    Contains business logic and data processing callbacks.

    • Business Implementation: Calls interfaces and processes transmitted/received data based on actual scenarios (such as wireless bridges, data acquisition).

    • Data Buffering: The application layer directly acquires data packets uploaded from the lower layer by registering user callback functions.

  • CDC ACM Class Driver

    Strictly adhering to the USB CDC ACM protocol specification, this layer implements the core business logic for the interaction between the host and the ACM device. Its main responsibilities include:

    • Enumeration and Interface Binding: Responsible for identifying the communication interface class (0x02, CDC_COMM) and data interface class (0x0A, CDC_DATA), automatically parsing interface descriptors, and applying for corresponding pipe resources.

    • Transmission Parameter Negotiation: During the initial connection phase, it is responsible for sending requests such as SetLineCoding to initialize link parameters, ensuring configuration synchronization between the host and device.

    • Notification Event Handling: Listens for status notifications reported by the device (such as SerialState) via interrupt transfers and triggers corresponding event callbacks.

    • Data Transmission: Encapsulates data streams from the upper layer into USB Bulk Transfer Requests (URB) and passes received USB data packets to the application layer via callback functions.

  • USB Core Driver

    Responds to hardware interrupts in real-time, responsible for handling USB standard enumeration, transfer management, and underlying physical data transmission scheduling.

Class Driver Implementation

The CDC ACM class driver plays a connecting role in the system architecture. Its implementation logic mainly revolves around the following three core interaction interfaces:

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

  • Application-Facing Callback API: The class driver provides an asynchronous event notification mechanism to the upper-layer application via the usbh_cdc_acm_cb_t callback structure.

  • Application-Facing API: After the application layer calls these APIs, the driver switches the internal state machine status and initiates data transmission scheduling.

Driver Callback Mechanism
../../_images/usb_host_cdc_acm_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 CDC ACM class driver application layer callback structure usbh_cdc_acm_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 BULK IN data, used by the application layer to process data reported by the device.

transmit

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

notify

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

Loading and Unloading Class Driver

These two functions are responsible for the allocation and release of memory resources, as well as the registration and deregistration of the class driver to the USB core.

usbh_cdc_acm_init() is the top-level function used to load the CDC ACM host class driver. It primarily completes the following tasks:

  • Saves the user-provided callback functions and calls the user init callback.

  • Allocates memory for line_coding (current device parameters) and user_line_coding (user expected parameters).

  • Calls usbh_register_class() to register the CDC ACM class driver with the USB host core.

Example:

int usbh_cdc_acm_init(usbh_cdc_acm_cb_t *cb)
{
    /* 1. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
      cdc->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. Allocate memory */
    cdc->line_coding = (usb_cdc_line_coding_t *)usb_os_malloc(sizeof(usb_cdc_line_coding_t));
    /* ... */

    /* 3. Register class driver*/
    usbh_register_class(&usbh_cdc_acm_driver);
    return ret;
}
Connection and Disconnection Handling

When the USB core detects the insertion or removal of a device matching the CDC ACM class, the following callback functions are called.

usbh_cdc_acm_attach is a key step in device enumeration, responsible for parsing interface descriptors and allocating pipe resources:

  • Locate Communication Interface (Comm Interface): If found, parses its interrupt endpoint and opens the Interrupt IN pipe.

  • Locate Data Interface (Data Interface): If found, parses its bulk endpoints and opens Bulk IN and Bulk OUT pipes.

  • Initialize the state machine to IDLE state.

  • Call the user attach callback to inform the application layer of the connection status.

Example:

static int usbh_cdc_acm_attach(usb_host_t *host)
{
  /* 1. Get the Communication interface and open the interrupt pipe */
  dev_id.bInterfaceClass = USB_CDC_COMM_INTERFACE_CLASS_CODE;
  itf_data = usbh_get_interface_descriptor(host, &dev_id);
  if (itf_data) {
    usbh_open_pipe(host, intr_in, ep_desc);
  }
  /* 2. Get the Data interface and open the Bulk IN & Bulk OUT pipe */
  dev_id.bInterfaceClass = USB_CDC_DATA_INTERFACE_CLASS_CODE;
  itf_data = usbh_get_interface_descriptor(host, &dev_id);
  /* Open bulk_in / bulk_out pipes */

  /* 3. Initialize the state machine */
  cdc->state = USBH_CDC_ACM_STATE_IDLE;

  /* 4. Notify the user layer */
  if ((cdc->cb != NULL) && (cdc->cb->attach != NULL)) {
      cdc->cb->attach();
  }
  return HAL_OK;
}
Class Driver State Machine

The usbh_cdc_acm_process callback function is the core state machine handler for the host-side CDC ACM class. Unlike the passive response mechanism on the device side, the host-side driver must actively maintain the device state. Its core responsibilities include maintaining the class driver lifecycle states (e.g., IDLE, TRANSFER, ERROR), handling the setting and verification of Line Coding, and distributing data transmission tasks.

State Machine Management and Scheduling

usbh_cdc_acm_process manages the scheduling of control transfers (such as baud rate configuration) and bulk/interrupt data transfers based on the current class driver state.

State Enum

Description

Key Actions

IDLE

Idle state

Waits for user commands or data transmission requests.

SET_LINE_CODING

Send baud rate setting request

Sends a SET_LINE_CODING request (EP0). Upon success, it automatically transitions to GET_LINE_CODING for read-back verification.

GET_LINE_CODING

Get/Verify baud rate

Sends a GET_LINE_CODING request. After acquisition, it compares the value with the user setting. If consistent, it triggers the user line_coding_changed callback.

SET_CONTROL_LINE_STATE

Control handshake signals

Configures RTS/DTR level states.

TRANSFER

Data transferring

Distributes tasks to specific TX/RX processing functions based on the pipe number.

ERROR

Error state

Attempts to clear endpoint features (Clear Feature) to restore communication.

Line Coding Configuration (Set & Verify)

The CDC ACM host driver implements a “Set-Readback-Verify” closed-loop process to ensure the device has correctly accepted parameters like the baud rate.

  • Set: Sends the SET_LINE_CODING request. Upon success, the state automatically transitions to GET_LINE_CODING.

  • Readback (Get): Sends the GET_LINE_CODING request to read the current configuration from the device, ensuring serial parameters are synchronized.

  • Verify: Compares the configuration requested by the user with the actual configuration returned by the device. If they match, the configuration is successful. The line_coding_changed callback is called to notify the application layer of the successful modification.

Example:

case USBH_CDC_ACM_STATE_SET_LINE_CODING:
  /* 1. Send a setting request */
    req_status = usbh_cdc_acm_process_set_line_coding(host, cdc->user_line_coding);
  if (req_status == HAL_OK) {
    /* 2. After successful setup, immediately goto to get the setting for verification */
    cdc->state = USBH_CDC_ACM_STATE_GET_LINE_CODING;
    }
  /* ... error handling ... */
  break;

  case USBH_CDC_ACM_STATE_GET_LINE_CODING:
    req_status = usbh_cdc_acm_process_get_line_coding(host, cdc->line_coding);
    if (req_status == HAL_OK) {
      cdc->state = USBH_CDC_ACM_STATE_IDLE;
      /* 3. Verify whether the readback data is consistent with the user settings */
      if ((cdc->line_coding->b.dwDteRate == cdc->user_line_coding->b.dwDteRate) &&
      /* ... compare other fields ... */ ) {
        /* 4. Config Match, notify the application layer that the setting has been changed. */
        if ((cdc->cb != NULL) && (cdc->cb->line_coding_changed != NULL)) {
          cdc->cb->line_coding_changed(cdc->line_coding);
        }
    }
  }
  break;

Transfer Processing Distribution

When in the TRANSFER state, processing is distributed to specific TX (transmit), RX (receive), or INTR (interrupt) handler functions based on the pipe number (Pipe ID) that triggered the event.

Example:

case USBH_CDC_ACM_STATE_TRANSFER:
/* Distribute transmission tasks according to pipe numbers */
if (event.msg.pipe_num == cdc->bulk_out.pipe_num) {
  usbh_cdc_acm_process_tx(host);      // Handle BULK OUT transfer
} else if (event.msg.pipe_num == cdc->bulk_in.pipe_num) {
  usbh_cdc_acm_process_rx(host);      // Handle BULK IN transfer
} else if (event.msg.pipe_num == cdc->intr_in.pipe_num) {
  usbh_cdc_acm_process_intr_rx(host); // Handle Interrupt IN transfer(e.g. Serial State)
}
break;

Error Recovery

When an error occurs during other state processing, the driver attempts to clear endpoint features (Clear Feature) and restore to the IDLE state.

Example:

switch (cdc->state) {
/* ... IDLE state ... */

case USBH_CDC_ACM_STATE_ERROR:
  /* Error recovery mechanism */
  req_status = usbh_ctrl_clear_feature(host, 0x00U);
  if (req_status == HAL_OK) {
    cdc->state = USBH_CDC_ACM_STATE_IDLE;
  }
break;
}
Class-Specific Request Processing

Setup Phase Processing

The usbh_cdc_acm_setup callback function is invoked after the basic enumeration is completed. It initiates the first class-specific request: Get Line Coding, to synchronize the state between the host and the device.

Example:

static int usbh_cdc_acm_setup(usb_host_t *host)
{
    /* Initiate a Get Line Coding request */
    status = usbh_cdc_acm_process_get_line_coding(host, cdc->line_coding);
    return status;
}

APIs for Application

Interfaces provided to the upper-layer application for triggering configuration or data transmission.

  • Set Control State

  • Set/Get Line Coding

    • usbh_cdc_acm_set_line_coding(): Assembles the Setup packet; the driver switches the state to SET_LINE_CODING and schedules the initiation of the control request within process.

    • usbh_cdc_acm_get_line_coding(): Returns the device parameters read back for verification after the SET_LINE_CODING state.

Data Transfer Processing

The following three APIs for application serve as the interface layer between the USB driver and the application. The driver switches the state to TRANSFER and schedules the distribution of data transmission within the process callback.

  • CDC ACM Data Interface:

    • usbh_cdc_acm_transmit(): The application layer passes in data to initiate transmission. It handles Zero Length Packet (ZLP) logic and notifies the application layer of the transmission result via the user transmit callback function.

    • usbh_cdc_acm_receive(): Initiates reception and passes data through to the application layer via the user receive callback function.

  • CDC ACM Communication Interface:

    • usbh_cdc_acm_notify_receive(): Initiates interrupt transfer reception and passes data through to the application layer via the user notify callback function.

Transmission Logic and Zero Length Packet (ZLP) Processing

In the bulk data transmission function usbh_cdc_acm_transmit(), the driver must handle the Zero Length Packet (ZLP) issue defined in the USB protocol.

If the length of the transmitted data is exactly an integer multiple of the endpoint’s Maximum Packet Size (MPS), the host must send an additional ZLP to inform the device that the transmission has ended. This is a standard requirement for USB bulk transfers.

Example:

int usbh_cdc_acm_transmit(u8 *buf, u32 len)
{
    usbh_cdc_acm_host_t *cdc = &usbh_cdc_acm_host;
    usb_host_t *host = cdc->host;
    usbh_pipe_t *pipe = &cdc->bulk_out;

    if (pipe->xfer_state == USBH_EP_XFER_IDLE) {
        pipe->xfer_buf = buf;
        pipe->xfer_len = len;

        /* If the data length is greater than 0 and an integer multiple of MPS, a ZLP needs to be sent */
        if ((pipe->xfer_len > 0) && (pipe->xfer_len % pipe->ep_mps) == 0) {
            pipe->trx_zlp = 1;
        } else {
            pipe->trx_zlp = 0;
        }

        cdc->state = USBH_CDC_ACM_STATE_TRANSFER;
        pipe->xfer_state = USBH_EP_XFER_START;
        /* ... notify state change ... */
    }
}

Class-Specific Request Implementation

This driver stack follows the USB CDC ACM specification, encapsulating the implementation and transmission process of core Class-Specific Requests. Although the specification marks some requests as optional, the host driver implements the following requests to support standard virtual serial port applications. Developers can refer to the source code path {SDK}/component/usb/host/cdc_acm for extensions.

Class-Specific Request

Description

SetLineCoding

Configures line coding. Sends parameters such as baud rate, stop bits, and parity bits to the device. In pure passthrough mode, this request is mainly used to complete the protocol handshake process.

GetLineCoding

Gets line coding. Reads the current line configuration parameters from the device, used to verify if the configuration has taken effect or to synchronize with the device’s default state.

SetControlLineState

Sets control signal state. Used to control RTS (Request To Send) and DTR (Data Terminal Ready) signal levels, commonly used for flow control handshakes or to notify the device that the host side is ready.

Pipe Configuration

The CDC ACM host driver parses the configuration descriptor during the device enumeration phase (usbh_cdc_acm_attach callback function), automatically finding and allocating corresponding transmission resources based on interface subclasses to establish complete data and control channels.

Count

Description

2

For default control transfer 0. Used for sending standard USB requests and CDC class-specific control requests (e.g., SetLineCoding).

1

For Interrupt IN transfer. Belongs to the Communication Interface (CCI), used to receive notification events (Notify) actively reported by the device. The maximum timeout is set to 1000 ticks.

1

For Bulk IN transfer. Belongs to the Data Interface (DCI), used to receive raw data streams (RX Data) uploaded by the device, supporting large packet transmission and ZLP processing.

1

For Bulk OUT transfer. Belongs to the Data Interface (DCI), used to send raw data streams (TX Data) to the device. The driver layer optimizes DMA scheduling to ensure high-bandwidth transmission.

API Reference

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

Application Example

Application Design

This section details the complete development process for the CDC ACM Host driver, covering driver initialization, hot-plug management, data transmission and reception mechanisms, and resource release.

Driver Initialization

Before using the CDC ACM Host driver, it is necessary to define the configuration structure and register callback functions, followed by sequentially calling core interfaces to start the USB host controller and the ACM class driver.

Step Description:

  • Hardware Configuration: Set the USB speed mode (High Speed/Full Speed) and related interrupt priorities.

  • Callback Registration: Define the usbh_cdc_acm_cb_t structure and mount handler functions for each phase (connection, disconnection, data transmission, notification).

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

  • Class Driver Initialization: Call usbh_cdc_acm_init() to initialize the CDC ACM class driver.

/* 1. Configure USB speed (High Speed or Full Speed), isr priority and main task priority. */
static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_HIGH,
    .ext_intr_enable = USBH_SOF_INTR,
    .isr_priority = INT_PRI_MIDDLE,
    //...
};

/* 2. Define USB user-level callbacks. */
static usbh_user_cb_t usbh_usr_cb = {
    .process = cdc_acm_cb_process    /* USB callback to handle class-independent events in the application */
};

/* 3. Define user callbacks for CDC ACM events. */
static usbh_cdc_acm_cb_t cdc_acm_usr_cb = {
    .init   = cdc_acm_cb_init,        /* Class init callback */
    .deinit = cdc_acm_cb_deinit,      /* Class deinit callback */
    .attach = cdc_acm_cb_attach,      /* Device attach callback */
    .detach = cdc_acm_cb_detach,      /* Device detach callback */
    .setup  = cdc_acm_cb_setup,       /* Class setup callback */
    .transmit = cdc_acm_cb_transmit,  /* Data transmit callback */
    .receive  = cdc_acm_cb_receive,   /* Data receive callback */
    .notify   = cdc_acm_cb_notify,    /* Interrupt notify callback */
};

int status = 0;

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

/* 5. Initialize CDC ACM class driver.  */
status = usbh_cdc_acm_init(&cdc_acm_usr_cb);
if (status != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbh_deinit();
    return;
}
Hot-Plug Event Handling

Monitor the connection and disconnection of CDC ACM devices by registering the attach and detach callback functions in usbh_cdc_acm_cb_t.

In the example code, semaphore mechanisms are used to synchronize states:

  • Attach: When a device is inserted and enumerated successfully, the attach callback is triggered, releasing the cdc_acm_attach_sema to notify the main thread to start data testing.

  • Detach: When a device is removed, the detach callback is triggered, releasing the cdc_acm_detach_sema to trigger the hot-plug management thread for resource cleanup and re-initialization.

/* 1. Configure USB status change callback */
static usbd_cdc_acm_cb_t cdc_acm_cb = {
    .status_changed = cdc_acm_cb_status_changed
};

/* 2. Configure Callback executed in ISR context */
static void cdc_acm_cb_status_changed(u8 old_status, u8 status)
{
    RTK_LOGS(TAG, RTK_LOG_INFO, "Status change: %d -> %d \n", old_status, status);

    cdc_acm_attach_status = status;
    rtos_sema_give(cdc_acm_attach_status_changed_sema);
}

/* 3. Create thread handling the state machine */
static void cdc_acm_hotplug_thread(void *param)
{
    int ret = 0;
    UNUSED(param);

    for (;;) {
        if (rtos_sema_take(cdc_acm_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            if (cdc_acm_attach_status == USBD_ATTACH_STATUS_DETACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");

                /* 1.Clean up resources */
                usbd_cdc_acm_deinit();
                ret = usbd_deinit();
                if (ret != 0) {
                    break;
                }

                /* 2.Re-initialize for next connection */
                ret = usbd_init(&cdc_acm_cfg);
                if (ret != 0) {
                    break;
                }
                ret = usbd_cdc_acm_init(CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE, CONFIG_CDC_ACM_BULK_IN_XFER_SIZE, &cdc_acm_cb);
                if (ret != 0) {
                    usbd_deinit();
                    break;
                }
            } else if (cdc_acm_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
            } else {
                RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
            }
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Hotplug thread fail\n");
    rtos_task_delete(NULL);
}
Data Transfer Processing

Once the CDC ACM device is successfully enumerated, the host can perform control transfers, data transfers, and interrupt notification reception.

static void cdc_acm_request_test(void)
{
    int ret;
    usbh_cdc_acm_line_coding_t line_coding;
    usbh_cdc_acm_line_coding_t new_line_coding;

    /* Wait for device attach */
    ......

    /* 1. Get current line coding from device */
    ret = usbh_cdc_acm_get_line_coding(&line_coding);
    if (ret != HAL_OK) {
        /* Get failed */
    }

    /* 2. Modify configuration parameters */
    /* Baudrate: 38400, Stop bits: 1, Parity: None, Data bits: 8*/
    line_coding.b.dwDteRate = 38400;
    line_coding.b.bCharFormat = CDC_ACM_LINE_CODING_CHAR_FORMAT_1_STOP_BITS;
    line_coding.b.bParityType = CDC_ACM_LINE_CODING_PARITY_NO;
    line_coding.b.bDataBits = 8;

    /* 3. Set new line coding to device */
    ret = usbh_cdc_acm_set_line_coding(&line_coding);
    if (ret != HAL_OK) {
        /* Set failed */
    }

    /* Wait for the setting to take effect */
    rtos_time_delay_ms(10);

    /* 4. Get line coding again to verify */
    ret = usbh_cdc_acm_get_line_coding(&new_line_coding);

    /* 5. Compare set values with read-back values */
    ......
}
  • Bulk Transfer

    The driver uses an asynchronous transmission mechanism, combined with semaphores to implement synchronization logic:

    1. Send: Call usbh_cdc_acm_transmit() to send data. Upon completion, the driver calls back the transmit function and releases cdc_acm_send_sema.

    2. Receive: Call usbh_cdc_acm_receive() to prepare to receive data. Upon receiving data, the driver calls back the receive function and releases cdc_acm_receive_sema.

/* RX Callback: Handle Reception Completion */
static int cdc_acm_cb_receive(u8 *buf, u32 len, u8 status)
{
    if (status == HAL_OK) {
    u16 cdc_acm_bulk_in_mps = usbh_cdc_acm_get_bulk_ep_mps();

    /* 1. Copy data to internal buffer (Logic for buffer overflow check included) */
    if ((len > 0) && ((cdc_acm_total_rx_len + len) <= USBH_CDC_ACM_LOOPBACK_BUF_SIZE)) {
        /* memcpy(cdc_acm_loopback_rx_buf + cdc_acm_total_rx_len, buf, len); */
    }
    cdc_acm_total_rx_len += len;

    /* 2. Check for ZLP, short packet, or buffer full to complete the transfer */
    if ((len == 0) || (len % cdc_acm_bulk_in_mps)
        || ((len % cdc_acm_bulk_in_mps == 0) && (len < USBH_CDC_ACM_LOOPBACK_BUF_SIZE))
        || (cdc_acm_total_rx_len > USBH_CDC_ACM_LOOPBACK_BUF_SIZE)) {

        cdc_acm_total_rx_len = 0;

        /* 3. Signal the receive semaphore */
        rtos_sema_give(cdc_acm_receive_sema);
    }
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "RX fail: %d\n", status);
    }
    return HAL_OK;
}

/* TX Callback: Handle Transmission Completion */
static int cdc_acm_cb_transmit(u8 status)
{
    if (status == HAL_OK) {
        /* TX done, signal semaphore */
        rtos_sema_give(cdc_acm_send_sema);
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "TX fail: %d\n", status);
    }

    return HAL_OK;
}

/* Task thread: Handle Loopback Test Logic */
static void cdc_acm_loopback_test(void)
{
    int i;
    int ret;

    /* Initialize TX buffer */
    //...

    for (i = 0; i < USBH_CDC_ACM_LOOPBACK_CNT; i++) {

        /* Prepare RX buffer and check device status */
        //...

        /* 1. Transmit Data */
        ret = usbh_cdc_acm_transmit(cdc_acm_loopback_tx_buf, USBH_CDC_ACM_LOOPBACK_BUF_SIZE);
        if (ret < 0) {
            RTK_LOGS(TAG, RTK_LOG_ERROR, "TX fail: %d\n", ret);
            return;
        }

        /* 2. Wait for TX Complete signal */
        if (rtos_sema_take(cdc_acm_send_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* 3. Prepare to Receive Data */
            usbh_cdc_acm_receive(cdc_acm_loopback_rx_buf, USBH_CDC_ACM_LOOPBACK_BUF_SIZE);
        }

        /* 4. Wait for RX Complete signal and Verify Data */
        if (rtos_sema_take(cdc_acm_receive_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* Check rx data integrity */
            //...
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Bulk loopback test PASS\n");
}
  • Interrupt Notify

    Use usbh_cdc_acm_notify_receive() to listen for interrupt transfers from the device (such as Serial State changes). When a notification is received, the notify callback is triggered.

/* Callback: Handle Interrupt Notify */
static int cdc_acm_cb_notify(u8 *buf, u32 len, u8 status)
{
    /* Handle buf and len */
    //...

    if (status == HAL_OK) {
        /* Notify received, signal semaphore to notify the task */
        rtos_sema_give(cdc_acm_notify_sema);
    } else {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Notify fail: %d\n", status);
    }
    return HAL_OK;
}

/* Task Thread: Execute Notify Test Logic */
static void cdc_acm_notify_test_thread(void *param)
{
    /* Wait for device ready */
    //...

    /* 1. Optional: Change control line state to trigger device notification */
    usbh_cdc_acm_set_control_line_state();

    rtos_time_delay_ms(2000);

    for (int i = 0; i < USBH_CDC_ACM_LOOPBACK_CNT; i++) {
        /* 2. Submit Interrupt IN Request to listen for notification */
        usbh_cdc_acm_notify_receive(cdc_acm_notify_rx_buf, USBH_CDC_ACM_NOTIFY_BUF_SIZE);

        /* 3. Wait for Notify Callback */
        if (rtos_sema_take(cdc_acm_notify_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            /* Notification received. Check data (e.g. Serial State at offset 8) */
            RTK_LOGS(TAG, RTK_LOG_INFO, "Intr rx success(0x%02x 0x%02x)\n",
                cdc_acm_notify_rx_buf[9], cdc_acm_notify_rx_buf[8]);
        }
    }
    RTK_LOGS(TAG, RTK_LOG_INFO, "Intr notify test PASS\n");
    rtos_task_delete(NULL);
}

Note

For complete data testing logic, please refer to the SDK example code: {SDK}/component/example/usb/usbh_cdc_acm/example_usbh_cdc_acm.c.

Driver Deinitialization

When the device is disconnected or the USB host function needs to be disabled, the class driver and the host core driver must be unloaded in sequence, and related system resources must be released.

/* 1. Deinitialize CDC ACM class driver. */
usbh_cdc_acm_deinit();

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

Operation method

This section introduces a complete USB CDC ACM Host example, demonstrating how Ameba, acting as a host, identifies a CDC device and performs baud rate setting, data loopback testing, and interrupt notification testing.

The example code path: {SDK}/component/example/usb/usbh_cdc_acm.

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

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

    - Choose `CONFIG USB --->`:
    
      [*] Enable USB
          USB Mode (Host)  --->
      [*] CDC ACM
    
Result Verification
  • Start Host

    Restart the development board and observe the serial log; the following startup information should be displayed:

    [ACM-I] USBD CDC ACM demo start
    
  • Connect Device

    Use a USB cable to connect a specific USB CDC ACM device (e.g., another Ameba board running the usbd_cdc_acm example code) to the USB port of this development board.

  • Serial Communication Test

    If the operation is normal after connection, the control transmission test and bulk transmission loopback test will be conducted in sequence. The serial port log will display the following success message:

    ```
    [ACM-I] Ctrl test PASS
    [ACM-I] Bulk loopback test start, loopback times:100, size: 1024
    [ACM-I] Bulk loopback test PASS
    ```
    

Ethernet Communication Host Solution

Overview

The Ethernet Control Model (ECM) within the USB Communication Device Class (CDC) defines a standard protocol for transmitting Ethernet frames over a USB interface. In Host mode, the system uses this protocol to identify and drive external USB network devices—such as USB-to-Ethernet adapters and LTE cellular modules—abstracting them into local network interfaces.

The Ameba platform integrates a USB-IF compliant CDC ECM Host protocol stack, capable of seamless integration with the system’s built-in LwIP network stack. This solution provides flexible network expansion capabilities, enabling devices to easily access wired LANs or 4G cellular networks via the USB interface.

Ameba connected to a USB-to-Ethernet Adapter

../../_images/usb_host_cdc_ecm_overview_user_rj45.svg

Ameba connected to a USB 4G Dongle

../../_images/usb_host_cdc_ecm_overview_user_lte.svg

Features

  • Supports standard CDC ECM devices (such as USB network cards, LTE modules)

  • Deep integration with the LwIP protocol stack, abstracted as a standard network interface (Netif)

  • Supports composite devices, capable of parallel processing of multiple interface functions (such as AT commands + networking)

  • Supports USB hot plugging

  • Optimize bulk transmission to provide high-performance data throughput

Application Scenarios

Acting as a USB Host, Ameba is responsible for enumerating, configuring, and loading drivers for connected CDC ECM devices. This allows users to flexibly extend connectivity without modifying board-level hardware designs. For example,

  • Wired Ethernet Expansion (USB-to-Ethernet): Ameba drives external standard USB-to-Ethernet adapters (supporting mainstream chip solutions like Realtek RTL815x) via the USB interface. This provides plug-and-play wired access for hardware platforms lacking native Ethernet ports.

  • 4G Cellular Access (LTE Dongle): Ameba connects to ECM-enabled 4G modules or USB dongles. The protocol stack converts USB data streams into standard network packets, enabling internet access via cellular networks. This is ideal for outdoor IoT gateways or vehicular terminals requiring mobile backhaul or Wi-Fi link backup.

  • Dual-Interface Routing and Bridging: By combining Ameba’s native Wi-Fi interface with the USB-expanded physical channel, the platform acts as a network hub. It can perform data forwarding, load balancing, or failover between interfaces, functioning as a simple wireless router or dual-port bridge.

Protocol Introduction

The Communication Device Class (CDC) is a general standard defined by the USB specification for communication devices. As a subclass, the Ethernet Control Model (ECM) specifically defines how to encapsulate and transmit Ethernet data frames over a USB interface, enabling a USB device to be recognized by the operating system as a standard Network Interface Controller (NIC).

Protocol Documentation

The USB-IF officially publishes the base CDC class protocol and the ECM subclass specification. Please refer to the following core documents during development:

Specification Type

Document

CDC 1.2 (Communication Class Base Protocol)

Download

ECM 1.2 (Ethernet Control Model)

Included as ECM120.pdf within the CDC 1.2 zip archive above.

Terminology

Common CDC ECM technical terms used in this document are defined as follows:

Term

Description

CCI (Communication Class Interface)

The interface used for managing device connection status, sending control commands, and receiving network status notifications (e.g., cable plug/unplug). It typically utilizes Control and Interrupt endpoints.

DCI (Data Class Interface)

The interface used for the actual transmission of Ethernet packets. It typically utilizes Bulk IN and Bulk OUT endpoints.

Ethernet Frame

The data payload transmitted by CDC ECM, usually following the IEEE 802.3 standard (containing Destination MAC, Source MAC, EtherType, and Payload).

Notification

Asynchronous events actively reported by the device to the host via the Interrupt endpoint, such as NETWORK_CONNECTION (link status change) or CONNECTION_SPEED_CHANGE.

Functional Descriptor

CDC-specific descriptors embedded within the standard configuration descriptor. They describe the specific capabilities of the network device (e.g., MAC address, maximum segment size).

Protocol Framework

The CDC ECM Host protocol stack adopts a layered architecture designed to decouple the USB transport layer from the LwIP network protocol stack.

../../_images/usb_cdc_ecm_spec_arch.svg
Component Responsibilities
  • Network Application Layer

    Located at the top of the architecture; represents specific network applications.

  • LwIP/Network Stack

    The network stack is unaware of underlying USB details. It operates on an abstract standard network interface (Netif) to provide network services to upper layers, handling TCP/IP, UDP, DHCP, and other network layer logic.

  • CDC ECM Class Driver

    The core middleware implementing the behavior defined by the ECM specification:

    • Enumeration Parsing: Parses CDC functional descriptors to retrieve the MAC address.

    • Control Management: Configures the Packet Filter and handles network status interrupt notifications.

    • Data Tx/Rx: Encapsulates pbufs from the network stack into USB transfer requests (URB/Transfer) and restores received USB packets into Ethernet frames for LwIP.

  • USB Core & HCD (Host Controller Driver)

    The low-level driver responsible for standard USB enumeration, endpoint management, and physical data transmission scheduling (e.g., DMA operations), abstracting hardware controller differences from the class driver.

Communication Mechanism

A standard CDC ECM device typically consists of two USB Interfaces, associated via a Union Functional Descriptor:

Communication Interface (CCI) - Control & Notification

  • Control Transfer

    • Mapped Endpoint: Default Control Endpoint 0.

    • Enumeration & Configuration: Transmits standard USB descriptors and CDC-specific functional descriptors.

    • Class-Specific Requests: Handles ECM protocol control commands. The most critical is SetEthernetPacketFilter, used to configure the device to receive broadcast, multicast, or unicast packets.

  • Interrupt Transfer

    • Interrupt IN: Mandatory. Used by the device to send Notifications to the host.

    • Typical Applications:

      NETWORK_CONNECTION: Reports cable insertion (Link Up) or removal (Link Down) status. CONNECTION_SPEED_CHANGE: Reports current upstream and downstream connection speeds.

Data Interface (DCI) - Bulk Pipes

The Data Interface typically contains two Alternate Settings, which is a key design point of the ECM protocol:

  • Alt Setting 0: Idle Mode. Contains no endpoints. When the network is disconnected or the driver is not loaded, the host should switch the interface to this mode to save bus bandwidth.

  • Alt Setting 1: Active Mode**. Contains a pair of Bulk endpoints (Bulk IN / Bulk OUT).

    • Bulk IN: Device -> Host. Uploads received network data packets.

    • Bulk OUT: Host -> Device. Sends Ethernet frames to be forwarded to the network.

Descriptor Structure

In the standard configuration descriptor, CDC ECM devices use Class-Specific Interface Descriptors to define network capabilities in detail. These are collectively referred to as Functional Descriptors.

CDC ECM Descriptor Topology

Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│       ├── Header Functional Descriptor
│       ├── Union Functional Descriptor
│       ├── Ethernet Networking Functional Descriptor
│       └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        ├── Endpoint Descriptor(Bulk In)
        └── Endpoint Descriptor(Bulk Out)

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│       ├── Header Functional Descriptor
│       ├── Union Functional Descriptor
│       ├── Ethernet Networking Functional Descriptor
│       └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        ├── Endpoint Descriptor(Bulk In)
        └── Endpoint Descriptor(Bulk Out)

Functional Descriptors Details

  • Header Functional Descriptor

Header Functional Descriptor
├── bLength (1 byte): Fixed 0x05
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x00 (HEADER)
└── bcdCDC (2 bytes): CDC specification version
  • Union Functional Descriptor

Union Functional Descriptor
├── bLength (1 byte): 0x05 + n (n = number of subordinate interfaces)
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x06 (UNION)
├── bControlInterface (1 byte): Master interface number
└── bSubordinateInterface[n] (n bytes): One or more slave interface numbers
  • Ethernet Networking Functional Descriptor

Ethernet Networking Functional Descriptor
├── bLength (1 byte): Total descriptor length
├── bDescriptorType (1 byte): 0x24 (CS_INTERFACE)
├── bDescriptorSubtype (1 byte): 0x0F
├── iMACAddress (1 byte): MAC address string index
├── bmEthernetStatistics (4 bytes): Supported statistics counters
├── wMaxSegmentSize (2 bytes): Maximum frame size (e.g., 1518)
├── wNumberMCFilters (2 bytes): Multicast filtering capability
└── bNumberPowerFilters (1 byte): Number of wake patterns

Class-Specific Requests

The CDC ECM Host driver controls device behavior by sending the following requests via Control Endpoint 0.

Request

Requirement

Description

SendEncapsulatedCommand

Optional

Sends an encapsulated command.

GetEncapsulatedResponse

Optional

Retrieves an encapsulated response.

SetEthernetMulticastFilters

Optional

Sets the multicast address filter list.

SetEthernetPowerManagementPatternFilter

Optional

Configures power management patterns (e.g., Wake-on-LAN/WoL).

GetEthernetPowerManagementPatternFilter

Optional

Retrieves the power management pattern filter.

SetEthernetPacketFilter

Mandatory

Sets the packet filter.

The host uses this command to notify the device which types of packets to receive (Unicast, Broadcast, Multicast, Promiscuous mode, etc.).

GetEthernetStatistic

Optional

Retrieves device transmission statistics (e.g., dropped packets, error frames).

Data Transmission Format

Data transmission in CDC ECM is straightforward. The USB Payload is the raw Ethernet Frame, without additional header encapsulation (unlike RNDIS or NCM). The figure below shows a full Ethernet frame (1514 bytes) being split into 3 USB packets for transmission.

../../_images/usb_cdc_ecm_spec_split_ethernet_packet.svg

Note

ZLP (Zero Length Packet)

If the length of the transmitted Ethernet frame is exactly a multiple of the endpoint’s wMaxPacketSize (e.g., 512 bytes in High Speed), the host or device must send a Zero Length Packet (ZLP) to indicate the end of the transfer.

Class Driver

This section details the internal implementation of the CDC ECM Host driver, covering the driver framework, supported class-specific requests, and endpoint resource allocation schemes.

Implementation Details

The USB Host CDC ECM driver stack adopts a modular design, enabling efficient interaction between the network protocol stack and the USB hardware controller via a layered architecture. This architecture ensures data transmission stability and system scalability.

The system is divided into several core modules based on functional responsibilities:

Network Protocol Stack Adapter Layer

Responsible for Encapsulation & Decapsulation of Network Layer Packets.

Acting as the interface layer between the USB driver and the TCP/IP stack (LwIP), it functions effectively as the Data Link Layer:

  • TX (Data Producer): Receives PBUF data packets from LwIP and converts them into USB transfer requests.

  • RX (Data Consumer): Receives Ethernet frames reported by the USB lower layer, encapsulates them into PBUFs, and submits them to LwIP for upper-layer protocol processing.

CDC ECM Driver Architecture

This is the core component of the CDC ECM class driver. It is fully compliant with the USB CDC ECM specification and implements the essential logic for interaction between the Host and the ECM device. Key responsibilities include:

  • Enumeration & Configuration Parsing: Identifies CDC communication and data interfaces, parses device descriptors, and extracts key parameters such as the MAC address.

  • Network Control Management: Manages device behavior via the Control Endpoint, such as configuring Packet Filters and multicast lists.

  • Data Tx/Rx Abstraction: Provides standardized data transmission/reception APIs, serving as a bridge between the upper network stack (LwIP) and the underlying USB transport.

  • Self-Healing Mechanism: Immediately triggers recovery processes upon detecting transmission anomalies (e.g., timeouts or blocking), ensuring automatic network connection restoration and uninterrupted communication.

USB Core Driver

Handles real-time hardware interrupts, standard USB enumeration, transfer management, and low-level physical data transmission scheduling.

Core Interaction Interfaces

The ECM Host class driver serves as a crucial bridge in the system architecture. Its implementation logic revolves around three core interaction interfaces:

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

  • Application Callback API: The class driver provides an asynchronous event notification mechanism to upper-layer applications via the usbh_cdc_ecm_state_cb_t callback structure.

  • Application API: When the application layer invokes these APIs, the driver transitions its internal state machine, thereby triggering and scheduling data transfers.

Loading and Unloading the Class Driver

These two functions are responsible for allocating and freeing memory resources, as well as registering and unregistering the class driver with the USB Core.

usbh_cdc_ecm_init() is the top-level function for loading the CDC ECM host class driver. It primarily performs the following tasks:

  • Saves the user-provided callback functions and invokes the user’s init callback.

  • Saves the ECM private data structure (usbh_cdc_ecm_priv_data_t) configured by the user.

  • Allocates the necessary memory resources for control transfers.

  • Parses the private data passed from the application layer and stores it internally for subsequent specific use cases.

  • Calls usbh_register_class() to register the cdc_ecm class driver with the USB Host Core.

Example:

int usbh_cdc_ecm_init(usbh_cdc_ecm_state_cb_t *cb, usbh_cdc_ecm_priv_data_t *priv)
{
    /* 1. Save the frame count param */
    if (priv == NULL) {
        USBH_ECM_FREE_MEM(cdc->led_array);
        cdc->led_cnt = 0;
    } else {
        if (priv->mac_value) {
            usbh_cdc_ecm_set_dongle_mac(priv->mac_value);
        }
        if ((priv->led_array != NULL) && (priv->led_cnt > 0)) {
            usbh_cdc_ecm_set_dongle_led_array(priv->led_array, priv->led_cnt);
        }
    }

    /* 2. Allocate memory */
    cdc->dongle_ctrl_buf = (u8 *)usb_os_malloc(CDC_ECM_MAC_STRING_LEN);

    /* 3. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
        cdc->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;
            }
        }
    }

    /* 4. Register class driver*/
    usbh_register_class(&usbh_cdc_ecm_driver);

    return HAL_OK;
}
Connection and Disconnection Handling

When the USB Core detects the attachment or detachment of a device matching the cdc_ecm class, it automatically triggers the corresponding callback functions.

usbh_cdc_ecm_attach is a critical step in the device enumeration phase. It parses interface descriptors and allocates pipe resources:

  • Find CDC Control Interface: Parses and retrieves the MAC address string index, interrupt endpoint information, etc.

  • Find CDC Data Interface: Parses and retrieves the interface index and pipe information for network data.

  • Open Pipes: Allocates and opens status detection pipes and data transmission/reception pipes based on the acquired descriptor information.

  • Initialize State Machine: Sets the device state to CDC_ECM_STATE_IDLE, indicating readiness to receive data.

  • Notify Application Layer: Invokes the user’s attach callback to report the connection status to the application layer.

static int usbh_cdc_ecm_attach(usb_host_t *host)
{
    /* 1. Parse descriptors to get Ctrl and Data information */
    status = usbh_cdc_ecm_parse_interface_desc(host);
    if (status) {
        return status;
    }

    /* 2. Open the pipe for steaming transfer */
    /* control in */
    pipe_info = &(cdc->intr_rx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
        pipe_info->buf = (u8 *)usb_os_malloc(pipe_info->pipe.ep_mps);
        pipe_info->buf_len = pipe_info->pipe.ep_mps;
        pipe_info->pipe.xfer_state = USBH_EP_XFER_START;
        cdc->intr_check_tick = pipe_info->pipe.ep_interval;
    }

    /* bulk out */
    pipe_info = &(cdc->bulk_tx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
    }

    /* bulk in */
    pipe_info = &(cdc->bulk_rx);
    if (pipe_info->valid) {
        usbh_open_pipe(host, &(pipe_info->pipe), &(pipe_info->ep_desc));
        /* ecm use bulk, the max ethernet packet size is 1542, malloc (512*3) to rx a whole ethernet packet*/
        pipe_info->buf = (u8 *)usb_os_malloc(USBH_CDC_ECM_BULK_BUF_MAX_SIZE);
        pipe_info->buf_len = USBH_CDC_ECM_BULK_BUF_MAX_SIZE;
        pipe_info->pipe.xfer_state = USBH_EP_XFER_START;
    }

    /* 3. Initialize the state machine */
    cdc->state = CDC_ECM_STATE_IDLE;

    /* 4. Notify the user layer */
    if ((cdc->cb != NULL) && (cdc->cb->attach != NULL)) {
        cdc->cb->attach();
    }
    return HAL_OK;
}
Class Driver State Machine

The usbh_cdc_ecm_process callback function acts as the central state machine processor for the host-side CDC ECM class.

Unlike the device side, which passively responds to requests, the host-side driver must proactively maintain the device state. Its core duty is to manage the lifecycle of the class driver and orchestrate transfer tasks.

State Machine Management and Scheduling

usbh_cdc_ecm_process coordinates control transfers (e.g., retrieving the MAC address) and data transfers based on the current state of the class driver.

State Enum

Description

Key Actions

CDC_ECM_STATE_IDLE

Idle State

Waits for user commands or data transfer requests.

CDC_ECM_STATE_PRE_SETTING

Control Transfer

Invokes corresponding control processing functions based on the current stage (e.g., Get MAC, Set MAC).

CDC_ECM_STATE_TRANSFER

Data Transfer

Dispatches tasks to specific TX/RX processing functions based on the Pipe ID.

CDC_ECM_STATE_ERROR

Error State

Attempts to send a Clear Feature request to recover communication.

Control Transfer Dispatching Example

When in the CDC_ECM_STATE_PRE_SETTING state, the state machine dispatches tasks to specific control transfer processing functions according to the triggering event.

Example:

case CDC_ECM_STATE_PRE_SETTING:
    /* Ctrl Request */
    req_status = usbh_cdc_ecm_ctrl_setting(host);
    if (req_status == HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "ECM alt setting finish %d\n", cdc->intr_rx.pipe.pipe_num);
        cdc->state = CDC_ECM_STATE_TRANSFER;
        usbh_notify_class_state_change(host, cdc->intr_rx.pipe.pipe_num);
    } else {
        usbh_notify_class_state_change(host, 0);
    }
break;

Data Transfer Dispatching Example

When in the CDC_ECM_STATE_TRANSFER state, the state machine dispatches data processing tasks to specific TX/RX handling functions based on the triggered Pipe ID.

Example:

case CDC_ECM_STATE_TRANSFER:
    /* Bulk Tx Pipe */
    if (msg == cdc->bulk_tx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_bulk_out(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->bulk_tx.pipe.pipe_num);
        }
    }
    /* Bulk Rx Pipe */
    else if (msg == cdc->bulk_rx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_bulk_in(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->bulk_rx.pipe.pipe_num);
        }
    }
    /* Intr In Pipe */
    else if (msg == cdc->intr_rx.pipe.pipe_num) {
        cdc->next_xfer = 0;
        usbh_cdc_ecm_process_intr_in(host);
        if (cdc->next_xfer) {
            usbh_notify_class_state_change(host, cdc->intr_rx.pipe.pipe_num);
        }
    }
    break;

Error Recovery Mechanism

Should an error occur during other state processing operations, the driver will attempt to send a Clear Feature request to the device in an effort to restore communication and return to the IDLE state.

Example:

/* ... Error state ... */
case CDC_ECM_STATE_ERROR:
    /* Error recovery mechanism */
    ret = usbh_ctrl_clear_feature(host, 0x00U);
    if (ret == HAL_OK) {
        cdc->xfer_state = CDC_ECM_STATE_IDLE;
    }
break;
Data Transfer Processing

CDC ECM data interaction is broadly divided into two categories: CDC Control Transfer and CDC Data Transfer.

CDC Control Transfer

Responsible for managing and configuring the functional behavior of the CDC ECM device. The driver implements the following state machine flows to process various configuration requests:

case CDC_ECM_STATE_GET_MAC_STR:
    /* get Mac String */
    state = usbh_cdc_ecm_get_mac_str(host);
    if (state == HAL_OK) {
        cdc->sub_status ++;
    } else if (state != HAL_BUSY) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "Get MAC fail error[%d]\n", state);
        usb_os_sleep_ms(10);
    }
    break;

CDC-DATA Transfer:

CDC ECM network data stream transmission encompasses the reporting of network status and the actual transmission/reception of network data. Network status transfer utilizes Interrupt (INTR) transfers, whereas network data transfer relies on Bulk (BULK) transfers.

Code example(Logic for retrieving network status via INTR transfer):

static int cdc_ecm_cb_intr_receive(u8 *buf, u32 length)
{
    if (buf && length >= 8) {
        if (length == 8 && buf[0] == 0xA1 && buf[1] == CDC_ECM_NOTIFY_NETWORK_CONNECTION) {
            /* parse to get the value */
        }
        else if (length == 16 && buf[0] == 0xA1 && buf[1] == CDC_ECM_NOTIFY_CONNECTION_SPEED_CHANGE) {
            /*/* parse to get the value */
        }
    }
    return HAL_OK;
}

static void usbh_cdc_ecm_process_intr_in(usb_host_t *host)
{
    usbh_pipe_t *ep = &(cdc->intr_rx->pipe);

    switch (ep->xfer_state) {
    case USBH_EP_XFER_START:
        /* 1. Do Transfer */
        ep->xfer_buf = pipe_info->buf;
        ep->xfer_len = pipe_info->buf_len;
        usbh_transfer_data(host, ep);
        ep->tick = usbh_get_tick(host);
        pipe_info->busy_tick = usbh_get_tick(host);
        ep->xfer_state = USBH_EP_XFER_BUSY;
        break;

    case USBH_EP_XFER_BUSY:
        /* 2. Check the urb state */
        urb_state = usbh_get_urb_state(host, ep);
        switch (urb_state) {
        case USBH_URB_DONE:
            /* 2.1. Xfer Complete */
            len = usbh_get_last_transfer_size(host, ep);
            if (len > 0) {
                cdc_ecm_cb_intr_receive(ep->xfer_buf, len);
            }
            break;

        case USBH_URB_BUSY:
            /* 2.2 Handle urb busy status */
            if (usbh_get_elapsed_ticks(host, pipe_info->busy_tick) >= cdc->intr_check_tick) {
                ep->xfer_state = USBH_EP_XFER_IDLE;
            }
            break;

        case USBH_URB_ERROR:
        case USBH_URB_STALL:
            /* 2.3 Handle error status */
            ep->xfer_state = USBH_EP_XFER_IDLE;
            break;

        case USBH_URB_IDLE:
            /* 2.4 Check idle time */
            if (usbh_get_elapsed_ticks(host, ep->tick) >= cdc->intr_check_tick) {
                ep->xfer_state = USBH_EP_XFER_IDLE;
            }
            break;
        }
        break;
    }
}

Application APIs

These interfaces are exposed for upper-layer applications to retrieve device information, configure operational parameters, and initiate data transfers.

Configuration Interfaces

Data Stream Interfaces

  • usbh_cdc_ecm_send_data(): Dispatches network data packets to the device, returning upon completion of the transmission.

Class-Specific Request Implementation

The driver stack strictly adheres to the USB CDC ECM 1.2 specification, encapsulating the composition and transmission processes for core Class-Specific Requests.

The driver layer implements default logic for SetEthernetPacketFilter, SetEthernetMulticastFilters, and GetEthernetStatistic. Developers can refer to the source code to implement extensions for other request types.

Source Path: {SDK}/component/usb/host/cdc_ecm

Request Type

Notes

SetEthernetPacketFilter

Sets the Ethernet Packet Filter

Used to control which types of network packets the device receives (e.g., Unicast, Broadcast, Multicast, or Promiscuous Mode).

SetEthernetMulticastFilters

Sets the Multicast Address Filter

When the upper network stack (e.g., IGMP) needs to listen to specific multicast groups, this request pushes the list of Multicast MAC addresses down to the device.

GetEthernetStatistic

Retrieves Ethernet Statistics

Used to read internal statistical counters maintained by the device (e.g., successful TX/RX frames, CRC errors, dropped packets). This is typically used for network diagnostics.

Endpoint Configuration

During the device enumeration phase, the CDC ECM Host driver parses the configuration descriptor and automatically looks up and allocates corresponding endpoint resources based on interface types to establish complete communication pipes.

Pipe Type

Description

Control IN/OUT

Default Control Endpoint 0 (EP0).

Used for sending Standard USB Requests and CDC Class-Specific Control Requests (e.g., SetEthernetPacketFilter).

Interrupt IN

Interrupt Input Endpoint.

Belongs to the Communication Interface (CCI). Used to receive active network notifications reported by the device (e.g., NetworkConnection for cable plug/unplug status).

Bulk IN

Bulk Input Endpoint.

Belongs to the Data Interface (DCI). Used to receive network data packets uploaded by the device (RX Data).

Bulk OUT

Bulk Output Endpoint.

Belongs to the Data Interface (DCI). Used to send network data packets to the device (TX Data).

API Reference

For detailed function prototypes and usage, please refer to the Device class driver API

Application Example

CDC ECM Host Driver Development Guide

This section provides a detailed guide to the full development process for the CDC ECM Host driver, covering driver initialization, hotplug management, network data transmission/reception mechanisms, and resource release.

Driver Initialization

Before using the CDC ECM Host driver, developers must define the configuration structure, register callback functions, and then sequentially call the core interfaces to start the USB Host Controller and the ECM Class Driver.

Step-by-Step Description:

  • Hardware Configuration: Set the USB speed mode (High Speed/Full Speed) and configure interrupt/task priorities.

  • Callback Registration: Define user callback structures to hook processing functions for various stages (Connect, Disconnect, Data Transfer).

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

  • Class Driver Loading: Call usbh_cdc_ecm_init() to initialize the CDC ECM Class Driver.

/*
 * 1. Configure USB speed (High Speed or Full Speed) ,isr priority and main task priority.
 */
static usbh_config_t usbh_ecm_cfg = {
        .speed = USB_SPEED_HIGH,
        .isr_priority = USBH_ECM_ISR_PRIORITY,
        .main_task_priority = USBH_ECM_MAIN_THREAD_PRIORITY,
};
/*
 * Define USB user-level callbacks.
 */
static usbh_user_cb_t usbh_ecm_usr_cb = {
        .process = cdc_ecm_cb_process,            /* USB callback to handle class-independent events in the application */
        .validate = cdc_ecm_cb_device_check,      /* USB callback to validate a device when multiple devices are connected to a hub */
};

/*
 * 2. Define user callbacks for CDC ECM events.
 */
static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .init   = cdc_ecm_cb_init,                /* USB init callback */
        .deinit = cdc_ecm_cb_deinit,              /* USB deinit callback */
        .attach = cdc_ecm_cb_attach,              /* USB attach callback */
        .detach = cdc_ecm_cb_detach,              /* USB detach callback */
        .setup  = cdc_ecm_cb_setup,               /* USB setup callback */
        .bulk_send     = cdc_ecm_cb_bulk_send,    /* Data transmission complet callback */
        .bulk_received = cdc_ecm_cb_bulk_receive, /* Data transmission complet callback */
        .intr_received = cdc_ecm_cb_intr_receive, /* Data transmission complet callback */
};

int ret = 0;

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

/*
 * 4. Initialize CDC ECM class driver.
 */
ret = usbh_cdc_ecm_init(&cdc_ecm_usb_cb);
if (ret != HAL_OK) {
        /* If class driver init fails, clean up the core driver */
        usbh_deinit();

        return;
}
Hotplug Event Handling

As a host, the system must robustly handle the dynamic insertion and removal of USB network adapters. The SDK natively supports hotplug mechanisms and coordinates with the LwIP protocol stack to manage network interface states (e.g., DHCP restart, default route switching).

Handling Logic:

  • Device Detach: Triggers a callback to release a semaphore. The application thread captures this signal, executes de-initialization, and frees heap memory.

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

  • Link Status Maintenance: Monitors the physical link (Link Up/Down) status. Based on the status, it triggers DHCP requests or releases the IP and configures routing.

/* USB detach callback */
static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .detach = cdc_ecm_cb_detach,
};

/* Callback executed in main task */
static void cdc_ecm_cb_detach(void)
{
        cdc_ecm_is_ready = 0;
        usb_os_sema_give(cdc_ecm_detach_sema);
}

static int usbh_cdc_ecm_doinit(void)
{
        int status;

        status = usbh_init(&usbh_ecm_cfg, &usbh_ecm_usr_cb);
        if (status != HAL_OK) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "Host init fail %d\n", status);
                return USBH_CORE_INIT_FAIL;
        }

        status = usbh_cdc_ecm_init(&cdc_ecm_usb_cb, params);
        if (status != HAL_OK) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "Init driver fail %d\n", status);
                return USBH_CLASS_INIT_FAIL;
        }

        return HAL_OK;
}

/* Thread Context: Handle the state machine */
static void ecm_hotplug_thread(void *param)
{
        int ret = 0;

        UNUSED(param);

        for (;;) {
                usb_os_sema_take(cdc_ecm_detach_sema, USB_OS_SEMA_TIMEOUT);
                RTK_LOGS(TAG, RTK_LOG_INFO, "Hot plug\n");

                /* Stop transfer, release resource */
                usbh_cdc_ecm_deinit();
                usbh_deinit();
                usbh_cdc_ecm_tx_status_check();
                usbh_ecm_timer_unregister();

                RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap size: 0x%08x\n", usb_os_get_free_heap_size());

                /* Re-init */
                ret = usbh_cdc_ecm_doinit();
                if (ret != HAL_OK) {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init fail %d\n", ret);
                        break;
                }
        }
}

/**
  * Get ecm device connect status
  */
int usbh_cdc_ecm_get_connect_status(void)//1 up
{
        u8 ret1 = cdc_ecm_is_ready;
        u8 ret2 = ecm_hw_connect;
        int ret = ret1 & ret2;

        return ret;;
}

/* Link Status Task */
static void ecm_link_change_thread(void *param)
{
        u8 *mac;
        u32 dhcp_status = 0;
        u8 link_is_up = 0;
        eth_state_t ethernet_unplug = ETH_STATUS_IDLE;

        UNUSED(param);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Enter link status task!\n");

        cdc_ecm_do_init();

        while (1) {
                link_is_up = usbh_cdc_ecm_get_connect_status();

                if (1 == link_is_up && (ethernet_unplug < ETH_STATUS_INIT)) {   // unlink -> link
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Do DHCP\n");

                        ...

                        dhcp_status = LwIP_IP_Address_Request(NETIF_ETH_INDEX);
                        if (DHCP_ADDRESS_ASSIGNED == dhcp_status) {
                                netifapi_netif_set_default(pnetif_eth); //Set default gw to ether netif
                                RTK_LOGS(TAG, RTK_LOG_INFO, "Switch to link\n");
                        } else {
                                RTK_LOGS(TAG, RTK_LOG_INFO, "DHCP Fail\n");
                        }
                } else if (0 == link_is_up && (ethernet_unplug >= ETH_STATUS_INIT)) {   // link -> unlink
                        ethernet_unplug = ETH_STATUS_DEINIT;
                        netif_set_default(pnetif_sta);  //switch to other netif
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Swicth to unlink\n");
                } else {
                        rtos_time_delay_ms(1000);
                }
        }
}
Ethernet Output Process (TX)

When the upper LwIP protocol stack has packets to send, it calls the usbh_cdc_ecm_send_data() interface provided by the USB CDC ECM Class Driver.

Process Description:

  • Submit Request: LwIP calls the send function; the driver layer calls usbh_cdc_ecm_bulk_send() to submit a Bulk OUT transfer request.

  • Wait for Completion: The driver blocks via usb_os_sema_take(), waiting for the transfer completion semaphore.

  • USB Transfer: The USB Core handles the low-level DMA transfer, sending data to the device.

  • Callback Release: Upon transfer completion, the cdc_ecm_cb_bulk_send() callback is triggered, releasing the semaphore.

  • Return to Upper Layer: The function acquires the semaphore and returns to LwIP, indicating the transmission is complete.

static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
        .bulk_send     = cdc_ecm_cb_bulk_send,
};

/* USB transfer finish callback */
static int cdc_ecm_cb_bulk_send(usbh_urb_state_t state)
{
        usb_os_sema_give(cdc_ecm_tx_sema);
        if (state != USBH_URB_DONE) {
                RTK_LOGS(TAG, RTK_LOG_ERROR, "BULK TX fail %d\n", state);
        }

        return HAL_OK;
}

/* Tansfer APi, used for LwIP */
int usbh_cdc_ecm_send_data(u8 *buf, u32 len)
{
        int ret;
        u8 retry_cnt = 0;

        while (1) {
                ret = usbh_cdc_ecm_bulk_send(buf, len);
                if (ret == HAL_OK) {
                        //success
                        break;
                }
                if (++retry_cnt > 100) {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "TX drop(%d)\n", len);
                        ret = HAL_ERR_UNKNOWN;
                        break;
                } else {
                        usb_os_delay_ms(1);
                }
        }

        /* *wait cdc_ecm_cb_bulk_send to give the sema */
        if (ret == HAL_OK) {
                usb_os_sema_take(cdc_ecm_tx_sema, USB_OS_SEMA_TIMEOUT);
        }

        return ret;
}
Ethernet Input Process (RX)

When the USB network adapter receives network data, it uploads it to the host via the Bulk IN endpoint.

Process Description:

  • Register Callback: During initialization, ethernetif_mii_recv() is registered as the reception handler for the Class Driver.

  • USB Reception: After the low-level driver completes a Bulk IN transfer, cdc_ecm_cb_bulk_receive() is triggered.

  • Data Delivery: The callback function invokes ethernetif_mii_recv().

  • PBUF Encapsulation: LwIP pbuf is allocated, and the received raw data is copied into the pbuf payload.

  • Submit to Stack: The netif->input() function is called to deliver the packet to the TCP/IP stack for processing.

static usbh_cdc_ecm_state_cb_t cdc_ecm_usb_cb = {
    .bulk_received = cdc_ecm_cb_bulk_receive,
};

report_data = ethernetif_mii_recv;

static int cdc_ecm_cb_bulk_receive(u8 *buf, u32 length)
{
    if ((report_data != NULL) && (length > 0)) {
        report_data(buf, length);
    }

    return HAL_OK;
}

void ethernetif_mii_recv(u8 *buf, u32 frame_len)
{
    struct eth_drv_sg sg_list[MAX_ETH_DRV_SG];
    struct pbuf *p, *q;
    u32 total_len = frame_len;
    struct netif *netif = pnetif_eth;

    memcpy((u8*)RX_BUFFER, buf, frame_len);

    /* Allocate buffer to store received packet */
    p = pbuf_alloc(PBUF_RAW, total_len, PBUF_POOL);
    if (p == NULL) {
        RTK_LOGW(TAG, "\n\r[%s]Cannot allocate pbuf to receive packet(%d)\n", __func__, total_len);
        return;
    }

    /* Create scatter list */
    for (q = p; q != NULL && sg_len < MAX_ETH_DRV_SG; q = q->next) {
        sg_list[sg_len].buf = (unsigned int) q->payload;
        sg_list[sg_len++].len = q->len;
    }
    rltk_mii_recv(sg_list, sg_len);

    /* Pass received packet to the interface */
    if (ERR_OK != netif->input(p, netif)) {
        pbuf_free(p);
    }
}
Driver Unloading

When the system shuts down or switches modes, resources should be released in the reverse order of initialization.

/* 1. Deinitialize CDC ECM class driver first */
usbh_cdc_ecm_deinit();

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

Usage Example

This section demonstrates how to configure the Ameba development board as a USB CDC ECM Host and access the network via an external USB-to-Ethernet Adapter.

The Ameba identifies the Dongle, obtains an IP address via DHCP, and performs Ping and iPerf tests.

Example Path: {SDK}/component/example/usb/usbh_cdc_ecm (Provides a complete design reference for developers implementing network routing features).

Configuration & Compilation
  • Compile and Flash

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

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

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] CDC ECM
            Select USB Ethernet (USB Ethernet)  --->
    
Result Verification
  • Start Device

    Reboot the development board. The serial log should show successful initialization:

[ECM-I] USBH ECM demo start
  • Connect Network Adapter

    Insert the USB Network Adapter into the development board and connect the Ethernet cable.

    Hardware Topology:

../../_images/usb_host_cdc_ecm_example_hw_top.svg
  • Function Test

    • Query Network Status (AT+WLSTATE)

      Check if the IP address has been successfully obtained. See AT+WLSTATE for details.

    AT+WLSTATE
    
    [+WLSTATE]: _AT_WLAN_INFO_
    WLAN0 Status: Running
    ==============================
    ....
    
    Interface ethernet
    ==============================
    MAC => 00:e0:4c:58:64:18
    IP  => 192.168.31.87
    GW  => 192.168.31.1
    MSK  => 255.255.255.0
    
    • Ping Test (AT+PING)

      Test connectivity with the gateway. See Ping Test for details.

    AT+PING=192.168.31.1
    
    [+PING]: _AT_WLAN_PING_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2978368
    
    #
    
    [ping_test] PING 192.168.31.1 32(60) bytes of data
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=1 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=2 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=3 time=0 ms
    
    [ping_test] 32 bytes from 192.168.31.1: icmp_seq=4 time=0 ms
    
    [ping_test] 4 packets transmitted, 4 received, 0% packet loss, average 0 ms
    [ping_test] min: 0 ms, max: 0 ms
    
    • Uplink Input Test (RX Test)

      Ameba: Server (Receiver) PC: Client (Sender) See AT+IPERF for details.

    EVB:
    
    AT+IPERF=-s,-i,1
    [+IPERF]: _AT_WLAN_IPERF1_TCP_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2977792
    
    #
    Start TCP server! id = [0]
    tcp_server_func: Create socket fd = 0
    tcp_server_func: Bind socket successfully
    tcp_server_func: Listen port 5001
    tcp_server_func: Accept connection successfully
    tcp_s: id[0] Receive 9383 KBytes in 1000 ms, 76866 Kbits/sec
    tcp_s: id[0] Receive 11391 KBytes in 1000 ms, 93323 Kbits/sec
    tcp_s: id[0] Receive 11400 KBytes in 1000 ms, 93393 Kbits/sec
    tcp_s: id[0] Receive 11393 KBytes in 1000 ms, 93334 Kbits/sec
    tcp_s: id[0] Receive 11324 KBytes in 1000 ms, 92774 Kbits/sec
    tcp_s: [END] id[0] Totally receive 55040 KBytes in 5013 ms, frame_num = 38604, 89943 Kbits/sec
    TCP server stopped!
    
    PC:
    
    C:\Users\test\Desktop>iperf-2-1-9-win.exe -c 192.168.31.87 -i 1 -t 5
    ------------------------------------------------------------
    Client connecting to 192.168.31.87, TCP port 5001
    TCP window size: 64.0 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.31.234 port 53419 connected with 192.168.31.87 port 5001
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.00-1.00 sec  9.25 MBytes  77.6 Mbits/sec
    [  1] 1.00-2.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 2.00-3.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 3.00-4.00 sec  11.1 MBytes  93.3 Mbits/sec
    [  1] 4.00-5.00 sec  11.0 MBytes  92.3 Mbits/sec
    [  1] 0.00-5.01 sec  53.8 MBytes  89.9 Mbits/sec
    
    • Downlink Output Test (TX Test)

      Ameba: Client (Sender) PC: Server (Receiver) See AT+IPERF for details.

    EVB:
    
    AT+IPERF=-c,192.168.31.234,-i,1,-t,5
    [+IPERF]: _AT_WLAN_IPERF1_TCP_TEST_
    
    OK
    
    [MEM] After do cmd, available heap 2977792
    
    #
    Start TCP client! id = [0]
    tcp_client_func: Server IP=192.168.31.234, port=5001
    tcp_client_func: Create socket fd = 0
    tcp_client_func: Connect to server successfully
    tcp_c: id[0] Send 11317 KBytes in 1000 ms, 92715 Kbits/sec
    tcp_c: id[0] Send 11326 KBytes in 1000 ms, 92785 Kbits/sec
    tcp_c: id[0] Send 11343 KBytes in 1000 ms, 92926 Kbits/sec
    tcp_c: id[0] Send 11379 KBytes in 1000 ms, 93218 Kbits/sec
    tcp_c: id[0] Send 11332 KBytes in 1000 ms, 92832 Kbits/sec
    tcp_c: [END] id[0] Totally send 56709 KBytes in 5001 ms, 92893 Kbits/sec
    tcp_client_func: Close client socket
    TCP client stopped!
    
    PC:
    
    Desktop>iperf-2-1-9-win.exe -s -i 1
    ------------------------------------------------------------
    Server listening on TCP port 5001
    TCP window size: 64.0 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.31.234 port 5001 connected with 192.168.31.87 port 56481
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.00-1.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 1.00-2.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 2.00-3.00 sec  11.1 MBytes  92.9 Mbits/sec
    [  1] 3.00-4.00 sec  11.1 MBytes  93.2 Mbits/sec
    [  1] 4.00-5.00 sec  11.1 MBytes  92.8 Mbits/sec
    [  1] 0.00-5.00 sec  55.4 MBytes  92.9 Mbits/sec
    

Mass Storage Host Solution

Overview

The USB Mass Storage Class (MSC) protocol establishes a standard communication specification between a host and mass storage devices.

Ameba has implemented a complete USB MSC host protocol stack based on the official MSC protocol standards released by the USB-IF. It supports interaction with MSC devices via the SCSI (Small Computer System Interface) command set, enabling stable and high-speed file reading and writing.

../../_images/usb_host_msc_overview.svg

Features

  • Supports standard MSC devices (such as U-disks, SD card readers, etc.)

  • The maximum supported storage capacity is 32GB

  • Supports read and write operations of the FAT32 file system

  • Supports USB hot-plug

  • Automatically parses descriptors and adapts to speed modes

  • Configurable parameters such as BULK transfer length

Application Scenarios

As a USB storage host, Ameba can mount external mass storage devices via the USB interface. By combining this with network technologies to implement various data interactive applications. For example,

  • Smart Network Storage / Private Cloud: Combines network technology to bridge local storage with cloud connectivity, supporting automatic bidirectional file synchronization and secure remote access based on DDNS/VPN, to build a lightweight home private cloud.

  • Industrial IoT Gateway: Caches sensor data or serial logs to USB storage devices in unstable network environments (e.g., mines, offshore), and automatically executes resume-from-break uploads to the cloud once connectivity is restored, ensuring the integrity and reliability of industrial data.

  • AI Media Library & Storage Expansion: Utilizes external storage to house massive media files or AI knowledge bases, overcoming internal Flash capacity limitations to enable local offline retrieval and low-cost functional expansion.

  • Hardware Security Token: During startup or privilege verification, the device scans a connected U disk for specific encrypted certificates or License files; upon successful validation, it activates advanced system privileges, acting as a physically isolated, low-cost hardware authorization lock.

Protocol Introduction

The MSC protocol defines the transmission and control functionalities required to implement storage devices under the USB specification. Common devices that adhere to this standard include USB flash drives, external hard drives, and card readers.

Descriptor Structure

In addition to complying with standard USB descriptor specifications (such as Device Descriptors, Configuration Descriptors, and Endpoint Descriptors), MSC devices are required to:

  • Declare the communication protocol used (e.g., SCSI) via the Interface Descriptor.

  • Encapsulate transmission commands and data through bulk endpoints.

The following section illustrates a standard USB MSC descriptor structure based on the Bulk-Only Transport (BOT) mode utilizing the SCSI command set:

Device Descriptor
└── Identifies basic device information

Configuration Descriptor
└── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
    ├── bInterfaceClass: 0x08 (Mass Storage)
    ├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
    ├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
    └── bNumEndpoints: 2 (2 Endpoints)
        ├── Endpoint Descriptor(BULK IN)
        └── Endpoint Descriptor(BULK OUT)

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor(Interface 0, Alternate Setting 0)
    ├── bInterfaceClass: 0x08 (Mass Storage)
    ├── bInterfaceSubClass: 0x06 (SCSI transparent command set)
    ├── bInterfaceProtocol: 0x50 (Bulk-Only Transport)
    └── bNumEndpoints: 2 (2 Endpoints)
        ├── Endpoint Descriptor(BULK IN)
        └── Endpoint Descriptor(BULK OUT)

Protocol document

The USB-IF has officially released the MSC-class basic protocol and specifications for the BOT transfer protocol. During the development process, please refer to the following core documents:

Software and Hardware Architecture

The figure below illustrates the software and hardware layers that commands and data traverse between the host and the device.

../../_images/usb_msc_arch.svg

Taking a read operation as an example, when a user reads a file from a U disk, the process is as follows:

  • Host Side:

    • Application Request: The user initiates a file read request within an application (e.g., File Manager).

    • File System Conversion: The file system converts the filename and offset into a Logical Block Address (LBA) read request and generates a standard SCSI READ command.

    • Protocol Encapsulation: The host MSC class driver encapsulates the SCSI command into the command packet format defined by the MSC protocol.

    • Hardware Transmission: The USB host controller driver transmits the data packet to the bus via the physical USB port.

  • Device Side:

    • Hardware Reception: The USB device controller receives the data packet from the physical port.

    • Protocol Parsing: The MSC device class driver verifies the integrity of the packet and parses the encapsulated SCSI command.

    • Media Access: Based on the parsed command parameters (such as LBA address and length), data is read from the underlying storage medium (e.g., an SD card).

    • Data and Status Return: The read data is returned to the host, followed by the command execution status response.

BOT Transmission Flow

Once the MSC device is connected to the host and enumeration is complete, if it is identified as a Mass Storage device supporting BOT (Bulk-Only Transport) mode, all subsequent data communication occurs exclusively through bulk endpoints. Bulk transfers are not strictly time-critical, ensuring maximum data integrity.

According to the MSC BOT transmission protocol specification, all transfers follow a three-stage “Command -> Data -> Status” flow:

  • CBW (Command Block Wrapper): Sent from the host to the device. It encapsulates the specific SCSI command (e.g., READ, WRITE, INQUIRY).

  • Data (Data Stage): Transfers the actual file or control data. (The direction depends on the SCSI command type; for commands without data interaction, this stage is omitted).

  • CSW (Command Status Wrapper): Sent from the device to the host. It reports the execution result of the previous CBW command (Success, Failure, or Phase Error).

../../_images/usb_msc_bot_flow.svg

The data transmission process is as follows:

  • Host Initiates Request: The host MSC class driver encapsulates the SCSI command into a CBW packet and sends it to the device via the Bulk OUT endpoint.

  • Device Parsing and Execution: The device receives the CBW packet, performs a validity check, and parses the SCSI command. If the CBW is valid, the device operates on the underlying physical storage medium according to the command:

    • Write Operation (e.g., WRITE): Receives the data stream sent by the host via the Bulk OUT endpoint and writes it to the storage medium.

    • Read Operation (e.g., READ): Reads data from the storage medium and transmits it back to the host via the Bulk IN endpoint.

    • No-Data Command (e.g., TEST UNIT READY): Skips the data stage and proceeds directly to the status stage.

  • Device Returns Status: After data transmission is complete (or if no data transmission is required), the device sends a CSW packet via the Bulk IN endpoint to report the command execution result to the host.

  • Host Confirms Completion: The host parses the received CSW packet and checks the bCSWStatus field to confirm whether the command was executed successfully, thereby concluding the operation.

Class-Specific Requests

Control requests for MSC devices are categorized into Standard Requests and Class-Specific Requests.

This section focuses on the Class-Specific Requests unique to the MSC BOT specification. These requests are used to implement specific storage functions. There are only two such requests:

MSC Class-Specific Request

Requirement

Description

Bulk-Only Mass Storage Reset

Mandatory

Resets the device interface and associated endpoints.

Get Max LUN

Mandatory

Queries the maximum number of Logical Units supported by the device.

SCSI Commands

The primary MSC BOT SCSI commands are listed below:

SCSI Command

Requirement

Description

INQUIRY

Mandatory

The first command sent by the host to query device information.

REQUEST_SENSE

Mandatory

Sent by the host to retrieve detailed error information whenever a command fails.

TEST_UNIT_READY

Mandatory

Checks if the device is ready.

READ_CAPACITY(10)

Mandatory

Queries the capacity of the storage medium.

READ(10)

Mandatory

The core command for reading data.

WRITE(10)

Mandatory

The core command for writing data.

MODE_SENSE(6)

Optional

Queries specific device parameters, such as caching policy or write- protection status.

ALLOW_MEDIUM_REMOVAL

Optional

Used to allow or prevent medium removal, implementing the “Safely Remove Hardware” feature.

START_STOP_UNIT

Optional

Used to load or eject the medium.

VERIFY(10)

Optional

Requests the device to verify that specified blocks are readable without transferring data.

READ_FORMAT_CAPACITIES

Optional

Provides more detailed capacity and format information than READ_CAPACITY.

Note

For detailed information on the SCSI standard, please refer to the official documentation.

Class Driver

The USB protocol stack provides a standard MSC host class driver based on the BOT (Bulk-Only Transport) protocol, utilizing the SCSI command set.

Pipe Configuration

Count

Description

2

Control IN/OUT Transfer

1

Bulk IN Transfer

1

Bulk OUT Transfer

Class-Specific Request Implementation

The driver implements two Class-Specific Requests for MSC.

MSC Class-Specific Request

Description

Bulk-Only Mass Storage Reset

The host requests a reset of the transfer when a BOT transfer error occurs.

Get Max LUN

After the device is connected, the host queries the number of Logical Units supported by the device.

SCSI Command Implementation

The SCSI commands implemented by the driver under the MSC BOT specification are listed below. Developers can refer to existing implementations to extend other commands.

  • INQUIRY

  • REQUEST_SENSE

  • TEST_UNIT_READY

  • READ_CAPACITY(10)

  • READ(10)

  • WRITE(10)

FatFS Disk Driver API

The class driver defines a FatFS disk driver USB_disk_Driver typed with ll_diskio_drv:

API

Description

disk_initialize

Initializes USB disk

disk_deinitialize

Deinitializes USB disk

disk_status

Returns disk status (RES_OK/RES_ERROR)

disk_read

Reads data from USB disk (sector-aligned)

disk_write

Writes data to USB disk (requires _USE_WRITE set in FatFS)

disk_ioctl

Implements FatFS IO control commands:

  • CTRL_SYNC: No-op (USB implements write-through)

  • GET_SECTOR_COUNT: Returns total sectors

  • GET_SECTOR_SIZE: Returns sector size

  • GET_BLOCK_SIZE: Returns erase block size

API Reference

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

Application Example

Application Design

Driver Initialization

Use the following example code to define the configuration structure and callback functions, then invoke the initialization interface to initialize the USB host core and the MSC class driver.

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_msc_cb_t usbh_msc_usr_cb = {
  .attach = usbh_msc_cb_attach,                  /* USB device attach callback */
  .setup = usbh_msc_cb_setup,                    /* USB device setup done, indicate that device is ready for bulk transfer */
};

static usbh_user_cb_t usbh_usr_cb = {
  .process = usbh_msc_cb_process
};

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

ret = usbh_msc_init(&usbh_msc_usr_cb);          /* Initializes the MSC host class with MSC class user callback. */
if (ret != HAL_OK) {
    usbd_msc_disk_deinit();
    return;
}
File Read And Write

Perform file read and write operations on the device after initialization.

static rtos_sema_t msc_attach_sema;
static __IO int msc_is_ready = 0;

static int usbh_msc_cb_attach(void)
{
  RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACH\n");
  rtos_sema_give(msc_attach_sema);
  return HAL_OK;
}

static int usbh_msc_cb_setup(void)
{
  RTK_LOGS(TAG, RTK_LOG_INFO, "SETUP\n");
  msc_is_ready = 1;
  return HAL_OK;
}

static int usbh_msc_cb_process(usb_host_t *host, u8 msg)
{
  UNUSED(host);

  switch (msg) {
  case USBH_MSG_DISCONNECTED:
    msc_is_ready = 0;
    break;
  case USBH_MSG_CONNECTED:
    break;
  default:
    break;
  }

  return HAL_OK;
}

static u32 filenum = 0;
static u8 *msc_wt_buf;
static u8 *msc_rd_buf;
FATFS fs;
FIL f;
int drv_num = 0;
char logical_drv[4];
char path[64] = {'0'};
u32 br;
u32 bw;

rtos_sema_create(&msc_attach_sema, 0U, 1U);

msc_wt_buf = (u8 *)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);
if (msc_wt_buf == NULL) {
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to alloc test buf\n");
  goto exit_deinit;
}

msc_rd_buf = (u8 *)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);
if (msc_rd_buf == NULL) {
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to alloc test buf\n");
  goto exit_deinit;
}

/* Register USB disk driver to fatfs*/
drv_num = FATFS_RegisterDiskDriver(&USB_disk_Driver);
if (drv_num < 0) {
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to register\n");
  goto exit_deinit;
}

logical_drv[0] = drv_num + '0';
logical_drv[1] = ':';
logical_drv[2] = '/';
logical_drv[3] = 0;

while (1) {
  if (msc_is_ready) {
    rtos_time_delay_ms(10);  /* Wait for MSC device is ready for class-specific communication */
    break;
  }
}

/* Mount logical drive */
if (f_mount(&fs, logical_drv, 1) != FR_OK) {
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to mount logical drive\n");
  FATFS_UnRegisterDiskDriver(drv_num);
  goto exit_deinit;
}

while (1) {
  if (rtos_sema_take(msc_attach_sema, RTOS_SEMA_MAX_COUNT) != RTK_SUCCESS) {
    RTK_LOGS(TAG, RTK_LOG_ERROR, "Fail to take sema\n");
    continue;                /* Wait for MSC device attach*/
  }
}

/* Construct the device file path */
strcpy(path, logical_drv);
sprintf(&path[3], "TEST%ld.DAT", filenum);

/* Open test file */
f_open(&f, path, FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
/* Write the data from a buffer to an opened file */
f_write(&f, (void *)msc_wt_buf, USBH_MSC_TEST_BUF_SIZE, (UINT *)&bw);
/* Move the file pointer to the file head */
f_lseek(&f, 0);
/* Read data from an opened file to a buffer */
f_read(&f, (void *)msc_rd_buf, USBH_MSC_TEST_BUF_SIZE, (UINT *)&br);
/* Close source file */
f_close(&f);

exit_deinit:

  rtos_sema_delete(msc_attach_sema);

  if (msc_wt_buf) {
    rtos_mem_free(msc_wt_buf);
  }
  if (msc_rd_buf) {
    rtos_mem_free(msc_rd_buf);
  }

  usbh_msc_deinit();
  usbh_deinit();
  return;
Driver Deinitialization

When storage functionality is no longer needed or during system shutdown, release resources in the reverse order of initialization.

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

Operation method

This section introduces a complete USB Mass Storage (MSC) application example, which demonstrates how to configure the Ameba development board as a USB storage host through the MSC protocol stack.

When the development board is connected to a standard MSC device such as a U-disk supporting the FAT32 format, the host can recognize it and perform simple file read and write tests based on FatFS.

Example path: {SDK}/component/example/usb/usbh_msc. It provides a complete design reference for developers designing custom USB storage host 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_msc -p
    
  • Confirmation of Menuconfig configuration

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

    - Choose `CONFIG USB --->`:
    
      [*] Enable USB
          USB Mode (Host)  --->
      [*] MSC
    
Verification
  • Device Startup

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

    [MSC-I] USBH MSC demo start
    [MSC-I] Register USB disk
    [MSC-I] FatFS USB W/R performance test start...
    
  • Connect to Device

    Connect a U-disk (formatted as FatFS) to the USB port of the development board using a OTG cable.

  • Recognition and Testing

    The Ameba development board will identify the MSC device and perform read/write performance tests.

    [USBH-A] Device attached,speed 0
    [USBH-A] PID: 0x6545
    [USBH-A] VID: 0x0930
    [USBH-A] Address 1 assigned
    [USBH-A] MFG: TOSHIBA
    [USBH-A] PROD: TransMemory
    [USBH-A] SN: C03FD5F7715FE3417000DE76
    [USBH-A] Enum done, total 1 cfg
    [USBH-A] Switch to itf: 0
    [USBH-A] Class: 0x08
    [USBH-A] SubClass: 0x06
    [USBH-A] Protocol: 0x50
    [MSC-I] ATTACH
    [MSC-I] Max lun 1
    [MSC-I] Lun 0
    [MSC-I] SETUP
    [MSC-I] Open file: 0:/TEST0.DAT
    [MSC-I] W test: size 512, round 20...
    [MSC-I] W rate 204.0 KB/s for 20 round @ 49 ms
    [MSC-I] R test: size = 512 round = 20...
    [MSC-I] R rate 476.1 KB/s for 20 round @ 21 ms
    [MSC-I] W test: size 1024, round 20...
    [MSC-I] W rate 540.5 KB/s for 20 round @ 37 ms
    [MSC-I] R test: size = 1024 round = 20...
    [MSC-I] R rate 800.0 KB/s for 20 round @ 25 ms
    [MSC-I] W test: size 2048, round 20...
    [MSC-I] W rate 655.7 KB/s for 20 round @ 61 ms
    [MSC-I] R test: size = 2048 round = 20...
    [MSC-I] R rate 1212.1 KB/s for 20 round @ 33 ms
    [MSC-I] W test: size 4096, round 20...
    [MSC-I] W rate 1095.8 KB/s for 20 round @ 73 ms
    [MSC-I] R test: size = 4096 round = 20...
    [MSC-I] R rate 1600.0 KB/s for 20 round @ 50 ms
    [MSC-I] FatFS USB W/R performance test done
    [MSC-I] File close OK
    

Audio Host Solution

Overview

The USB Audio Class (UAC) specification defines the industry standard for transmitting audio data over USB interfaces. In Host mode, the Ameba platform utilizes this protocol to identify, configure, and drive external USB audio devices.

The current UAC Host stack on the Ameba platform is optimized for Audio Playback scenarios. It integrates a UAC 1.0 compliant protocol stack (verified against USB-IF standards), abstracting external USB UAC devices into local system audio output interfaces. This solution supports plug-and-play functionality and seamlessly integrates with the system’s built-in audio processing framework, providing convenient and high-quality audio output expansion capabilities for embedded devices.

Ameba USB Audio Playback Topology

../../_images/usb_host_uac_overview_user.svg

Features

The Ameba UAC Host driver is designed to deliver stable and compatible audio output. Key features include:

  • Broad Device Compatibility: Fully supports USB devices compliant with the UAC 1.0 standard, such as USB speakers, USB headsets, and USB-to-3.5mm audio adapters.

  • Automatic Enumeration & Configuration: Automatically parses device descriptors, identifies audio streaming endpoints, and establishes Isochronous transfer channels without manual intervention.

  • Mainstream Audio Format Support: Please refer to the Supported Audio Formats section for detailed specifications.

  • Deep System Integration: Exposes a unified API to upper-layer applications, effectively abstracting and masking underlying USB protocol details.

  • Hot-Plug Support: Supports dynamic plug-and-play and removal of USB peripherals during runtime without requiring a system reboot.

Application Scenarios

As a USB Host, the Ameba platform handles the complexity of enumerating UAC devices, parsing audio descriptors, and maintaining stable data transmission channels. This solution is ideal for embedded applications that demand high-quality audio playback with minimal development overhead, including:

  • Smart Audio Terminals: Devices that require voice prompts, advertisement broadcasting, or public address functionality via external UAC devices (e.g., active speakers or headphones).

  • Digital Signage & Kiosks: Systems that combine local storage or network streaming to play background music or multimedia commentary through high-fidelity USB audio peripherals to enhance user experience.

  • IoT Audio Gateways: Lightweight audio response nodes that receive audio commands or content from Wi-Fi/Cloud sources and output them through generic USB audio devices.

Protocol Introduction

Note

The current host solution only supports UAC 1.0

UAC (USB Audio Class) is a universal audio device class standard defined by USB-IF, aimed at standardizing the encapsulation and transmission of digital audio data streams over USB interfaces. USB audio devices (such as USB speakers, headphones, and microphones) can be automatically recognized as standard audio input/output terminals in the host system through standard interfaces for data transmission and function control, without the need to install proprietary drivers.

Version Comparison

USB-IF has officially released multiple versions of the UAC protocol. The download links for the specification documents of the mainstream versions are shown in the table below:

Version

Specification Document

1.0

Audio Device Document 1.0

2.0

Audio Device Rev. 2.0

Terminology Definitions

The following table defines common technical terms related to UAC used in this document:

Term

Description

AC Interface (Audio Control Interface)

Responsible for managing the topology of the audio device (e.g., Input/Output Terminals, Feature Units) and issuing control commands (such as volume adjustment and mute) via the Control Endpoint.

AS Interface (Audio Streaming Interface)

Responsible for the actual transmission of audio payload data, typically using Isochronous endpoints. An AS Interface can contain multiple Alternate Settings, each corresponding to different sample rates, bit depths, or channel configurations.

Terminal Type

Identifies the physical or logical attributes of an audio signal source or sink (e.g., USB Streaming Terminal represents a USB data stream, Speaker Terminal represents a physical speaker).

Feature Unit

A processing node within the audio topology that provides specific audio control capabilities (e.g., master volume adjustment, channel gain, mute control).

Sample Rate / Bit Resolution

Core parameters of the audio format. Common combinations in UAC 1.0 include 48 kHz / 16-bit, 44.1 kHz / 16-bit, etc.

Definitions of common audio technology terms used in this document are as follows:

Terms

Introduction

PCM

Pulse Code Modulation, audio data is a raw stream.

Channel

A channel sound is an independent audio signal captured or played in different positions, so the number of channels is the number of sound sources.

Bit Depth

Bit depth represents the bits effectively used in the process of audio signals.

Sampling depth shows the resolution of the sound.

The larger the value, the higher the resolution.

Sample rate

The audio sampling rate refers to the number of frames that the signal is sampled per second.

The higher the sampling frequency, the higher quality the sound will be.

Protocol Framework

The UAC system architecture supports audio data transmission and control by defining different interface sets. Logically, UAC device interfaces are primarily divided into two major categories: Audio Control Interface and Audio Streaming Interface.

  • Audio Control (AC) Interface:

    • Responsible for managing the overall functional behavior of the audio device, such as volume adjustment, mute control, input source selection, etc.

    • An AC interface contains a defined internal topology that describes the flow and processing of audio signals from the Input Terminal to the Output Terminal.

  • Audio Streaming (AS) Interface:

    • Responsible for transmitting the actual audio payload data.

    • A UAC device can contain multiple AS interfaces, each configurable to transmit audio data with different formats, sampling rates, or bit depths.

Protocol Interaction Example:

../../_images/usb_uac_spec.svg

Descriptor Structure

In addition to adhering to standard USB descriptors (such as Device Descriptor, Configuration Descriptor, Endpoint Descriptor), UAC devices also define Class-Specific Descriptors. These descriptors are categorized based on their associated interface into Class-Specific Control Interface Descriptors and Class-Specific Audio Streaming Interface Descriptors.

There are differences in descriptor definitions between different protocol versions:

Descriptor Topology

Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── Interface Association Descriptor (IAD)
│       └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (Interface 0, Control Class)
│       └── Class-Specific Descriptor Collection
│               ├── Audio Control Interface Header (declares UAC version)
│               ├── Clock Source (internal clock or external clock)
│               ├── Clock Source (internal clock or external clock)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               ├── Output Terminal (destination of audio stream)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│       ├── Alternate Setting 0: Control transfer active state (control transfer only)
│       │
│       ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│       │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│       │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│       │ ├── Format Descriptor (audio format and bit width)
│       │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│       │ └── Class-Specific Endpoint Descriptor (no special control)
│       │
│       ├── Alternate Setting 2
│       │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        │ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
        │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
        │ ├── Format Descriptor (audio format and bit width)
        │ ├── Standard Endpoint Descriptor (ISO IN endpoint)
        │ └── Class-Specific Endpoint Descriptor (no special control)
        │
        ├── Alternate Setting 2
        │ ...... Can configure multiple different setting as needed

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── Interface Association Descriptor (IAD)
│       └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (Interface 0, Control Class)
│       └── Class-Specific Descriptor Collection
│               ├── Audio Control Interface Header (declares UAC version)
│               ├── Clock Source (internal clock or external clock)
│               ├── Clock Source (internal clock or external clock)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               ├── Output Terminal (destination of audio stream)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│       ├── Alternate Setting 0: Control transfer active state (control transfer only)
│       │
│       ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│       │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│       │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│       │ ├── Format Descriptor (audio format and bit width)
│       │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│       │ └── Class-Specific Endpoint Descriptor (no special control)
│       │
│       ├── Alternate Setting 2
│       │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        │ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
        │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
        │ ├── Format Descriptor (audio format and bit width)
        │ ├── Standard Endpoint Descriptor (ISO IN endpoint)
        │ └── Class-Specific Endpoint Descriptor (no special control)
        │
        ├── Alternate Setting 2
        │ ...... Can configure multiple different setting as needed

UAC Audio Control (AC) Interface Descriptor

  • Audio Control Interface Header

Audio Control Interface Header Descriptor
├── bLength            : 1 byte  → Total descriptor length (fixed = 9)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x01 (HEADER)
├── bcdADC             : 2 bytes → Audio Device Class Specification Release Number
├── bCategory          : 1 byte  → Indicates the classification/function of the device
├── wTotalLength       : 2 bytes → Total length of all AC Class-Specific descriptors, including this one
└── bmControls         : 1 byte  → Bitmap indicating the availability of non-addressable control functions
  • Clock Source Descriptor

Clock Source Descriptor
├── bLength            : 1 byte  → Total descriptor length (fixed = 8)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x0A (Clock Source)
├── bClockID           : 1 byte  → Unique Clock ID, ranging from 1 to 255
├── bmAttributes       : 1 byte  → Clock type (0=Internal, 1=External)
├── bmControls         : 1 byte  → Bitmap indicating the control attributes of the clock
└── iClockSource       : 1 byte  → String descriptor index
  • Input Terminal Descriptor

Input Terminal Descriptor
├─ bLength                : 1 byte   → Total descriptor length (fixed = 17)
├─ bDescriptorType        : 1 byte   → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x02 (INPUT_TERMINAL)
├─ bTerminalID            : 1 byte   → Unique ID of this terminal (referenced in topology)
├─ wTerminalType          : 2 bytes  → Terminal type (little-endian)
│                           ├─ 0x0101 = USB Streaming (Host audio stream input)
│                           └─ Other values refer to UAC2.0 Appendix B (e.g., Microphone)
├─ bAssocTerminal         : 1 byte   → Associated Output Terminal ID (0 = no pairing)
├─ bCSourceID             : 1 byte   → Associated Clock Source ID
├─ bNrChannels            : 1 byte   → Number of logical output channels (e.g., 2 = stereo)
├─ bmChannelConfig        : 4 bytes  → Spatial location bitmap for channels
├─ iChannelNames          : 1 byte   → String index for channel names
├─ bmControls             : 2 byte   → Control bitmap
└─ iTerminal              : 1 byte   → String index for describing this terminal
  • Feature Unit Descriptor

Feature Unit Descriptor
├─ bLength                : 1 byte   → otal descriptor length in bytes
│                                      = 6 + (1 + bNrChannels) × 4
├─ bDescriptorType        : 1 byte   → = 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → = 0x06 (FEATURE_UNIT)
├─ bUnitID                : 1 byte   → Unique ID of this Feature Unit
├─ bSourceID              : 1 byte   → ID of the connected Source Unit or Terminal
├─ bmaControls[0]         : 4 bytes  → Master Channel Control Bitmap
├─ bmaControls[1]         : 4 bytes  → Logical Channel 1 Control Bitmap
├─ bmaControls[2]         : 4 bytes  → Logical Channel 2 Control Bitmap
│    ⋮
├─ bmaControls[N]         : 4 bytes  → Logical Channel N Control Bitmap (Total bNrChannels entries)
└─ iFeature               : 1 byte   → String descriptor index
  • Output Terminal Descriptor

Output Terminal Descriptor
├─ bLength                : 1 byte   → Total descriptor length (fixed = 12 bytes)
├─ bDescriptorType        : 1 byte   → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x03 (OUTPUT_TERMINAL)
├─ bTerminalID            : 1 byte   → Unique ID of this terminal
├─ wTerminalType          : 2 bytes  → Terminal type
│                           ├─ 0x0301 = Speaker
│                           ├─ 0x0302 = Headphones
│                           ├─ 0x0603 = SPDIF
│                           └─ Other values refer to Appendix B
├─ bAssocTerminal         : 1 byte   → Associated Input Terminal ID
├─ bSourceID              : 1 byte   → ID of the connected Source Unit or Terminal
├─ bCSourceID             : 1 byte   → Associated Clock Source ID
└─ iTerminal              : 1 byte   → String index for describing this terminal

Audio Streaming Interface Descriptor

  • Class-Specific AS Interface Descriptor

Class-Specific AS Interface Descriptor
├─ bLength                : 1 byte   → Fixed as 0x10 (16 bytes)
├─ bDescriptorType        : 1 byte   → 0x24(CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x01(AS_GENERAL)
├─ bTerminalLink          : 1 byte   → Associated Terminal ID (Input or Output Terminal)
├─ bmControls             : 1 bytes  → Bitmap of endpoint control capabilities
├─ bFormatType            : 1 byte   → Format type
│                           └─ 0x01 = FORMAT_TYPE_I(PCM)
├─ bmFormats              : 4 bytes  → Bitmap of supported audio formats
├─ bNrChannels            : 1 bytes  → Number of supported audio channels
├─ bmChannelConfig        : 4 bytes  → Supported audio channel configuration bitmap
└─ iChannelNames          : 1 byte   → String index for channel names
  • Audio Streaming Format Type Descriptor

Audio Streaming Format Type Descriptor
├─ bLength            : 1 byte   → Total length of descriptor in bytes (6 bytes)
├─ bDescriptorType    : 1 byte   → = 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte   → = 0x02(FORMAT_TYPE)
├─ bFormatType        : 1 byte   → = 0x01(FORMAT_TYPE_I)
├─ bSubslotSize       : 1 byte   → Container size for each audio sample (in bytes)
│                                   • Typical values: 1, 2, 3, 4
├─ bBitResolution     : 1 byte   → Number of valid bits in each sample(≤ bSubslotSize × 8)
└─                                  • Example: 16 represents 16-bit PCM

Note

For detailed field definitions, please refer to the official USB-IF UAC protocol documentation.

Class-Specific Requests

The control requests issued by the UAC host to the device are categorized into Standard Requests and Class-Specific Requests.

This section primarily introduces the Class-Specific Requests unique to UAC. These requests are used to implement the distinctive features of audio devices and mainly include Audio Control Requests (targeting AC interfaces) and Audio Streaming Requests (targeting AS interfaces).

  • Audio Control Requests (AC Requests)

    AC Requests are class-specific control transfers sent by the host over Endpoint 0 to dynamically configure and manage audio functionality within a USB audio device.

AudioControl Type

Required

Description

Mute Control Request

Optional

Manipulate the mute control in the Feature Unit of the audio function

Volume Control Request

Optional

Manipulate the volume control in the Feature Unit of the audio function

Sampling Frequency Control

Optional

Manipulate the actual sampling frequency of the clock signal.

Mixer Unit Control Request

Optional

Manipulate the control inside a Mixer Unit of the audio function

Terminal Control Request

Optional

Manipulate the control inside a Mixer Unit of the audio function

Selector Unit Control Request

Optional

Manipulate the control inside a Selector Unit of the audio function

Effect Unit Control Request

Optional

Manipulate the Controls inside an Effect Unit of the audio function

Processing Unit Control Request

Optional

Manipulate the Controls inside a Processing Unit of the audio function

Extension Unit Control Requests

Optional

Manipulate the Controls inside an Extension Unit of the audio function

  • Audio Streaming Requests (AS Requests)

    AS Requests are class-specific control transfers issued by the host over Endpoint 0 to configure and manage parameters related to audio data streams, pecifically those associated with an Audio Streaming Interface.

AudioStreaming Type

Required

Description

Interface Control Request

Optional

Manipulate the Controls inside an AudioStreaming interface of the audio function

Encoder Control Request

Optional

Manipulate the Encoder Controls inside an AudioStreaming interface of the audio function

Decoder Control Request

Optional

Manipulate the Decoder Controls inside an AudioStreaming interface of the audio function

Endpoint Control Request

Optional

Manipulate the Controls inside an AudioStreaming endpoint of the audio function

Data Transmission Format

UAC audio data streams typically use Linear PCM encoding and are encapsulated in a Multi-Channel Interleaved format. The specific data arrangement depends on the number of channels and bit depth.

For more details on supported formats, please refer to Supported Audio Formats.

An example of 2-Channel interleaved data is shown below:

../../_images/usb_uac_audio_2_channel_data_interleaved.svg

An example of 4-Channel interleaved data is shown below:

../../_images/usb_uac_audio_4_channel_data_interleaved.svg

An example of N-Channel interleaved data is shown below:

../../_images/usb_uac_audio_n_channel_data_interleaved.svg

Note

Channel Alignment Rule:

The UAC protocol typically requires the transmitted number of channels to be a power of two (e.g., 2, 4, 8, 16, etc.). If the actual physical channel count (e.g., 10 channels) does not comply with this rule, it must be rounded up to the nearest power of two (configured for transmission as 16 channels). The extra channel positions are filled with invalid data.

Class Driver

This section details the internal architecture of the USB Host UAC 1.0 driver stack, the responsibilities of key modules, support for class-specific requests, and the allocation strategy for underlying channel resources.

Driver Framework

The USB Host UAC 1.0 driver stack adopts a layered modular design, facilitating efficient interaction between the audio subsystem and the USB hardware controller through clearly defined interfaces. The architecture is specifically optimized for real-time Isochronous Transfer (ISOC) processing to ensure the stable output of high-fidelity audio streams.

The system is organized from top to bottom into the following core functional modules:

Audio Adapter Layer

Acting as middleware between the USB driver and the upper-layer audio framework, this layer is primarily responsible for:

  • Write Operations: Functioning as a data producer, it reads PCM data from the upper-layer audio buffer and writes it into the UAC Class Driver’s ring buffer.

  • Control Interaction: Maps upper-layer application operations, such as volume adjustment and mute toggling, to the corresponding UAC Class Driver control APIs.

UAC Class Driver Architecture

This is the core component of the UAC implementation, composed of protocol handling logic and Ringbuffer Management.

  • Enumeration & Configuration Parsing: Automatically identifies Audio Control (AC) and Audio Streaming (AS) interfaces, parsing parameters such as Terminal Types, Feature Units, sample rates, and bit depths.

  • Stream Management: Dynamically selects the appropriate Alternate Setting for the AS interface based on target audio parameters and activates the corresponding Isochronous OUT endpoint.

  • Buffer Scheduling: Maintains a ring buffer to handle data packetization and scheduling, ensuring a continuous, jitter-free data supply.

  • Protocol Encapsulation: Encapsulates standard USB requests and UAC class-specific requests (e.g., SET_CUR) to control the device.

USB Core Driver

Handles real-time hardware interrupts, standard USB enumeration, transfer management, and low-level physical data transmission scheduling.

Core Interaction Interfaces

The UAC Host Class Driver serves as a bridge within the system architecture. Its implementation logic revolves around three core interaction interfaces:

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

  • Application-Facing Callback API: The class driver provides an asynchronous event notification mechanism to upper-layer applications via the usbh_uac_cb_t callback structure.

  • Application-Facing API: When the application layer calls these APIs, the driver switches its internal state machine to initiate data transmission scheduling.

Driver Callback Mechanism:

../../_images/usb_host_uac.svg
Init and Deinit the Class Driver

These functions manage memory resource allocation and deallocation, as well as the registration and deregistration of the class driver with the USB Core.

usbh_uac_init() is the top-level function for loading the UAC Host Class Driver. It performs the following tasks:

  • Save Callbacks: Stores the user-provided callback functions and invokes the user’s init callback.

  • Save Parameters: Saves the user-configured frame count frame_cnt for the ring buffer.

  • Allocate Memory: Allocates memory buffers for control transfers and TX audio transmission.

  • Register Driver: Calls usbh_register_class() to register the UAC class driver with the USB Host Core.

Example:

int usbh_uac_init(usbh_uac_cb_t *cb, int frame_cnt)
{
    /* 1. Save the frame count param */
    uac->frame_cnt = frame_cnt;

    /* 2. Allocate memory */
    uac->audio_ctrl_buf = (u8 *)usb_os_malloc(UBSH_UAC_AUDIO_CTRL_BUF_MAX_LEN);
    uac->isoc_tx_buf = (u8 *)usb_os_malloc(USBH_UAC_ISOC_BUF_LENGTH);

    /* 3. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
      uac->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;
        }
      }
    }

    /* 4. Register class driver*/
    usbh_register_class(&usbh_uac_driver);

    return HAL_OK;
}
Attach & Detach Handling

Callbacks are triggered when the USB Core detects the insertion or removal of a device matching the UAC class.

usbh_uac_cb_attach is a critical step in device enumeration, responsible for parsing interface descriptors and allocating pipe resources:

  • Find AC Interface: Parses and retrieves audio control information, including volume ranges and mute capabilities.

  • Find AS Interface: Parses and retrieves audio format information and endpoint descriptors.

  • Open Pipe: Allocates and opens the ISOC OUT pipe based on the retrieved descriptor information.

  • Initialize State Machine: Sets the initial state to UAC_STATE_GET_MUTE to prepare for fetching audio information.

  • Notify Application: Calls the user attach callback to inform the application layer of the connection status.

static int usbh_uac_cb_attach(usb_host_t *host)
{
  /* 1. Parse descriptors to get AC and AS information */
  status = usbh_uac_parse_interface_desc(host);
  if (status) {
    return status;
  }

  /* 2. Open the pipe for steaming transfer */
  if (uac->as_isoc_out) {
      as_itf = uac->as_isoc_out;
      as_itf->choose_alt_idx = 0;

      pipe = &(as_itf->pipe);
      ep_desc = &(as_itf->itf_info_array[as_itf->choose_alt_idx].ep_desc);

      usbh_open_pipe(host, pipe, ep_desc);
  }

  /* 3. Initialize the state machine */
  uac->ctrl_state = UAC_STATE_GET_MUTE;

  /* 4. Notify the user layer */
  if ((uac->cb != NULL) && (uac->cb->attach != NULL)) {
      uac->cb->attach();
  }
  return HAL_OK;
}
Class Driver State Machine

The usbh_uac_cb_process callback is the core state machine handler for the UAC class on the Host side.

Unlike the Device side, which passively responds to requests, the Host driver must actively maintain the device state. Its primary responsibilities are managing the lifecycle state of the class driver and dispatching transfer tasks.

State Management and Scheduling

usbh_uac_cb_process manages the scheduling of control transfers (e.g., sample rate configuration) and data transfers based on the current class driver state.

State Enum

Description

Key Action

IDLE

Idle State

Waits for user commands or data transfer requests.

TRANSFER

Data Transferring

Dispatches tasks to specific TX/RX handlers based on pipe numbers.

ERROR

Error State

Attempts to Clear Feature on endpoints to restore communication.

Transfer Dispatch Example

When in the TRANSFER state, processing is dispatched to specific transfer handlers based on the pipe ID triggering the event.

Example:

case UAC_STATE_TRANSFER:
     /* Distribute transmission tasks according to pipe numbers */
     if (event.msg.pipe_num == 0) {
         ret = usbh_uac_ctrl_setting(host, 0);      // Handle ctrl message transfer
     }
  break;

Error Recovery

If an error occurs during state processing, the driver attempts to send a Clear Feature request and reset to the IDLE state.

Example:

/* ... Error state ... */
case UAC_STATE_ERROR:
    /* Error recovery mechanism */
    ret = usbh_ctrl_clear_feature(host, 0x00U);
    if (ret == HAL_OK) {
        uac->xfer_state = UAC_STATE_IDLE;
    }
break;
Data Transfer Processing

UAC data transfer is categorized into two types: Audio Control Transfer and Audio Streaming Transfer .

Audio Control Transfer

Responsible for managing the functional behavior of the audio device. The driver implements a state machine flow to handle configuration requests in the following sequence:

case UAC_STATE_SET_OUT_ALT:
    ret = usbh_uac_process_set_out_alt(host);
    if (ret == HAL_OK) {
        uac->ctrl_state = UAC_STATE_SET_OUT_FREQ;
    } else if (ret != HAL_BUSY) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "OUT alt err\n");
        uac->ctrl_state = UAC_STATE_SET_OUT_FREQ;
    }
    break;

Audio Streaming Transfer (ISOC):

To ensure real-time performance and low latency for audio streams, the UAC Class Driver utilizes USB hardware interrupt mechanisms (Start of Frame SOF or Transfer Completed) to precisely trigger the scheduling of the next frame of data.

  • Completed Interrupt: Triggered when the current data has been successfully sent to the bus.

Compact Scheduling: When the endpoint transfer interval (bInterval) is 1 (transmission required every frame), the driver maximizes bandwidth utilization and minimizes latency by immediately filling and submitting the request for the next frame within the Completed callback of the previous frame.

  • SOF (Start Of Frame) Interrupt: Triggered at the start of each USB frame.

Interval Scheduling: When the endpoint transfer interval (bInterval) is greater than 1 (transmission every N frames), the driver uses the SOF interrupt counter to track frame numbers. Once the scheduled interval is reached, it immediately prepares and submits the next data transfer request.

Code Example (Completed Callback Logic):

static usbh_class_driver_t usbh_uac_driver = {
    .completed = usbh_uac_cb_completed,
};

static int usbh_uac_cb_completed(usb_host_t *host, u8 pipe_num)
{
    u32 cur_frame = usbh_get_current_frame_number(host);

    if (pdata_ctrl->next_xfer == 1) {
        /* Check if this is the audio streaming pipe */
        if ((uac->as_isoc_out) && (pipe_num == uac->as_isoc_out->pipe.pipe_num)) {
            pipe->xfer_state = USBH_EP_XFER_IDLE;

            /* If ring buffer has data, prepare next transfer */
            if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_manager))) {

                /* Trigger next xfer logic: check if interval is met */
                if (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval) {
                    usbh_uac_isoc_out_process_xfer(host, cur_frame);
                } else {
                    /* Wait for next SOF if interval not met yet */
                    pipe->xfer_state = USBH_EP_XFER_WAIT_SOF;
                }
            } else {
                /* Buffer empty, go idle */
                pipe->xfer_state = USBH_EP_XFER_IDLE;
            }
        }
    }

    return HAL_OK;
}

Application-Facing APIs

These interfaces are provided to the upper-layer application for information retrieval, device configuration, and data transmission.

Configuration APIs

Data Streaming APIs

  • usbh_uac_write():Writes PCM data into the driver’s internal ring buffer. The class driver automatically fragments large data packets into smaller packets that fit the USB frame size based on the audio format.

  • usbh_uac_start_play():Initiates the playback process; the driver begins extracting data from the ring buffer and sending it.

  • usbh_uac_stop_play():Stops playback and terminates the ISOC OUT transmission.

Class-Specific Request Implementation

The driver stack includes built-in encapsulation for core requests defined by the USB Audio Class 1.0 specification. The current implementation focuses on Audio Playback scenarios and enables control requests targeting Feature Units by default.

Developers can view the source code at {SDK}/component/usb/host/uac and extend it to support additional request types as needed.

Request Type

Note

Mute Control

Sends a SET_CUR request to the Mute Control Selector of a Feature Unit to mute/unmute audio.

Volume Control

Sends a SET_CUR request to the Volume Control Selector of a Feature Unit to set gain values.

Channel Configuration

During the device enumeration phase, the driver parses the configuration descriptor. When an Audio Streaming (AS) Interface is detected as active, the driver automatically requests the corresponding USB pipe resources.

Pipe Type

Usage Description

Control IN/OUT Pipe

Used for standard device requests (enumeration, configuration) and UAC class-specific requests (volume, sample rate settings).

Isochronous OUT Pipe

Belongs to the Active Alternate Setting (typically Alt 1) of the Audio Streaming (AS) Interface. Used by the Host to send PCM audio data streams (TX Data) to the UAC device.

API Reference

Driver API

Application Example

Application Design

This section details the process required to develop a complete USB UAC 1.0 Host application, covering driver initialization, hot-plug event handling, audio data writing mechanisms, and resource release strategies.

Driver Initialization

Before using the UAC 1.0 Host driver, hardware parameter configuration, callback registration, and core protocol stack initialization must be completed in the specified sequence.

Step-by-Step Description:

  • Hardware Configuration: Configure the USB speed mode (Full Speed) and interrupt/task priorities.

  • Callback Registration: Define user callback structures and mount handlers for various stages (connection, disconnection, data transmission).

  • Core Startup:Call usbh_init() and usbh_uac_init() sequentially to start the protocol stack.

/*
 * 1. Configure USB speed (Full Speed) ,isr priority and main task priority.
 */
static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_FULL,
    .isr_priority = INT_PRI_MIDDLE,
    .main_task_priority = USBH_UAC_MAIN_THREAD_PRIORITY,
    .ext_intr_enable = USBH_SOF_INTR,
};
/*
 * Define USB user-level callbacks.
 */
static usbh_user_cb_t usbh_usr_cb = {
    .process = usbh_uac_cb_process,            /* USB callback to handle class-independent events in the application */
};

/*
 * 2. Define user callbacks for UAC events.
 */
static usbh_uac_cb_t usbh_uac_cfg = {
    .init   = usbh_uac_cb_init,                /* USB init callback */
    .deinit = usbh_uac_cb_deinit,              /* USB deinit callback */
    .attach = usbh_uac_cb_attach,              /* USB attach callback */
    .detach = usbh_uac_cb_detach,              /* USB detach callback */
    .setup  = usbh_uac_cb_setup,               /* USB setup callback */
    .isoc_transmitted     = usbh_uac_cb_isoc_transmitted,    /* Data transmission complet callback */
};

int ret = 0;

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

/*
 * 4. Initialize UAC class driver.
 */
ret = usbh_uac_init(&usbh_uac_cfg, USBH_UAC_FRAME_CNT);
if (ret != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbh_deinit();

    return;
}
Hot-Plug Event Handling

The system must possess a robust hot-plug handling mechanism to cope with the dynamic removal and re-insertion of UAC devices. The SDK provides standard state machine and callback mechanisms to notify the upper-layer application.

Handling Logic:

  • Detach: Triggers a callback to release a semaphore. The application thread captures this, executes de-initialization, and frees heap memory.

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

/* USB detach callback */
static usbh_uac_cb_t usbh_uac_cfg = {
    .detach = usbh_uac_cb_detach,
};

/* Callback executed in main task */
static int usbh_uac_cb_detach(void)
{
    RTK_LOGS(TAG, RTK_LOG_INFO, "DETACH\n");
    rtos_sema_give(usbh_uac_detach_sema);
    usbh_uac_is_ready = 0;
    return HAL_OK;
}

/* Thread Context: Handle the state machine */
static void usbh_uac_hotplug_thread(void *param)
{
    int ret = 0;

    UNUSED(param);

    for (;;) {
        usb_os_sema_take(usbh_uac_detach_sema, USB_OS_SEMA_TIMEOUT);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Hot plug\n");

        /* Stop transfer, release resource */
        usbh_uac_deinit();
        usbh_deinit();

        rtos_time_delay_ms(10);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap size: 0x%08x\n", usb_os_get_free_heap_size());

        /* Re-init */
        ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
        if (ret != HAL_OK) {
            break;
        }

        ret = usbh_uac_init(&usbh_uac_cfg, USBH_UAC_FRAME_CNT);
        if (ret < 0) {
            usbh_deinit();
            break;
        }
    }
}
Audio Data Stream Mechanism

The UAC Class Driver utilizes a Ring Buffer mechanism to buffer audio data generated by the upper-layer application and drives continuous data transmission via SOF (Start of Frame) or Transfer Complete interrupts.

  • Audio Write

The upper-layer application calls the usbh_uac_write() interface to fill PCM data into the ring buffer. If the buffer is full, this function will block and wait based on the configured timeout_ms.

static int usbh_uac_write_ring_buf(usbh_uac_buf_ctrl_t *pdata_ctrl, u8 *buffer, u32 size, u32 *written_len)
{
    u32 written_size = handle->written;

    /* Fill it into the end of the data that was not completed last time to form a whole package */
    if (written_size) {
        xfer_len = usbh_uac_next_packet_size(pdata_ctrl);
        can_copy_len = xfer_len - written_size;
        copy_len = size < can_copy_len ? size : can_copy_len;

        usb_ringbuf_write_partial(handle, buffer, copy_len);
        offset += copy_len;
        *written_len += copy_len;

        if (size >= can_copy_len) {
            size -= copy_len;
            usb_ringbuf_finish_write(handle);
            pdata_ctrl->sample_accum = pdata_ctrl->last_sample_accum;
        } else {
            return 0;
        }
    }

    /* Fill the entire package in a cycle */
    do {
        if (usb_ringbuf_is_full(handle)) {
            return 1;
        }

        xfer_len = usbh_uac_next_packet_size(pdata_ctrl);

        if (size >= xfer_len) {
            usb_ringbuf_add_tail(handle, buffer + offset, xfer_len);

            *written_len += xfer_len;
            size -= xfer_len;
            offset += xfer_len;

            pdata_ctrl->sample_accum = pdata_ctrl->last_sample_accum;
        } else {
            break;
        }
    } while (1);

    /* Write the remaining data at the end */
    if (size > 0) {
        if (usb_ringbuf_is_full(handle)) {
            return 1;
        }

        usb_ringbuf_write_partial(handle, buffer + offset, size);

        *written_len += size;
    }
    return 0;
}

/* Tansfer APi, used for Audio */
u32 usbh_uac_write(u8 *buffer, u32 size, u32 timeout_ms)
{
    /* check usb status */
    if (usbh_uac_usb_status_check() != HAL_OK) {
        return 0;
    }

    /* loop to write data to the ringbuffer */
    while (written_len < size && pdata_ctrl->next_xfer) {

        if (usb_ringbuf_is_full()) {
            if (usbh_uac_wait_isoc_with_status_check(pdata_ctrl, timeout_ms) != HAL_OK) {
                break;
            }
        }

        try_len = size - written_len;
        just_written = 0;

        usbh_uac_write_ring_buf(pdata_ctrl, buffer + written_len, try_len, &just_written);

        if (just_written > 0) {
            written_len += just_written;
            last_zero = 0;
        } else {
            //wait sema and retry
            last_zero = 1;
        }
    }

    return written_len;
}
  • Audio Output

The underlying driver automatically retrieves data from the ring buffer via interrupt callbacks and sends it to the USB bus.

  • SOF Interrupt: Triggered periodically (every 1ms). It checks if the current frame number reaches the transmission Interval. If so, it retrieves data from the RingBuffer and submits the transfer.

  • Commplete Interrupt: Triggered after the previous transfer completes. It checks if the RingBuffer has remaining data; if so, it schedules the transmission task for the next frame.

static usbh_class_driver_t usbh_uac_driver = {
    .sof = usbh_uac_cb_sof,
    .completed = usbh_uac_cb_completed,
};

static void usbh_uac_isoc_out_process_xfer(usb_host_t *host, u32 cur_frame)
{
    if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))) {
        /* check valid data */
        pbuf = usb_ringbuf_get_head(&(pdata_ctrl->buf_list));
        if (pbuf && pbuf->buf_len > 0) {
            pipe->frame_num = usbh_uac_frame_num_inc(cur_frame, 1);
            pipe->xfer_buf = pbuf->buf;
            pipe->xfer_len = pbuf->buf_len;
            usbh_transfer_data(host, pipe);
            pipe->xfer_state = USBH_EP_XFER_BUSY;
        }
}

static int usbh_uac_cb_sof(usb_host_t *host)
{
    /* this class right not just support isoc out */
    if (pdata_ctrl->next_xfer == 1) {
        /* check the condition for transmission */
        if ((usbh_get_elapsed_frame_cnt(host, pipe->frame_num) >= pipe->ep_interval) ||
            ((pipe->xfer_state == USBH_EP_XFER_WAIT_SOF) &&
             (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval))) {
            usbh_uac_isoc_out_process_xfer(host, cur_frame);
        }
    }
    return HAL_OK;
}

static int usbh_uac_cb_completed(usb_host_t *host, u8 pipe_num)
{
    if (pdata_ctrl->next_xfer == 1) {
        if ((uac->as_isoc_out) && (pipe_num == uac->as_isoc_out->pipe.pipe_num)) {
            usbh_uac_isoc_out_process_complete(host);
            if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))) {
                /* trigger next xfer after binterval */
                if (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval) {
                    usbh_uac_isoc_out_process_xfer(host, cur_frame);
                } else {
                    pipe->xfer_state = USBH_EP_XFER_WAIT_SOF;
                }
            } else {
                /* TX ISOC OUT token only when play*/
                pipe->xfer_state = USBH_EP_XFER_IDLE;
            }
        }
    }

    return HAL_OK;
}
Driver Deinit

When the system shuts down or a full reset of the USB stack is required, resources must be released strictly in the reverse order: Class Driver first, then Core Driver, to avoid memory leaks or pointer errors.

/* 1. Deinitialize UAC class driver first */
usbh_uac_deinit();

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

Operation method

This section uses the scenario of Ameba connecting to a USB Headset for audio playback to demonstrate how to configure the Ameba development board as a USB UAC 1.0 Host and output audio through an external standard USB headset.

  • Default Behavior: Identify UAC Device -> Configure as 48 kHz / 16-bit / 2-channels -> Loop playback of a preset PCM audio clip.

  • Path: {SDK}/component/example/usb/usbh_uac, This provides a complete design reference for developers creating products such as voice announcement systems or audio gateways.

Note

This example requires XDK (Extended Development Kit) support. For XDK download, please refer to SDK Download.

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

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

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] UAC
    
Result Verification
  • Start Device

    Reset the development board and observe the serial log (Log UART). When the following log appears, the USB Host initialization is successful:

[UAC-I] USBH UAC demo start
  • Connect Device

    Insert a UAC 1.0 compatible USB headset into the development board.

  • Functional Tests

    • Auto Play Test

      Upon successful connection, the system will automatically begin audio stream transmission.

      Expected Result: The preset audio clip is heard through the headphones (defaults to looping 60 times, 1 second per loop).

      Note

      The loop count configuration can be modified in example_usbh_uac.c.

    • Mute Control Test

      Enter the following commands in the serial console:

      • uach_mute 1:Mute the headphones.

      • uach_mute 0:Unmute and restore sound.

    • Volume Control Test

      Enter the following commands in the serial console:

      • uach_vol 10:Set volume to 10% (lower volume).

      • uach_vol 90:Set volume to 90% (higher volume).

Video Host Solution

Overview

The USB Video Class (UVC) protocol defines the industry standard for transmitting video data over a USB interface. In Host mode, the Ameba platform can identify and drive external USB camera devices through this protocol.

The UVC host protocol stack of the Ameba platform focuses on the Video Capture scenario. It integrates the USB-IF compliant UVC 1.5 protocol, abstracting external USB cameras as system local video input interfaces. This solution supports Plug-and-Play, can seamlessly interface with the system’s built-in video processing framework, and provides convenient, flexible visual perception extension capabilities for devices.

../../_images/usb_host_uvc_user.svg

Features

The Ameba UVC Host driver aims to provide efficient and compatible video input capabilities. The main features are as follows:

  • Extensive Device Compatibility: Supports UVC-compliant USB camera devices (such as USB webcams, industrial cameras, USB microscopes).

  • Automatic Enumeration Configuration: Automatically parses device descriptors, identifies video streaming interfaces and control interfaces, and establishes Isochronous transmission channels.

  • Mainstream Video Format Support:

    • Compressed formats: MJPEG, H.264

    • Uncompressed formats: YUV

  • Deep System Integration: Exposes a unified API interface to upper-layer applications, shielding underlying USB transmission details.

  • Hot-Plug Support: Supports Plug-and-Play and dynamic removal of USB peripherals without restarting the system.

Application Scenarios

As a USB Host, Ameba is responsible for enumerating USB cameras, parsing video descriptors, and establishing a stable image data stream. This solution is suitable for embedded applications that have requirements for visual acquisition while pursuing low power consumption and rapid integration, such as:

  • Intelligent Monitoring and Security: Ameba captures images in real-time via USB cameras, combined with network transmission or local storage, used for home monitoring, doorbell peepholes, or industrial site surveillance.

  • Visual Recognition Terminals: As a front-end acquisition device for edge computing nodes, it acquires image data and passes it to subsequent AI algorithms for face recognition, QR code scanning, or object detection.

  • Video Calling Devices: Combined with Wi-Fi or cellular network modules, it captures user video streams through generic USB cameras to realize low-cost VoIP video intercom functions.

Protocol Introduction

The UVC (USB Video Class) protocol defines standard interfaces within the USB specification framework for implementing Control Management and Video Stream Transmission between a host and video capture devices. The host driver establishes video data channels through this protocol to achieve real-time camera preview, recording, and parameter adjustment.

Protocol Documents

USB-IF has officially released the UVC class base protocol and specifications for multiple Payload formats. Please refer to the following core documents during development:

Specification Type

Document

UVC 1.5 (Video Class Base Protocol)

Video Class v1.5 document set

Payload Specs (Payload Formats)

Contained within the USB_Video_Payload_*.pdf files in the archive above.

Term Definition

The definitions of general UVC (USB Video Class) technical terms used in this document are as follows:

Term

Description

VC Interface (Video Control Interface)

Video Control Interface. As the core control center of the UVC device, it manages the topology of the video device (such as the connection relationship between Units and Terminals). The host sends control requests via this interface, such as adjusting brightness, contrast, or performing Pan/Tilt/Zoom control.

VS Interface (Video Streaming Interface)

Video Streaming Interface. Responsible for the actual transmission of video payload data, usually using Isochronous or Bulk pipes. Each VS Interface contains specific video format information (such as YUV, MJPEG, H.264) and related frame descriptors.

Input Terminal (IT)

Input Terminal. The entry point where the video data stream enters the UVC function topology. Common input terminals include Camera Sensors or Composite Video Input interfaces. It represents the physical source of data.

Output Terminal (OT)

Output Terminal. The exit point where the video data stream leaves the UVC function topology. The most common output terminal is the USB Streaming Terminal, indicating that data will be sent to the host via the USB bus.

Processing Unit (PU)

Processing Unit. A processing node located after the Input Terminal, used to adjust the video image itself. It provides control capabilities over image quality, such as Brightness, Contrast, Hue, Saturation, and Sharpness.

Extension Unit (XU)

Extension Unit. A functional module that the UVC specification allows manufacturers to customize. Through XU, manufacturers can define specific control commands outside the standard UVC specification and access them on the host side via matching drivers or applications.

Probe & Commit Control

Negotiation and Commit Control. This is a critical mechanism in the process of establishing a video stream. The host first sends a “Probe” request to query the bandwidth and parameters supported by the device. After reaching an agreement, it sends a “Commit” request to lock the configuration before the video stream transmission can be started.

Payload Header

Payload Header. Header information in UVC video stream data packets, containing key synchronization information such as frame toggle (Frame ID), timestamp (PTS/SCR), and error flags.

Protocol Framework

The UVC Host protocol stack adopts a layered architecture design, aiming to decouple the USB transport layer from the upper-layer video application or multimedia framework.

../../_images/usb_uvc_arch.svg
Component Responsibilities
  • Application

    Located at the top layer of the architecture, responsible for specific business logic processing. Includes video preview, recording applications, or AI algorithms and network streaming services based on video streams.

  • Video Middleware

    Acts as an abstraction layer connecting the upper and lower layers. It provides a unified data acquisition interface upwards for the application layer, shielding underlying differences; it is responsible for video stream encoding/decoding processing, format conversion, and buffer queue management.

  • UVC Class Driver

    The core intermediate layer, implementing behaviors defined by the UVC specification:

    • Topology Parsing: Parses the internal topology (Unit/Terminal) of the Video Control Interface (VC) and format descriptors of the Video Streaming Interface (VS).

    • Stream Negotiation: Implements the Probe and Commit processes to negotiate parameters such as resolution, frame rate, and bandwidth.

    • Frame Reassembly and Submission: Parses the UVC Payload Header, handles frame start/end indicators (FID/EOF), reassembles scattered USB packets into complete video frames, and submits them to the middleware layer frame by frame.

  • USB Core & HCD (Host Controller Driver)

    Underlying drivers responsible for handling USB standard enumeration, isochronous pipe management, and scheduling of underlying physical data transmission.

Communication Mechanism

Standard UVC devices aggregate the following interfaces through the Interface Association Descriptor (IAD):

Video Control Interface (VC Interface) - Topology Control

  • Transfer Mechanism: Based on Control Transfer over the default pipe 0 (EP0).

  • Core Function: Sends class-specific requests to control Unit/Terminal attributes, completing parameter negotiation via Probe and Commit processes.

Video Streaming Interface (VS Interface) - Data Pipeline

  • Transfer Mechanism: Uses Isochronous transfer mode to carry high-bandwidth video streams.

  • Features: Guarantees bandwidth and low latency (no retransmission), suitable for real-time video preview.

Descriptor Structure

In addition to standard USB descriptors (such as Device Descriptor, Configuration Descriptor, Endpoint Descriptor), UVC devices also define Class-Specific Descriptors.

These descriptors are classified into Class-Specific Video Control Interface Descriptors (VC) and Class-Specific Video Streaming Interface Descriptors (VS) based on the interface they belong to.

Descriptor Topology

Device Descriptor
└── Identifies basic device information (USB Version 2.00, Composite Device)

Configuration Descriptor
├── Contains total length, power supply (500mA), etc.
│
├── Interface Association Descriptor (IAD)
│       └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│       ├── Class-Specific VC Header (declares UVC version, clock frequency)
│       ├── Class-Specific Descriptor Collection (Topology)
│       │       ├── Input Terminal (Camera)
│       │       ├── Processing Unit
│       │       ├── Extension Unit (Vendor Specific Controls)
│       │       └── Output Terminal (USB Streaming)
│       └── Standard Endpoint Descriptor (Interrupt IN for Status)
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
                ├── Alternate Setting 0: Control transfer active state (negotiation only, no data endpoint)
                │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
                │ ├── Class-Specific VS Header
                │ ├── Format Descriptor
                │ │     └── Frame Descriptor (various resolutions and frame rates)
                │ ├── Format Descriptor
                │ │     └── Frame Descriptor (various resolutions and frame rates)
                │ ├── Still Image Frame Descriptor
                │ └── Color Matching Descriptor
                │
                ├── Alternate Setting 1: Data transfer active state (with data endpoint)
                │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
                │ └── Standard Endpoint Descriptor (ISO IN endpoint, e.g., Low Bandwidth)
                │
                ├── Alternate Setting 2
                │ ...... Can configure multiple different settings as needed (e.g., Medium to High Bandwidth)
                │
                └── ...... (Other Alternate Settings for different packet sizes)

Device Qualifier Descriptor
└── Device information while running in another speed mode (e.g., High Speed vs Full Speed capability)

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode
│
├── Interface Association Descriptor (IAD)
│       └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│       └── Class-Specific Descriptor Collection (Same Topology as main configuration)
│               ├── Input Terminal
│               ├── Processing Unit
│               ├── Extension Unit
│               └── Output Terminal
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
                ├── Alternate Setting 0: Control transfer active state (negotiation only)
                │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
                │ ├── Class-Specific VS Header
                │ ├── Format Descriptor
                │ │     └── Frame Descriptor
                │ └── Color Matching Descriptor
                │
                ├── Alternate Setting 1: Data transfer active state
                │ ├── Standard Interface Descriptor (Streaming Class)
                │ └── Standard Endpoint Descriptor (ISO IN endpoint)
                │
                └── ...... (Other alternate settings typically available in other speed modes)

UVC Video Control (VC) Interface

  • Video Control Interface Descriptor

Interface Header Descriptor
├── bLength            : 1 byte  → Total descriptor length (13 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x01 (VC_HEADER)
├── bcdUVC             : 2 bytes → Video Class Specification Release Number (0x0100 = 1.00)
├── wTotalLength       : 2 bytes → Total number of bytes for all VC descriptors
├── dwClockFreq        : 4 bytes → Clock frequency in Hz (e.g., 0x02DC6C00 = 48 MHz)
├── bInCollection      : 1 byte  → Number of VideoStreaming interfaces
└── baInterfaceNr(1)   : 1 byte  → Interface number of the first VideoStreaming interface (0x01)
  • Input Terminal Descriptor (Camera)

Input Terminal Descriptor
├── bLength            : 1 byte  → Total descriptor length (18 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x02 (VC_INPUT_TERMINAL)
├── bTerminalID        : 1 byte  → Unique ID of this Terminal (0x01)
├── wTerminalType      : 2 bytes → 0x0201 (ITT_CAMERA)
├── bAssocTerminal     : 1 byte  → ID of associated Output Terminal (0x00 = None)
├── iTerminal          : 1 byte  → String descriptor index
├── wObjectiveFocalMin : 2 bytes → Min focal length (0 = not supported)
├── wObjectiveFocalMax : 2 bytes → Max focal length
├── wOcularFocalLength : 2 bytes → Ocular focal length
├── bControlSize       : 1 byte  → Size of bmControls (3 bytes)
└── bmControls         : 3 bytes → Bitmap of supported controls
                                                        • D1: Auto-Exposure Mode
                                                        • D3: Exposure Time (Absolute)
                                                        • D5: Focus (Absolute)
                                                        • D9: Zoom (Absolute)
                                                        • D11: Pan (Absolute)
                                                        • D15: Tilt (Absolute)
  • Processing Unit Descriptor

Processing Unit Descriptor
├── bLength            : 1 byte  → Total descriptor length (11 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x05 (VC_PROCESSING_UNIT)
├── bUnitID            : 1 byte  → Unique ID of this Unit (0x02)
├── bSourceID          : 1 byte  → ID of the source connected to this unit (0x01 = Camera IT)
├── wMaxMultiplier     : 2 bytes → Max digital zoom multiplier
├── bControlSize       : 1 byte  → Size of bmControls (2 bytes)
└── bmControls         : 2 bytes → Bitmap of supported image controls
                                                        • D0: Brightness
                                                        • D1: Contrast
                                                        • D2: Hue
                                                        • D3: Saturation
                                                        • D4: Sharpness
                                                        • D6: White Balance Temperature
  • Extension Unit Descriptor

Extension Unit Descriptor
├── bLength            : 1 byte  → Total descriptor length (29 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x06 (VC_EXTENSION_UNIT)
├── bUnitID            : 1 byte  → Unique ID of this Unit (0x03)
├── guidExtensionCode  : 16 bytes→ Vendor-specific GUID (e.g., {0FB885C3-...})
├── bNumControls       : 1 byte  → Number of controls in this XU (0x05)
├── bNrInPins          : 1 byte  → Number of input pins (0x01)
├── baSourceID[1]      : 1 byte  → ID of the source connected (0x02 = PU)
├── bControlSize       : 1 byte  → Size of bmControls (4 bytes)
└── bmControls         : 4 bytes → Bitmap of supported vendor controls
  • Output Terminal Descriptor

Output Terminal Descriptor
├── bLength            : 1 byte  → Total descriptor length (9 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x03 (VC_OUTPUT_TERMINAL)
├── bTerminalID        : 1 byte  → Unique ID of this terminal (0x05)
├── wTerminalType      : 2 bytes → 0x0101 (TT_STREAMING)
├── bAssocTerminal     : 1 byte  → Associated Input Terminal ID
├── bSourceID          : 1 byte  → ID of the connected Source (0x04 = XU)
└── iTerminal          : 1 byte  → String descriptor index

UVC Video Stream (VS) Interface

  • Video Stream Interface Descriptor

Class-Specific VS Input Header Descriptor
├── bLength            : 1 byte  → Total descriptor length (15 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x01 (VS_INPUT_HEADER)
├── bNumFormats        : 1 byte  → Number of video formats supported (0x02)
├── wTotalLength       : 2 bytes → Total length of all VS specific descriptors
├── bEndpointAddress   : 1 byte  → Address of the ISO IN endpoint (0x81)
├── bmInfo             : 1 byte  → Capabilities (0x00)
├── bTerminalLink      : 1 byte  → ID of the Output Terminal in VC interface (0x05)
├── bStillCaptureMethod: 1 byte  → Method of still image capture (0x02)
├── bTriggerUsage      : 1 byte  → Trigger usage (0x00)
├── bControlSize       : 1 byte  → Size of control field (1 byte)
└── bmaControls(n)     : n bytes → Controls for each format
  • Video Stream Format Type Descriptor

Video Streaming Format Type Descriptor(MJPEG)
├── bLength            : 1 byte  → Total descriptor length (11 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x06 (VS_FORMAT_MJPEG)
├── bFormatIndex       : 1 byte  → Index of this format (0x01)
├── bNumFrameDescriptors: 1 byte → Number of frame descriptors (0x09)
├── bmFlags            : 1 byte  → Characteristics (0x01 = Fixed Sample Size)
├── bDefaultFrameIndex : 1 byte  → Default frame index (0x01)
├── bAspectRatioX      : 1 byte  → X dimension of aspect ratio
├── bAspectRatioY      : 1 byte  → Y dimension of aspect ratio
├── bmInterlaceFlags   : 1 byte  → Interlace information (0x00 = Progressive)
└── bCopyProtect       : 1 byte  → Duplication restrictions (0x00)
  • Video Stream Frame Type Descriptor

Video Streaming Frame Type Descriptor (MJPEG)
├── bLength            : 1 byte  → Total descriptor length (e.g., 34 bytes)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x07 (VS_FRAME_MJPEG)
├── bFrameIndex        : 1 byte  → Index of this frame (0x01)
├── bmCapabilities     : 1 byte  → Still image support, etc.
├── wWidth             : 2 bytes → Frame Width (e.g., 0x0A20 = 2592)
├── wHeight            : 2 bytes → Frame Height (e.g., 0x0798 = 1944)
├── dwMinBitRate       : 4 bytes → Min Bit Rate (bps)
├── dwMaxBitRate       : 4 bytes → Max Bit Rate (bps)
├── dwMaxVideoFrameBuf : 4 bytes → Max Frame Buffer Size (bytes)
├── dwDefaultFrameInterval: 4 bytes→ Default frame interval in 100ns units
├── bFrameIntervalType : 1 byte  → 0 = Continuous, Non-0 = Discrete
└── dwFrameInterval(n) : 4×n bytes→ Discrete frame intervals supported (e.g., 33ms for 30fps)

Note

For detailed field definitions, please refer to the official USB-IF UVC (USB Video Class) 1.5 protocol documentation.

Note

Note: Confirm Device Capabilities at Current Speed (FS vs HS)

Please be aware that cameras often present different descriptor structures in Full Speed and High Speed modes:

  • Format Support Differences: Due to bandwidth limitations, certain uncompressed formats (like YUY2) may only be visible in High Speed mode. In Full Speed, the device might only support MJPEG or extremely low-resolution YUY2.

  • Descriptor Variations: The aforementioned VS_FRAME_UNCOMPRESSED descriptors may not exist at all when connected at Full Speed, or parameters such as resolution and frame rate may change significantly.

Class-Specific Requests

The UVC Host driver controls device behavior by sending the following requests via control pipe 0. These requests usually target specific Unit IDs or Interfaces.

Request Name

Requirement

Description

SET_CUR

Mandatory

Sets the current attribute value. Used for Probe/Commit negotiation, or controlling PU attributes like brightness and contrast.

GET_CUR

Mandatory

Gets the current attribute value. Reads negotiated parameters or current device status.

GET_MIN / GET_MAX

Optional

Gets the adjustable range of an attribute. The host driver uses this range to limit input values from the application layer.

GET_RES

Optional

Gets the resolution (step size) of an attribute.

GET_DEF

Optional

Gets the default value of an attribute.

GET_LEN

Optional

Gets the data length.

Stream Negotiation

Before officially starting video stream transmission (Stream On), the host and device must strictly follow the UVC state machine to negotiate parameters and bandwidth; otherwise, the device may refuse to start the video stream. According to the official spec, this process consists of two main phases: Probe (Three-step Handshake) and Commit.

Stream Negotiation Timing Diagram

../../_images/usb_uvc_probecommit.svg

Detailed Core Steps

  1. PROBE1: Initiate Negotiation (SET_CUR)

    The host sends a SET_CUR (PROBE) request to the Video Streaming Interface (VS Interface), carrying the desired video parameter structure.

    • Purpose: The host proposes “I want this resolution, frame rate, and format”.

  2. PROBE2: Get Feedback (GET_CUR)

    The host sends a GET_CUR (PROBE) request to read back the actual parameters supported by the device.

    • Device Correction: If the parameters requested by the host are not supported, the device returns the closest corrected parameters.

    • Key Data: In the returned structure, the dwMaxPayloadTransferSize field is calculated and filled by the device, which is the basis for subsequent bandwidth selection.

  3. PROBE3: Final Confirmation (SET_CUR)

    The host sends the parameters read back in step 2 (which may have been corrected) to the device again via SET_CUR (PROBE).

    • Purpose: Both sides synchronize states, the host confirms acceptance of the device’s correction suggestions, and ends the negotiation phase.

  4. COMMIT: Commit to Effect (SET_CUR)

    After parameters are agreed upon, the host sends a SET_CUR (COMMIT) request.

    • Purpose: Notifies the device that “parameters are determined”.

Note

Key Parameters

In the Probe/Commit process, the host mainly relies on the following three core fields to determine the video stream configuration:

  • bFormatIndex: Format Index

  • bFrameIndex: Frame Index

  • dwFrameInterval: Frame Interval

Other Parameters

Whether other parameters in the structure (such as wCompWindowSize, wCompQuality, etc.) are effective or adjustable depends entirely on the actual support of the specific device. For most standard devices, usually only the above three core parameters need attention, and other parameters can maintain the default values returned by the device.

For the complete definition of the Probe and Commit Control structure and details of all fields, please refer to the official spec.

Stream Bandwidth Matching and Activation (Stream Activation)

When parameter negotiation is complete, the host needs to select appropriate hardware interface settings based on the bandwidth requirements fed back by the device to officially open the transmission pipe.

Stream Bandwidth Allocation Diagram

../../_images/usb_uvc_select_bw.svg
  • Match Bandwidth

The driver will traverse all Alternate Settings of the video streaming interface based on the dwMaxPayloadTransferSize value obtained during the negotiation phase.

  • Set Interface

Once a suitable Alternate Setting is found, the host sends the standard USB request SET_INTERFACE. This step marks the formal reservation of USB Isochronous Transfer bandwidth, and the video stream transmission begins immediately.

Note

Bandwidth Matching Principle

In isochronous transfer mode, bandwidth selection is crucial. The driver needs to follow the principle of sufficient and minimal:

  • Search Target: Among all Alternate Settings, look for settings where wMaxPacketSize (endpoint maximum packet size) is greater than or equal to the negotiated value dwMaxPayloadTransferSize.

  • Optimal Choice: Among all settings that meet the above conditions, choose the one with the smallest wMaxPacketSize.

If bandwidth is insufficient, it will cause data packet truncation, resulting in screen corruption or frame loss. If bandwidth is excessive, it will occupy valuable periodic USB bus bandwidth, potentially causing other devices on the bus (such as USB audio) to fail due to insufficient bandwidth.

Data Transmission Format

UVC video data is not a pure raw data stream but is encapsulated in data packets with a Payload Header. The host driver must first parse and strip this header to obtain the valid video Payload.

Payload Header Detailed Structure
../../_images/usb_uvc_payloadheader.png
  • Basic Header Fields

    All Payload Headers start with two core bytes:

    • HLE (Header Length): 1 byte. Specifies the length of the entire header (in bytes), including HLE itself, BFH, and optional PTS/SCR fields.

    • BFH[0] (Bit Field Header): 1 byte. Bit field flags indicating attributes of subsequent data and the presence of optional fields.

  • BFH[0] Bit Field Definition

    Bit

    Name

    Description

    D0

    FID

    Frame Identifier. Used to distinguish different video frames or segments (behavior depends on video format, see below).

    D1

    EOF

    End of Frame. Indicates whether the current packet is the last packet of a frame or segment.

    D2

    PTS

    Presentation Time Stamp. When set to 1, indicates the header contains a 4-byte PTS field.

    D3

    SCR

    Source Clock Reference. When set to 1, indicates the header contains a 6-byte SCR field.

    D4

    RES

    Reserved. Usually set to 0.

    D5

    STI

    Still Image. Set to 1 indicates the sample belongs to a static image (Stream-based formats usually set to 0).

    D6

    ERR

    Error Bit. Set to 1 indicates an error occurred on the device during streaming.

    D7

    EOH

    End of Header. Set to 1 indicates the end of BFH fields (i.e., no extension header).

  • Optional Extension Fields

    • PTS (4 bytes): Exists when BFH[0].D2 (PTS) is set. Represents the presentation time of the video frame.

    • SCR (6 bytes): Exists when BFH[0].D3 (SCR) is set. Source clock reference used for audio/video synchronization.

Video Data Transmission Formats

In the UVC specification, video data transmission formats are mainly divided into two categories: Frame-based and Stream-based. These two modes determine how the host parses video data and processes the Payload Header.

  • Frame-based

    This is the most common mode, applicable to MJPEG, Uncompressed (YUV/NV12), and other formats.

    • Characteristics: Video data is strictly divided into individual independent images (frames).

    • Transmission Logic: The host driver focuses on “frame boundaries”, assembling a complete frame by detecting the FID toggle or EOF flag in the Payload Header.

  • Stream-based

    Mainly used for compressed stream formats like H.264/H.265.

    • Characteristics: Data is treated as a continuous byte stream without a strict physical “frame” boundary concept (or the boundary is handled internally by the decoder).

    • Transmission Logic: The host driver is mainly responsible for transporting the data stream, usually not relying on the Payload Header to determine the start or end of a frame, but passing the data directly to the upper-layer application or decoder to parse the content.

Note

Engineering Practice: Actual Behavior of Stream-based Devices

Although the UVC specification states that Stream-based formats may not use FID and EOF to delimit boundaries, in actual engineering applications:

  • Device Side (Camera):

    Even when outputting Stream-based formats like H.264/H.265, the vast majority of manufacturers still try to follow Frame-based rules as much as possible, marking the boundaries of data blocks (such as NAL Units or frame slices) by toggling FID or setting EOF.

  • Host Side (Host):

    To simplify logic, most generic UVC drivers (such as Linux uvcvideo, Windows system drivers) default to uniformly slicing data packets based on FID jumps or EOF flags.

Tip

Engineering Practice: Host Driver Data Processing Mechanism

In actual engineering implementation, the UVC Host Driver adopts a unified transmission processing strategy for Frame-based and Stream-based formats, without making special distinctions for stream transmission. Its core processing logic is as follows:

  • Format Independence of Transport Layer:

    The driver layer is only responsible for data transport and reassembly and is not aware of the specific video encoding format or stream type. Regardless of the data type declared by the device descriptor, the driver treats it as a generic data payload.

  • Unified Packet Assembly Mechanism:

    The driver strictly relies on the FID (Frame Identifier) state toggle and EOF (End of Frame) flag bit in the Payload Header for data delimitation. The driver assembles data packets belonging to the same logical sequence into a complete Payload Buffer and submits it to the user space as one frame at the driver level.

  • Separation of Parsing Responsibility:

    Parsing of data content is the responsibility of the Application Layer.

    For example, for Stream-based formats like H.264, the Buffer submitted by the driver may contain multiple NAL Units or partial frame data, and the application layer needs to parse the stream data structure itself to obtain the actual video frames.

Class Driver

This section details the internal implementation details of the USB UVC host driver, including driver architecture, video stream management, support for class-specific requests, and pipe resource allocation schemes.

Detailed Implementation

The USB Host UVC driver stack is based on a modular design, achieving efficient interaction between the upper-layer application and the USB hardware controller through a layered architecture. This architecture ensures stable capture and processing of high-bandwidth video data and provides a flexible frame buffer management mechanism.

Its core architecture and data flow are shown in the figure below:

../../_images/usb_host_uvc_flow.svg

Divided by functional responsibilities, it consists of the following core modules:

Application Adapter

Responsible for buffer management and extraction of Video Frame data.

As the interface layer between the USB driver and the User Application, it manages the final output of video data:

  • Parameter Configuration: Provides the usbh_uvc_set_param() interface, allowing the application layer to set resolution, frame rate, and video format (MJPEG/H.264/YUV).

  • Data Acquisition: Provides the usbh_uvc_get_frame() interface, through which the application layer retrieves fully assembled video frames from the Ready Queue.

  • Stream Control: Provides usbh_uvc_stream_on() and usbh_uvc_stream_off() interfaces to control the start and stop of the underlying video stream transmission.

UVC Class Driver Architecture

The UVC host-side driver uses the UVC Class Driver to handle standard protocol handshakes, descriptor topology parsing, and video stream maintenance.

This module strictly follows the USB Video Class 1.1/1.5 protocol specifications, implementing the core business logic for interaction between the host and the UVC device. Its main responsibilities include:

  • Enumeration and Interface Binding: Responsible for parsing the Video Control Interface (VC Interface) and Video Streaming Interface (VS Interface), and establishing the internal UVC topology (such as Unit and Terminal).

  • Format Negotiation (Probe & Commit): Before the video stream starts, executes the standard Probe/Commit process to negotiate resolution, frame rate, and optimal bandwidth settings with the device.

  • Dynamic Bandwidth Allocation: Based on negotiation results, automatically selects the best matching Alt Setting and requests corresponding Isochronous pipe resources.

Core Process Task (Process Task)

Responsible for USB protocol stack core logic and state machine management.

  • State Machine Maintenance: Manages UVC-specific state transitions, including UVC_STATE_CTRL (control transfer processing), UVC_STATE_TRANSFER (data transfer processing), and state migration during the stream startup process.

  • Event Processing: Responds to underlying transfer completion events, coordinating the timing of control transfers and data stream transfers.

  • Dynamic Hot-Plug: Handles device connection (Attach) and removal (Detach) events, automatically releasing stream buffers and pipe resources.

Frame Assembly Service

Responsible for the parsing and reassembly of raw USB data packets (Stream Decoding):

  • URB Processing: Receives URB (USB Request Block) data packets reported by the bottom layer and parses the UVC Payload Header.

  • Frame Assembly: Handles FID (Frame ID) toggle logic, strips protocol headers, and fills valid video payloads into the Frame Buffer.

  • Error Detection: Detects packet loss or Error Bits during transmission to ensure video frame integrity. If hardware acceleration is enabled, this module interfaces directly with the hardware decoder.

Class-Specific Request Implementation

This driver stack follows the USB Video Class specification, encapsulating the implementation and sending process of core Class-Specific Requests.

The driver layer mainly implements support for Video Probe Control and Commit Control. Source code path: {SDK}/component/usb/host/uvc

Class-Specific Request Type

Remarks

SET_CUR

Sets the current attribute. Mainly used in Probe and Commit phases to send desired video stream parameters (e.g., dwFrameInterval, bFormatIndex) to the device.

GET_CUR

Gets the current attribute. Reads the currently active configuration parameters from the device, used to verify settings or retrieve current status.

Pipe Configuration

The UVC host driver parses the configuration descriptor during the device enumeration phase (usbh_uvc_attach), automatically identifies Video Control and Video Streaming interfaces, and requests corresponding pipe resources based on Alt Settings.

Pipe Type

Description

Control IN/OUT Pipe

Default Control Pipe 0 (EP0). Used for sending standard requests and UVC specific requests (e.g., Video Probe and Commit Control).

Isochronous IN Pipe

Belongs to the Video Streaming Interface (VS Interface). Used for receiving high-bandwidth video payload data. The driver supports automatically selecting the appropriate Packet Size (MPS) and transfer mode based on USB speed (Full/High Speed).

API Description

Driver API

Application Example

UVC Host Driver Development Guide

This section details the complete development process of the UVC (USB Video Class) Host driver, covering driver initialization, hot-plug management, video stream control and data processing, as well as example descriptions for different application scenarios.

Driver Initialization

Before using the UVC Host driver, a configuration structure must be defined and callback functions registered, followed by calling core interfaces in sequence to start the USB host controller and the UVC class driver.

Step Description:

  • Hardware Configuration: Set the USB speed mode and related interrupt priorities.

  • Callback Registration: Define the usbh_uvc_cb_t structure and mount handler functions for various stages (initialization, connection, disconnection, parameter setting completion).

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

  • Class Driver Loading: Call usbh_uvc_init() to initialize the UVC class driver.

/*
 * 1. Configure USB speed, ISR priority, and main task priority.
 */
static usbh_config_t usbh_cfg = {
        .speed = USB_SPEED_HIGH,
        .ext_intr_enable = USBH_SOF_INTR,
        .isr_priority = INT_PRI_MIDDLE,
        .main_task_priority = CONFIG_USBH_UVC_MAIN_THREAD_PRIORITY,
        .tick_source = USBH_SOF_TICK,
};

/*
 * 2. Define USB user-level callbacks.
 */
static usbh_user_cb_t usbh_usr_cb = {
        .process = uvc_cb_process,
};

/*
 * 3. Configure UVC user-level HW priority.
 */
static usbh_uvc_ctx_t uvc_cfg = {
#if USBH_UVC_USE_HW
        .hw_isr_pri = CONFIG_USBH_UVC_HW_IRQ_PRIORITY,
#endif
};

/*
 * 4. Define user callbacks for UVC events.
 */
static usbh_uvc_cb_t uvc_cb = {
        .init = uvc_cb_init,
        .deinit = uvc_cb_deinit,
        .attach = uvc_cb_attach,
        .detach = uvc_cb_detach,
        .setup = uvc_cb_setup,
        .setparam = uvc_cb_setparam,
};

int ret = 0;

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

/*
 * 6. Initialize UVC class driver.
 */
ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
if (ret != HAL_OK) {
        /* If class driver init fails, clean up the core driver */
        usbh_deinit();
        goto usb_deinit_exit;
}
Hot-Plug Event Handling

Listen for UVC camera connection and disconnection by registering attach and detach callback functions in usbh_uvc_cb_t.

In the example code, a Semaphore mechanism is used to synchronize states:

  • Attach: When a camera is inserted and successfully enumerated, the attach callback is triggered, releasing uvc_attach_sema to notify the main thread to create the video capture task.

  • Detach: When the camera is removed, the detach callback is triggered, releasing uvc_detach_sema to trigger the hot-plug management thread to perform resource cleanup and re-initialization.

/* USB detach callback */
static usbh_uvc_cb_t uvc_cb = {
        .detach = uvc_cb_detach,
};

/* Callback executed when device is removed */
static int uvc_cb_detach(void)
{
        RTK_LOGS(TAG, RTK_LOG_INFO, "DETACH\n");
        rtos_sema_give(uvc_detach_sema);
        usbh_uvc_is_ready = 0;
        return HAL_OK;
}

/* Thread Context: Handle the state machine for Hotplug */
static void uvc_hotplug_thread(void *param)
{
        int ret = 0;

        UNUSED(param);

        for (;;) {
                /* Wait for detach signal */
                if (rtos_sema_take(uvc_detach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
                        rtos_time_delay_ms(100);

                        /* Stop transfer, release resource */
                        usbh_uvc_deinit();
                        usbh_deinit();
                        rtos_time_delay_ms(10);
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());

                        /* Re-init Host Core */
                        ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
                        if (ret != HAL_OK) {
                                RTK_LOGS(TAG, RTK_LOG_ERROR, "Init USBH fail\n");
                                break;
                        }

                        /* Re-init UVC Class */
                        ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
                        if (ret < 0) {
                                RTK_LOGS(TAG, RTK_LOG_ERROR, "Init UVC fail\n");
                                usbh_deinit();
                                break;
                        }
                }
        }

        RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
        rtos_task_delete(NULL);
}

/* Main entry task to initialize USB and wait for connection */
static void example_usbh_uvc_task(void *param)
{
        rtos_task_t uvc_task;
        rtos_task_t hotplug_task;
        int ret = 0;

        /* ... Initialization of semaphores and mutexes ... */

        /* Init USB Host Core */
        ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
        if (ret != HAL_OK) {
                goto free_sema_exit;
        }

        /* Init UVC Class Driver */
        ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
        if (ret != HAL_OK) {
                usbh_deinit();
                goto usb_deinit_exit;
        }

        /* Create Hotplug detection thread */
        ret = rtos_task_create(&hotplug_task, "uvc_hotplug_thread", uvc_hotplug_thread, NULL, 1024U, CONFIG_USBH_UVC_HOTPLUG_THREAD_PRIORITY);
        if (ret != RTK_SUCCESS) {
                goto usbh_uvc_deinit_exit;
        }

        /* Wait for device attach callback to release semaphore */
        if (rtos_sema_take(uvc_attach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
                /* Create the main UVC test/streaming thread */
                .......
        }

        goto example_exit;

        /* ... Error handling labels ... */
        .......
}
Video Stream Control and Data Processing

After the UVC device is successfully enumerated, the host needs to configure video parameters and start the video stream, then periodically acquire image frame data.

  1. Parameter Configuration (Set Parameters)

    Before starting the video stream, use usbh_uvc_set_param() to set the desired format (MJPEG/H264/YUV), resolution, and frame rate. After the setting request is issued, wait for the uvc_setparam_sema semaphore to confirm that the parameter set has been accepted by the device.

  2. Start Video Stream (Stream On)

    Call usbh_uvc_stream_on() to enable video transmission for the specified interface. The device will start sending ISOC data packets on the USB bus.

  3. Frame Data Acquisition and Processing

    The driver internally maintains a malloc-based Frame Pool with a size defined by CONFIG_USBH_UVC_FRAME_BUF_SIZE. The application layer needs to follow the “Get Frame -> Process -> Put Frame” flow in a loop:

    • Get Frame:

      Call usbh_uvc_get_frame() to obtain a filled data frame from the driver.

      Note

      Drop Oldest Mechanism:

      This function internally implements a Drop Oldest Frame strategy. To ensure the application layer always gets the Newest image, if the consumer’s processing speed is slower than the production speed, the driver will automatically discard old frames in the queue that have not yet been read, ensuring the frame buffer pool can retrieve the current latest frame.

    • Process:

      Consume the data at the application layer (e.g., copy for display, save to file, upload to network, or just perform statistical analysis).

      Note

      Data Processing Description:

      The UVC driver, as a producer, delivers data to the consumer in units of Frames (i.e., aggregating all Payloads of an image). The driver layer does not analyze the specific data content inside the Payload, so the application layer needs to implement logic for parsing and processing the Payload itself according to the specific video format.

    • Put Frame:

      Regardless of how the application layer processes the frame (even if it just counts the data size or decides to discard the frame), usbh_uvc_put_frame() must be called after processing is complete.

      Note

      This function returns the buffer to the internal frame pool of the driver. Otherwise, the frame pool will be exhausted, causing failure to acquire new data subsequently.

  4. Stop Video Stream (Stream Off)

    When video data is no longer needed or before disconnecting, call usbh_uvc_stream_off(). This notifies the device to stop ISOC transmission, releases bus bandwidth, and resets related internal states of the driver.

/* Define user callbacks for UVC events */
static usbh_uvc_cb_t uvc_cb = {
        .setup = uvc_cb_setup,
        .setparam = uvc_cb_setparam,
};

/* Define USB user-level callbacks */
static usbh_user_cb_t usbh_usr_cb = {
        .process = uvc_cb_process,
};

/* Define USB user-level setup callback */
static int uvc_cb_setup(void)
{
        RTK_LOGS(TAG, RTK_LOG_INFO, "SETUP\n");
        usbh_uvc_is_ready = 1;
        rtos_sema_give(uvc_start_sema);
        return HAL_OK;
}

/* Define USB user-level setparam callback */
static int uvc_cb_setparam(void)
{
        RTK_LOGS(TAG, RTK_LOG_INFO, "SETPARAM\n");
        rtos_sema_give(uvc_setparam_sema);
        return HAL_OK;
}

/* Define USB user-level process callback */
static int uvc_cb_process(usb_host_t *host, u8 msg)
{
        UNUSED(host);

        switch (msg) {
        case USBH_MSG_DISCONNECTED:
                usbh_uvc_is_ready = 0;
                break;

        case USBH_MSG_CONNECTED:
                break;

        default:
                break;
        }

        return HAL_OK;
}

/* Main UVC Test Thread */
static void uvc_test(void *param)
{
        usbh_uvc_frame_t *buf;
        int ret = 0;

        /* Wait for the device to be ready (Enumeration Complete) */
        if (rtos_sema_take(uvc_start_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {

                /* 1. Configure UVC Parameters */
                /* Set the desired format, resolution, and frame rate */
                uvc_s_ctx.fmt_type = CONFIG_USBH_UVC_FORMAT_TYPE; // e.g., MJPEG
                uvc_s_ctx.width = CONFIG_USBH_UVC_WIDTH;          // e.g., 1080
                uvc_s_ctx.height = CONFIG_USBH_UVC_HEIGHT;        // e.g., 720
                uvc_s_ctx.frame_rate = CONFIG_USBH_UVC_FRAME_RATE;// e.g., 30
                uvc_s_ctx.frame_buf_size = CONFIG_USBH_UVC_FRAME_BUF_SIZE;

                /* Trigger the UVC parameter setting process */
                ret = usbh_uvc_set_param(&uvc_s_ctx, CONFIG_USBH_UVC_IF_NUM_0);
                if (ret != RTK_SUCCESS) {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "Set param req failed: %d\n", ret);
                        goto exit;
                }

                /* Wait for the semaphore indicating parameter setting is actually completed */
                if (rtos_sema_take(uvc_setparam_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Set params OK\n");
                } else {
                        RTK_LOGS(TAG, RTK_LOG_ERROR, "Set params timeout\n");
                        goto exit;
                }

                /* ... Initialize consumer tasks (e.g., VFS thread or HTTPC thread) ... */

                /* 2. Start Video Stream */
                RTK_LOGS(TAG, RTK_LOG_INFO, "Stream on\n");
                ret = usbh_uvc_stream_on(&uvc_s_ctx, CONFIG_USBH_UVC_IF_NUM_0);
                if (ret) goto exit;

                /* 3. Main Capture Loop */
                while (/* Condition: e.g., Loop Count < MAX or Device Connected */) {

                        if (!usbh_uvc_is_ready) break; // Device disconnected

                        // 3.1 Get Frame from USB Stack
                        /* Retrieve a filled frame buffer from the UVC driver */
                        buf = usbh_uvc_get_frame(CONFIG_USBH_UVC_IF_NUM_0);

                        if (buf == NULL) {
                                /* Frame not ready yet, wait and retry */
                                rtos_time_delay_ms(10);
                                continue;
                        }

                        // 3.2 Process the Frame Data
                        /* CRITICAL: Buffer overflow detected! */
                        /* This means the camera sent a frame larger than our allocated buffer. */
                        /* ACTION: Please increase 'CONFIG_USBH_UVC_FRAME_BUF_SIZE' in example_usbh_uvc.c */
                        /* to match the camera's actual output size for the current resolution/format. */
                        if (buf->byteused > CONFIG_USBH_UVC_FRAME_BUF_SIZE) {
                                RTK_LOGS(TAG, RTK_LOG_ERROR, "Frame %d overflow %d > %d\n", img_cnt, buf->byteused, CONFIG_USBH_UVC_FRAME_BUF_SIZE);

                                /* Even on error, we must return the frame buffer */
                                usbh_uvc_put_frame(buf, CONFIG_USBH_UVC_IF_NUM_0);
                                return;
                        } else if (buf->byteused > 0) {
                                /* CONSUMER LOGIC: */
                                /* The actual data processing happens here. */
                                /* - Simple Mode:  Just count bytes. */
                                /* - VFS Mode: Write `buf->buf` to SD Card. */
                                /* - HTTPC Mode: Send `buf->buf` to Network. */
                                usbh_uvc_img_prepare(buf);
                                ......
                        }

                        // 3.3 Put Frame back to USB Stack
                        /* CRITICAL: Must return the buffer to driver for the next capture */
                        usbh_uvc_put_frame(buf, CONFIG_USBH_UVC_IF_NUM_0);
                }

                /* 4. Stop Video Stream */
                if (usbh_uvc_is_ready) {
                        usbh_uvc_stream_off(CONFIG_USBH_UVC_IF_NUM_0);
                        RTK_LOGS(TAG, RTK_LOG_INFO, "Stream off\n");
                }
        }
exit:
        rtos_task_delete(NULL);
}
Driver De-initialization

When the device is disconnected or the USB host function needs to be turned off, the class driver and host core driver must be unloaded in order, and related system resources released.

/* 1. Deinitialize UVC class driver. */
usbh_uvc_deinit();

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

Operation method

This example demonstrates how Ameba, acting as a USB UVC host, captures video frames from a camera. To meet different application scenarios, the example provides three working modes:

  • USBH_UVC_APP_SIMPLE: Basic Test Mode, captures video frames only but does not process them (discards directly), used for verifying the driver path and statistical throughput.

  • USBH_UVC_APP_VFS: SD Card Storage Mode, writes captured video frames to an SD card via the VFS interface.

  • USBH_UVC_APP_HTTPC: Network Upload Mode, sends captured video frames to an HTTP server.

Users can select the currently active mode by modifying the CONFIG_USBH_UVC_APP macro definition in the code.

  1. Software Configuration


Open the example_usbh_uvc.c file and modify the CONFIG_USBH_UVC_APP macro definition according to testing requirements:

/* Supported application example: USBH_UVC_APP_SIMPLE, USBH_UVC_APP_VFS, USBH_UVC_APP_HTTPC */
#define CONFIG_USBH_UVC_APP                        USBH_UVC_APP_SIMPLE

/* Supported formats: USBH_UVC_FORMAT_MJPEG, USBH_UVC_FORMAT_YUV, USBH_UVC_FORMAT_H264
* Note: Users must verify which formats their specific camera supports and
* adjust the definition below accordingly. */
#define CONFIG_USBH_UVC_FORMAT_TYPE                USBH_UVC_FORMAT_MJPEG

/* Target resolution and compression ratio.
* If the specific camera device does not support
* these values, the host stack will automatically select the closest match.
* Always check the logs to confirm the actual parameters applied. */
#define CONFIG_USBH_UVC_WIDTH                      1280
#define CONFIG_USBH_UVC_HEIGHT                     720
#define CONFIG_USBH_UVC_FRAME_RATE                 30

/* Frame buffer size in bytes
* Size depends on format, resolution, and scene complexity.
* Please increase this value if an oversize error occurs. */
#define CONFIG_USBH_UVC_FRAME_BUF_SIZE             (150 * 1024)

Depending on the selected mode, pay attention to the following configuration items:

If using HTTPC mode, configure the target server’s IP address and port, and ensure the Wi-Fi connection information is correct.

#define USBH_UVC_HTTPC_SERVER            "192.168.1.100"
#define USBH_UVC_HTTPC_PORT              5090
  1. 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_uvc -p

If compilation fails, please execute ameba.py menuconfig and confirm that USBD CDC ACM has been selected.

- Choose `CONFIG USB --->`:

   [*] Enable USB
         USB Mode (Host)  --->
   [*] UVC
  1. Result Verification


General Test Steps:

  1. Connect the USB camera to the USB interface of the development board.

  2. Reset the development board and observe the serial logs.

  3. Ensure there are no USB-related error messages (e.g., enumeration failure).

Check the specific verification steps and expected logs below according to the selected mode:

Test Description:

In this mode, after the system identifies the camera, it will periodically capture images (default 200 frames), only calculating throughput without saving data.

Expected Log:

```
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=20832
[UVC-I] Captured frame 1, len=20912
...
[UVC-I] Captured frame 199, len=108264
[UVC-I] TP 4126 KB/s @ 4953 ms, fps 40/s
[UVC-I] TP 4.0 MB/s-40 (0_20930888/200)
[UVC-I] Stream off
[UVC-I] Test done
```

Comosite Host Solution

Overview

The USB Composite Device architecture allows a single physical USB device to logically present multiple independent functions by configuring multiple Interface Descriptors or Interface Association Descriptors (IAD).

The USB host protocol stack of the Ameba platform fully supports the enumeration and driver loading of composite devices. The system can intelligently parse the device configuration structure and load the corresponding class drivers for different interfaces. This enables Ameba to handle multiple different types of traffic streams concurrently through a single USB port.

../../_images/usb_host_composite_overview.svg

Features

  • Supports the following combinations of functions:

    • CDC ACM + CDC ECM

    • HID + UAC

  • Supports USB hot-plug

  • Automatically parse descriptors and adapt to speed modes

Application Scenarios

As a USB host, Ameba can support a combination of multiple interface classes through composite class drivers, enabling interaction with complex USB composite devices on the market. It is suitable for a wide range of IoT and multimedia application scenarios, such as:

  • Cellular Network Access (CDC ECM + CDC ACM): This is the most common composite mode for 4G/LTE Cat.1/Cat.4 modules and USB Dongles. Ameba utilizes the CDC ECM interface for high-speed Internet access, which is widely used for data backhaul in industrial IoT gateways or networking functions in vehicle-mounted terminals. Meanwhile, the CDC ACM interface is used in parallel to transmit AT commands, enabling real-time monitoring and remote control of the module’s signal status.

  • Interactive Audio Devices (UAC + HID): This is a typical application for USB headphones, smart speakers, or game controllers equipped with physical control buttons. Ameba utilizes the UAC interface to handle high-quality voice calls, background music, or game sound effects streaming, while simultaneously responding to physical buttons on the device (such as volume adjustment, mute, media playback control, or controller input) in real-time through the HID interface, achieving a seamless integration of audio experience and user interaction.

Protocol Introduction

The USB Composite Device specification defines a device architecture capable of supporting multiple independent function interfaces via a single physical interface within the USB framework.

This mechanism enables the host to identify and enumerate a single physical device as a collection of logical functions, facilitating parallel processing and independent control across different device classes (e.g., HID, MSC, CDC).

Common implementations include wireless keyboard/mouse receivers, USB headsets with integrated audio cards, and industrial devices combining storage and debugging capabilities.

Descriptor Structure

While adhering to standard USB descriptors (Device and Configuration Descriptors), composite devices define multi-functionality by aggregating multiple Interface Descriptors within the Configuration Descriptor set.

Single-Interface Function Class

When a single interface represents a standalone function, the composite device simply aggregates these functional classes. Typical examples of single-interface function classes include HID and MSC.

Taking a HID Keyboard + HID Mouse composite device as an example, the descriptor topology is illustrated below:

Device Descriptor
|  bDeviceClass: 0xEF (Miscellaneous)
|  bDeviceSubClass: 0x02 (Common Class)
|  bDeviceProtocol: 0x01 (Interface Association Descriptor)
│
└── Configuration Descriptor
   |   bNumInterfaces: 2  (2 Interfaces)
   │   ...
   ├── Interface Descriptor 0 (HID Keyboard)
   │   bInterfaceNumber: 0
   │   bInterfaceClass: 0x03 (HID)
   │   bInterfaceSubClass: 0x01 (Boot Interface)
   │   bInterfaceProtocol: 0x01 (Keyboard)
   │
   └── Interface Descriptor 1 (HID Mouse)
       bInterfaceNumber: 1
       bInterfaceClass: 0x03 (HID)
       bInterfaceSubClass: 0x01 (Boot Interface)
       bInterfaceProtocol: 0x02 (Mouse)

HID Keyboard + HID Mouse composite device compared to individual keyboard/mouse devices, with descriptor characteristics as follows:

Descriptor Level

Single Function Device

Composite Device

Device Descriptor

bDeviceClass defines the type

bDeviceClass is typically 0xEF (Misc) or 0x00 (Defined by Interface)

Configuration Descriptor

1 Configuration

1 Configuration

Interface Descriptor

1 Interface

2 (or more) Interfaces

Endpoint Descriptor

Belong to the single interface

Each interface has independent endpoints

Note

bDeviceClass = 0xEF: This is a standard flag for composite devices, which triggers the host driver to parse the IAD (optional) and multiple interface descriptors in the configuration descriptor, decompose different device functions, and create these logical sub-devices. Then load the keyboard driver for Interface 0 and the mouse driver for Interface 1.

Multi-Interface Function Class

If a logical function requires the use of multiple interfaces to complete, the Interface Association Descriptor (IAD) must be included when using this type of function to associate these interfaces.

Typical multi-interface functional class

  • CDC (Communication Device Class): Typically requires 1 Control Interface (CCI) + 1 Data Interface (DCI).

  • UVC (USB Video Class): Requires 1 Video Control Interface (VC) + 1 or more Video Streaming Interfaces (VS).

  • UAC (USB Audio Class): Requires 1 Audio Control Interface (AC) + 1 or more Audio Stream Interfaces (AS).

Interface Association Descriptor (IAD)

IAD declares to the host that a set of consecutive interfaces that follow closely belong to the same function and should be loaded and managed by the same driver. Without IAD, the host sometimes recognizes them as two separate devices, or the driver fails to load.

Interface Association Descriptor (IAD)
├── bLength                 : 1 byte  → 0x08 (Fixed Length)
├── bDescriptorType         : 1 byte  → 0x0B (IAD type code)
├── bFirstInterface         : 1 byte  → First Interface Number
├── bInterfaceCount         : 1 bytes → Count of associated interfaces
├── bFunctionClass          : 1 byte  → Function Class Code
├── bFunctionSubClass       : 1 byte  → Function SubClass Code
├── bFunctionProtocol       : 1 byte  → Function Protocol Code
└── iFunction               : 1 byte  → Function String Descriptor Index

IAD Usage Example

../../_images/usb_iad_descriptor.svg
  • Function 1: Class utilize two interfaces. In order for the host to recognize them as a single logical function, it is necessary to use IAD to associate these two interfaces.

  • Function 2: Class utilize a separate interface, eliminating the need for IAD association.

Class-specific request

The USB protocol specification does not define a specific “composite class request”. Their core logic lies in precisely directing Class-Specific Requests to the designated interface through the addressing mechanism in USB standard requests.

In the bmRequestType field of the SETUP packet, the lower 5 bits (Bits 0..4) represent the Recipient.

  • General single-function device: Control requests are typically sent to the “entire device”, meaning the Recipient is set to Device (00000).

  • Composite device: A large number of control requests must be precisely sent to a “specific interface”, which means setting the Recipient to Interface (00001).

The following are the most notable key points regarding the handling of control requests by composite devices:

Field

Value

Meaning

Implementation in Composite Devices

bmRequestType

0x21 / 0xA1

Class Request, Recipient=Interface

Most common scenario. Examples include setting CDC baud rates or controlling HID keyboard LEDs.

wIndex

Interface Number

Interface Number

When the Recipient is Interface, wIndex must specify the target interface index (e.g., 0, 1, 2). The driver routes the request to the corresponding function driver based on this value.

Class Driver

A composite device refers to a device that incorporates multiple independent functions within a single physical USB device. The primary responsibility of the composite host driver is to accurately parse the device’s configuration descriptor, load the corresponding sub-drivers for each function, and enumerate a single physical device as a collection of multiple logical functions. Each function operates independently without interfering with each other (for example, sending AT commands while transmitting network data, or receiving key inputs while playing music).

This section provides a detailed analysis on how to design and implement a USB composite host class driver. The composite class driver acts as a “parent class”, responsible for managing resource scheduling, request distribution, and data processing of multiple “child classes” (such as CDC ECM, HID, UAC, etc.).

../../_images/usb_host_composite_driver_arch.png

The Composite 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 composite class driver interacts with the underlying USB Core by registering a standard usbh_class_driver_t structure.

  • Application-oriented callback API: Provides asynchronous event notifications (such as connection, disconnection, and data reception) to upper-layer applications through the usbh_composite_cb_t, usbh_user_cb_t, and sub-function-driven callback functions usbh_composite_function_class_xx_usr_cb_t registered at initialization in the application layer.

  • Application-oriented API: Provides functional interfaces for the application layer to call. For example, driver loading/unloading, scheduling data transmission, etc.

Application Callback API

The driver provides callback interface definitions for the application layer. These are implemented by the application layer to allow application code to respond to USB events and handle business logic.

  • Host Application Layer Callback APIs

    usbh_user_cb_t is the callback for the entire host.

  • Composite Application Layer Callback APIs

    usbh_composite_cb_t is the callback for the top-level composite driver.

  • Sub-function Class Application Layer Callback APIs

    These are custom callbacks for each sub-function and are generally optional to implement:

    typedef struct {
        int(* init)(void);
        int(* deinit)(void);
        int(* attach)(void);
        int(* detach)(void);
        int(* setup)(void);
        int(* received)(u8 *buf, u32 len);
        void(* transmitted)(u8 status);
    } usbh_composite_function_class_xx_usr_cb_t;
    

    API

    Description

    init

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

    deinit

    Called when the class driver is de-initialized to release application-related resources.

    attach

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

    detach

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

    setup

    Called when the class driver executes the setup callback to indicate 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 is complete; used by the application layer to obtain the OUT transfer status.

HID + UAC sub-function class application layer callback example:

 /* HID user Callback Interface. */
 typedef struct {
     int(* init)(void);                               /**< Callback when HID driver is initialized */
     int(* deinit)(void);                             /**< Callback when HID driver is de-initialized */
     int(* attach)(void);                             /**< Callback when a HID device is attached */
     int(* detach)(void);                             /**< Callback when a HID device is detached */
     int(* setup)(void);                              /**< Callback during the Setup stage of a control transfer */
     int(* report)(usbh_composite_hid_event_t *event);/**< Callback when a HID event report is received */
 } usbh_composite_hid_usr_cb_t;

/* UAC User Callback Interface. */
 typedef struct {
     int(* init)(void);                                /**< Callback when UAC driver is initialized */
     int(* deinit)(void);                              /**< Callback when UAC driver is de-initialized */
     int(* attach)(void);                              /**< Callback when a UAC device is attached */
     int(* detach)(void);                              /**< Callback when a UAC device is detached */
     int(* setup)(void);                               /**< Callback during the Setup stage of a control transfer */
     int(* isoc_transmitted)(usbh_urb_state_t state);  /**< Callback when isochronous OUT (Play) transfer completes */
     int(* isoc_received)(u8 *buf, u32 len);           /**< Callback when isochronous IN (Record) data is received */
 } usbh_composite_uac_usr_cb_t;

Composite Class Driver Implementation

This section primarily defines the composite host and the implementation of class driver callbacks.

  • Sub-function Class Driver

    Each sub-function (e.g., CDC ACM, UAC, HID) is an independent class driver. For details, please refer to the corresponding chapters of the sub-function solutions.

    • Independent Driver Structure: Each sub-driver defines a standard usbh_class_driver_t structure to implement its own business logic.

    • Independent Resource Management: Each sub-driver is responsible for parsing the corresponding interface and managing its own pipes, data buffers, and data transmission/reception processing.

  • Composite Class Driver

    • The composite class driver needs to define a standard usbh_class_driver_t structure. This serves as a unified entry point registered with the USB Core. The composite host driver will iterate through the callback functions corresponding to each sub-function class driver.

    • The composite class driver defines a standard usbh_composite_host_t structure. This is the core of the composite host instance, used to manage all sub-function class drivers.

    /* Composite Device */
    static usbh_composite_host_t usbh_composite_host;
    
    /* Composite Class Driver */
    static const usbh_class_driver_t usbh_composite_driver = {
        .id_table = composite_devs,                 /* List of supported device IDs; the core layer uses this table to match inserted devices to decide whether to load this driver */
        .attach = usbh_composite_cb_attach,         /* Called after device connection and successful matching */
        .detach = usbh_composite_cb_detach,         /* Called when the device is disconnected */
        .setup = usbh_composite_cb_setup,           /* Called when enumeration is complete and the class request phase begins; used to send class-specific standard control requests and complete necessary configurations before the device enters the data transfer state */
        .process = usbh_composite_cb_process,       /* Core state machine processing function after the class driver is ready */
        .sof = usbh_composite_cb_sof,               /* Called during SOF interrupts; used for processing logic with strict timing requirements */
        .completed = usbh_composite_cb_completed,   /* Called when a transfer on a pipe is completed */
    };
    

The following takes HID + UAC as an example to introduce the host class driver instance implementation:

/* Composite Host structure. */
 typedef struct {
     usbh_class_driver_t *hid;
     usbh_class_driver_t *uac;
     usbh_composite_cb_t *cb;
     usb_host_t *host;
 } usbh_composite_host_t;

 /* Composite Host */
 static usbh_composite_host_t usbh_composite_host;

 /* Composite Device ID */
 static const usbh_dev_id_t composite_devs[] = {
     {
         .mMatchFlags = USBH_DEV_ID_MATCH_ITF_INFO,
         .bInterfaceClass = USB_UAC1_CLASS_CODE,
         .bInterfaceSubClass = USB_UAC1_SUBCLASS_AUDIOSTREAMING,
         .bInterfaceProtocol = 0x00,
     },
     {
     },
 };

 /* Composite Class Driver */
 static usbh_class_driver_t usbh_composite_driver = {
     .id_table = composite_devs,
     .attach = usbh_composite_hid_uac_cb_attach,
     .detach = usbh_composite_hid_uac_cb_detach,
     .setup = usbh_composite_hid_uac_cb_setup,
     .process = usbh_composite_hid_uac_cb_process,
     .sof = usbh_composite_hid_uac_cb_sof,
     .completed = usbh_composite_hid_uac_cb_completed,
 };

/********************** Function 1: HID Class *********************/
/* HID host structure. */
typedef struct {
   usbh_composite_host_t *driver;        /**< Pointer to the parent composite host structure. */
   usbh_composite_hid_usr_cb_t *cb;      /**< Pointer to the user-registered callback structure. */
   usbh_pipe_t pipe;                     /**< Interrupt IN pipe handler. */
 //....
} usbh_composite_hid_t;
/* HID Host */
static usbh_composite_hid_t usbh_composite_hid;

/* HID Class Driver */
 const usbh_class_driver_t usbh_composite_hid_driver = {
     .attach = usbh_composite_hid_cb_attach,
     .detach = usbh_composite_hid_cb_detach,
     .setup = usbh_composite_hid_cb_setup,
     .process = usbh_composite_hid_cb_process,
     .sof = usbh_composite_hid_cb_sof,
 };

/********************** Function 2: UAC Class *********************/
/* UAC host structure. */
 typedef struct {
     usbh_composite_host_t *driver;        /**< Pointer to the parent composite host structure. */
     usbh_composite_uac_usr_cb_t *cb;      /**< Pointer to the user-registered callback structure. */
     usbh_uac_ac_itf_info_t  ac_isoc_in;    /**< Audio Control Interface info (Topology) */
     usbh_uac_as_itf_info_t *as_isoc_out;   /**< Pointer to Audio Streaming Output interface info */
     usbh_uac_as_itf_info_t *as_isoc_in;    /**< Pointer to Audio Streaming Input interface info */
     //...
 } usbd_composite_msc_dev_t;
/* UAC Host */
static usbh_composite_uac_t usbh_composite_uac;

/* UAC Class Driver */
 const usbh_class_driver_t usbh_composite_uac_driver = {
     .attach = usbh_composite_uac_cb_attach,
     .detach = usbh_composite_uac_cb_detach,
     .setup = usbh_composite_uac_cb_setup,
     .process = usbh_composite_uac_cb_process,
     .sof = usbh_composite_uac_cb_sof,
     .completed = usbh_composite_uac_cb_completed,
 };
Loading and Unloading Class Driver

The application layer controls the lifecycle of the entire Composite class driver through the following two main functions:

  • usbh_composite_init(): Initialization Function

    • Receives parameters passed by the application layer, such as the callback set for each sub-function.

    • Calls the driver load function for each sub-function.

    • Links the sub-function driver instance to the usbh_composite_host_t structure.

    • Finally, calls usbh_register_class() to register the composite host driver with the USB Core to make it effective.

usbh_composite_deinit(): De-initialization Function

  • Calls usbh_unregister_class() to unregister the composite host driver from the USB Core.

  • Iterates through the unload function of each sub-function to release all resources.

HID + UAC Example

/**
* @brief  Init composite class
* @param  cb: User callback
* @retval Status
*/
int usbh_composite_init(usbh_composite_hid_usr_cb_t *hid_cb, usbh_composite_uac_usr_cb_t *uac_cb, int frame_cnt)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((hid_cb == NULL) || (uac_cb == NULL)) {
        ret = HAL_ERR_PARA;
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user CB\n");
        return ret;
    }

    ret = usbh_composite_hid_init(chost, hid_cb);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init HID itf fail: %d\n", ret);
        return ret;
    }
    chost->hid = (usbh_class_driver_t *)&usbh_composite_hid_driver;

    ret = usbh_composite_uac_init(chost, uac_cb, frame_cnt);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init UAC itf fail: %d\n", ret);
        usbh_composite_hid_deinit();
        return ret;
    }
    chost->uac = (usbh_class_driver_t *)&usbh_composite_uac_driver;
    usbh_register_class(&usbh_composite_driver);

    return HAL_OK;
}

/**
* @brief  Deinit composite class
* @retval Status
*/
int usbh_composite_deinit(void)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    usbh_unregister_class(&usbh_composite_driver);

    usbh_composite_deinit_uac_class();
    usbh_composite_deinit_hid_class();

    chost->cb = NULL;

    return HAL_OK;
}
Connection and Disconnection Handling

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

  • Device Connection

    When the Core layer enumerates a Composite device matching usbh_dev_id_t, the attach callback is called, which internally iterates through the attach callback function of each sub-function.

    The main responsibilities of each sub-function driver are:

    • Match the sub-function’s usbh_dev_id_t to obtain and parse the corresponding interface descriptor.

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

    • Initialize the transfers corresponding to each pipe.

    • Call the user attach callback of the sub-function driver to notify the application layer.

  • Device Disconnection

    When the device is unplugged, the detach callback is called, which internally iterates through the detach callback function of each sub-function.

    The main responsibilities of each sub-function driver are:

    • Reset the sub-function driver state machine to IDLE.

    • Call usbh_close_pipe() to close all opened pipes.

    • Call the user detach callback of the sub-function driver to notify the application layer.

HID + UAC Example

/**
* @brief  Attach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_attach(usb_host_t *host)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    chost->host = host;

    if ((chost->hid != NULL) && (chost->hid->attach != NULL)) {
        ret = chost->hid->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_hid_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support hid\n");
        }
    }

    if ((chost->uac != NULL) && (chost->uac->attach)) {
        ret = chost->uac->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_uac_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support uac\n");
        }
    }

    return HAL_OK;
}

/**
* @brief  Detach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_detach(usb_host_t *host)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((chost->hid != NULL) && (chost->hid->detach != NULL)) {
        chost->hid->detach(host);
    }

    if ((chost->uac != NULL) && (chost->uac->detach)) {
        chost->uac->detach(host);
    }

    return HAL_OK;
}
Class Driver State Machine

Unlike the device side, which passively responds to requests, the host driver needs to actively maintain the device state. The USB host class driver uses a state machine to manage data transfer processing. The driver state is updated by API calls and underlying interrupt feedback. The process callback is the core state machine processing function for the host-side composite class driver. It internally iterates through the process callback function of each sub-function, and each sub-function independently maintains its own state machine.

HID + UAC Example

/**
* @brief  State machine handling callback
* @param  host: Host handle
* @param  msg: Event message
* @retval Status
*/
static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
{
    usbh_composite_host_t *chost = &usbh_composite_host;
    int ret = HAL_BUSY;

    /* If the pocess has handle the msg, it return HAL_OK, else return HAL_BUSY */
    if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
        ret = chost->hid->process(host, msg);
    }

    if ((ret != HAL_OK) && (chost->uac != NULL) && (chost->uac->process != NULL)) {
        ret = chost->uac->process(host, msg);
    }

    return ret;
}
Data Transfer Processing

Data transfer is scheduled and handled by each sub-function class driver. For detailed data transmission and reception processes, please refer to the corresponding chapters of the sub-function solutions.

HID + UAC Example

  • Audio Playback: Please refer to the Class Driver chapter of the Audio Host solution.

  • Button Event Reporting:
     /*************** HID + UAC Composite Class Driver *********************/
     /* USB Standard Host driver */
     static usbh_class_driver_t usbh_composite_driver = {
         .process = usbh_composite_hid_uac_cb_process,
     };
    
     static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_host_t *chost = &usbh_composite_host;
         int ret = HAL_BUSY;
    
          //1. HID process
         if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
             ret = chost->hid->process(host, msg);
         }
    
         //2. UAC process
         return ret;
     }
    
     /*************** HID Sub-funtion Class Driver *************************/
     /* HID user Callback Interface. */
     typedef struct {
         int(* report)(usbh_composite_hid_event_t *event);
     } usbh_composite_hid_usr_cb_t;
    
     /* USB Standard Class Driver */
     const usbh_class_driver_t usbh_composite_hid_driver = {
         .process = usbh_composite_hid_cb_process,
         .sof = usbh_composite_hid_cb_sof,
     };
    
     static int usbh_composite_hid_cb_sof(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
    
         //Regular reporting
         if (usbh_get_elapsed_ticks(host, pipe->tick) > UBSH_COMPOSITE_HID_TRIGGER_MAX_CNT) {
             usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
         }
    
         return HAL_OK;
     }
     /* Phase 1: Callback Process */
     static int usbh_composite_hid_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_event_t event;
         event.d32 = msg;
    
         // 1. Check if the event belongs to the HID pipe
         if ((hid->hid_ctrl_buf) && (event.msg.pipe_num == pipe->pipe_num)) {
             // 2. Clear transfer flag
             hid->next_xfer = 0;
             // 3. Handle Interrupt IN transfer state machine
             usbh_composite_hid_in_process(host);
             // 4. If transfer started/updated, notify the main host driver
             if (hid->next_xfer) {
                 usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
             }
             return HAL_OK;
         }
         return HAL_BUSY;
     }
    
     /* Phase 2: IN Transfer Process (Producer) */
     static void usbh_composite_hid_in_process(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_urb_state_t urb_state = USBH_URB_IDLE;
         u32 len;
    
         /* Handle Transfer State Machine */
         switch (pipe->xfer_state) {
         // State A: Ready to start new transfer
         case USBH_EP_XFER_START:
             if (usbh_get_elapsed_ticks(host, pipe->tick) > pipe->ep_interval) {
                 pipe->tick = usbh_get_tick(host);
                 pipe->xfer_state = USBH_EP_XFER_BUSY;
                 pipe->xfer_len = pipe->ep_mps;
                 usbh_transfer_data(host, pipe);//Start USB Hardware transfer
                 hid->next_xfer = 1;            //Flag to notify main state machine
             }
             break;
         // State B: Waiting for transfer completion
         case USBH_EP_XFER_BUSY:
             urb_state = usbh_get_urb_state(host, pipe);
             /* Check if hardware finished transfer */
             if (urb_state == USBH_URB_DONE) {
                 len = usbh_get_last_transfer_size(host, pipe);
                 /* Save raw HID data to ring buffer */
                 usb_ringbuf_add_tail(&(hid->report_msg), pipe->xfer_buf, len, 1);
                 pipe->xfer_state = USBH_EP_XFER_START;  //Set for next transfer
             }
             break;
         default:
             break;
         }
     }
    
     /* Phase 3: Parsing Thread (Consumer) */
     static void usbh_composite_hid_msg_parse_thread(void *param)
     {
         UNUSED(param);
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usb_ringbuf_manager_t *handle = &(hid->report_msg);
         usbh_composite_hid_event_t *event = &(hid->report_event);
         u8 report_msg[10];
         u32 read_cnt;
    
         while (hid->parse_task_exit == 0) {
             // 1. Try to read data from ring buffer
             read_cnt = usb_ringbuf_remove_head(handle, report_msg, 10, NULL);
             if (read_cnt) {
                 if (hid->hid_ctrl) {
                     // 2. Parse the raw bytes into a meaningful event
                     ret = usbh_composite_hid_parse_hid_report(report_msg, len, &(hid->vol_caps));
                     if (ret != HAL_OK) {
                         return;
                     }
    
                     // 3. Trigger User Callback
                     if ((hid->cb != NULL) && (hid->cb->report != NULL)) {
                         hid->cb->report(event);
                     }
    
                 }
             } else {
                 rtos_time_delay_ms(50);
             }
         }
     }
    
    /****************** Application *************************/
     static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
         .report = usbh_hid_cb_report,
     };
    
     static int usbh_hid_cb_report(usbh_composite_hid_event_t *event)
     {
         switch (event->type) {
         case VOLUME_EVENT_CONSUMER_UP:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Up (Consumer) ===\n");
             break;
         case VOLUME_EVENT_CONSUMER_DOWN:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Down (Consumer) ===\n");
             break;
         //Other event
         default:
             break;
         }
         return HAL_OK;
     }
    

API Reference

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

Application Example

This section takes USB Composite (HID + UAC) as an example to introduce the complete application implementation and the method of running example application.

Application Design

This section provides a detailed introduction to the complete development and design process of composite host drivers, covering driver initialization, hotplug management, and resource release.

Driver Initialization

Before using the Composite driver, configuration structures must be defined and callback functions registered. Subsequently, initialization interfaces are called to load the USB core driver and the Composite 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_composite_cb_t and usbh_composite_fucntion_xx_usr_cb mount processing functions for various stages.

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

  • Class Driver Initialization: Call usbh_composite_init() to initialize the Composite class driver.

HID + UAC Example

static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_FULL,
    .ext_intr_enable = USBH_SOF_INTR,
    .isr_priority = INT_PRI_MIDDLE,
    .main_task_priority = USBH_UAC_MAIN_THREAD_PRIORITY,
    .tick_source = USBH_SOF_TICK,
};

static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .init = usbh_uac_cb_init,
    .deinit = usbh_uac_cb_deinit,
    .attach = usbh_uac_cb_attach,
    .detach = usbh_uac_cb_detach,
    .setup = usbh_uac_cb_setup,
    .isoc_transmitted = usbh_uac_cb_isoc_transmitted,
};

static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
    .report = usbh_hid_cb_report,
};

static usbh_user_cb_t usbh_usr_cb = {
    .process = usbh_uac_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_composite_init(&usbh_hid_cfg, &usbh_uac_cfg, USBH_UAC_FRAME_CNT);
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.

HID + UAC Example

static rtos_sema_t usbh_uac_detach_sema;

/* USB detach callback */
static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .detach = usbh_uac_detach_sema,
};

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

static void usbh_uac_hotplug_thread(void *param)
{
    int ret = 0;

    UNUSED(param);

    for (;;) {
        if (rtos_sema_take(usbh_uac_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_composite_deinit();
            /* 2. De-initialize USB core */
            usbh_deinit();
            rtos_time_delay_ms(10);
            RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\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) {
                break;
            }

            ret = usbh_composite_init(&usbh_hid_cfg, &usbh_uac_cfg, USBH_UAC_FRAME_CNT);
            if (ret < 0) {
                usbh_deinit();
                break;
            }
        }
    }
    RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
    rtos_task_delete(NULL);
}
Data Transfer Processing

After the device is connected, the application layer can call application-oriented APIs to perform parameter configuration, data transmission scheduling, and other tasks. The device status and transmission results are obtained through the callback functions registered by the application layer during initialization, including usbh_composite_cb_t, usbh_user_cb_t, and the sub-function driver’s callback function, namely usbh_composite_function_class_xx_usr_cb_t. For detailed data transmission and reception processes, please refer to the corresponding chapters of the sub-function solution.

Driver Deinitialization

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

HID + UAC Example

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

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

Operation method

This example demonstrates how to configure the Ameba development board as a host with both USB HID (Human Interface Device) and UAC (Audio Device Class) functions through a composite host protocol stack.

When the development board is connected to a standard USB headset, the system will recognize two separate logical devices. The development board will send audio data to the headset, and if a button on the headset is pressed, host will recognize the command.

The example code path is: {SDK}/component/example/usb/usbh_composite_hid_uac. It provides provides a complete reference solution for developers to design custom composite 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 then program the generated ‘Image’ file onto 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_composite_hid_uac -p
    
  • Confirmation of Menuconfig configuration

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

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] Composite
                Select Composite Class (HID + UAC)  --->
                    (X) HID + UAC
                        Select UAC Version (UAC 1.0)  --->
    
Verification
  • Host Startup

    Reset the development board and observe the serial port log. The following startup information should be displayed:

    [COMP-I] USBH UAC&HID composite demo start
    
  • Connect to Device

    Connect the development board to a USB Type-C wired headphone using a USB cable.

  • Auto Play Test

    The Ameba development board will recognize the headphones and automatically perform an audio playback test.

  • Volume Control Test

    If the headphones also have a volume control button, when press the button, the volume change information will be output on the serial port:

    ```
    Executing Volume Up
    
    Executing Volume Down
    ```
    

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.

../../_images/usb_host_vendor_overview.svg

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

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

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.

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.

  • 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:

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}/component/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}/component/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