Transparent Communication Device Solution

Overview

The USB CDC ACM (Communication Device Class - Abstract Control Model) protocol employs the USB Bulk Transfer mechanism to provide a generic high-speed data pass-through channel, suitable for real-time transmission of high-throughput data streams and command interaction.

Ameba implements CDC ACM functionality in compliance with USB-IF standards, establishing a bidirectional transparent data transmission link. The Host can exchange raw data directly with Ameba’s internal logic or backend peripherals via the standard USB interface, effectively masking and abstracting the underlying transmission details.

../../_images/usb_device_cdc_acm_user.svg

Features

  • Compatible with standard host terminal tools such as PuTTY/TeraTerm

  • Supports hot-plug

  • Fully customizable descriptors

  • Configurable bulk transfer lengths/transfer buffer size/speed mode

  • Optional interrupt IN endpoint flexible for compatibility and bandwidth optimization

  • Supports bidirectional communication through virtual COM port, supports synchronous or asynchronous echo

Application Scenarios

Functioning as a USB data pass-through bridge, Ameba establishes a high-speed data link between the master device (Host, such as a PC or embedded host) and the external world via the USB interface. By combining this with other peripheral interfaces, it enables several typical applications:

  • Sensor Acquisition and Robot Control: Ameba acts as a high-performance sensor node, collecting high-speed environmental or motion data from the front end. This data is uploaded in real-time to the robot control system via the USB channel, supporting zero-packet-loss transmission of high-frequency raw data and rapid dispatch of closed-loop control commands.

  • Device Management and Firmware Maintenance: Geared towards the lifecycle management of IoT devices, management terminals can establish communication with the device via the CDC channel. This facilitates parameter configuration, operational status diagnostics, and supports firmware upgrades (OTA) and maintenance through this data link.

  • Wireless Data Gateway: Leveraging its integrated Wi-Fi and Bluetooth modules, Ameba serves as a wireless bridge. It transparently forwards data streams received from the Host via USB to the wireless network, providing plug-and-play wireless access capabilities for industrial control hosts or management terminals that lack native wireless functionality.

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 1.2 (Communication Device Class Basic Protocol)

https://www.usb.org/sites/default/files/CDC1.2_WMC1.1_012011.zip

PSTN 1.2 (PSTN Subclass)

PSTN120.pdf, which is included in the aforementioned CDC 1.2 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

Driver Descriptor Structures

This section presents the CDC ACM class-specific descriptor structures defined at the driver layer. These structures correspond to the standard descriptor definitions within the USB protocol specification.

Device Descriptor
└─ USB Version 2.00 (CDC ACM)

Configuration Descriptor (Interfaces 2)
│
├─ Communication Interface (IF 0, CCI)
│  ├─ Header Functional (CDC 1.10)
│  ├─ Call Management Functional (Data IF=1, Handled by Host)
│  ├─ ACM Functional (Cap=0x02: Line Coding & Serial State)
│  ├─ Union Functional (Master=0, Slave=1)
│  └─ Endpoint: INTR IN, maxpkt=HS_INTR_SIZE, Interval=HS_Interval
│
└─ Data Interface (IF 1, DCI)
   ├─ Endpoint: BULK OUT, maxpkt=0x0200 (512 bytes)
   └─ Endpoint: BULK IN,  maxpkt=0x0200 (512 bytes)

Device Qualifier Descriptor
└─ USB 2.0

Other Speed Configuration Descriptor (Interfaces 2)
│
├─ Communication Interface (IF 0, CCI)
│  ├─ Header Functional (CDC 1.10)
│  ├─ Call Management Functional (Data IF=1, Handled by Host)
│  ├─ ACM Functional (Cap=0x02: Line Coding & Serial State)
│  ├─ Union Functional (Master=0, Slave=1)
│  └─ Endpoint: INTR IN, maxpkt=FS_INTR_SIZE, Interval=FS_Interval
│
└─ Data Interface (IF 1, DCI)
   ├─ Endpoint: BULK OUT, maxpkt=0x0040 (64 bytes)
   └─ Endpoint: BULK IN,  maxpkt=0x0040 (64 bytes)

Class-Specific Request Implementation

The driver implements the core Class-Specific Requests defined by the CDC ACM specification, primarily including:

  • SET_LINE_CODING

  • GET_LINE_CODING

  • SET_CONTROL_LINE_STATE

Note

Developers may refer to the existing implementation to extend other types of requests. The CDC ACM driver source code is located at: {SDK}/component/usb/device/cdc_acm

Endpoint Configuration

Endpoint

Count

Description

Control IN/OUT Endpoint

1

EP0, used to handle standard requests and CDC class-specific requests sent by the USB Host.

Interrupt IN Endpoint

1

Used to send serial state notifications (SERIAL_STATE) to the USB Host.

Bulk OUT Endpoint

1

Used to receive downstream data streams from the USB Host.

Bulk IN Endpoint

1

Used to send upstream data streams to the USB Host.

API Reference

Driver API

Application Example

Application Design

This section outlines the complete usage workflow of the CDC ACM driver, covering core aspects such as initialization, hot-plug management, data echo processing, and virtual serial port parameter configuration.

Driver Initialization

Define the configuration structure and register callback functions, then invoke the initialization interface to load the USB device core and the CDC ACM class driver.

// Do not change the settings unless indeed necessary
#define CONFIG_CDC_ACM_BULK_IN_XFER_SIZE            2048U
#define CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE           2048U

static usbd_config_t cdc_acm_cfg = {
    .speed = CONFIG_USBD_CDC_ACM_SPEED,
    .isr_priority = INT_PRI_MIDDLE,
    .intr_use_ptx_fifo  = 0U,
};

static usbd_cdc_acm_cb_t cdc_acm_cb = {
    .init = cdc_acm_cb_init,                       /* USB init callback */
    .deinit = cdc_acm_cb_deinit,                   /* USB deinit callback */
    .setup = cdc_acm_cb_setup,                     /* USB setup callback */
    .received = cdc_acm_cb_received,               /* USB received callback */
    .status_changed = cdc_acm_cb_status_changed,   /* USB status change callback */
};

int ret = 0;

/**
    * Initialize USB device core driver with configuration.
    * param[in] cfg: USB device configuration.
    * return 0 on success, non-zero on failure.
    */
ret = usbd_init(&cdc_acm_cfg);
if (ret != HAL_OK) {
    return;
}

/**
    * Initializes class driver with application callback handler.
    * param[in] bulk_out_xfer_size: BULK OUT xfer buffer malloc length.
    * param[in] bulk_in_xfer_size: BULK IN xfer buffer malloc length.
    * param[in] cb: Pointer to the user-defined callback structure.
    * return 0 on success, non-zero on failure.
    */
ret = usbd_cdc_acm_init(CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE, CONFIG_CDC_ACM_BULK_IN_XFER_SIZE, &cdc_acm_cb);
if (ret != HAL_OK) {
    /**
        * Deinitialize USB device core driver.
        * return 0 on success, non-zero on failure.
        */
    usbd_deinit();

    return;
}

Hot-Plug Event Handling

Monitor USB connection status changes (connection/disconnection) by registering the status_changed callback function. It is recommended to use a semaphore to notify a dedicated task thread for processing, thereby avoiding the execution of time-consuming operations within the interrupt context.

Refer to Device Connection Status Detection for further details. The example code is shown below:

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

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

/* 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");

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

                    /* 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 Transmission and Reception

Upon successful enumeration of CDC ACM, the Host sends data via the virtual serial port. The driver supports both synchronous and asynchronous data processing modes, controlled by the macro CONFIG_USBD_CDC_ACM_ASYNC_XFER.

  • Data Reception Callback

    All data from the Host is passed in via the cdc_acm_cb_received callback function.

  • Synchronous Echo (Sync Echo)

    If asynchronous transfer is not enabled, usbd_cdc_acm_transmit() is called directly within the reception callback to echo the received data back to the Host.

  • Asynchronous Transfer (Async Transfer)

    If asynchronous transfer is enabled, received data is stored in the cdc_acm_async_xfer_buf ring buffer, and the transmission thread cdc_acm_xfer_thread is woken up via a semaphore. This thread is responsible for sending data in packets to avoid time-consuming operations or blocking within the interrupt context.

/* In callback: Echo immediately */
static int cdc_acm_cb_received(u8 *buf, u32 len)
{
    /* Directly transmit received data back to host */
    return usbd_cdc_acm_transmit(buf, len);
}

Note

For the complete data transmission and reception logic, please refer to the SDK example code: {SDK}/component/example/usb/usbd_cdc_acm/example_usbd_cdc_acm.c.

Driver De-initialization

When the CDC ACM function is no longer needed or during system shutdown, release resources in the reverse order of initialization.

/* Deinitialize CDC ACM class driver. */
usbd_cdc_acm_deinit();

/* Deinitialize USB device core driver. */
usbd_deinit();

Operation method

This section introduces a complete USB CDC ACM (Virtual COM Port) echo example, demonstrating how to implement bidirectional character communication between a PC and the development board using the CDC ACM protocol stack.

The example code path is: {SDK}/component/example/usb/usbd_cdc_acm. It can serve as a baseline reference for USB-to-Serial pass-through or Command Line Interface (CLI) implementation.

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, execute ./menuconfig.py, select USBD CDC_ACM by following the steps below, then save and exit.

- Choose `CONFIG USB --->`:
        [*] Enable USB
                        USB Mode (Device)  --->
        [*]   CDC ACM
  • Compilation and Burning

    Execute the compilation command and burn the generated Image file to the development board:

cd amebaxxx_gcc_project
./build.py -a usbd_cdc_acm

Result Verification

  • Start Device

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

[ACM-I] USBD CDC ACM demo start
  • Connect to Host

    Connect the development board to a PC (e.g., Windows computer) using a USB cable.

  • Serial Communication Test

    Launch any serial debugging tool (e.g., Tera Term or Realtek Trace Tool) and open the virtual serial port corresponding to the Ameba development board’s USB port.

    Attempt to send any character message to the development board. Observe the terminal interface; the development board should echo the received message back to the Host exactly as received.

Note

For Windows 7/XP Host users, the system may not automatically install the driver. Please manually install the CDC ACM driver file RtkUsbCdcAcmSetup.INF.

Before installation, ensure that the INF file contains the VID/PID values used by the current CDC ACM class, such as:

[DeviceList]
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8720
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8721
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8722
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8730
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8006
%DESCRIPTION%=DriverInstall, USB\VID_0BDA&PID_8061
; Add support for new VID/PID

HID Device Solution

Overview

The USB Human Interface Device (HID) protocol is a standard interface specification defined by USB-IF for connecting human-computer interaction devices (such as keyboards, mice, game controllers, touchscreens, etc.).

The Ameba platform, based on the official USB-IF standards, implements a fully functional USB HID device-side protocol stack. This solution provides the system with a stable, low-latency human-computer interaction interface and supports highly flexible custom configurations.

../../_images/usb_device_hid_user.svg

Features

  • Supports USB hot-plug

  • Supports fully customizable descriptors

  • Simulate basic keyboard key presses and mouse movement events

  • Unidirectional/Bidirectional Communication Support:

    • Mouse: Supports unidirectional reporting of HID data (Input Report).

    • Keyboard: Supports bidirectional HID data communication (Input & Output Report), capable of handling host-sent LED status controls (e.g., “Caps Lock” indicator).

  • Supports configuring parameters such as speed mode

Application Scenarios

As a USB HID device, Ameba can send standard input reports to a host (such as a PC, tablet, or smart TV) via a USB interface. Leveraging its built-in Wi-Fi and Bluetooth® capabilities, Ameba enables various innovative cross-protocol, cross-medium applications. For example,

  • Wireless Peripheral Adapter (Dongle): Ameba acts as a USB receiver (dongle) plugged into the host. It receives data from remote wireless keyboards/mice via Wi-Fi or Bluetooth, converts it into standard USB HID packets, and transparently transmits them to the host, enabling plug-and-play wireless peripherals.

  • IoT Smart Control Gateway: Ameba serves as a bridge between physical input and digital control. It reads signals from local physical buttons, knobs, or sensors and maps them to standard input commands via the USB HID protocol or converts them into network control instructions, enabling precise control over smart home and industrial equipment.

  • Game Controller Adapter: Supports connecting non-standard interface game controllers (such as old console controllers or custom arcade joysticks) to Ameba. The internal protocol stack converts their signals into standard HID game controller reports, ensuring compatibility with PCs or modern gaming consoles.

Protocol Introduction

The HID (Human Interface Device) protocol is a standard interface and data transmission specification defined within the USB specification framework, specifically designed for human-computer interaction devices. This protocol defines the data interaction format and control response mechanism between the host and the device.

Common devices that comply with the HID standard include:

  • Input Devices: Keyboards, mice, gamepads, digitizers (touchscreens/drawing tablets).

  • Control Devices: Consumer electronics control knobs (e.g., volume adjustment), system sleep buttons, etc.

Protocol Documentation

USB-IF has officially released the core HID protocol specification and Usage Tables for defining device functionality. Developers can refer to the following core documents:

Specification Type

Document

HID 1.11 (Core Protocol)

https://www.usb.org/sites/default/files/hid1_11.pdf

Usage Tables 1.5

https://www.usb.org/sites/default/files/hut1_5.pdf

Terminology Definitions

The definitions of general HID technical terms used in this document are as follows:

Terms

Description

Report

The basic unit of data transmitted between an HID device and the host. The structure of the data packet is defined by the Report Descriptor.

Usage

An identifier describing the specific meaning of data. For example, defining whether a data bit represents a “left-click” or the “X-axis coordinate”.

Collection

A logical structure that groups related input/output data items. For example, grouping a mouse’s X-axis, Y-axis, and buttons into a “mouse” collection.

Item

The basic building block of a report descriptor, categorized into Main items, Global items, and Local items.

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

Protocol Framework

The HID protocol stack adopts a layered architecture designed to decouple low-level USB transport details from upper-layer application logic. The system architecture mainly consists of the following core components: the USB core transport driver, the HID class driver, and HID applications for specific types.

../../_images/usb_device_hid_architecture.svg

Component Responsibilities

  • HID Application

    Located at the top of the architecture, responsible for handling specific business logic. For example: collecting sensor data and packaging it into mouse coordinate reports, or receiving keyboard LED control commands and driving GPIOs to light the indicators.

  • HID Class Driver

    Implements the Device Class Definition for HID 1.11 specification. Its main responsibilities include: parsing and constructing HID protocol-specific data packets, managing the Report Descriptor, and handling Class-Specific Requests.

  • USB Core & Transfer Driver

    Responsible for handling the standard USB enumeration process, endpoint management, and low-level packet scheduling, shielding the upper layers from differences in hardware controllers.

Communication Mechanism

HID devices achieve low-latency or low-bandwidth interaction through specific Endpoint configurations. In terms of logical functionality, HID interfaces primarily rely on the following two transfer methods:

Control Pipe

  • Mapped Endpoint: Default control endpoint 0 (Endpoint 0).

  • Enumeration & Configuration: Transmits standard USB descriptors (Device, Configuration, Interface Descriptors) as well as HID-specific HID Descriptors and Report Descriptors.

  • Class-Specific Requests: Handles HID protocol control commands, such as Set Idle (set idle rate).

  • Low-Frequency Data Transfer: When the device is not configured with an Interrupt OUT endpoint, the host sends Output Reports (e.g., setting keyboard “Caps Lock” LED state) via Control Transfer.

Interrupt Pipe

  • Functional Description: Utilizes USB’s polling mechanism to ensure real-time data transmission.

  • Interrupt IN: Mandatory. Responsible for real-time transmission of asynchronous data generated by the device (e.g., key press, mouse movement, touchscreen coordinates) to the host.

  • Interrupt OUT: Optional. Responsible for the host sending low-latency downstream data to the device. Commonly used in scenarios requiring high real-time feedback, such as force feedback vibration commands for gamepads. If this endpoint is not defined, downstream data will fall back to the Control Pipe for transmission.

Descriptor Structure

In addition to adhering to standard USB descriptors, HID devices introduce their own unique descriptor system. The most crucial ones are the HID Descriptor and the Report Descriptor.

HID 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 Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        ├── Endpoint Descriptor(Interrupt In)
        └── Endpoint Descriptor(Interrupt 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)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        ├── Endpoint Descriptor(Interrupt In)
        └── Endpoint Descriptor(Interrupt Out)

HID Descriptor

  • Located within the configuration descriptor set, it informs the host about which subordinate descriptors (usually the report descriptor) this interface contains and their lengths.

HID Descriptor
├── bLength            : 1 byte  → Total descriptor length
├── bDescriptorType    : 1 byte  → 0x21 (HID Descriptor)
├── bcdADC             : 2 bytes → HID Version
├── bCountryCode       : 1 byte  → Numeric expression identifying country code of the localized hardware
├── bNumDescriptors    : 1 byte  → Numeric expression specifying the number of class descriptors
│                        └─ (always at least one i.e. Report descriptor.)
├── bDescriptorType    : 1 byte  → Constant name identifying type of class descriptor.
└── wDescriptorLength  : 2 byte  → Numeric expression that is the total size of the Report descriptor.

Report Descriptor

  • This is a character stream composed of specific Items, not a fixed structure.

  • It flexibly defines the data format, length, usage, and physical range that the device can generate or receive. The host must parse this descriptor to understand the data sent by the device.

Report Descriptor
├── Usage Page (Generic Desktop)
├── Usage (Keyboard)
├── Collection (Application)
│       ├── Usage Page (Key Codes)
│       ├── Usage Minimum (224)
|       ├── Usage Maximum (231)
│       ├── Logical Minimum (0)
│       ├── Logical Maximum (1)
│       ├── Report Count (8)
│       ├── Report Size (1)
│       ├── Input (Data, Variable, Absolute)   ; Modifier keys (8 bits, 1 per modifier)
│       ├── Report Count (1)
│       ├── Report Size (8)
│       ├── Input (Constant, Variable, Absolute) ; Reserved byte
|       |
│       ├── Usage Page (LEDs)
│       ├── Logical Minimum (0)
│       ├── Logical Maximum (1)
│       ├── Report Count (5)
│       ├── Report Size (1)
│       ├── Usage Minimum (Num Lock)
│       ├── Usage Maximum (Kana)
│       ├── Output (Data, Variable, Absolute)  ; 5 LED control bits
│       ├── Report Count (1)
│       ├── Report Size (3)
│       ├── Output (Constant, Variable, Absolute) ; Padding 3 bits
│       │
│       └── ....... Can configure multiple different setting as needed
│
└── End Collection

Note

Writing HID Report Descriptors is very flexible and complex. It is recommended to use the HID Descriptor Tool provided by USB-IF for generation and validation.

Class-Specific Requests

Control requests for HID devices are divided into Standard Requests and Class-Specific Requests.

This section mainly introduces HID-specific Class-Specific Requests, which are primarily used to manage report status and protocol behavior. The support requirements for different types of devices for these requests are shown in the table below:

Device Type

Get Report

Set Report

Get Idle

Set Idle

Get Protocol

Set Protocol

Boot Mouse

Mandatory

Optional

Optional

Optional

Mandatory

Mandatory

Non-Boot Mouse

Mandatory

Optional

Optional

Optional

Optional

Optional

Boot Keyboard

Mandatory

Optional

Mandatory

Mandatory

Mandatory

Mandatory

Non-Boot Keyboard

Mandatory

Optional

Mandatory

Mandatory

Optional

Optional

Other Device

Mandatory

Optional

Optional

Optional

Optional

Optional

Data Transmission Format

Unlike the fixed PCM stream in UAC (USB Audio Class), the data format for HID is entirely dynamically defined by the Report Descriptor.

The following is an example of a typical data structure for a standard keyboard:

Keyboard Input Report

Used to send keypress data to the host. The following is a data example for pressing “Left Shift” + “A”:

Filed

Length(bits)

Offset(bits)

Value

Left Ctrl

1

0

0

Left Shift

1

1

1

Left Alt

1

2

0

Left GUI

1

3

0

Right Ctrl

1

4

0

Right Shift

1

5

0

Right Alt

1

6

0

Right GUI

1

7

0

Pading

8

8

0

Key

48

16

0x04

Keyboard Output Report

Used by the host to control keyboard LED status. The following is a data example for turning on the “Caps Lock” light:

Filed

Length(bits)

Offset(bits)

Value

Num Lock

1

0

0

Caps Lock

1

1

1

Scroll Lock

1

2

0

Compose

1

3

0

Kana

1

4

0

Note

Report ID:

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 implementation specifics of the HID class driver, including the hierarchical structure of descriptors, the support status for class-specific requests, and the endpoint configuration scheme.

Driver Descriptor Structure

The descriptor topology defined within the HID class driver is shown below. These structures strictly adhere to the USB 2.0 protocol specification and HID class definitions.

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

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        └── Endpoint Descriptor(Interrupt In)

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)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        └── Endpoint Descriptor(Interrupt In)

Implementation of Class-Specific Requests

This driver stack complies with the USB HID 1.11 specification and has implemented the basic framework for core Class-Specific Requests.

The driver layer has completed the underlying message communication, protocol parsing, and data transmission flow. Developers do not need to focus on USB protocol details; they only need to reference the existing source code architecture and implement specific business logic in the provided interfaces. Source code path: {SDK}/component/usb/device/hid

Class-Specific Request Type

Note

Get Report

The driver has completed the request response flow. Note: The default implementation returns zero data. The specific Report content must be populated at the application layer.

Set Report

The driver has received the data packet. Developers need to implement the specific processing logic for data (Output/Feature Report) sent by the host.

Get Idle

Standard implementation.

Set Idle

Standard implementation.

Get Protocol

Used to query the current protocol mode (Boot/Report).

Set Protocol

The driver has parsed the request. Developers need to perform corresponding state switching based on the protocol mode (Boot or Report) sent by the host.

Endpoint Configurations

Endpoint

Quantity

Description

Control IN/OUT

1

EP0, used to handle standard enumeration requests sent by the USB host,descriptor retrieval, and HID class-specific control requests.

Interrupt IN

1

Used for the device to report data to the host in real-time (e.g., mouse movement).

API Reference

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

Application Example

Application Design

This section details the complete development process for the HID driver, covering driver initialization, hotplug management, data transmission mechanisms, and resource release.

Driver Initialization

Define the configuration structure, register callback functions, and then call the initialization interface to load the USB device core and the UAC class driver.

  • Configuration: Configure USB speed mode and interrupt priority.

  • Callback Registration: Define the user callback structure usbd_hid_usr_cb_t and mount handler functions for each stage.

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

  • Class Driver Init: Call usbd_hid_init() to initialize the HID class driver.

/*
 * 1. Configure USB speed (High Speed or Full Speed) and interrupt priority.
 */
static usbd_config_t hid_cfg = {
        .speed = CONFIG_USBD_HID_SPEED,
        .isr_priority = INT_PRI_MIDDLE,
};

/*
 * 2. Define user callbacks for HID events.
 */
static usbd_hid_usr_cb_t hid_usr_cb = {
        .init = hid_cb_init,                     /* USB init callback */
        .deinit = hid_cb_deinit,                 /* USB deinit callback */
        .setup = hid_cb_setup,                   /* USB setup callback */
        .transmitted = hid_cb_transmitted,       /* Data transmission complet callback */
        .received = hid_cb_received,             /* Data reception callback */
        .status_changed = hid_cb_status_changed, /* Connection status change callbac */
};

int ret = 0;

/**
 * Initialize USB device core driver with configuration.
 */
ret = usbd_init(&hid_cfg);
if (ret != HAL_OK) {
        return;
}

/*
 * 4. Initialize HID class driver. 512 is the transfer buffer size.
 */
ret = usbd_hid_init(512, &hid_usr_cb);
if (ret != HAL_OK) {
        /* If class driver init fails, clean up the core driver */
        usbd_deinit();

        return;
}

Hot-Plug Event Handling

To ensure system robustness, it is recommended to monitor USB connection and disconnection events by registering the status_changed callback function.

Note

Do not directly perform time-consuming resource release or re-initialization operations within an interrupt callback (ISR). It is recommended to use a semaphore to notify a dedicated task thread for processing.

Refer to Device Connection Status Detection for more details. Example code is shown below:

/* USB status change callback */
static usbd_hid_usr_cb_t hid_usr_cb = {
        .status_changed = hid_cb_status_changed,
};

/* Callback executed in ISR context */
static void hid_cb_status_changed(u8 old_status, u8 status)
{
        hid_attach_status = status;
        rtos_sema_give(hid_attach_status_changed_sema);
}

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

        for (;;) {
                /* Wait for status change signal */
                if (rtos_sema_take(hid_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
                        if (hid_attach_status == USBD_ATTACH_STATUS_DETACHED) {
                                RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");

                                /* 1. Clean up HID class resources */
                                usbd_hid_deinit();

                                /* 2. De-initialize USB core */
                                ret = usbd_deinit();
                                if (ret != 0) {
                                        break;
                                }

                                /* 3. Re-initialize for next connection */
                                ret = usbd_init(&hid_cfg);
                                if (ret != 0) {
                                        break;
                                }
                                ret = usbd_hid_init(512, &hid_usr_cb);
                                if (ret != 0) {
                                        usbd_deinit();
                                        break;
                                }
                        } else if (hid_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);
}

HID Report Sending Process (Input Report)

When the device acts as a data producer (e.g., mouse movement, keyboard keystroke), it needs to actively send Input Reports to the host. The sending process:

  • Wait for device ready: Monitor the semaphore triggered by the hid_cb_setup() callback to ensure the host has completed enumeration.

  • Construct report data: Fill the data buffer according to the format defined by the HID Report Descriptor.

  • Send data: Use usbd_hid_send_data() to send the data.

/* Mouse data structure (Defined by Report Descriptor) */
typedef struct {
        u8 left;     //left button. 0: release, 1: press
        u8 right;    //right button. 0: release, 1: press
        u8 middle;   //wheel button. 0: release, 1: press
        char x_axis; //x-axis pixels. relative value from -127 to 127, positive for right and negative for left
        char y_axis; //y-axis pixels. relative value from -127 to 127, positive for up and negative for down
        char wheel;  //scrolling units. relative value from -127 to 127, positive for up and negative for down.
} usbd_hid_mouse_data_t;

/* Mouse moving data */
static usbd_hid_mouse_data_t mdata[] = {
        {0,   0,   0,  50,   0,   0},   //move the cursor 50 pixels to the right
        {0,   0,   0,   0,  50,   0},   //move the cursor down 50 pixels
        {0,   0,   0, -50,   0,   0},   //move the cursor 50 pixels to the left
        {0,   0,   0,   0, -50,   0},   //move the cursor up 50 pixels
        {0,   0,   0,   0,   0,   5},   //scroll up for 5 units
        {0,   0,   0,   0,   0,  -5},   //scroll down for 5 units
        {0,   0,   1,   0,   0,   0},   //middle button pressed
        {0,   0,   0,   0,   0,   0},   //middle button released
        {0,   1,   0,   0,   0,   0},   //right button pressed
        {0,   0,   0,   0,   0,   0},   //right button released
        {0,   0,   0,  -5,   0,   0},   //move the cursor 5 pixels to the left
        {1,   0,   0,   0,   0,   0},   //left button pressed
        {0,   0,   0,   0,   0,   0},   //left button released
};

/* Callback when device is configured by Host */
static void hid_cb_setup(void)
{
        rtos_sema_give(hid_connect_sema);
}

/* Send function */
static void hid_send_device_data(void *pdata)
{
        u8 byte[4];
        usbd_hid_mouse_data_t *data = (usbd_hid_mouse_data_t *)pdata;

        memset(byte, 0, 4);

        /* mouse protocol:
                BYTE0
                        |-- bit7~bit3: RSVD
                        |-- bit2: middle button press
                        |-- bit1: right button press
                        |-- bit0: left button press
                BYTE1: x-axis value, -128~127
                BYTE2: y-axis value, -128~127
                BYTE3: wheel value, -128~127
        */

        if (data->left != 0) {
                byte[0] |= USBD_HID_MOUSE_BUTTON_LEFT;
        }
        if (data->right != 0) {
                byte[0] |= USBD_HID_MOUSE_BUTTON_RIGHT;
        }
        if (data->middle != 0) {
                byte[0] |= USBD_HID_MOUSE_BUTTON_MIDDLE;
        }

        byte[0] |= USBD_HID_MOUSE_BUTTON_RESERVED;
        byte[1] = data->x_axis;
        byte[2] = data->y_axis;
        byte[3] = data->wheel;

        usbd_hid_send_data(byte, 4);
}

rtos_sema_give(hid_transmit_sema);
/* Wait for USB configured */
rtos_sema_take(hid_connect_sema, RTOS_SEMA_MAX_COUNT);

u32 array_len = sizeof(mdata) / sizeof(usbd_hid_mouse_data_t);

do {
        for (i = 0; i < array_len; i++) {
                /* Wait for previous transfer to complete */
                rtos_sema_take(hid_transmit_sema, RTOS_SEMA_MAX_COUNT);

                /* Send the mouse data out */
                hid_send_device_data(&mdata[i]);

                /* Force 1000ms of sleep to slow down movement */
                rtos_time_delay_ms(1000);
        }

        rtos_time_delay_ms(5 * 1000); //next loop
} while (++loop < CONFIG_USBD_HID_CONSTANT_LOOP);

HID Report Receiving Process (Output Report)

When the host sends control commands to the device (e.g., turning on the keyboard CapsLock light, controller vibration), the device side needs to handle Output Reports through a callback function. The processing flow:

  • Register callback: Register hid_cb_received() during initialization.

  • Parse data: In the callback function, read the data length and content, and parse the corresponding HID Report ID (if it exists).

  • Perform action: Parse the protocol payload and control the corresponding hardware.

/* Define callbacks for HID receive. */
static usbd_hid_usr_cb_t hid_usr_cb = {
        .received = hid_cb_received,
};

/* Callback for received data (executed in ISR context) */
static void hid_cb_received(u8 *buf, u32 len)
{
        /* Example: Parse Keyboard LED status */
        if (len == 1) {
                u8 led_status = buf[0];
                // Control GPIO based on led_status bits (NumLock, CapsLock, etc.)
        }
}

Note

To test the Output Report function, ensure that USBD_HID_DEVICE_TYPE in {SDK}/component/usb/device/hid/usbd_hid.h is configured as a device type that supports bidirectional communication (e.g., USBD_HID_KEYBOARD_DEVICE).

Driver Deinitialization

When the system shuts down or HID functionality is no longer needed, resources should be released in the reverse order of initialization to prevent memory leaks.

/* 1. Deinitialize HID class driver first */
usbd_hid_deinit();

/* 2. Deinitialize USB device core driver */
usbd_deinit();

Operation method

This section uses a USB Mouse Simulator as an example to demonstrate how to configure the Ameba development board as a USB HID device.

Functional Description

  • Device Type: Standard USB HID Mouse.

  • Behavior: After connecting to a PC, the development board will automatically simulate mouse movement trajectories.

  • Source Path: {SDK}/component/example/usb/usbd_hid. This provides a complete reference solution for developers designing custom USB keyboards, mice, game controllers, and other products.

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, type ./menuconfig.py, and follow the steps below to select USBD HID . save and exit.

[*] Enable USB
                USB Mode (Device)  --->
[*]     HID
  • Compilation and Flashing

    Compile according to the instructions below, and flash the image file to the development board. After flashing successfully, restart the board.

cd amebaxxx_gcc_project
./build.py -a usbd_hid

Result Verification

  • Start the Device

    Reboot the development board and observe the serial port log. The following startup information should be displayed:

[HID-I] USBD HID demo start
  • Connect to Host

    Use a USB cable to connect the development board to a PC (e.g., a Windows computer).

  • Function Test

The default example code configures the development board as a USB mouse.

  • Observed Phenomenon: After a successful connection, the mouse cursor on the PC screen will automatically begin moving (the movement trajectory is defined by an array in the Example code).

  • Data Flow: This process verifies the Input Report transmission function from Device -> Host (Device to Host).

Note

If the cursor does not move, please check:

  • Hardware Connection: Ensure the USB cable is properly plugged in and that it supports data transfer (not a power-only charging cable).

  • Device Conflict: Check if any other physical mouse or virtual mouse driver is interfering with the cursor movement.

  • Enumeration Status: … (translation of the final phrase would continue here based on the incomplete original text, e.g., “check the enumeration status in the device manager.”)

Note

Currently, only Mouse and Keyboard HID devices are supported. To support other devices, the corresponding Report Descriptor needs to be replaced in the code.

Mass Storage Device Solution

Overview

The USB Mass Storage Class (MSC) protocol defines the standard interface and transmission specifications for USB mass storage devices (such as U disks and SD card readers).

Based on the official MSC protocol standard released by USB-IF, Ameba implements complete USB storage device functionality. It supports interaction with the host via the SCSI (Small Computer System Interface) command set, providing efficient capabilities for reading, writing, erasing, and querying the status of storage media.

../../_images/usb_device_msc_overview.svg

Features

  • Supports Bulk-Only Transport (BOT) protocol

  • Supports Svarious storage medium

    • RAM

    • SD Card (SD Mode: using SDIO interface)

    • SD Card (SPI Mode: using SPI interface)

    • External Flash

  • Supports USB hot-plug

  • Supports fully customizable descriptors

  • Supports configuring parameters such as speed mode

Application Scenarios

As a USB storage device, Ameba supports flexible access and management of various storage media. It can be combined with wireless connection technologies, such as Wi-Fi and Bluetooth, to implement diverse data storage and interaction solutions. For example,

  • Personal Storage and Wireless Expansion: Ameba can function as a standard U disk or SD/TF card reader for file transfer, system boot disk creation, and in-car media playback. When combined with Wi-Fi or Bluetooth, it upgrades to a “Wireless USB Drive,” allowing mobile phones or PCs to access storage content via a wireless network, breaking the physical limitations of traditional wired connections.

  • Multimedia Device Data Bridging: In applications such as digital cameras, dashcams, or digital multimedia players (MP3/MP4), Ameba emulates internal storage or expansion cards as a generic USB drive. When connected to a PC, users can manage photos, videos, and music files directly via drag-and-drop.

  • Convenient Firmware Upgrade: When connected to a PC, the device is recognized as a storage drive. Users simply need to drag the new firmware file (e.g., in .bin or .uf2 format) into the drive. The device automatically verifies the file and completes the system update, significantly lowering the maintenance barrier for end-users.

  • Smart Industrial Data Logger: Addressing data acquisition needs in industrial, agricultural, or scientific research fields, Bluetooth is used for low-power parameter configuration (e.g., modifying sampling frequency or file naming rules). When processing massive amounts of historical data, the device connects to a PC via the USB interface for high-speed export, balancing configuration flexibility with transmission efficiency.

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_device_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_device_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

Descriptor Implementation

This section presents the MSC class descriptor structures defined at the driver layer. These structures correspond to the standard descriptor definitions in the USB protocol specification.

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)

Full customization of device descriptors is supported. Refer to Device Descriptor Customization.

Endpoint Configurations

Endpoint

Count

Description

Control IN/OUT Endpoint

1

Handles control requests sent by the USB host.

Bulk IN Endpoint

1

Sends data to the USB host.

Bulk OUT Endpoint

1

Receives data from the USB host.

Implementation of Class-Specific Requests

The driver implements two Class-Specific Requests for MSC.

MSC Class-Specific Request

Description

Bulk-Only Mass Storage Reset

Resets the BOT transmission state and prepares to receive the next CSW.

Get Max LUN

The driver currently supports only one Logical Unit.

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.

SCSI Command

Description

INQUIRY

Returns constant MSC device information.

REQUEST_SENSE

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

TEST_UNIT_READY

Indicates if the device is ready to receive the next command.

READ_CAPACITY(10)

Returns the capacity of the storage medium.

READ(10)

Reads from the storage medium.

WRITE(10)

Writes data to the storage medium.

MODE_SENSE(6)

Returns constant MSC device parameters.

ALLOW_MEDIUM_REMOVAL

The MSC driver does not support medium removal and returns success directly.

START_STOP_UNIT

The MSC driver does not support loading/ejecting media; the medium is usable once initialized.

VERIFY(10)

Checks if the specified block address is within a reasonable range.

READ_FORMAT_CAPACITIES

Provides more detailed capacity and format information than READ_CAPACITY.

MODE_SENSE(10)

Returns more constant MSC device parameters than MODE_SENSE(6).

MODE_SELECT(6)

Does not allow the host to modify internal parameters; the driver returns success directly.

MODE_SELECT(10)

Same implementation as MODE_SELECT(6).

READ(12)

Same implementation as READ(10).

WRITE(12)

Same implementation as WRITE(10).

API Reference

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

Application Example

Application Design

This section outlines the complete usageflow of the MSC driver, covering core stages such as initialization and hot-plug management.

Driver Initialization

Define the configuration structure and callback functions, then call the initialization interface to initialize the underlying storage, the USB device core and the MSC class driver.

static usbd_config_t usbd_msc_cfg = {
    .speed = CONFIG_USBD_MSC_SPEED,
    .isr_priority = INT_PRI_MIDDLE,
};

static usbd_msc_cb_t usbd_msc_cb = {
    .status_changed = usbd_msc_cb_status_changed  /* USB status change callback. */
};

int ret = 0;
ret = usbd_msc_disk_init();    /* Initializes the underlying storage disk. */
if (ret != HAL_OK) {
    return;
}

ret = usbd_init(&usbd_msc_cfg);     /* Initialize USB device core driver with configuration. */
if (ret != HAL_OK) {
    usbd_msc_disk_deinit();
    return;
}

ret = usbd_msc_init(&usbd_msc_cb);  /* Initializes the MSC device class. */
if (ret != HAL_OK) {
    usbd_msc_disk_deinit();
    usbd_deinit();
    return;
}

USB Hot-plug Event Handling

Monitor USB connection status changes (connected/disconnected) by registering the status_changed callback function.

Refer to Device Connection Status Detection for more details.

Note

It is recommended to use a semaphore to notify a dedicated task thread for processing, avoiding time-consuming operations within the interrupt context.

static u8 msc_usb_attach_status;
static rtos_sema_t msc_usb_status_changed_sema;

static usbd_msc_cb_t usbd_msc_cb = {
    .status_changed = usbd_msc_cb_status_changed   /* USB status change callback. */
};

/* Callback executed in ISR context */
static void usbd_msc_cb_status_changed(u8 old_status, u8 status)
{
  RTK_LOGS(TAG, RTK_LOG_INFO, "USB status change: %d -> %d\n", old_status, status);
  msc_usb_attach_status = status;
  rtos_sema_give(msc_usb_status_changed_sema);
}

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

  UNUSED(param);

  for (;;) {
    if (rtos_sema_take(msc_usb_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
      if (msc_usb_attach_status == USBD_ATTACH_STATUS_DETACHED) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
        /* Clean up resources */
        usbd_msc_deinit();
        ret = usbd_deinit();
        if (ret != 0) {
          break;
        }
        usbd_msc_disk_deinit();
        RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());

        /* Re-initialize for next connection */
        usbd_msc_disk_init();
        ret = usbd_init(&msc_cfg);
        if (ret != 0) {
          break;
        }
        ret = usbd_msc_init(&msc_cb);
        if (ret != 0) {
          usbd_deinit();
          break;
        }
      } else if (msc_usb_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
      } else {
        RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
      }
    }
  }
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
  rtos_task_delete(NULL);
}

Driver Deinitialization

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

/* De-initializes the underlying storage disk. */
usbd_msc_disk_deinit();
/* Deinitialize MSC device class driver. */
usbd_msc_deinit();
/* Deinitialize USB device core driver. */
usbd_deinit();

Operation method

This section introduces a complete USB Mass Storage Class (MSC) application example, demonstrating how to configure the Ameba development board as a USB storage device using the MSC protocol stack.

When the development board is connected to a USB host (e.g., a PC), the system recognizes it as a removable disk. The host can directly read from and write to the storage media (e.g., SD card) on the board via the USB interface, supporting USB hot-plugging.

The example code path is: {SDK}/component/example/usb/usbd_msc. It provides a complete design reference for developers designing custom USB storage products.

Note

Use the SD card hot-swap function with caution. Hot-swapping during data transmission carries the risk of file system corruption and data loss.

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, type ./menuconfig.py, follow the steps below to select USBD MSC and the storage medium, then save and exit.

    - Choose `CONFIG USB --->`:
    
      [*] Enable USB
          USB Mode (Device)  --->
      [*] MSC
          Select storage media (RAM)  --->
    
  • Compilation and Flashing

    Compile according to the instructions below, and flash the image file to the development board.

    cd amebaxxx_gcc_project
    ./build.py -a usbd_msc
    

Verification

  • Device Startup

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

    [MSC-I] USBD MSC demo start
    
  • Connect to Host

    Connect the development board to a PC (or other host devices supporting USB MSC) using a USB cable.

  • System Recognition

    A new removable disk drive should automatically appear in the PC’s file manager. Users can double-click to open the drive and perform read/write operations on files to verify that data transmission is working correctly.

    Note

    When using RAM as the storage medium, it must be formatted before it can be used normally.

Audio Device Solution

Overview

USB Audio Class (UAC) protocol defines the standard interface and functional control specifications for USB audio devices (such as USB headphones, microphones, sound cards, and other audio interface devices).

Ameba, based on the official UAC protocol standards released by USB-IF, implements comprehensive USB audio device functionality, capable of providing the system with convenient and high-quality audio data transmission capabilities.

../../_images/usb_device_uac_user.svg

Features

  • Supports USB hot-plug

  • Supports fully customizable descriptors

  • Supports volume/mute control

  • Supports configuring parameters such as speed mode

Application Scenarios

As a USB audio device, Ameba can acquire audio data streams from host devices (such as TVs, PCs, or other audio sources) via the USB interface and, in combination with wireless technologies like Wi-Fi and Bluetooth, enables various innovative applications. For example,

  • Wi-Fi Wireless Multi-channel Audio System: Ameba acts as a USB audio receiver, obtaining multi-channel audio data from a host device (e.g., TV/PC), and multicasts or unicasts it via Wi-Fi to multiple subordinate playback devices. Each playback device parses and plays the audio stream of a designated channel, collectively building a wireless surround sound system.

  • Bluetooth Audio Transmitter (Dongle): Ameba serves as a USB sound card plugged into a PC, acquires the audio data being played by the system, and forwards the audio stream to Bluetooth headphones or speakers via its onboard Bluetooth protocol stack.

  • USB Wired Speaker: Ameba receives the USB audio stream from a PC and forwards it to an external audio codec or amplifier module via interfaces such as I2S/PCM, enabling wired audio playback.

Supported Audio Formats

Different UAC protocol versions (1.0/2.0) have differences in audio format support. A specific overview is shown in the figure below:

../../_images/usb_uac_overview.png

Protocol Stack Support Details

The Ameba USB protocol stack provides a complete UAC device class driver, supporting various mainstream audio formats and sample rates. The specific supported features and parameters are as follows:

  • UAC 2.0 (High/Full-Speed) class driver, speaker-only, supports following configurable audio parameters:

Sample Rate

Bit-depth

Channel Count

2

4

6

8

44.1

16

Y

Y

Y

Y

24/32

Y

Y

Y

Y

48

16

Y

Y

Y

Y

24/32

Y

Y

Y

Y

96

16

Y

Y

24/32

Y

Y

192

16

Y

24/32

Y

  • Supports volume/mute control

  • Fully customizable descriptors

  • Supports hot-plug

  • Support speed mode configuration

Note

  • USB core driver doesn’t support UAC 2.0 high-bandwidth endpoints, max one isochronous OUT transfer per microframe is allowed.

  • The supported audio formats depend on the whole path of UAC host/device and hardware/software frameworks

Protocol Introduction

The UAC (USB Audio Class) protocol defines a standard interface for implementing data transmission​ and functional control​ of audio devices within the USB specification framework. Common devices that adhere to this standard include USB microphones, USB headphones, and USB sound cards.

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

https://www.usb.org/sites/default/files/audio10.pdf

2.0

https://www.usb.org/sites/default/files/Audio2.0_final.zip

Terminology Definitions

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.

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 for UAC devices are divided 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

This section explains the transmission format of UAC audio data streams. UAC typically transmits PCM data in a multi-channel interleaved format. The specific data arrangement depends on the channel count 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_device_uac_audio_2_channel_data_interleaved.svg

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

../../_images/usb_device_uac_audio_4_channel_data_interleaved.svg

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

../../_images/usb_device_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

Descriptor Implementation

This section presents the UAC class-specific descriptor structures defined at the driver layer. These structures correspond to the standard descriptor definitions in the USB protocol specification.

Device Descriptor
└─ USB Version 2.00

Configuration Descriptor(Interfaces 2)
│
├─ Interface Association Descriptor (IAD)
├── Binds audio control and streaming interfaces into a single functional unit
│
├─ AudioControl Interface (IF 0, Alt 0)
│  ├─ Audio Control Interface Header (Header 2.0, desktop speaker)
│  ├─ Clock Source (ID=0x15, Internal, Sync to SOF)
│  ├─ Input Terminal (ID=0x01, USB Streaming)
│  ├─ Feature Unit (ID=0x05, Mute+Volume, 8 ch)
│  └─ Output Terminal (ID=0x09, Speaker, clocked=0x15)
│
└─ AudioStreaming Interface (IF 1)
   ├─ Alt 0: Inactive (no endpoints)
   │
   ├─ Alt 1: 16-bit | 2 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(2)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x001C (28 bytes)
   │
   ├─ Alt 2: 16-bit | 4 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(4)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x0038 (56 bytes)
   │
   ├─ Alt 3: 16-bit | 6 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(6)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x0054 (84 bytes)
   │
   ├─ Alt 4: 16-bit | 8 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(8)
   │        AS Format: BitResolution(16)
   └        Endpoint: ISO OUT, maxpkt=0x0070 (112 bytes)

Device Qualifier Descriptor
└─ USB 2.0

Other Speed Configuration Descriptor(Interfaces 2)
│
├─ Interface Association Descriptor (IAD)
├── Binds audio control and streaming interfaces into a single functional unit
│
├─ AudioControl Interface (IF 0, Alt 0)
│  ├─ Audio Control Interface Header (Header 2.0, desktop speaker)
│  ├─ Clock Source (ID=0x15, Internal, Sync to SOF)
│  ├─ Input Terminal (ID=0x01, USB Streaming)
│  ├─ Feature Unit (ID=0x05, Mute+Volume, 8 ch)
│  └─ Output Terminal (ID=0x09, Speaker, clocked=0x15)
│
└─ AudioStreaming Interface (IF 1)
   ├─ Alt 0: Inactive (no endpoints)
   │
   ├─ Alt 1: 16-bit | 2 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(2)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x00C4 (196 bytes)
   │
   ├─ Alt 2: 16-bit | 4 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(4)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x0188 (392 bytes)
   │
   ├─ Alt 3: 16-bit | 6 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(6)
   │        AS Format: BitResolution(16)
   │        Endpoint: ISO OUT, maxpkt=0x024C (588 bytes)
   │
   ├─ Alt 4: 16-bit | 8 ch | PCM
   │        AS Interface: TerminalLink(0x01) channels(8)
   │        AS Format: BitResolution(16)
   └        Endpoint: ISO OUT, maxpkt=0x0310 (784 bytes)

Implementation of Class-Specific Requests

The driver has implemented the core class-specific requests defined by the UAC specification, primarily including Sampling Frequency Control, Volume Control, and Mute Control.

Developers can refer to the existing implementation to extend other types of requests. The UAC driver source code path is: {SDK}/component/usb/device/uac

Audio Control Requests (AC Requests)

AudioControl Type

Required

Note

Mute Control Request

Optional

Control the Current Sound Mute Function

Volume Control Request

Optional

Control the Current Sound Volume Function

Sampling Frequency Control Request

Optional

Control the Current Sound Sampling Frequency Function

Audio Streaming Requests (AS Requests)

AudioStreaming Type

Required

Note

Interface Control Request

Optional

Switching between different interfaces to choose the different audio format.

Endpoint Configurations

Endpoint Type

Count

Description

Control IN/OUT

1

Handle host control requests

Isochronous OUT

1

Audio data reception

API Reference

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

Application Example

Application Design

This section outlines the complete usage flow of the UAC driver, covering core aspects such as initialization, hot-plug management, audio data processing, and configuration changes.

Driver Initialization

Define the configuration structure, register callback functions, and then call the initialization interface to load the USB device core and the UAC class driver.

/*
        Configure different USB speeds according to different UAC applications
        UAC 1.0 only support full speed, while UAC 2.0 can support both full speed and high speed
*/
static usbd_config_t uac_cfg = {
        .speed = CONFIG_USBD_UAC_SPEED,
        .isr_priority = INT_PRI_MIDDLE,
};

static usbd_uac_cb_t uac_cb = {
        .in = {.enable = 1,},                      /* Audio out params */
        .out = {.enable = 0,},                     /* Audio in params */
        .init = uac_cb_init,                       /* USB init callback */
        .deinit = uac_cb_deinit,                   /* USB deinit callback */
        .setup = uac_cb_setup,                     /* USB setup callback */
        .set_config = uac_cb_set_config,           /* USB set_config callback */
        .status_changed = uac_cb_status_changed,   /* USB status change callback */
        .mute_changed = uac_cb_mute_changed,       /* Audio mute change callback */
        .volume_changed = uac_cb_volume_changed,   /* Audio volume change callback */
        .format_changed = uac_cb_format_changed,   /* Audio format change callback */
};

int ret = 0;

/**
 * Initialize USB device core driver with configuration.
 * param[in] cfg: USB device configuration.
 * return 0 on success, non-zero on failure.
 */
ret = usbd_init(&uac_cfg);
if (ret != HAL_OK) {
        return;
}

/**
 * Initializes class driver with application callback handler.
 * param[in] cb: Pointer to the user-defined callback structure.
 * return 0 on success, non-zero on failure.
 */
ret = usbd_uac_init(&uac_cb);
if (ret != HAL_OK) {
        /**
         * Deinitialize USB device core driver.
         * return 0 on success, non-zero on failure.
         */
        usbd_deinit();

        return;
}

Hot-Plug Event Handling

Monitor USB connection status changes (connected/disconnected) by registering the status_changed callback function. It is recommended to use a semaphore to notify a dedicated task thread for processing, avoiding time-consuming operations within interrupt context.

Please refer to Device Connection Status Detection for more details. Example code is provided below:

/* USB status change callback */
static usbd_uac_cb_t uac_cb = {
        .status_changed = uac_cb_status_changed,
};

/* Callback executed in ISR context */
static void uac_cb_status_changed(u8 old_status, u8 status)
{
        uac_attach_status = status;
        rtos_sema_give(uac_attach_status_changed_sema);
}

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

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

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

                                /* Re-initialize for next connection */
                                ret = usbd_init(&uac_cfg);
                                if (ret != 0) {
                                        break;
                                }
                                ret = usbd_uac_init(&uac_cb);
                                if (ret != 0) {
                                        usbd_deinit();
                                        break;
                                }
                        } else if (uac_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);
}

Audio Data Reception Flow

Once UAC starts, the host (Host) will issue configuration requests and begin transmitting the audio stream. The processing flow is as follows:

  • Respond to Format Changes

    The host triggers the format_changedcallback when issuing configurations. Parameters like sample rate and bit width should be saved here.

static void uac_cb_format_changed(u32 sampling_freq, u8 ch_cnt, u8 byte_width)
{
        if (sampling_freq != 0U) {
                out.sampling_freq = sampling_freq;
        }
        if (ch_cnt != 0U) {
                out.ch_cnt = ch_cnt;
        }
        if (byte_width != 0U) {
                out.byte_width = byte_width;
        }

        ...
}
  • Configure and Start Data Reception

    Call usbd_uac_config() to apply new parameters, and call usbd_uac_start_play() to initiate data reception. It is recommended to check the startup status in a loop to handle unstable cable connections.

usbd_uac_config(&(out), 0, 0);

do {
        if (usbd_uac_start_play() == HAL_OK) {
                break;
        }
} while (uac_attach_status == USBD_ATTACH_STATUS_ATTACHED);

if(uac_attach_status != USBD_ATTACH_STATUS_ATTACHED ) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "USB Detached\n");
        return;
}
while (!audio_task_stop) {
        read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
        if (read_dat_len > 0) {
                total_len += read_dat_len;
        } else {
                rtos_time_delay_ms(1);
        }
}

Note

For the complete playback logic (including interaction with hardware Codec), please refer to the SDK example code: {SDK}/component/example/usb/usbd_uac/example_usbd_uac.c. Ensure that CONFIG_USBD_AUDIO_EN is set to 1.

Dynamic Audio Format Switching

If the user changes audio settings on the Host side (e.g., switching from 48kHz to 96kHz in Windows), the device must gracefully restart the stream.

  • Stop Playback: Set the stop flag in the callback function uac_cb_format_changed() to notify the loop to exit data reading.

  • Stop Interface: Call usbd_uac_stop_play() to stop receiving data.

  • Reconfigure: Call usbd_uac_config() to apply the new audio parameters.

  • Restart Playback: Call usbd_uac_start_play() to start receiving data and resume the data reading loop.

/* In callback: Signal stop */
static void uac_cb_format_changed(u32 sampling_freq, u8 ch_cnt, u8 byte_width)
{
        .......
        audio_task_stop = 1;
}

/* In task loop: Handle restart */
// 1. Exit read loop
while (!audio_task_stop) {
        read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
        ......
}

// 2. Stop UAC interface
usbd_uac_stop_play();

// 3. Re-configure
usbd_uac_config(&(out), 0, 0);

// 4. Restart UAC interface
do {
        if (usbd_uac_start_play() == HAL_OK) {
                break;
        }
} while (uac_attach_status == USBD_ATTACH_STATUS_ATTACHED);

if(uac_attach_status != USBD_ATTACH_STATUS_ATTACHED ) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "USB Detached\n");
        return;
}

// 5. Resume reading
while (!audio_task_stop) {
        read_dat_len = usbd_uac_read(recv_buf, USB_AUDIO_BUF_SIZE, 500);
        ....
}

Driver Deinitialization

Release resources in the reverse order of initialization when audio functionality is no longer needed or during system shutdown.

/* Deinitialize UAC class driver. */
usbd_uac_deinit();

/* Deinitialize USB device core driver. */
usbd_deinit();

Operation method

This section introduces a complete USB Audio Class (UAC) speaker application example, demonstrating how to configure the Ameba development board as a USB audio output device via the UAC protocol stack.

When the development board is connected to a PC, the system recognizes it as a speaker. Digital audio streams played on the PC are transmitted to the development board via USB and output in analog form via the onboard Codec/speaker.

The example code path is: {SDK}/component/example/usb/usbd_uac. It provides a complete reference solution for developers designing custom USB audio products.

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, type ./menuconfig.py, and follow the steps below to select USBD UAC and Audio Framework. Save and exit.

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

- Choose UAC version 1.0 or 2.0 :
        [*] UAC
                        Select UAC Version (UAC 2.0)  --->

- Choose `CONFIG APPLICATION --->`
        `Audio Config --->`:
                [*] Enable Audio Framework
                                Select Audio Interfaces (PassThrough)  --->

Note

If audio playback is not smooth, change the Audio Framework configuration to Select Audio Interfaces (Mixer). Refer to the audio module for more information.

  • Compilation and Flashing

    Compile according to the instructions below, and flash the image file to the development board. After flashing successfully, restart the board.

cd amebaxxx_gcc_project
./build.py -a usbd_uac

Result Verification

  • Start the Device

    Reboot the development board and monitor the serial log. The following initialization message should be displayed:

[UAC-I] USBD uac demo start
  • Connect to Host

    Connect the development board to a PC (e.g., a Windows computer) using a USB cable.

  • System Recognition

    The audio device named "Realtek UAC Device speaker device" should appear in the Windows Sound settings.

Note

If the Ameba audio device is not visible, open the computer’s sound control panel and locate the “Realtek UAC Device speaker device”.

  • Ensure it is Enabled (if not, right-click and select Enable).

  • Set it as the default device (right-click and select Set as Default Device).

  • Format Switching Test

    Try switching between different bit depths and sample rates (e.g., 16-bit 48000Hz) to verify that the device successfully re-negotiates the connection and plays audio normally.

FullMAC Device Solution

Overview

The INIC (Internet Network Interface Controller) enables a USB-based FullMAC implementation, functioning as a network interface card to provide host connectivity via USB.

For FullMAC details, refer to Wi-Fi Card Mode .

SDK provides:

  • INIC device class driver with following features:

    • Supports WiFi-only mode

    • Fully customizable descriptors

  • INIC device application example with following features:

    • Compatible with dedicated Linux FullMAC drivers (contact Realtek FAE)

    • Supports USB hot-plug

    • Support speed mode configuration

Endpoint Configurations

Endpoint

Count

Description

Control IN/OUT

1

Handle host control requests

Bulk IN

1

Data transmission

Bulk OUT

2

Data reception

Class Driver

INIC device class driver

Application Example

Location: {SDK}/component/example/usb/usbd_inic

The example defines an USB INIC device, refer to the README.md in the example directory for details.

This example serves as a reference implementation for USB FullMAC applcations.

Composite Device Solution

Overview

The USB Composite Device specification defines the device architecture and enumeration standards for carrying multiple independent function classes (such as Audio, HID, Storage, Serial, etc.) through a single physical USB interface.

Based on the USB specification officially released by the USB-IF, Ameba implements a flexible composite device functional framework. It supports aggregating multiple functional interfaces via Interface Descriptors or Interface Association Descriptors (IAD), providing capabilities for parallel enumeration, independent driver loading, and collaborative operation of multiple logical devices on the host side.

../../_images/usb_device_composite_overview.svg

Features

  • Supports the following function combinations:

    • CDC ACM + HID

    • CDC ACM + MSC

    • CDC ACM + UAC

    • HID + UAC

  • Supports USB hot-plug

  • Supports fully customizable descriptors

  • Supports configuration of parameters such as transfer buffer size and speed mode

Application Scenarios

As a USB composite device, Ameba can simultaneously enumerate multiple device classes through a single USB physical interface, enabling parallel processing of data transmission and control interaction. It is suitable for a wide range of complex application scenarios. For example,

  • USB Audio Device with Remote Control (UAC + HID): Ameba provides USB audio input/output functionality (UAC) while utilizing the HID interface for media control. Users can enjoy a high-quality audio streaming experience (such as listening to music through headphones or recording with a microphone), and also interact with features like volume adjustment, muting, song switching, or RGB lighting effects control through the HID channel.

  • Smart Industry and 3D Printing Control (MSC + CDC ACM): Ameba combines the MSC high-capacity storage and CDC virtual serial port functions. In 3D printer or data logger scenarios, the MSC interface can be simulated as a USB flash drive for storing G-code slicing files or sensor historical data, while the CDC interface simultaneously serves as a console for the host computer to send AT commands in real time, monitor temperature, or calibrate parameters.

  • Automated Testing and Assistive Input (HID Mouse + CDC ACM): Ameba combines the HID mouse and CDC serial port functions. In this scenario, the CDC interface is responsible for receiving raw data (such as coordinate instructions, head tracking data) from backend scripts or sensors, while the HID interface simulates cursor movement and click actions based on these instructions. It is widely used in hardware automated testing tools or assistive input devices for the disabled.

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

Composite devices do not have a dedicated “composite device 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

This section provides a detailed analysis of how to design and implement a USB composite device class driver. The composite class driver acts as a “parent class,” responsible for managing resource scheduling, request dispatching, and data processing for multiple “child classes” (such as CDC ACM, HID, MSC, etc.).

../../_images/usb_device_composite_driver_arch.png

Descriptor Structure

To allow the host to correctly identify the composite device and the multiple functions it contains, the driver must dynamically assemble a complete and precise set of descriptors at runtime. The following points must be noted when generating device descriptors:

  • Device Descriptor

    bDeviceClass: Usually set to 0xEF (Miscellaneous) or 0x00 (defined by the interface).

  • Configuration Descriptor

    This is a dynamically generated “aggregate” that contains the descriptors for all sub-functions.

    • bNumInterfaces: Must be the sum of the number of interfaces for all sub-functions. For example, for a composite device consisting of a CDC function (occupying 2 interfaces) and an MSC function (occupying 1 interface), this value should be 3.

    • wTotalLength: Must be the sum of the lengths of all descriptors (Configuration, IAD, Interface, Endpoint). This value needs to be calculated precisely at runtime.

    • Interface Association Descriptor (IAD): If a sub-function contains multiple interfaces (e.g., CDC ACM), an IAD must be used to “bundle” these interfaces together, declaring that they belong to the same function.

  • Endpoint Descriptor

    The configuration of endpoints must be tailored to the hardware capabilities of the chip. Please refer to Hardware Configuration for details.

    • Endpoint selection: Available endpoints must be selected based on functional requirements (IN/OUT) and hardware support.

    • Maximum Packet Size (MPS): Hardware limitations need to be considered. Especially when using a dedicated transmit buffer, it is important to ensure that the size of the transmit buffer area of the IN endpoint can accommodate at least one maximum packet.

CDC ACM + MSC Example

The following section illustrates the descriptor topology of the CDC ACM + MSC composite device. These structures correspond to the standard descriptor definitions in the USB protocol specifications.

  • CDC ACM (Virtual Serial Port): Occupies two interfaces. To ensure the host recognizes them as a single logical function, an IAD (Interface Association Descriptor) must be used to associate these two interfaces.

  • MSC (Mass Storage): Operates as an independent interface and does not require IAD association.

The composite device (CDC ACM + MSC) solution utilizes 5 non-zero endpoints (excluding the default control endpoint EP0).

Interface number

Interface Class

Endpoints

Description

Interface 0

CDC Control (ACM)

1x Interrupt IN

Used to notify Serial State and management commands.

Interface 1

CDC Data

1x Bulk OUT, 1x Bulk IN

Responsible for sending (OUT) and receiving (IN) virtual serial port data.

Interface 2

MSC (Mass Storage)

1x Bulk OUT, 1x Bulk IN

Responsible for data read/write of the mass storage device (SCSI commands/data).

In the class driver, various descriptors are generally defined as arrays. When obtaining configuration descriptors, the callback function get_descriptor of each sub-function class driver is called sequentially to aggregate them together.

 /* USB Standard Device Descriptor */
 static const u8 usbd_composite_dev_desc[USB_LEN_DEV_DESC] = {
    //...
    0xEF,       /* bDeviceClass: Miscellaneous */
    0x02,       /* bDeviceSubClass: Common Class */
    0x01,       /* bDeviceProtocol: Interface Association Descriptor */
    //...
 };

 /* USB Standard Configuration Descriptor */
 static const u8 usbd_composite_config_desc[USB_LEN_CFG_DESC] = {
    //...
    0x00, 0x00,  /* wTotalLength: calculated at runtime */
    0x03,        /* bNumInterfaces */
    //...
 };

/**
 * @brief  Get descriptor callback
 * @param  dev: USB device instance
 * @param  req: Setup request handle
 * @param  buf: Poniter to Buffer
 * @return Descriptor length
 */
 static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
 {
    usbd_composite_dev_t *cdev = &usbd_composite_dev;
    usb_speed_type_t speed = dev->dev_speed;
    u16 len = 0;
    u16 desc_len;
    u16 total_len = 0;

    switch (USB_HIGH_BYTE(req->wValue)) {
    //...
    case USB_DESC_TYPE_CONFIGURATION:
    case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
       usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
       buf += USB_LEN_CFG_DESC;
       total_len += USB_LEN_CFG_DESC;
       desc_len = cdev->cdc->get_descriptor(dev, req, buf);
       buf += desc_len;
       total_len += desc_len;
       desc_len = cdev->msc->get_descriptor(dev, req, buf);
       total_len += desc_len;
       buf = dev->ep0_in.xfer_buf;
       if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
          buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
       }
       buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
       buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
       len = total_len;
       break;
       }

    return len;
 }

The complete topology structure of the device descriptor is as follows:

Device Descriptor
|  bDeviceClass: 0xEF (Miscellaneous)
|  bDeviceSubClass: 0x02 (Common Class)
|  bDeviceProtocol: 0x01 (Interface Association Descriptor)
|
└── Configuration descriptor
    |   bNumInterfaces: 3 (3 Interfaces)
    |
    |   /* Function 1: CDC ACM */
    |
    ├── Interface Association Descriptor (IAD)
    |    bFirstInterface: 0
    |    bInterfaceCount: 2 (Associate Interface 0 with Interface 1)
    |    bFunctionClass:  0x02 (CDC Control)
    |    bFunctionSubClass: 0x02 (ACM)
    |
    ├── Interface Descriptor 0 (CDC Control Interface)
    |   |  bInterfaceNumber: 0
    |   |  bInterfaceClass:  0x02 (CDC Control)
    |   |  bInterfaceSubClass: 0x02 (ACM)
    |   |  ...
    |   ├── CDC Class Specific Descriptors (Header, Call Mgmt, ACM...)
    |   └── Endpoint Descriptor (Interrupt IN)
    |
    ├── Interface Descriptor 1 (CDC Data Interface)
    |   |  bInterfaceNumber: 1
    |   |  bInterfaceClass:  0x0A (CDC Data)
    |   |  ...
    |   ├── Endpoint Descriptor (Bulk OUT)
    |   └── Endpoint Descriptor (Bulk IN)
    |
    |   /* Function 2: MSC */
    |
    └── Interface Descriptor 2 (MSC Interface)
        |  bInterfaceNumber: 2
        |  bInterfaceClass:  0x08 (Mass Storage)
        |  bInterfaceSubClass: 0x06 (SCSI Transparent Command Set)
        |  bInterfaceProtocol: 0x50 (Bulk-Only Transport / BBB)
        |   ...
        ├── Endpoint Descriptor (Bulk OUT)
        └── Endpoint Descriptor (Bulk IN)

Note

Composite Class Driver Implementation

It mainly involves defining composite devices and implementing class-driven callback functions.

  • Sub-function Class Driver

    Each sub-function (such as CDC, MSC, HID) is an independent class driver.

    • Independent driver structure: Each sub-driver defines a standard usbd_class_driver_t structure to implement its own process logic.

    • Independent resource management: Each sub-driver is responsible for managing its own endpoints, data buffers, and data transmission and reception processing.

  • Composite Class Driver

    • The composite Class driver needs to define a standard usbd_class_driver_t structure. This structure serves as the unified entry point registered to the USB Core. The composite device driver is responsible for dispatching events to, or iterating through, the callback functions of the sub-function class drivers.

    • The composite Class driver defines a standard usbd_composite_dev_t structure. This is the core of the composite device instance, used to manage all sub-funcion class drivers.

    /* Composite Device */
    static usbd_composite_dev_t usbd_composite_dev;
    
    /* Composite Class Driver Interface */
    static const usbd_class_driver_t usbd_composite_driver = {
        .get_descriptor = usbd_composite_get_descriptor,    /* Iterate through all sub-function classes to obtain the aggregated configuration descriptor */
        .set_config = usbd_composite_set_config,            /* Iterate to initialize all sub-function class endpoints and resources */
        .clear_config = usbd_composite_clear_config,        /* Iterate to release all sub-function class endpoints and resources */
        .setup = usbd_composite_setup,                      /* Dispatch class control requests to different interfaces: wIndex = Interface xx */
        .sof = usbd_composite_sof,                          /* Called during SOF interrupt, used for processing logic with strict timing requirements */
        .ep0_data_out = usbd_composite_handle_ep0_data_out, /* After the device is ready, dispatch and handle sub-function class requests for control OUT endpoints */
        .ep0_data_in = usbd_composite_handle_ep0_data_in,   /* After the device is ready, dispatch and handle sub-function class request results for control IN endpoints */
        .ep_data_in = usbd_composite_handle_ep_data_in,     /* Dispatch IN endpoint data processing; use ep_addr to determine which sub-function class the data belongs to */
        .ep_data_out = usbd_composite_handle_ep_data_out,   /* Dispatch OUT endpoint data processing; use ep_addr to determine which sub-function class the data belongs to */
        .status_changed = usbd_composite_status_changed,    /* Monitor connection status and notify the application layer or all sub-function class state machines when necessary */
    };
    

CDC ACM + MSC Example

The following section uses CDC ACM + MSC as an example to detail the implementation of the composite device class driver.

/* Composite Device structure. */
typedef struct {
   usb_setup_req_t ctrl_req;  /* Control setup request */
   usbd_class_driver_t *cdc;  /* CDC ACM class */
   usbd_class_driver_t *msc;  /* MSC class */
   usbd_composite_cb_t *cb;   /* Composite user callback */
   usb_dev_t *dev;            /* USB device instance */
} usbd_composite_dev_t;
/* Composite Device */
static usbd_composite_dev_t usbd_composite_dev;

/* Composite Class Driver */
static const usbd_class_driver_t usbd_composite_driver = {
   .get_descriptor = usbd_composite_get_descriptor,
   .set_config = usbd_composite_set_config,
   .clear_config = usbd_composite_clear_config,
   .setup = usbd_composite_setup,
   .ep0_data_out = usbd_composite_handle_ep0_data_out,
   .ep_data_in = usbd_composite_handle_ep_data_in,
   .ep_data_out = usbd_composite_handle_ep_data_out,
   .status_changed = usbd_composite_status_changed,
};

/********************** Function 1: CDC ACM class *********************/
/* CDC ACM device structure. */
typedef struct {
   usbd_composite_dev_t *cdev;           /**< Pointer to the parent composite device structure. */
   usbd_composite_cdc_acm_usr_cb_t *cb;  /**< Pointer to the user-registered callback structure. */
   usbd_ep_t ep_bulk_in;                 /**< Bulk IN endpoint handler. */
   usbd_ep_t ep_bulk_out;                /**< Bulk OUT endpoint handler. */
#if CONFIG_COMP_CDC_ACM_NOTIFY
   usbd_ep_t ep_intr_in;                 /**< Interrupt IN endpoint handler (for notifications). */
#endif
} usbd_composite_cdc_acm_dev_t;
/* CDC ACM Device */
static usbd_composite_cdc_acm_dev_t composite_cdc_acm_dev;

/* CDC ACM Class Driver */
const usbd_class_driver_t usbd_composite_cdc_acm_driver = {
   .get_descriptor = composite_cdc_acm_get_descriptor,
   .set_config = composite_cdc_acm_set_config,
   .clear_config = composite_cdc_acm_clear_config,
   .setup = composite_cdc_acm_setup,
   .ep_data_in = composite_cdc_acm_handle_ep_data_in,
   .ep_data_out = composite_cdc_acm_handle_ep_data_out,
   .ep0_data_out = composite_cdc_acm_handle_ep0_data_out,
};

/********************** Function 2: MSC class *********************/
/* MSC device structure.  */
typedef struct {
   usbd_ep_t ep_bulk_in;                 /**< Bulk IN endpoint handler. */
   usbd_ep_t ep_bulk_out;                /**< Bulk OUT endpoint handler. */
   usbd_composite_dev_t *cdev;           /**< Pointer to the parent composite device structure. */
   //...
} usbd_composite_msc_dev_t;
/* MSC Device */
static usbd_composite_msc_dev_t usbd_composite_msc_dev;

/* MSC Class Driver */
const usbd_class_driver_t usbd_composite_msc_driver = {
   .get_descriptor = usbd_composite_msc_get_descriptor,
   .set_config = usbd_composite_msc_set_config,
   .clear_config = usbd_composite_msc_clear_config,
   .setup = usbd_composite_msc_setup,
   .ep_data_in = usbd_composite_msc_handle_ep_data_in,
   .ep_data_out = usbd_composite_msc_handle_ep_data_out,
};

The specific callbak implementation of the composite driver usbd_composite_driver:

/**
* @brief  Set composite class configuration
* @param  dev: USB device instance
* @param  config: USB configuration index
* @return Status
*/
static int usbd_composite_set_config(usb_dev_t *dev, u8 config)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->dev = dev;

   cdev->cdc->set_config(dev, config);
   cdev->msc->set_config(dev, config);

   return ret;
}

/**
* @brief  Clear composite configuration
* @param  dev: USB device instance
* @param  config: USB configuration index
* @return Status
*/
static int usbd_composite_clear_config(usb_dev_t *dev, u8 config)
{
   int ret = 0U;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->cdc->clear_config(dev, config);
   cdev->msc->clear_config(dev, config);

   return ret;
}

/**
* @brief  Handle class specific control requests
* @param  dev: USB device instance
* @param  req: USB control requests
* @return Status
*/
static int usbd_composite_setup(usb_dev_t *dev, usb_setup_req_t *req)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;
   usbd_ep_t *ep0_in = &dev->ep0_in;
   int ret = HAL_OK;

   switch (req->bmRequestType & USB_REQ_TYPE_MASK) {
   //...
   case USB_REQ_TYPE_CLASS:
      if ((req->wIndex == USBD_COMP_CDC_COM_ITF) || (req->wIndex == USBD_COMP_CDC_DAT_ITF)) {
         ret = cdev->cdc->setup(dev, req);
      } else if (req->wIndex == USBD_COMP_MSC_ITF) {
         ret = cdev->msc->setup(dev, req);
      } else {
         RTK_LOGS(TAG, RTK_LOG_WARN, "Invalid class req\n");
      }
      break;
   }

   return ret;
}

/**
* @brief  Data sent on non-control IN endpoint
* @param  dev: USB device instance
* @param  ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_in(usb_dev_t *dev, u8 ep_addr, u8 status)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if ((ep_addr == USBD_COMP_CDC_BULK_IN_EP) || (ep_addr == USBD_COMP_CDC_INTR_IN_EP)) {
      if (cdev->cdc->ep_data_in != NULL) {
         ret = cdev->cdc->ep_data_in(dev, ep_addr, status);
      }
   } else if (ep_addr == USBD_COMP_MSC_BULK_IN_EP) {
      if (cdev->msc->ep_data_in != NULL) {
         ret = cdev->msc->ep_data_in(dev, ep_addr, status);
      }
   }

   return ret;
}

/**
* @brief  Data received on non-control OUT endpoint
* @param  dev: USB device instance
* @param  ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_out(usb_dev_t *dev, u8 ep_addr, u16 len)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if (ep_addr == USBD_COMP_CDC_BULK_OUT_EP) {
      if (cdev->cdc->ep_data_out != NULL) {
         ret = cdev->cdc->ep_data_out(dev, ep_addr, len);
      }
   } else if (ep_addr == USBD_COMP_MSC_BULK_OUT_EP) {
      if (cdev->msc->ep_data_out != NULL) {
         ret = cdev->msc->ep_data_out(dev, ep_addr, len);
      }
   }

   return ret;
}

/**
* @brief  Handle EP0 Rx Ready event
* @param  dev: USB device instance
* @return Status
*/
static int usbd_composite_handle_ep0_data_out(usb_dev_t *dev)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->cdc->ep0_data_out(dev);

   return ret;
}

/**
* @brief  USB attach status change
* @param  dev: USB device instance
* @param  status: USB attach status
* @return void
*/
static void usbd_composite_status_changed(usb_dev_t *dev, u8 old_status, u8 status)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   UNUSED(dev);

   if (cdev->cb->status_changed) {
      cdev->cb->status_changed(old_status, status);
   }
}

/**
* @brief  Get descriptor callback
* @param  dev: USB device instance
* @param  req: Setup request handle
* @param  buf: Poniter to Buffer
* @return Descriptor length
*/
static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;
   usb_speed_type_t speed = dev->dev_speed;
   u16 len = 0;
   u16 desc_len;
   u16 total_len = 0;

   switch (USB_HIGH_BYTE(req->wValue)) {
   //...
   case USB_DESC_TYPE_CONFIGURATION:
   case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
      usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
      buf += USB_LEN_CFG_DESC;
      total_len += USB_LEN_CFG_DESC;
      desc_len = cdev->cdc->get_descriptor(dev, req, buf);
      buf += desc_len;
      total_len += desc_len;
      desc_len = cdev->msc->get_descriptor(dev, req, buf);
      total_len += desc_len;
      buf = dev->ep0_in.xfer_buf;
      if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
         buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
      }
      buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
      buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
      len = total_len;
      break;
      }

   return len;
}

Application Callback API

The driver provides callback function interfaces for the application layer, allowing application code to respond to USB events and handle business logic.

  • Composite Application Callback API

    usbd_composite_cb_t is a callback structure for the status of the entire composite device, where the application layer implements specific business logic.

  • Function Class Application Callback API

    This is the callback customized for each sub-function, This is a callback customized for each function, implemented by the application layer. Generally, the following can be implemented:

    typedef struct {
       int(* init)(void);
       int(* deinit)(void);
       int(* setup)(usb_setup_req_t *req, u8 *buf);
       int(* set_config)(void);
       void (*status_changed)(u8 old_status, u8 status);
       int(* sof)(void);
       int(* received)(u8 *buf, u32 len);
       void(* transmitted)(u8 status);
    } usbd_composite_function_class_xx_usr_cb_t;
    

    API

    Description

    init

    Called during class driver initialization; used to initialize application-specific resources.

    deinit

    Called during class driver de-initialization; used to release application-specific resources.

    setup

    Called during the setup or data stage of a control transfer; used to handle application-specific control requests.

    set_config

    Called within the class driver’s set_config callback; used to notify the application layer that the UAC class driver is ready.

    status_changed

    Called when the USB connection status changes; used by the application layer to handle USB hot-plug events.

    sof

    Called when an SOF interrupt is received; used by the application layer to handle clock synchronization.

    transmitted

    Called upon completion of an IN transfer; used by the application layer to asynchronously obtain the IN transfer status.

    received

    Called upon completion of an OUT transfer; used by the application layer to asynchronously obtain the OUT transfer status.

CDC ACM + MSC Example

The following section takes CDC ACM + MSC as an example to introduce the customized sub-function application layer callback (MSC does not use application layer callback).

/**
* @brief User callback structure for CDC ACM events.
* @details This structure allows the application layer to handle CDC ACM events.
*/
typedef struct {
   int(* init)(void);                             /**< Called during class driver initialization for application resource setup. */
   int(* deinit)(void);                           /**< Called during class driver deinitialization for resource cleanup. */
   int(* setup)(usb_setup_req_t *req, u8 *buf);   /**< Called during control transfer SETUP/DATA phases to handle application-specific control requests. */
   int(* received)(u8 *buf, u32 len);             /**< Called when new data is received on the Bulk OUT endpoint. */
   void(* transmitted)(u8 status);                /**< Called after data transmission on the Bulk IN endpoint is complete. */
} usbd_composite_cdc_acm_usr_cb_t;

API for Application

The application layer controls the lifecycle of the entire composite device driver through the following two main functions:

  • usbd_composite_init(): Initialization Function

    • Receives parameters passed by the application layer, such as endpoint buffer size and callback function sets for each sub-function.

    • Calls the initialization function for each sub-function.

    • Links the sub-function driver instances to the usbd_composite_dev structure.

    • Finally, calls usbd_register_class() to register the composite device driver with the USB Core, making it effective.

  • usbd_composite_deinit(): De-initialization Function

    • Calls usbd_unregister_class() to unregister the composite device driver from the USB Core.

    • Sequentially calls the de-initialization function for each sub-function to release all resources.

CDC ACM + MSC Example

The following section takes CDC ACM + MSC as an example to introduce the implementation of application-layer-oriented APIs for composite device class drivers.

/**
* @brief  Init composite class
* @param  cdc_bulk_out_xfer_size: CDC ACM bulk out xfer buffer size
* @param  cdc_bulk_in_xfer_size: CDC ACM bulk in xfer buffer size
* @param  cdc_cb: CDC ACM user callback
* @param  cb: composite user callback
* @return Status
*/
int usbd_composite_init(u16 cdc_bulk_out_xfer_size, u16 cdc_bulk_in_xfer_size, usbd_composite_cdc_acm_usr_cb_t *cdc_cb, usbd_composite_cb_t *cb)
{
   int ret;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if (cdc_cb == NULL) {
      ret = HAL_ERR_PARA;
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user cb\n");
      return ret;
   }

   if (cb != NULL) {
      cdev->cb = cb;
   }

   ret = usbd_composite_cdc_acm_init(cdev, cdc_bulk_out_xfer_size, cdc_bulk_in_xfer_size, cdc_cb);
   if (ret != HAL_OK) {
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Init CDC ACM itf fail: %d\n", ret);
      return ret;
   }

   ret = usbd_composite_msc_init(cdev);
   if (ret != HAL_OK) {
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Init MSC itf fail: %d\n", ret);
      usbd_composite_cdc_acm_deinit();
      return ret;
   }

   cdev->cdc = (usbd_class_driver_t *)&usbd_composite_cdc_acm_driver;
   cdev->msc = (usbd_class_driver_t *)&usbd_composite_msc_driver;

   usbd_register_class(&usbd_composite_driver);

   return ret;
}

/**
* @brief  DeInit composite class
* @param  void
* @return Status
*/
void usbd_composite_deinit(void)
{
   usbd_unregister_class();

   usbd_composite_msc_deinit();
   usbd_composite_cdc_acm_deinit();
}

Note

For detailed class driver descriptions, please refer to: Vendor-Specific Device Solution

API Reference

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

Application Example

This section takes USB Composite (CDC ACM + MSC) 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 device drivers, covering driver initialization, hotplug management, and resource release.

Driver Initialization

The initialization process involves sequentially completing USB Core initialization, and Composite class driver loading. Defining the configuration structure and registering user callback functions are essential steps.

  • Configuration: Configure USB speed mode and interrupt priority.

  • Callback Registration: Define the user callback structure:cpp:struct:usbd_composite_cb_tusbd_composite_fucntion_xx_usr_cb and mount handler functions for each stage.

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

  • Class Driver Init: Call usbd_composite_init() to initialize the composite class driver.

The following section uses CDC ACM + MSC as an example to introduces the implementation of composite device driver initialization.

Most of the interactions with the MSC are automatically handled by the protocol stack, while the application layer primarily focuses on disk initialization and deinitialization. Prior to USB initialization, it is essential to ensure that the storage medium (such as an SD card or Flash) is ready and invoke the disk initialization interface.

static usbd_config_t composite_cfg = {
   .speed = CONFIG_USBD_COMPOSITE_SPEED,
   .isr_priority = CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY,
   .intr_use_ptx_fifo = 0U,
}

static usbd_composite_cdc_acm_usr_cb_t composite_cdc_acm_usr_cb = {
   .init = composite_cdc_acm_cb_init,
   .deinit = composite_cdc_acm_cb_deinit,
   .setup = composite_cdc_acm_cb_setup,
   .received = composite_cdc_acm_cb_received,
   .transmitted = composite_cdc_acm_cb_transmitted
};

static usbd_composite_cb_t composite_cb = {
   .status_changed = composite_cb_status_changed,
};

int ret = 0;

/* Initializes the underlying storage disk. */
ret = usbd_composite_msc_disk_init();
if (ret != HAL_OK) {
   return;
}

/* Initialize USB device core driver with configuration. */
ret = usbd_init(&composite_cfg);
if (ret != HAL_OK) {
   usbd_composite_msc_disk_deinit();
   return;
}

/* Initialize composite class driver.   */
ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
                     CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
                     &composite_cdc_acm_usr_cb,
                     &composite_cb);
if (ret != HAL_OK) {
   usbd_composite_msc_disk_deinit();
   usbd_composite_deinit();
   return;
}

USB Hot-plug Event Handling

Monitor USB connection status changes (connected/disconnected) by registering the status_changed callback function.

Refer to Device Connection Status Detection for more details.

Note

It is recommended to use a semaphore to notify a dedicated task thread for processing, to avoid executing time-consuming operations within the interrupt context.

The following section uses CDC ACM + MSC as an example to introduces the USB Hot-plug Event Handling of composite device driver.

static u8 composite_attach_status;
static rtos_sema_t composite_attach_status_changed_sema;

/* USB status change callback */
static usbd_composite_cb_t composite_cb = {
   .status_changed = composite_cb_status_changed,
};

/* Callback executed in ISR context */
static void composite_cb_status_changed(u8 old_status, u8 status)
{
   composite_attach_status = status;
   rtos_sema_give(composite_attach_status_changed_sema);
}

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

   UNUSED(param);

   for (;;) {
         /* Wait for status change signal */
      if (rtos_sema_take(composite_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
         if (composite_attach_status == USBD_ATTACH_STATUS_DETACHED) {
            RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
            /* 1. Clean up composite class resources */
            usbd_composite_deinit();
            /* 2. De-initialize USB core */
            ret = usbd_deinit();
            if (ret != 0) {
               break;
            }
            usbd_composite_msc_disk_deinit();
            /* 3. Re-initialize for next connection */
            usbd_composite_msc_disk_init();
            ret = usbd_init(&composite_cfg);
            if (ret != 0) {
               break;
            }
            ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
                              CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
                              &composite_cdc_acm_usr_cb,
                              &composite_cb);
            if (ret != 0) {
               usbd_deinit();
               break;
            }
         } else if (composite_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
            RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
         } else {
            RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
         }
      }
   }
   RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
   rtos_task_delete(NULL);
}

Driver Deinitialization

When the USB function is no longer needed or the system is shut down, resources need to be released in the reverse order of initialization. The following section uses CDC ACM + MSC as an example to introduces the implementation of composite device driver deinitialization.

/* De-initializes the underlying storage disk. */
usbd_composite_msc_disk_deinit();

/* Deinitialize composite class driver first */
usbd_composite_deinit();

/* Deinitialize USB device core driver */
usbd_deinit();

Operation method

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

This example demonstrates how to use the composite device protocol stack to configure the Ameba development board as a device featuring both CDC ACM (Virtual Serial Port) and MSC (Mass Storage) capabilities simultaneously.

When the development board is connected to a USB Host (e.g., a PC), the system will recognize two independent logical devices. Users can communicate with the board via a serial tool and, at the same time, read from and write to the on-board SD card just like operating a standard USB flash drive.

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, type ./menuconfig.py, follow the steps below to select Composite Device and the specific composite class combination (CDC ACM + MSC), then save and exit.

    - Choose `CONFIG USB --->`:
       [*] Enable USB
             USB Mode (Device) --->
       [*] Composite
             Select Composite Class (CDC ACM + MSC) --->
    
          (X) CDC ACM + MSC
             Select storage media (SD Card (SD mode))  --->
    
  • Compilation and Flashing

    Compile according to the instructions below, and flash the image file to the development board.

    cd amebaxxx_gcc_project
    ./build.py -a usbd_composite_cdc_acm_msc
    

Verification

  • Device Startup

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

    [COMP] USBD COMP demo start
    
  • Connect to Host

    Connect the development board to a PC using a USB cable.

    When the device is connected to a Windows PC host, the Device Manager will present the following hierarchical structure:

    • Under Universal Serial Bus Controller, there appears: USB Composite Device (driven by usbccgp.sys).

    • Under the Universal Serial Bus Controller, there appears: USB Mass Storage Device (corresponding to the MSC function).

    • Under Ports (COM and LPT), there appears: USB Serial Device (COMx) (corresponding to the CDC ACM function).

  • Function verification 1: CDC ACM (virtual serial port)

    • Open the serial port debugging tool (such as Realtek Trace Tool) on the PC.

    • Select the virtual serial port number enumerated by the development board.

    • Send any character, and the development board will echo the received data as is to verify normal communication.

  • Function verification 2: MSC (mass storage)

    A new removable disk drive letter should automatically pop up in the PC’s file explorer. Users can double-click to open the drive letter and perform file read and write operations on the inserted SD card.

    Note

    This example uses an SD card as the underlying storage medium for the MSC. Please ensure that a formatted SD card is inserted into the on-board SDIOH slot of the development board. Please avoid removing the SD card or disconnecting the USB connection during data reading and writing to prevent damage to the file system.

Vendor-Specific Device 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 endpoint configurations and transfer types. Ameba implements Vendor device functionality based on the underlying USB protocol stack, establishing a highly flexible and exclusive data channel. The Host can interact with Ameba using private command sets and specific data formats via dedicated drivers or generic libraries (such as WinUSB), achieving customized control and functional expansion that transcends the limitations of standard device classes.

../../_images/usb_device_vendor_overview.svg

Features

  • Support hot-plugging

  • Support full customization of descriptors

  • Support configuration of parameters such as speed mode

  • Support synchronous or asynchronous loopback operations for Bulk/Interrupt/Isochronous transfer data

  • Support adaptation with Vendor host application examples; for details, refer to Vendor-Specific Host Solution

Application scenarios

As a highly customizable USB device, 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 device 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).

  • Composite Device Function Extension: Based on standard classes (HID/CDC/UVC), a Vendor interface is added to implement additional control channels.

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

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_device_vendor_app_perform.svg

Class Driver

Driver Architecture

This driver implements a generic USB Vendor Class custom device. It is not affiliated with any standard USB classes (such as HID, MSC), but provides a flexible basic framework that allows developers to customize the transmission protocol.

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

  • Core Driver Layer: Responsible for handling low-level hardware interrupts and processing standard USB protocols.

  • Vendor Class Driver Layer (core implementation of this driver): responsible for managing endpoints, assembling descriptors, and handling transmission processing.

  • User Application Layer: Implement specific business logic. Interact with the Vendor class driver layer by registering the callback struct usbd_vendor_cb_t.

Requirements Planning And Endpoint Design

When designing a Vendor device, developers should plan reasonably according to actual application requirements (such as data throughput, real-time requirements) and the limitations of chip hardware resources (refer to Hardware Features).

Endpoint Type Selection

Select the endpoint type according to the specific data transmission scenario:

Scenario

Recommended Endpoint

Data Integrity

Real-time (Latency)

Typical Application

Command & Status Control

Control

High (Retransmit)

Medium

Parameter setting, version retrieval

High Throughput Data

Bulk

High (Retransmit)

Low (No guarantee)

Firmware upgrade, file transfer, logging

Low Latency Events

Interrupt

High (Retransmit)

High (Fixed)

Keypress, alarm, status synchronization

Audio/Video Stream

Isochronous

Low (Packet Loss)

Very High (Constant)

Microphone, camera

Note

  • Avoid using Interrupt transfers for large data: Although high-speed mode allows interrupt endpoints to have large throughput, they use reserved bandwidth. Excessive occupation will exhaust the periodic bandwidth of the bus.

  • Note the non-real-time nature of Bulk transfers: Bulk transfers have the lowest priority in USB bus arbitration. Although extremely fast when the bus is idle, they may face scheduling delays of tens of milliseconds when busy. If the device requires hard real-time response (e.g., <2ms), please use Interrupt transfers.

Hardware Constraint Assessment

  • Number and Direction of Endpoints: The number and direction of endpoints supported by the chip are fixed.

    For example: The RTL8721F chip supports 5 IN and 5 OUT endpoints in addition to EP0.

    EP0: INOUT
    EP1: IN
    EP2: INOUT
    EP3: INOUT
    EP4: IN
    EP5: OUT
    EP6: INOUT
    EP7: OUT
    
  • FIFO Size: The receive and transmit buffers of the endpoints in the chip are configurable but have maximum limits. Ensure that the endpoint buffer size can accommodate at least one MPS length data packet.

    For example: Hardware configuration of the RTL8721F chip in device mode.

    Device mode total DFIFO: 1024 DWORD (1 DWORD = 4 Bytes)
    Shared Rx FIFO max depth: 1024 DWORD
    6 Dedicated Tx FIFO:
    
    - Tx FIFO 0 max depth: 32  DWORD = 128  Bytes
    - Tx FIFO 1 max depth: 16  DWORD = 64   Bytes
    - Tx FIFO 2 max depth: 256 DWORD = 1024 Bytes
    - Tx FIFO 3 max depth: 32  DWORD = 128  Bytes
    - Tx FIFO 4 max depth: 256 DWORD = 1024 Bytes
    - Tx FIFO 5 max depth: 128 DWORD = 512  Bytes
    

Endpoint Configuration Example

In this example, besides EP0, the Vendor device includes a set of different endpoint types with opposite directions: BULK IN & BULK OUT, ISOC IN & ISOC OUT, INTR IN & INTR OUT.

Taking the RTL8721F chip as an example, the following explains the endpoint selection and DFIFO configuration process:

  • Hardware Resource Assessment: First, confirm that the hardware endpoint count can satisfy the requirements of the 3 groups (6 endpoints) of non-control endpoints mentioned above.

  • MPS (Max Packet Size) Setting

    The FIFO size must accommodate at least one MPS transmission for the endpoint. Assuming hardware FIFO space permits, it is recommended to set the MPS to the maximum value allowed by the USB specification to improve throughput:

    • Bulk (High-Speed): 512 Bytes

    • Interrupt (High-Speed): 1024 Bytes

    • Isochronous (High-Speed): 1024 Bytes

  • DFIFO Allocation Strategy

    Based on the hardware characteristics of the RTL8721F chip:

    • TX FIFO (Dedicated Transmit Buffer): Each IN endpoint has an independent transmit buffer.

    • RX FIFO (Shared Receive Buffer): All OUT endpoints share the same receive buffer.

    • DMA Requirement: In DMA mode, 1 DWORD (4 Bytes) of buffer space must be reserved for each endpoint.

    Recommended Allocation Logic:

    • Transmit Buffer: Allocate sufficient space for each enabled IN endpoint based on the MPS values above.

    • Receive Buffer: Allocate all remaining available space to the RX FIFO to ensure stability during data reception.

    • Formula: RX FIFO Size = Total FIFO Space - Σ(TX FIFO config values of enabled IN endpoints) - Σ(1 DWORD reserved per endpoint)

  • The final suitable set of endpoints and DFIFO configuration is determined as follows:

    /* Vendor Device Endpoint Address */
    #define USBD_VENDOR_BULK_IN_EP            0x86U  /* EP6 for BULK IN */
    #define USBD_VENDOR_BULK_OUT_EP           0x03U  /* EP3 for BULK OUT */
    #define USBD_VENDOR_ISOC_IN_EP            0x82U  /* EP2 for ISOC IN */
    #define USBD_VENDOR_ISOC_OUT_EP           0x02U  /* EP2 for ISOC OUT */
    #define USBD_VENDOR_INTR_IN_EP            0x84U  /* EP4 for INTR IN */
    #define USBD_VENDOR_INTR_OUT_EP           0x05U  /* EP5 for INTR OUT */
    
    /* Vendor Device DFIFO config */
    static usbd_config_t vendor_cfg = {
        /* DFIFO total 1024 DWORD, resv 12 DWORD for each EP’s DMA addr and IN EP0 with fixed 32 DWORD */
        .ptx_fifo_depth = {16U, 256U, 32U, 256U, 128U, }, // For IN EP: 1,2,3,4,6
        .rx_fifo_depth = 292U,                            // All remaining fifo space is allocated to RX
    };
    

Descriptor Structure

Vendor class devices adhere to the USB 2.0 standard descriptor architecture. Developers need to focus on configuration descriptor adaptation for multi-rate support and compliance settings for endpoint parameters. The protocol stack supports descriptor customization; please refer to Device Descriptor Customization.

The complete set of Vendor Device Descriptors includes:

  • Device Descriptor: The root node, defining identity information such as VID/PID.

  • Configuration Descriptor Collection: Includes configuration, interface, and endpoint descriptors.

  • Multi-rate Support Descriptors (Optional): Device Qualifier Descriptor and Other Speed Configuration Descriptor.

  • String Descriptors (Optional): Provide text information such as manufacturer, product name, and serial number.

Device Descriptor

Regardless of the speed at which the device operates, typically only one common device descriptor needs to be maintained.

Key Field Settings:

  • bDeviceClass: Set to 0xFF (vendor-defined)/0x00 (class defined in interface descriptor).

  • bMaxPacketSize0: Controls the maximum packet size for endpoint 0, which is typically set to 64 by default.

  • idVendor / idProduct: Vendor ID and Product ID.

Example:

Standard Device Descriptor
├── bLength            : 1 byte   → Size of the descriptor (18 bytes)
├── bDescriptorType    : 1 byte   → 0x01 (DEVICE)
├── bcdUSB             : 2 bytes  → USB Specification Release Number (0x0200 = USB 2.0)
├── bDeviceClass       : 1 byte   → 0x00 (Class defined at Interface level)
├── bDeviceSubClass    : 1 byte   → 0x00
├── bDeviceProtocol    : 1 byte   → 0x00
├── bMaxPacketSize0    : 1 byte   → Max Packet Size for Control Endpoint 0 (64 bytes)
├── idVendor           : 2 bytes  → Vendor ID (VID)
├── idProduct          : 2 bytes  → Product ID (PID)
├── bcdDevice          : 2 bytes  → Device Release Number
├── iManufacturer      : 1 byte   → Index of string descriptor describing manufacturer
├── iProduct           : 1 byte   → Index of string descriptor describing product
├── iSerialNumber      : 1 byte   → Index of string descriptor describing the device's serial number
└── bNumConfigurations : 1 byte   → Number of possible configurations (1)

Configuration Descriptor and Multi-speed Adaptation

  • If the device is designed to support only one speed (e.g., Full Speed only), only the configuration set corresponding to that speed needs to be defined.

  • If the device needs to support both Full Speed and High Speed, parameters must be dynamically adapted according to the connection speed.

    Developers need to define two sets of configuration descriptors in the code and return the corresponding set based on the actual speed during enumeration:

    • High Speed Configuration Descriptor Set: Used when the device enumerates as High Speed.

    • Full Speed Configuration Descriptor Set: Used when the device enumerates as Full Speed.

    Note

    When writing these two sets of descriptors, the wMaxPacketSize and bInterval fields in the endpoint descriptors must be strictly adjusted for the current speed. Using Full Speed parameters in High Speed mode (or vice versa) will lead to enumeration failure or serious transmission performance issues.

Max Packet Size (wMaxPacketSize)

According to the USB 2.0 specification, the maximum packet size limits for endpoints differ at different speeds. The maximum packet size for each endpoint needs to be configured reasonably based on the actual bandwidth requirements of the application.

Endpoint maximum packet size specification

Endpoint Type | Full Speed (Bytes) | High Speed (Bytes)

Control

8, 16, 32, 64

64 (Fixed)

Interrupt

≤ 64

≤ 1024

Bulk

8, 16, 32, 64

512 (Fixed)

Isochronous

≤ 1023

≤ 1024

Polling Interval (bInterval)

The physical time unit of the bInterval field depends on the current speed. Incorrect settings will cause abnormal polling frequencies.

bInterval Differences and Calculation Formula

USB Mode

Time Unit

Value Range

Calculation Formula

Full-Speed

1ms (Frame)

1 – 255

\(Interval = 1ms \times bInterval\)

High-Speed

125μs (Microframe)

1 – 16

\(Interval = 125\mu s \times 2^{(bInterval-1)}\)

Note

Example: Setting a 1ms Polling Interval

  • Full Speed Mode: Set bInterval to 1 (i.e., \(1ms \times 1 = 1ms\)).

  • High Speed Mode: Set bInterval to 4 (i.e., \(125\mu s \times 2^{(4-1)} = 125\mu s \times 8 = 1000\mu s = 1ms\)).

Below is an example of configuration descriptors:

High Speed Configuration
├── Configuration Descriptor
│   ├── bLength            : 1 byte   → 9 bytes
│   ├── bDescriptorType    : 1 byte   → 0x02 (CONFIGURATION)
│   ├── wTotalLength       : 2 bytes  → Total length (Config + Interface + EPs)
│   ├── bNumInterfaces     : 1 byte   → 1
│   ├── bConfigurationValue: 1 byte   → 1
│   ├── iConfiguration     : 1 byte   → Index of string descriptor
│   ├── bmAttributes       : 1 byte   → 0xC0 (Self-powered) or 0x80 (Bus-powered)
│   └── bMaxPower          : 1 byte   → Max power consumption (e.g., 100mA)
│
├── Interface Descriptor
│   ├── bLength            : 1 byte   → 9 bytes
│   ├── bDescriptorType    : 1 byte   → 0x04 (INTERFACE)
│   ├── bInterfaceNumber   : 1 byte   → 0
│   ├── bAlternateSetting  : 1 byte   → 0
│   ├── bNumEndpoints      : 1 byte   → 2 (Example: 1 Bulk IN + 1 Bulk OUT)
│   ├── bInterfaceClass    : 1 byte   → 0xFF (Vendor Specific)
│   ├── bInterfaceSubClass : 1 byte   → 0xFF
│   ├── bInterfaceProtocol : 1 byte   → 0xFF
│   └── iInterface         : 1 byte   → Index of string descriptor
│
├── Endpoint Descriptor (BULK OUT)
│   ├── bLength            : 1 byte   → 7 bytes
│   ├── bDescriptorType    : 1 byte   → 0x05 (ENDPOINT)
│   ├── bEndpointAddress   : 1 byte   → 0x01 (OUT Address)
│   ├── bmAttributes       : 1 byte   → 0x02 (Transfer Type: BULK)
│   ├── wMaxPacketSize     : 2 bytes  → 512 bytes (High Speed Fixed)
│   └── bInterval          : 1 byte   → 0 (Ignored for Bulk)
│
└── Endpoint Descriptor (BULK IN)
    ├── bLength            : 1 byte   → 7 bytes
    ├── bDescriptorType    : 1 byte   → 0x05 (ENDPOINT)
    ├── bEndpointAddress   : 1 byte   → 0x81 (IN Address)
    ├── bmAttributes       : 1 byte   → 0x02 (Transfer Type: BULK)
    ├── wMaxPacketSize     : 2 bytes  → 512 bytes (High Speed Fixed)
    └── bInterval          : 1 byte   → 0 (Ignored for Bulk)

Device Qualifier & Other Speed Configuration Descriptors

  • If the device is designed to support only one speed mode, these two descriptors do not need to be implemented.

  • If the device is designed to support both Full Speed and High Speed modes, these two descriptors must be implemented to inform the host “what capabilities the device would have if it were operating at the other speed”:

    • Device Qualifier Descriptor: Structure is similar to the Device Descriptor but does not contain static identity information like VID/PID (which is speed-independent). It describes basic information about the device at the “other speed” (e.g., bMaxPacketSize0 at that speed).

    • Other Speed Configuration Descriptor: Describes the complete configuration tree of the device at the “other speed”.

Device Qualifier Descriptor

Focus on the following key fields of the device at the other speed:

  • bMaxPacketSize0: Defines the maximum packet size for endpoint 0 at the other speed.

  • bNumConfigurations: Defines the number of configurations supported at the other speed.

Example:

Device Qualifier Descriptor
├── bLength            : 1 byte   → Size of the descriptor (10 bytes)
├── bDescriptorType    : 1 byte   → 0x06 (DEVICE_QUALIFIER)
├── bcdUSB             : 2 bytes  → USB Specification Release Number (0x0200 = USB 2.0)
├── bDeviceClass       : 1 byte   → 0x00
├── bDeviceSubClass    : 1 byte   → 0x00
├── bDeviceProtocol    : 1 byte   → 0x00
├── bMaxPacketSize0    : 1 byte   → Max Packet Size for other speed (64 bytes)
├── bNumConfigurations : 1 byte   → Number of other-speed configurations (1)
└── bReserved          : 1 byte   → Reserved for future use (0x00)

Note

If the device enumerates as High Speed but does not provide a Device Qualifier Descriptor, Windows may report an error or run in degraded mode.

Other Speed Configuration Descriptor

The Device Qualifier Descriptor is just the entry point; the host will subsequently request the Other Speed Configuration Descriptor. At this point, the driver logic should execute a “Cross Return” strategy:

  • If currently running in High Speed mode, when the host asks about the “other speed”, return the Full Speed configuration descriptor set.

  • If currently running in Full Speed mode, when the host asks about the “other speed”, return the High Speed configuration descriptor set.

Descriptor Example

This device adheres to the USB 2.0 standard descriptor architecture and supports both Full Speed and High Speed. Below is an example of the device descriptor topology:

Device Descriptor
└─ USB Version 2.00 (Vendor Specific)

Configuration Descriptor
  └─ Interface (IF 0, Alt 0)
     ├─ Endpoint: BULK OUT, maxpkt=0x0200 (512 bytes)
     ├─ Endpoint: BULK IN, maxpkt=0x0200 (512 bytes)
     ├─ Endpoint: INTR OUT, maxpkt=0x0400 (1024 bytes)
     ├─ Endpoint: INTR IN, maxpkt=0x0400 (1024 bytes)
     ├─ Endpoint: ISOC OUT, maxpkt=0x0400 (1024 bytes)
     └─ Endpoint: ISOC IN, maxpkt=0x0400 (1024 bytes)

Device Qualifier Descriptor
└─ USB 2.0

Other Speed Configuration Descriptor
  └─ Interface (IF 0, Alt 0)
     ├─ Endpoint: BULK OUT, maxpkt=0x0040 (64 bytes)
     ├─ Endpoint: BULK IN, maxpkt=0x0040 (64 bytes)
     ├─ Endpoint: INTR OUT, maxpkt=0x0040 (64 bytes)
     ├─ Endpoint: INTR IN, maxpkt=0x0040 (64 bytes)
     ├─ Endpoint: ISOC OUT, maxpkt=0x03FF (1023 bytes)
     └─ Endpoint: ISOC IN, maxpkt=0x03FF (1023 bytes)

USB String Table
  ├── String Descriptor    (Index 0: Language ID Array)
  ├── String Descriptor    (Index 1: Manufacturer String)
  ├── String Descriptor    (Index 2: Product String)
  └── String Descriptor    (Index 3: SerialNumber String)

In the logical design, although the descriptor example defines only one interface, the interface is functionally divided into two modules: a Control Module responsible for command interaction and a Data Module responsible for business payload transmission.

  • Control Module

    Responsible for command interaction, status management, and the enumeration process. It is based on the default bidirectional control endpoint EP0, therefore it does not consume endpoint resources in the interface descriptor.

    • Enumeration and Status Reporting: Responds to standard requests to complete device identification and configuration.

    • Vendor Requests: Supports issuing custom commands via control transfers (e.g., entering test mode, reading/writing registers).

  • Data Module

    Responsible for full-rate data throughput and verification. It contains three sets of endpoints, providing the three non-control transfer types of the USB protocol.

    • Bulk Transfer: 1 pair of IN/OUT endpoints, used for non-periodic, large-block data transmission and loopback verification requiring high data integrity.

    • Interrupt Transfer: 1 pair of IN/OUT endpoints, used for periodic, low-latency small data transmission (e.g., status polling) and loopback verification.

    • Isochronous Transfer: 1 pair of IN/OUT endpoints, used for stream data transmission (e.g., audio/video streams) and loopback verification requiring high real-time performance and allowing minor packet loss.

Class Driver Implementation

Driver Callback Mechanism

The USB Class Driver is typically divided into two layers: the Core Layer (handles hardware interrupts, standard enumeration, and state machines) and the Class Driver Layer (handles specific business logic, such as HID, MSC, and Vendor).

../../_images/usb_device_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 usbd_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.”

  • get_descriptor: Called back during the enumeration process to obtain descriptors.

  • Setup: Called when an EP0 SETUP packet is received; it must determine if this is a Vendor request.

  • set_config: Traverses and initializes all endpoints and resources, and prepares to receive data.

  • clear_config: Traverses and releases all opened endpoints and resources.

  • sof: Called when an SOF interrupt occurs, used for processing logic with strict timing requirements.

  • ep0_data_in: Called after data is successfully sent to the host during the Control Transfer DATA IN stage.

  • ep0_data_out: Called after data is received from the host during the Control Transfer DATA OUT stage.

  • ep_data_in: Called after data is successfully sent to the host (Transmission Complete Interrupt).

  • ep_data_out: Called after data is received from the host (Reception Complete Interrupt).

Application Layer Callback Functions

The Vendor Class driver defines a callback structure usbd_vendor_cb_t facing the application layer. This is implemented by the user layer, and generally, the following can be selectively implemented:

Callback

Description

deinit

Called when the class driver is de-initialized. Used to release application-related resources.

setup

Called during the setup or data stage of a control transfer. Used to handle application-specific control requests.

set_config

Called within the class driver’s set_config callback. Used to notify the application layer that the class driver is ready.

status_changed

Called when the USB connection status changes. Used by the application layer to handle USB hot-plug events.

sof

Called when an SOF interrupt is received. Used by the application layer for clock synchronization handling.

transmitted

Called when an IN transfer completes. Used by the application layer to asynchronously obtain the IN transfer status.

received

Called when an OUT transfer completes. Used by the application layer to asynchronously obtain the OUT transfer status.

Example of Vendor Class Driver Application Layer Callbacks:

typedef struct {
    int(* init)(void);
    int(* deinit)(void);
    int(* setup)(usb_setup_req_t *req, u8 *buf);
    int(* set_config)(void);
    int(* bulk_received)(u8 *buf, u32 len);
    int(* intr_received)(u8 *buf, u32 len);
    int(* isoc_received)(u8 *buf, u32 len);
    void(* bulk_transmitted)(u8 status);
    void(* intr_transmitted)(u8 status);
    void(* isoc_transmitted)(u8 status);
    void (*status_changed)(u8 old_status, u8 status);
} usbd_vendor_cb_t;

Driver Initialization and Resource Allocation

The usbd_vendor_init function is the top-level function used to initialize the Vendor device. Its core responsibilities are allocating endpoint resources, configuring endpoint parameters, and registering the class driver.

Define Device Instance

Define a globally unique Vendor device instance pointer usbd_vendor_dev, and separately define pointers to its internal endpoint structures and callback functions.

Example:

typedef struct {
    usb_setup_req_t ctrl_req;   // Save control request for control transfer DATA OUT process.
    usbd_ep_t ep_bulk_out;      // BULK OUT endpoint.
    usbd_ep_t ep_bulk_in;       // BULK IN endpoint.
    // Other endpoint if exist.
    usb_dev_t *dev;             // USB device core.
    usbd_vendor_cb_t *cb;       // User callback.
    u8 alt_setting;             // Record the current interface alternate setting.
} usbd_vendor_dev_t;

/* 1. Vendor Device */
static usbd_vendor_dev_t usbd_vendor_dev;

Endpoint Parameter Initialization

  • Set the endpoint address, type, and allocate a buffer for it.

    • In DMA mode, ensure the endpoint transfer buffer address is aligned with the Cache line (e.g., 4-byte or 32-byte alignment, depending on the hardware platform).

    • Allocate at least one MPS (Max Packet Size) of space for the OUT endpoint’s receive buffer.

  • For OUT endpoints, specify the transfer length.

    • Since reception needs to be prepared in advance, set the transfer length; the length should not exceed the receive buffer size.

  • For periodic endpoints (Interrupt/Isochronous), set the polling interval binterval.

Example:

/* 2. Initialize Endpoint, such as Bulk OUT */
usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
usbd_ep_t *ep_bulk_out = &cdev->ep_bulk_out;

ep_bulk_out->addr = USBD_VENDOR_BULK_OUT_EP;
ep_bulk_out->type = USB_CH_EP_TYPE_BULK;
ep_bulk_out->xfer_len = USBD_VENDOR_BULK_BUF_SIZE;
ep_bulk_out->xfer_buf = (u8 *)usb_os_malloc(USBD_VENDOR_BULK_BUF_SIZE);

Execute User Callback Initialization

If a user init callback function is registered, call it. This gives the upper-layer application a chance to execute application-specific initialization logic.

Example:

/* 3. Execute User Init Callback */
if (cb != NULL) {
    cdev->cb = cb;
    if (cb->init != NULL) {
        ret = cb->init();
        if (ret != HAL_OK) {
            goto exit;
        }
    }
}

Register USB Class Driver

Call usbd_register_class() to register the configured usbd_class_driver_t class driver structure callbacks with the device core driver, making the device ready to respond to enumeration.

Example:

/* Vendor Class Driver */
static const usbd_class_driver_t usbd_vendor_driver = {
    .get_descriptor = usbd_vendor_get_descriptor,     /* Get descriptor (Device, Config, String, etc.) */
    .set_config     = usbd_vendor_set_config,         /* Called when the host sends SET_CONFIGURATION to initialize the endpoint */
    .clear_config   = usbd_vendor_clear_config,       /* Reset configuration, deinitialize endpoints */
    .setup          = usbd_vendor_setup,              /* Handle the SETUP packet on EP0 (core function) */
    .ep_data_in     = usbd_vendor_handle_ep_data_in,  /* Data reception completion callback */
    .ep_data_out    = usbd_vendor_handle_ep_data_out, /* Data transmittion completion callback */
    .status_changed = usbd_vendor_status_changed,     /* Device status change (Suspend/Resume) */
};

/* 4. Register Class Driver */
usbd_register_class(&usbd_vendor_driver);

Buffer allocation error handling

After each call to usb_os_malloc() to allocate memory, the code immediately checks the return value. If it is NULL, it is determined as insufficient memory, and the program jumps to the corresponding error handling label to implement reverse resource release.

Example:

/* 5. Critical: Validate memory allocation immediately */
if (ep_bulk_out->xfer_buf == NULL) {
    ret = HAL_ERR_MEM;
    goto init_exit;
}

/* Reverse Resource Cleanup */
init_exit:
    return ret;

Descriptor Implementation

The usbd_vendor_get_descriptor callback function is used to respond to the standard GET_DESCRIPTOR request from the host. It dynamically returns the corresponding descriptor based on the requested descriptor type (Device, Configuration, String, etc.) and the current connection speed.

  • USB_DESC_TYPE_DEVICE: Returns the device descriptor.

  • USB_DESC_TYPE_CONFIGURATION: Returns the corresponding configuration descriptor based on the connection speed.

  • USB_DESC_TYPE_DEVICE_QUALIFIER: The qualifier descriptor required for supporting high-speed devices.

  • USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION: Allows the host to query configuration information for the other speed.

In the driver code implementation, it is recommended to define the following core array structures and manage parameters (e.g., VID/PID, endpoint addresses) uniformly via macro definitions.

  • Device Descriptor: usbd_vendor_dev_desc[]

  • Config Descriptor (HS): usbd_vendor_hs_config_desc[]

  • Config Descriptor (FS): usbd_vendor_fs_config_desc[]

  • Device Qualifier Descriptor: usbd_vendor_device_qualifier_desc[]

  • String Descriptor: usbd_vendor_lang_id_desc[]

Different Speed Configurations

For configuration descriptors, the function returns the corresponding descriptor array based on the connection speed. Additionally, USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION must be handled.

Example:

case USB_DESC_TYPE_CONFIGURATION:
  /* Return current speed configuration */
  if (speed == USB_SPEED_HIGH) {
    desc = (u8 *)usbd_vendor_hs_config_desc;
    len = sizeof(usbd_vendor_hs_config_desc);
  } else {
    desc = (u8 *)usbd_vendor_fs_config_desc;
    len = sizeof(usbd_vendor_fs_config_desc);
  }
  break;

case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
  /* Return the OPPOSITE speed configuration */
  if (speed == USB_SPEED_HIGH) {
    desc = (u8 *)usbd_vendor_fs_config_desc; // Give FS desc
    len = sizeof(usbd_vendor_fs_config_desc);
  } else {
    desc = (u8 *)usbd_vendor_hs_config_desc; // Give HS desc
    len = sizeof(usbd_vendor_hs_config_desc);
  }
  /* Update type field strictly required by spec */
  // ... copy and modify logic ...
  break;

String Descriptors

Returns different strings based on the low byte (index) of wValue. Note that the product string may need to distinguish between High Speed and Full Speed (usually by appending a “(HS)” suffix to the string).

Example:

case USB_DESC_TYPE_STRING:
  switch (USB_LOW_BYTE(req->wValue)) {
  case USBD_IDX_PRODUCT_STR:
    if (speed == USB_SPEED_HIGH) {
      len = usbd_get_str_desc(USBD_VENDOR_PROD_HS_STRING, buf);
    } else {
      len = usbd_get_str_desc(USBD_VENDOR_PROD_FS_STRING, buf);
    }
    break;
  /* ... other strings */
  }
  break;

Vendor Request Processing

The usbd_vendor_setup callback function is responsible for handling SETUP packets on the control endpoint. It acts as the central hub for USB request distribution, handling both Standard requests and Vendor requests.

  • Standard Requests: Handles SET_INTERFACE, GET_INTERFACE, etc.

  • Vendor Requests: Implements custom protocols.

    • No Data Stage: Execute the command directly within the Setup callback.

    • Data Stage (IN): Call the user callback to populate data, then call usbd_ep_transmit() to send it to the host.

    • Data Stage (OUT): Save the SETUP packet and call usbd_ep_receive() to prepare for receiving subsequent data.

Standard Request Handling

Handles interface-specific operations. Primarily USB_REQ_SET_INTERFACE and USB_REQ_GET_INTERFACE, which are used to manage the interface’s Alternate Setting.

Example:

case USB_REQ_TYPE_STANDARD:
    switch (req->bRequest) {
        case USB_REQ_SET_INTERFACE:
            if (dev->dev_state == USBD_STATE_CONFIGURED) {
                /* Update alternate Setting */
                cdev->alt_setting = USB_LOW_BYTE(req->wValue);
            }
            break;
        /* Handled other requests */
    }

Vendor Request Handling (SETUP Stage)

Requests where bmRequestType is of type Vendor are handled within the usbd_vendor_setup callback. This represents the core business logic of the Vendor driver.

The processing flow relies on two key parameters: Data Transfer Direction (USB_REQ_DIR_MASK) and Data Stage Length (wLength).

  • No Data Stage

    If wLength is 0, the request is a simple control command with no subsequent data transfer. Execute the command logic directly in the user setup callback based on bRequest and return the status.

  • Data Stage

    If wLength > 0, further processing is required based on the transfer direction field (USB_REQ_DIR_MASK).

    Data Transmission Flow:

    • Call the user setup callback to prepare data and fill the transmit buffer.

    • Call usbd_ep_transmit() to send the data to the host.

Example:

case USB_REQ_TYPE_CLASS :
case USB_REQ_TYPE_VENDOR:
    /* 1. Check if there is a Data Stage (wLength > 0) */
    if (req->wLength) {
        /* IN transfer: Device-to-Host */
        if ((req->bmRequestType & USB_REQ_DIR_MASK) == USB_D2H) {
            /* Call user callback to prepare data in xfer_buf */
            ret = cdev->cb->setup(req, ep0_in->xfer_buf);

            /* If user callback succeeds, transmit data to Host (Data Stage) */
            if (ret == HAL_OK) {
                ep0_in->xfer_len = req->wLength;
                usbd_ep_transmit(dev, ep0_in);
            }
        /* OUT transfer: Host-to-Device */
        } else {
            /* Save the SETUP packet because the data comes in the next stage */
            usb_os_memcpy((void *)&cdev->ctrl_req, (void *)req, sizeof(usb_setup_req_t));

            /* Prepare EP0 OUT to receive the data payload */
            ep0_out->xfer_len = req->wLength;

            /* Start receiving data (Data Stage) */
            usbd_ep_receive(dev, ep0_out);
        }
    /* 2. No Data Stage (Control command only) */
    } else {
        /* Directly handle the command via user callback */
        cdev->cb->setup(req, NULL);
    }
    break;

Interface Configuration

The usbd_vendor_set_config callback function is a critical step in the device enumeration process. When the host sends the SET_CONFIGURATION standard request, the device protocol stack calls this function. It marks the device’s transition from the “Address State” to the “Configured State,” allowing it to begin normal data transmission.

Its core responsibilities are configuring non-control endpoint parameters (MPS/Interval) based on the negotiated speed, initializing endpoints, and immediately enabling reception.

  • Endpoint Parameter Configuration: Set non-control endpoint parameters (MPS/Interval) based on connection speed.

  • Endpoint Initialization: Call usbd_ep_init() to initialize non-control endpoints.

  • Pre-reception: For all OUT endpoints, call usbd_ep_receive() immediately after initialization to prepare for potential data from the host; otherwise, the first packet sent by the host results in a NAK or even a timeout.

Endpoint Parameter Speed Adaptation

Dynamically configure non-control endpoint parameters (MPS and bInterval) based on the current device connection speed (High-Speed or Full-Speed). Then call usbd_ep_init() to initialize the endpoints.

This is because the USB specification has different requirements for packet size and transmission interval in High-Speed and Full-Speed modes.

  • Maximum Packet Size (MPS): Packet length usually differs (e.g., Bulk transfer MPS is typically 64 for Full-Speed and 512 for High-Speed).

  • Polling Interval (bInterval): Intervals for Interrupt and Isochronous transfers are defined differently at different speeds.

Example:

u8 speed = dev->dev_speed;

/* Init INTR IN EP: Select MPS/Interval based on Speed */
ep_intr_in->mps = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_INTR_MPS : USBD_VENDOR_FS_INTR_MPS;
ep_intr_in->binterval = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_INTR_IN_INTERVAL : USBD_VENDOR_FS_INTR_IN_INTERVAL;

usbd_ep_init(dev, ep_intr_in);

/* Init BULK OUT EP: Select MPS based on Speed */
ep_bulk_out->mps = (speed == USB_SPEED_HIGH) ? USBD_VENDOR_HS_BULK_MPS : USBD_VENDOR_FS_BULK_MPS;
usbd_ep_init(dev, ep_bulk_out);

Pre-start Data Reception

For all OUT type endpoints (Host to Device), usbd_ep_receive() must be called immediately after usbd_ep_init() completes. This ensures the device hardware is ready to receive data the moment configuration is finished.

This is necessary because USB transfers are host-initiated. Once the host configures the device, it may send data immediately. If the device side hasn’t pre-configured the receive buffer and enabled reception, data packets sent by the host will be NAKed (rejected) or discarded by the device, leading to communication failure.

Example:

/* Critical: Prepare to receive data immediately after init */
ret = usbd_ep_receive(dev, ep_bulk_out);
if (ret != HAL_OK) {
    return ret;
}

Execute User Callback Initialization

If a user set_config callback function is registered, call it. This gives the upper-layer application a chance to execute application-specific config logic(For example, turning on a certain LED, or resetting the Buffer pointer of the application layer).

Example:

cdev->alt_setting = 0U;// Default interface alternate setting.

if (cdev->cb->set_config != NULL) {
    cdev->cb->set_config();
}

Data Transfer Processing

Data Processing Callbacks

The USB protocol stack calls these functions when a hardware transmission completes. They are responsible for notifying the application layer of the transmission result and preparing for the next transfer.

  • usbd_vendor_handle_ep_data_out: Called when a non-control endpoint successfully receives a data packet sent by the host.

  • usbd_vendor_handle_ep_data_in: Called when a data packet transmission from a non-control endpoint to the host is completed.

Data Reception Flow

Data reception is asynchronous and can be handled within the class driver without the upper layer actively calling a receive API:

  • The driver enables the first reception preparation in the usbd_vendor_set_config class driver callback.

  • When a non-control endpoint successfully receives a packet from the host, the usbd_vendor_handle_ep_data_out callback is invoked.

    • Identify Endpoint: Check the endpoint address to determine which endpoint the data came from.

    • Distribute to Upper Layer: Call the received callback registered by the application layer, passing the data buffer pointer and received length to the user.

    • Re-enable Reception: This is the most critical step. After processing the data, usbd_ep_receive() must be called immediately to receive the next packet; otherwise, subsequent data cannot be received.

    Example:

    static int usbd_vendor_handle_ep_data_out(usb_dev_t *dev, u8 ep_addr, u32 len)
    {
        usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
        usbd_ep_t *ep_intr_out = &cdev->ep_intr_out;
        int ret = HAL_OK;
    
        /* Example: Interrupt OUT handling */
        if (ep_addr == USBD_VENDOR_INTR_OUT_EP) {
            /* 1. Notify Application Layer */
            if (len > 0) {
                if (cdev->cb->intr_received) {
                    cdev->cb->intr_received(ep_intr_out->xfer_buf, len);
                }
            }
            /* 2. CRITICAL: Receive the next packet */
            ret = usbd_ep_receive(cdev->dev, ep_intr_out, ep_intr_out->xfer_buf, ep_intr_out->xfer_len);
            if (ret != HAL_OK) {
                return ret;
            }
        }
    
        return ret;
    }
    

Data Transmission Flow

  • The application layer actively sends data to the host via data transmission APIs.

    Example: usbd_vendor_transmit_intr_data() for sending interrupt data.

    int usbd_vendor_transmit_intr_data(u8 *buf, u32 len)
    {
        int ret = HAL_OK;
        usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
        usb_dev_t *dev = cdev->dev;
        usbd_ep_t *ep_intr_in = &cdev->ep_intr_in;
    
        /* Check if USB device has been enumerated. */
        if (!dev->is_ready) {
            return HAL_ERR_HW;
        }
    
        /* Interrupt transmission does not support high-speed and high-bandwidth yet. */
        if (len > ep_intr_in->mps) {
            len = ep_intr_in->mps;
        }
    
        /* Check if previous transfer is done. */
        if (ep_intr_in->xfer_state == 0U) {
            /* Set Busy */
            ep_intr_in->xfer_state = 1U;
             /* Copy data to dedicated buffer (handles DMA alignment) */
            usb_os_memcpy((void *)ep_intr_in->xfer_buf, (void *)buf, len);
            ep_intr_in->xfer_len = len;
             /* Trigger Hardware Transmission */
            ret = usbd_ep_transmit(dev, ep_intr_in);
        } else {
            ret = HAL_BUSY;
        }
    
        return ret;
    }
    

    Note

    • Length Check: Currently, Interrupt transfers do not support High-Speed High-Bandwidth. If the passed len is greater than the endpoint’s Max Packet Size (MPS), the transmission length is forced to MPS. Therefore, it is recommended to use Bulk transfers for large data blocks or manually split packets in the upper layer.

    • Data Copy: Copy user data into the endpoint’s transfer buffer. USB DMA requires the data buffer address to be Cache-line aligned. This copy operation prevents issues with unaligned upper-layer buffers. If the passed buffer address is already aligned, this step can be omitted.

    • usbd_vendor_transmit_isoc_data(): Used for sending Isochronous data. The logic is similar to Interrupt transfer, but since ISOC has no retransmission mechanism, checking the transmission status is not required.

    • usbd_vendor_transmit_bulk_data(): Used for sending Bulk data. The logic is similar to Interrupt transfer, but the send length is not split based on MPS; it only needs to be no larger than the buffer size.

  • When the data packet sent by the device to the host is completed, the usbd_vendor_handle_ep_data_in class driver callback is called.

    • Clear Busy Status: Reset the transmission state of the corresponding endpoint, allowing the application layer to call the transmit function again.

    • Transmission Completion Notification: If the application layer needs to know when the sending ends (e.g., to release the transfer buffer), call the user transmitted callback function.

    Example:

    static int usbd_vendor_handle_ep_data_in(usb_dev_t *dev, u8 ep_addr, u8 status)
    {
        usbd_vendor_dev_t *cdev = &usbd_vendor_dev;
        usbd_vendor_cb_t *cb = cdev->cb;
        usbd_ep_t *ep_intr_in = &cdev->ep_intr_in;
    
        if (ep_addr == USBD_VENDOR_INTR_IN_EP) {
            /* 1. Clear Busy Flag */
            ep_intr_in->xfer_state = 0U;
    
            /* 2. Notify Application Layer (Optional) */
            if (cb->intr_transmitted) {
                cb->intr_transmitted(status);
            }
        }
        return HAL_OK;
    }
    

Hot-Plug Handling

The driver must robustly handle connection state changes; generally, the status is passed directly to the application layer to support hot-plugging. For details, please refer to Device Connection Status Detection.

/**
* @brief  USB attach status change
* @param  dev: USB device instance
* @param  old_status: USB old attach status
* @param  status: USB USB attach status
* @retval void
*/
static void usbd_vendor_status_changed(usb_dev_t *dev, u8 old_status, u8 status)
{
   usbd_vendor_dev_t *cdev = &usbd_vendor_dev;

   UNUSED(dev);

   if (cdev->cb->status_changed) {
      cdev->cb->status_changed(old_status, status);
   }
}

Driver De-initialization and Resource Free

The usbd_vendor_deinit the top-level function used to de-initialize the USB Vendor device class driver. It is called when the device disconnects or when the host switches configuration.

Its core responsibilities are ensuring transfers end safely, cleaning up user-layer resources, unregistering the class driver, and freeing buffer memory for all endpoints.

Wait for Transfer Completion

Before starting de-initialization, first check the busy status is_busy of key endpoints (such as interrupt endpoints). If there are pending transfers, the driver will enter a wait loop to prevent exceptions caused by forcibly freeing resources during data transmission.

Example:

/* 1. Wait for Transfer Completion */
u8 is_busy = ep_intr_in->is_busy;

while (is_busy) {
    usb_os_delay_us(100);
}

Execute User Callback Cleanup

If a user deinit callback function is registered, it is called first. This allows the upper-layer application to clean up its private data or logic. Subsequently, the callback pointer is set to null.

Example:

/* 2. Execute User Deinit Callback */
if (cdev->cb != NULL) {
    if (cdev->cb->deinit != NULL) {
        cdev->cb->deinit();
    }
    cdev->cb = NULL;
}

Unregister USB Class Driver

Call usbd_unregister_class() to remove the Vendor driver from the USB core stack. After this operation, the device will no longer respond to relevant USB events.

Example:

/* 3. Unregister Class Driver */
usbd_unregister_class();

Free Resources

Iterate through all open endpoints, call usb_os_mfree() to release the endpoint buffer memory, and safely reset the pointers to NULL to prevent dangling pointer issues.

Example:

/* 4. Free Endpoint Buffers */
if (ep_bulk_in->xfer_buf != NULL) {
  usb_os_mfree(ep_bulk_in->xfer_buf);
  ep_bulk_in->xfer_buf = NULL;
}

/* Repeat usb_os_mfree for other endpoints... */

API 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 Vendor applications, covering core aspects such as driver initialization, hot-plug management, and custom data loopback processing.

Driver Initialization

Before using the Vendor driver, you need to define the configuration structure and register callback functions, then call the initialization interface to load the USB device core driver and the Vendor class driver.

Step Description:

  • Hardware Configuration: Configure USB speed mode, interrupt priority, etc.

  • Callback Registration: Define the user callback structure usbd_vendor_cb_t and mount processing functions for each stage.

  • Load Core Driver: Call usbd_init() to load the USB core driver.

  • Load Class Driver: Call usbd_vendor_init() to load the vendor class driver.

Example:

static usbd_config_t vendor_cfg = {
  .speed = CONFIG_USBD_VENDOR_SPEED,
  .isr_priority = INT_PRI_MIDDLE,
};

static usbd_vendor_cb_t vendor_cb = {
  .init = vendor_cb_init,                     /* USB init callback */
  .deinit = vendor_cb_deinit,                 /* USB deinit callback */
  .setup = vendor_cb_setup,                   /* USB setup callback */
  .set_config = vendor_cb_set_config,         /* USB set_config callback */
  .bulk_received = vendor_cb_bulk_received,   /* USB BULK OUT received callback */
  .isoc_received = vendor_cb_isoc_received,   /* USB ISOC OUT received callback */
  .intr_received = vendor_cb_intr_received,   /* USB INTR OUT received callback */
  .status_changed = vendor_cb_status_changed, /* USB status change callback */
};

int ret = 0;

/**
* Initialize USB device core driver with configuration.
* param[in] cfg: USB device configuration.
* return 0 on success, non-zero on failure.
*/
ret = usbd_init(&vendor_cfg);
if (ret != HAL_OK) {
  return;
}

/**
* Initialize class driver with application callback handler.
* param[in] cb: Pointer to the user-defined callback structure.
* return 0 on success, non-zero on failure.
*/
ret = usbd_vendor_init(&vendor_cb);
if (ret != HAL_OK) {
  /**
  * Deinitialize USB device core driver.
  * return 0 on success, non-zero on failure.
  */
  usbd_deinit();

  return;
}

Hot-Plug Event Handling

Register the callback function status_changed to monitor changes in the USB connection status (connected/disconnected). For more details, refer to USB Device Connection Status Detection.

Note

It is recommended to use a Semaphore to notify a dedicated task thread for processing, in order to avoid executing time-consuming operations in the context of an interrupt.

Example:

static u8 vendor_attach_status;
static rtos_sema_t vendor_attach_status_changed_sema;

/* USB status change callback */
static usbd_vendor_cb_t vendor_cb = {
  .status_changed = vendor_cb_status_changed
};

/* Callback executed in ISR context */
static void vendor_cb_status_changed(u8 old_status, u8 status)
{
  RTK_LOGS(TAG, RTK_LOG_INFO, "Status change: %d -> %d \n", old_status, status);
  vendor_attach_status = status;
  rtos_sema_give(vendor_attach_status_changed_sema);
}

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

  UNUSED(param);

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

        /* 1. Clean up resources */
        usbd_vendor_deinit();
        /* 2. De-initialize USB core */
        ret = usbd_deinit();
        if (ret != 0) {
          break;
        }

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

        /* 3. Re-initialize for next connection */
        ret = usbd_init(&vendor_cfg);
        if (ret != 0) {
          break;
        }
        ret = usbd_vendor_init(&vendor_cb);
        if (ret != 0) {
          usbd_deinit();
          break;
        }
      } else if (vendor_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
        RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
      } else {
        RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
      }
    }
  }
  RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
  rtos_task_delete(NULL);
}

Vendor request processing

By registering the setup callback function, handle the custom request corresponding to bRequest.

  • During the DATA IN phase of transmission control, in the user’s setup callback function, prepare data and fill it into the send buffer.

  • During the NO DATA/DATA OUT phase of control transmission, host parameters/commands can be parsed and obtained in the user’s setup callback function.

Example:

int vendor_cb_setup(usb_setup_req_t *req, u8 *buf)
{
  switch (req->bRequest) {
    case 0x01: /* Vendor request 1 */
      /* DATA IN stage: prepare send data */
      if ((bmRequestType & USB_REQ_DIR_MASK) == USB_D2H) {
        prepare_send_buffer(buf);
      }
      /* NO DATA/DATA OUT stage: parse host parameters */
      else {
        parse_host_parameters(req, buf);
      }
      break;
    case 0x02: /* Vendor request 2 */
      /* Handle request */
      break;
    default:/* Unknown request */
      return -1;
  }
  return 0;
}

Data Transmission and Reception Processing

After the Vendor device is successfully enumerated, the driver supports two data return processing modes: Synchronous and Asynchronous.

The following logic applies to BULK, INTR, and ISOC endpoint types. Here, BULK transmission is taken as an example.

/* In callback: Echo immediately */
static int vendor_cb_bulk_received(u8 *buf, u32 len)
{
    /* Directly transmit received data back to host */
    return usbd_vendor_transmit_bulk_data(buf, len);
}

Note

For the complete data transmission and reception logic, please refer to the SDK example code: {SDK}/component/example/usb/usbd_vendor/example_usbd_vendor.c.

Driver Deinitialization

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

Example:

/* Deinitialize VENDOR class driver. */
usbd_vendor_deinit();

/* Deinitialize USB device core driver. */
usbd_deinit();

Operation method

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

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

Configuration and Compilation

  • Menuconfig Configuration

    In the amebaxxx_gcc_project directory, type ./menuconfig.py, follow the steps below to select USBD VENDOR, then save and exit.

    - Choose `CONFIG USB --->`:
      [*] Enable USB
          USB Mode (Device) --->
      [*] Vendor
    
  • Compilation and Flashing

    Compile according to the instructions below, and flash the image file to the development board.

    cd amebaxxx_gcc_project
    ./build.py -a usbd_vendor
    

Verification

  • Device Startup

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

    [VND-I] USBD vendor demo start
    
  • Connect to Host

    Connect the development board to a PC (or Ameba Vendor host) using a USB cable.

  • Data communication test

    • Method 1: Use another development board to run the Vendor host solution of this USB protocol stack, and automatically test after connection. See Vendor-Specific Host Solution for details.

    • Method 2: Use a USB testing tool (such as Cypress Control Center) on the PC, write LibUSB/PyUSB scripts, or develop specific host computer software for testing.

Device Descriptor Customization

Overview

USB devices define their functionality through descriptors. The hierarchical structure of USB descriptors is illustrated below:

../../_images/usb_device_descriptors.svg

Where:

  • Each USB device contains exactly 1 Device Descriptor

  • One or multiple Configuration Descriptors (specified by bNumConfigurations), with only 1 active at any time

  • One or multiple Interface Descriptors per configuration (specified by bNumInterfaces), which can operate simultaneously

  • Zero, one or multiple Endpoint Descriptors per interface (specified by bNumEndPoints), which can operate simultaneously

Additional descriptor types include:

  • String Descriptors: Localized text descriptions for device/configuration/interface

  • Device Qualifier Descriptor: Required for dual-speed (HS/FS) devices

  • Other Speed Configuration Descriptor: Required for dual-speed (HS/FS) devices, alternate configuration for different speeds

  • Interface Association Descriptor (IAD): Mandatory for composite devices

  • Class-specific Descriptors: e.g., HID Report Descriptor

Device Descriptor
└─ Identifies basic device information (USB version, class, VID/PID, EP0 MPS, etc)

Configuration Descriptor
└─ Contains total length of the entire configuration(power supply information, interface count, etc)

Device Qualifier Descriptor
└─ Provides device class/EP0 MPS/config count for the other speed (HS↔FS)

Other Speed Configuration Descriptor
└─ Mirrors the configuration at the other speed (interfaces/endpoints/attributes)

String Descriptor
└─ Provides language IDs and textual identifiers (Manufacturer/Product, etc)

Note

Refer to USB specifications for complete descriptor definitions.

Data Structure

Standard host enumeration sequence:

  • Device Descriptor

  • Configuration Descriptor chain (active Configuration Descriptor and all subordinate descriptors)

  • String Descriptors (language ID, serial number, manufacturer, product)

Optional host requests:

  • Device Qualifier Descriptor (for dual-speed devices)

  • Other Speed Configuration chain (for dual-speed devices)

  • Class-specific descriptors (e.g., HID Report Descriptor for HID devices)

  • OS-specific descriptors (e.g., Microsoft OS String Descriptor @ index 0xEE)

Note

Unsupported descriptors should return STALL status without affecting functionality.

Based on standard USB host enumeration behavior, USB devices shall implement the following independent descriptor arrays:

  • Device Descriptor: Contains only the Device Descriptor

  • Configuration Descriptor:

    • One array per configuration

    • Includes the Configuration Descriptor plus all subordinate descriptors, e.g., Interface Descriptors and Endpoint Descriptors

  • String Descriptor

    • Language ID descriptor

    • Individual arrays for each string descriptor

  • Device Qualifier Descriptor: Contains only the Device Qualifier Descriptor

  • Other Speed Configuration Descriptor

    • One array per alternate speed configuration descriptor

    • Includes the Other Speed Configuration Descriptor plus all subordinate descriptors, e.g., Interface Descriptors and Endpoint Descriptors

    • May reuse Configuration Descriptor memory through dynamic descriptor type field replacement

  • Class-Specific Descriptor: Defined per device class specifications

  • Host-Specific Descriptor: Implemented per host requirements and application needs

Descriptor Customization

All the USB descriptors defined in USB device class drivers are open for modification, but for better compatibility, it is recommended to limit changes to:

VID/PID

  • Default: Realtek VID/PID

  • Custom VID: Requires USB-IF membership (https://www.usb.org/members)

  • Custom PID with Realtek VID: Contact Realtek FAE

Note

Using Realtek VID/PID doesn’t imply USB-IF compliance - certification still required.

String Descriptors

String descriptors are optional. The Device Descriptor, Configuration Descriptor, and Interface Descriptor all possess string descriptor index fields. If string descriptors are not supported, the descriptor index is 0. Non-zero index values can be defined along with their corresponding string descriptors:

Device Descriptor
│   iManufacturer            (Manufacturer String Index)
│   iProduct                 (Product String Index)
│   iSerialNumber            (SerialNumber String Index)
│
├── Configuration Descriptor
│   ├── iConfiguration       (Configuration String Index)
│   └── Interface Descriptor
│       └── iInterface       (Interface String Index)
│
└── USB String Table
    ├── String Descriptor    (Index 0, Must be read first)
    ├── String Descriptor    (Index 1)
    ├── String Descriptor    (Index 2)
    ├── String Descriptor    (Index 3)
    │    ...

String Descriptor Structure

The host reads the device descriptor during the initial enumeration stage, which contains three fields: iManufacturer/iProduct/iSerialNumber. The values of these fields are merely index numbers; for devices that do not have corresponding strings, developers only need to set these string index values to 0.

When the device is plugged into a computer, the operating system reads these strings and displays them in the Device Manager or popup windows. For example, if set sequentially to 1, 2, 3:

  • Index 1 (iManufacturer): Manufacturer Name. E.g., “Realtek Semiconductor Corp.”

  • Index 2 (iProduct): Product Name. E.g., “Realtek USB Controller”

  • Index 3 (iSerialNumber): Serial Number. E.g., “00E04C000001”

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 Controller" (Unicode)
│
├── String Descriptor     (Index 3: Pointed by the `iSerialNumber` index)
│   └── "00E04C000001" (Unicode)
│
└── String Descriptor     (Index 4/5/6/...: Optional/Customizable)
  • String Descriptor (Index 0)

    String Descriptor     (Special: Language ID)
    │   bLength           (Descriptor length)
    │   bDescriptorType   (String descriptor type. Fixed to 0x03)
    └── LANGID Code Array
        │ wLANGUAGEID(0)  (First Language ID)
        │ wLANGUAGEID(1)  (Second Language ID)
        │ ...
    
    • Role: It is the “descriptor of string descriptors”. Before retrieving any specific string, the host must first read Index 0 to confirm the language types supported by the device.

    • Specialty: It does not contain a string but rather a list of Language IDs supported by the device. When the host sends a request to get string index 0, it does not mean getting the string at index 0, but rather getting the list of supported Language IDs (LANGID).

    • Parameter Value: Typically, the Language ID is 0x0409 for American English. For Chinese, the Language ID can be set to 0x0804.

    Note

    Even if the developer’s device is used only in a specific country, it is recommended to support 0x0409, as this is the default ID attempted by Windows/Linux.

  • String Descriptor (Indices 1, 2, 3)

    String Descriptor
    │   bLength         (Descriptor length)
    │   bDescriptorType (String descriptor type. Fixed to 0x03)
    └── bString         (UNICODE encoded string)
    

    The bString string must be UNICODE (UTF-16LE) encoded, with each character occupying 2 bytes. For example ‘A’ -> 0x41, 0x00.

    Note

    • The USB specification requires string descriptors to use UNICODE encoding. The device core driver provides the usbd_get_str_desc() function to convert ASCII encoded strings into UNICODE encoded string descriptors, making it convenient for developers to maintain string information using ASCII strings.

    • The protocol stack currently only provides examples of English string descriptors. If support for other language types is needed, please refer to the USB specification to define the required Language ID descriptor and corresponding string descriptors.

  • Other Custom Strings (Indices 4, 5, 6…)

    As long as there is no conflict with the standard, custom strings with indices 4, 5, 6… can be defined.

Endpoint Descriptor

Customizable parameters:

  • bEndpointAddress: Endpoint address.

  • wMaxPacketSize: Maximum packet size. It is generally recommended to set it according to the maximum value defined by the USB specification (not higher than the maximum value supported by the SoC hardware). Unless there are special requirements, it is not recommended to modify it arbitrarily

  • bInterval: Transfer interval.

Note

Default configurations guarantee SoC and USB specification compatibility. Any modifications require:

  • Thorough understanding of SoC limitations, refer to Hardware Features

  • Compliance with USB specifications

Device Connection Status Detection

For self-powered USB devices, when the USB connection status changes, the USB device class driver and application layer must promptly detect these events and implement corresponding handling procedures for hot-plug support and/or power consumption reduction.

The USB device core driver provides following APIs for connection status monitoring:

These APIs require USB register access after USB power on to return valid data, thus they should only be invoked after USB core driver initialization.

For pre-power status detection, external circuitry shall be employed - such as GPIO interrupt monitoring VBUS power events, refer to the reference design of USB insert detection circuit in Hardware Design Guide.

This hybrid hardware/software solution enables detection of the following USB connection status change events:

Event

usbd_get_status

status_changed

VBUS GPIO Interrupt

VBUS Status

Reset (attached to host)

ATTACHED

INIT -> ATTACHED

N/A

ON

Reset (detached from host)

INIT

N/A

N/A

OFF

Reset (attached to charger)

INIT

N/A

N/A

ON

Attach to host

ATTACHED

DETACHED -> ATTACHED

Y (rising edge)

OFF -> ON

Detach from host

DETACHED

ATTACHED -> DETACHED

Y (falling edge)

ON -> OFF

Attach to charger

INIT

or

DETACHED

N/A

Y (rising edge)

OFF -> ON

Detach from charger

INIT

or

DETACHED

N/A

Y (falling edge)

ON -> OFF

Suspend

DETACHED

ATTACHED -> DETACHED

By USB host configuration

By USB host configuration

Resume

ATTACHED

DETACHED -> ATTACHED

By USB host configuration

By USB host configuration

​​Two typical application scenarios:

Software-Only Implementation (Recommended for Non-Power-Sensitive Applications)

When maintaining constant USB power is acceptable, the status_changed API provides sufficient software-based detection capability. Typical workflow includes:

  • Upon USB host disconnection: Terminate USB communications, deinitialize / re-initialize USB device, then await reconnection

  • Upon USB host connection: Resume USB communications

../../_images/usb_status_detection_sw.svg

Hardware-Assisted Implementation (For Power-Critical Applications)

When minimizing power consumption during USB idle states is required, hardware solution such as GPIO-interrupt VBUS monitoring shall be implemented. Typical workflow includes:

  • Upon USB host disconnection or USB charger connection: Terminate USB communications, deinitialize USB device to enter low-power state

  • Upon USB host connection: Initialize USB device and resume communications

../../_images/usb_status_detection_hw.svg