Composite Host Solution

Overview

The USB Composite Device architecture allows a single physical USB device to logically present multiple independent functions by configuring multiple Interface Descriptors or Interface Association Descriptors (IAD).

The USB host protocol stack of the Ameba platform fully supports the enumeration and driver loading of composite devices. The system can intelligently parse the device configuration structure and load the corresponding class drivers for different interfaces. This enables Ameba to handle multiple different types of traffic streams concurrently through a single USB port.

../../../_images/usb_host_composite_overview.svg

Features

  • Supports the following combinations of functions:

    • CDC ACM + CDC ECM

    • HID + UAC

  • Supports USB hot-plug

  • Automatically parse descriptors and adapt to speed modes

Application Scenarios

As a USB host, Ameba can support a combination of multiple interface classes through composite class drivers, enabling interaction with complex USB composite devices on the market. It is suitable for a wide range of IoT and multimedia application scenarios, such as:

  • Cellular Network Access (CDC ECM + CDC ACM): This is the most common composite mode for 4G/LTE Cat.1/Cat.4 modules and USB Dongles. Ameba utilizes the CDC ECM interface for high-speed Internet access, which is widely used for data backhaul in industrial IoT gateways or networking functions in vehicle-mounted terminals. Meanwhile, the CDC ACM interface is used in parallel to transmit AT commands, enabling real-time monitoring and remote control of the module’s signal status.

  • Interactive Audio Devices (UAC + HID): This is a typical application for USB headphones, smart speakers, or game controllers equipped with physical control buttons. Ameba utilizes the UAC interface to handle high-quality voice calls, background music, or game sound effects streaming, while simultaneously responding to physical buttons on the device (such as volume adjustment, mute, media playback control, or controller input) in real-time through the HID interface, achieving a seamless integration of audio experience and user interaction.

Protocol Introduction

The USB Composite Device specification defines a device architecture capable of supporting multiple independent function interfaces via a single physical interface within the USB framework.

This mechanism enables the host to identify and enumerate a single physical device as a collection of logical functions, facilitating parallel processing and independent control across different device classes (e.g., HID, MSC, CDC).

Common implementations include wireless keyboard/mouse receivers, USB headsets with integrated audio cards, and industrial devices combining storage and debugging capabilities.

Descriptor Structure

While adhering to standard USB descriptors (Device and Configuration Descriptors), composite devices define multi-functionality by aggregating multiple Interface Descriptors within the Configuration Descriptor set.

Single-Interface Function Class

When a single interface represents a standalone function, the composite device simply aggregates these functional classes. Typical examples of single-interface function classes include HID and MSC.

Taking a HID Keyboard + HID Mouse composite device as an example, the descriptor topology is illustrated below:

Device Descriptor
|  bDeviceClass: 0xEF (Miscellaneous)
|  bDeviceSubClass: 0x02 (Common Class)
|  bDeviceProtocol: 0x01 (Interface Association Descriptor)
│
└── Configuration Descriptor
   |   bNumInterfaces: 2  (2 Interfaces)
   │   ...
   ├── Interface Descriptor 0 (HID Keyboard)
   │   bInterfaceNumber: 0
   │   bInterfaceClass: 0x03 (HID)
   │   bInterfaceSubClass: 0x01 (Boot Interface)
   │   bInterfaceProtocol: 0x01 (Keyboard)
   │
   └── Interface Descriptor 1 (HID Mouse)
       bInterfaceNumber: 1
       bInterfaceClass: 0x03 (HID)
       bInterfaceSubClass: 0x01 (Boot Interface)
       bInterfaceProtocol: 0x02 (Mouse)

HID Keyboard + HID Mouse composite device compared to individual keyboard/mouse devices, with descriptor characteristics as follows:

Descriptor Level

Single Function Device

Composite Device

Device Descriptor

bDeviceClass defines the type

bDeviceClass is typically 0xEF (Misc) or 0x00 (Defined by Interface)

Configuration Descriptor

1 Configuration

1 Configuration

Interface Descriptor

1 Interface

2 (or more) Interfaces

Endpoint Descriptor

Belong to the single interface

Each interface has independent endpoints

Note

bDeviceClass = 0xEF: This is a standard flag for composite devices, which triggers the host driver to parse the IAD (optional) and multiple interface descriptors in the configuration descriptor, decompose different device functions, and create these logical sub-devices. Then load the keyboard driver for Interface 0 and the mouse driver for Interface 1.

Multi-Interface Function Class

If a logical function requires the use of multiple interfaces to complete, the Interface Association Descriptor (IAD) must be included when using this type of function to associate these interfaces.

Typical multi-interface functional class

  • CDC (Communication Device Class): Typically requires 1 Control Interface (CCI) + 1 Data Interface (DCI).

  • UVC (USB Video Class): Requires 1 Video Control Interface (VC) + 1 or more Video Streaming Interfaces (VS).

  • UAC (USB Audio Class): Requires 1 Audio Control Interface (AC) + 1 or more Audio Stream Interfaces (AS).

Interface Association Descriptor (IAD)

IAD declares to the host that a set of consecutive interfaces that follow closely belong to the same function and should be loaded and managed by the same driver. Without IAD, the host sometimes recognizes them as two separate devices, or the driver fails to load.

Interface Association Descriptor (IAD)
├── bLength                 : 1 byte  → 0x08 (Fixed Length)
├── bDescriptorType         : 1 byte  → 0x0B (IAD type code)
├── bFirstInterface         : 1 byte  → First Interface Number
├── bInterfaceCount         : 1 bytes → Count of associated interfaces
├── bFunctionClass          : 1 byte  → Function Class Code
├── bFunctionSubClass       : 1 byte  → Function SubClass Code
├── bFunctionProtocol       : 1 byte  → Function Protocol Code
└── iFunction               : 1 byte  → Function String Descriptor Index

IAD Usage Example

../../../_images/usb_iad_descriptor.svg
  • Function 1: Class utilize two interfaces. In order for the host to recognize them as a single logical function, it is necessary to use IAD to associate these two interfaces.

  • Function 2: Class utilize a separate interface, eliminating the need for IAD association.

Class-specific request

The USB protocol specification does not define a specific “composite class request”. Their core logic lies in precisely directing Class-Specific Requests to the designated interface through the addressing mechanism in USB standard requests.

In the bmRequestType field of the SETUP packet, the lower 5 bits (Bits 0..4) represent the Recipient.

  • General single-function device: Control requests are typically sent to the “entire device”, meaning the Recipient is set to Device (00000).

  • Composite device: A large number of control requests must be precisely sent to a “specific interface”, which means setting the Recipient to Interface (00001).

The following are the most notable key points regarding the handling of control requests by composite devices:

Field

Value

Meaning

Implementation in Composite Devices

bmRequestType

0x21 / 0xA1

Class Request, Recipient=Interface

Most common scenario. Examples include setting CDC baud rates or controlling HID keyboard LEDs.

wIndex

Interface Number

Interface Number

When the Recipient is Interface, wIndex must specify the target interface index (e.g., 0, 1, 2). The driver routes the request to the corresponding function driver based on this value.

Class Driver

A composite device refers to a device that incorporates multiple independent functions within a single physical USB device. The primary responsibility of the composite host driver is to accurately parse the device’s configuration descriptor, load the corresponding sub-drivers for each function, and enumerate a single physical device as a collection of multiple logical functions. Each function operates independently without interfering with each other (for example, sending AT commands while transmitting network data, or receiving key inputs while playing music).

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

../../../_images/usb_host_composite_driver_arch.png

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

  • Host-class driver callback API: The composite class driver interacts with the underlying USB Core by registering a standard usbh_class_driver_t structure.

  • Application-oriented callback API: Provides asynchronous event notifications (such as connection, disconnection, and data reception) to upper-layer applications through the usbh_composite_cb_t, usbh_user_cb_t, and sub-function-driven callback functions usbh_composite_function_class_xx_usr_cb_t registered at initialization in the application layer.

  • Application-oriented API: Provides functional interfaces for the application layer to call. For example, driver loading/unloading, scheduling data transmission, etc.

Application Callback API

The driver provides callback interface definitions for the application layer. These are implemented by the application layer to allow application code to respond to USB events and handle business logic.

  • Host Application Layer Callback APIs

    usbh_user_cb_t is the callback for the entire host.

  • Composite Application Layer Callback APIs

    usbh_composite_cb_t is the callback for the top-level composite driver.

  • Sub-function Class Application Layer Callback APIs

    These are custom callbacks for each sub-function and are generally optional to implement:

    typedef struct {
        int(* init)(void);
        int(* deinit)(void);
        int(* attach)(void);
        int(* detach)(void);
        int(* setup)(void);
        int(* received)(u8 *buf, u32 len);
        void(* transmitted)(u8 status);
    } usbh_composite_function_class_xx_usr_cb_t;
    

    API

    Description

    init

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

    deinit

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

    attach

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

    detach

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

    setup

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

    receive

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

    transmit

    Called when the class driver OUT data transfer is complete; used by the application layer to obtain the OUT transfer status.

HID + UAC sub-function class application layer callback example:

 /* HID user Callback Interface. */
 typedef struct {
     int(* init)(void);                               /**< Callback when HID driver is initialized */
     int(* deinit)(void);                             /**< Callback when HID driver is de-initialized */
     int(* attach)(void);                             /**< Callback when a HID device is attached */
     int(* detach)(void);                             /**< Callback when a HID device is detached */
     int(* setup)(void);                              /**< Callback during the Setup stage of a control transfer */
     int(* report)(usbh_composite_hid_event_t *event);/**< Callback when a HID event report is received */
 } usbh_composite_hid_usr_cb_t;

/* UAC User Callback Interface. */
 typedef struct {
     int(* init)(void);                                /**< Callback when UAC driver is initialized */
     int(* deinit)(void);                              /**< Callback when UAC driver is de-initialized */
     int(* attach)(void);                              /**< Callback when a UAC device is attached */
     int(* detach)(void);                              /**< Callback when a UAC device is detached */
     int(* setup)(void);                               /**< Callback during the Setup stage of a control transfer */
     int(* isoc_transmitted)(usbh_urb_state_t state);  /**< Callback when isochronous OUT (Play) transfer completes */
     int(* isoc_received)(u8 *buf, u32 len);           /**< Callback when isochronous IN (Record) data is received */
 } usbh_composite_uac_usr_cb_t;

Composite Class Driver Implementation

This section primarily defines the composite host and the implementation of class driver callbacks.

  • Sub-function Class Driver

    Each sub-function (e.g., CDC ACM, UAC, HID) is an independent class driver. For details, please refer to the corresponding chapters of the sub-function solutions.

    • Independent Driver Structure: Each sub-driver defines a standard usbh_class_driver_t structure to implement its own business logic.

    • Independent Resource Management: Each sub-driver is responsible for parsing the corresponding interface and managing its own pipes, data buffers, and data transmission/reception processing.

  • Composite Class Driver

    • The composite class driver needs to define a standard usbh_class_driver_t structure. This serves as a unified entry point registered with the USB Core. The composite host driver will iterate through the callback functions corresponding to each sub-function class driver.

    • The composite class driver defines a standard usbh_composite_host_t structure. This is the core of the composite host instance, used to manage all sub-function class drivers.

    /* Composite Device */
    static usbh_composite_host_t usbh_composite_host;
    
    /* Composite Class Driver */
    static const usbh_class_driver_t usbh_composite_driver = {
        .id_table = composite_devs,                 /* List of supported device IDs; the core layer uses this table to match inserted devices to decide whether to load this driver */
        .attach = usbh_composite_cb_attach,         /* Called after device connection and successful matching */
        .detach = usbh_composite_cb_detach,         /* Called when the device is disconnected */
        .setup = usbh_composite_cb_setup,           /* Called when enumeration is complete and the class request phase begins; used to send class-specific standard control requests and complete necessary configurations before the device enters the data transfer state */
        .process = usbh_composite_cb_process,       /* Core state machine processing function after the class driver is ready */
        .sof = usbh_composite_cb_sof,               /* Called during SOF interrupts; used for processing logic with strict timing requirements */
        .completed = usbh_composite_cb_completed,   /* Called when a transfer on a pipe is completed */
    };
    

The following takes HID + UAC as an example to introduce the host class driver instance implementation:

/* Composite Host structure. */
 typedef struct {
     usbh_class_driver_t *hid;
     usbh_class_driver_t *uac;
     usbh_composite_cb_t *cb;
     usb_host_t *host;
 } usbh_composite_host_t;

 /* Composite Host */
 static usbh_composite_host_t usbh_composite_host;

 /* Composite Device ID */
 static const usbh_dev_id_t composite_devs[] = {
     {
         .mMatchFlags = USBH_DEV_ID_MATCH_ITF_INFO,
         .bInterfaceClass = USB_UAC1_CLASS_CODE,
         .bInterfaceSubClass = USB_UAC1_SUBCLASS_AUDIOSTREAMING,
         .bInterfaceProtocol = 0x00,
     },
     {
     },
 };

 /* Composite Class Driver */
 static usbh_class_driver_t usbh_composite_driver = {
     .id_table = composite_devs,
     .attach = usbh_composite_hid_uac_cb_attach,
     .detach = usbh_composite_hid_uac_cb_detach,
     .setup = usbh_composite_hid_uac_cb_setup,
     .process = usbh_composite_hid_uac_cb_process,
     .sof = usbh_composite_hid_uac_cb_sof,
     .completed = usbh_composite_hid_uac_cb_completed,
 };

/********************** Function 1: HID Class *********************/
/* HID host structure. */
typedef struct {
   usbh_composite_host_t *driver;        /**< Pointer to the parent composite host structure. */
   usbh_composite_hid_usr_cb_t *cb;      /**< Pointer to the user-registered callback structure. */
   usbh_pipe_t pipe;                     /**< Interrupt IN pipe handler. */
 //....
} usbh_composite_hid_t;
/* HID Host */
static usbh_composite_hid_t usbh_composite_hid;

/* HID Class Driver */
 const usbh_class_driver_t usbh_composite_hid_driver = {
     .attach = usbh_composite_hid_cb_attach,
     .detach = usbh_composite_hid_cb_detach,
     .setup = usbh_composite_hid_cb_setup,
     .process = usbh_composite_hid_cb_process,
     .sof = usbh_composite_hid_cb_sof,
 };

/********************** Function 2: UAC Class *********************/
/* UAC host structure. */
 typedef struct {
     usbh_uac_ac_itf_info_t ac_isoc_desc;  /**< Audio Control interface topology (Feature Units + Terminals) */
     usbh_uac_channel_t isoc_out;          /**< OUT channel state (Playback/Speaker) */
     usbh_uac_channel_t isoc_in;           /**< IN channel state (Record/Microphone) */
     usbh_composite_uac_usr_cb_t *cb;      /**< User callback structure registered via usbh_composite_uac_init */
     usbh_composite_host_t *driver;        /**< Pointer to the parent USB Host Composite handle */
     //...
 } usbh_composite_uac_t;
/* UAC Host */
static usbh_composite_uac_t usbh_composite_uac;

/* UAC Class Driver */
 const usbh_class_driver_t usbh_composite_uac_driver = {
     .attach = usbh_composite_uac_cb_attach,
     .detach = usbh_composite_uac_cb_detach,
     .setup = usbh_composite_uac_cb_setup,
     .process = usbh_composite_uac_cb_process,
     .sof = usbh_composite_uac_cb_sof,
     .completed = usbh_composite_uac_cb_completed,
 };

Loading and Unloading Class Driver

The application layer controls the lifecycle of the entire Composite class driver through the following two main functions:

  • usbh_composite_init(): Initialization Function

    • Receives parameters passed by the application layer, such as the callback set for each sub-function.

    • Calls the driver load function for each sub-function.

    • Links the sub-function driver instance to the usbh_composite_host_t structure.

    • Finally, calls usbh_register_class() to register the composite host driver with the USB Core to make it effective.

usbh_composite_deinit(): De-initialization Function

  • Calls usbh_unregister_class() to unregister the composite host driver from the USB Core.

  • Iterates through the unload function of each sub-function to release all resources.

HID + UAC Example

/**
* @brief  Init composite class
* @param  cb: User callback
* @retval Status
*/
int usbh_composite_init(usbh_composite_hid_usr_cb_t *hid_cb, usbh_composite_uac_usr_cb_t *uac_cb)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((hid_cb == NULL) || (uac_cb == NULL)) {
        ret = HAL_ERR_PARA;
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user CB\n");
        return ret;
    }

    ret = usbh_composite_hid_init(chost, hid_cb);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init HID itf fail: %d\n", ret);
        return ret;
    }
    chost->hid = (usbh_class_driver_t *)&usbh_composite_hid_driver;

    ret = usbh_composite_uac_init(chost, uac_cb);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init UAC itf fail: %d\n", ret);
        usbh_composite_hid_deinit();
        return ret;
    }
    chost->uac = (usbh_class_driver_t *)&usbh_composite_uac_driver;
    usbh_register_class(&usbh_composite_driver);

    return HAL_OK;
}

/**
* @brief  Deinit composite class
* @retval Status
*/
int usbh_composite_deinit(void)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    usbh_unregister_class(&usbh_composite_driver);

    usbh_composite_deinit_uac_class();
    usbh_composite_deinit_hid_class();

    chost->cb = NULL;

    return HAL_OK;
}

Connection and Disconnection Handling

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

  • Device Connection

    When the Core layer enumerates a Composite device matching usbh_dev_id_t, the attach callback is called, which internally iterates through the attach callback function of each sub-function.

    The main responsibilities of each sub-function driver are:

    • Match the sub-function’s usbh_dev_id_t to obtain and parse the corresponding interface descriptor.

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

    • Initialize the transfers corresponding to each pipe.

    • Call the user attach callback of the sub-function driver to notify the application layer.

  • Device Disconnection

    When the device is unplugged, the detach callback is called, which internally iterates through the detach callback function of each sub-function.

    The main responsibilities of each sub-function driver are:

    • Reset the sub-function driver state machine to IDLE.

    • Call usbh_close_pipe() to close all opened pipes.

    • Call the user detach callback of the sub-function driver to notify the application layer.

HID + UAC Example

/**
* @brief  Attach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_attach(usb_host_t *host)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    chost->host = host;

    if ((chost->hid != NULL) && (chost->hid->attach != NULL)) {
        ret = chost->hid->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_hid_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support hid\n");
        }
    }

    if ((chost->uac != NULL) && (chost->uac->attach)) {
        ret = chost->uac->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_uac_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support uac\n");
        }
    }

    return HAL_OK;
}

/**
* @brief  Detach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_detach(usb_host_t *host)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((chost->hid != NULL) && (chost->hid->detach != NULL)) {
        chost->hid->detach(host);
    }

    if ((chost->uac != NULL) && (chost->uac->detach)) {
        chost->uac->detach(host);
    }

    return HAL_OK;
}

Class Driver State Machine

Unlike the device side, which passively responds to requests, the host driver needs to actively maintain the device state. The USB host class driver uses a state machine to manage data transfer processing. The driver state is updated by API calls and underlying interrupt feedback. The process callback is the core state machine processing function for the host-side composite class driver. It internally iterates through the process callback function of each sub-function, and each sub-function independently maintains its own state machine.

HID + UAC Example

/**
* @brief  State machine handling callback
* @param  host: Host handle
* @param  msg: Event message
* @retval Status
*/
static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
{
    usbh_composite_host_t *chost = &usbh_composite_host;
    int ret = HAL_BUSY;

    /* If the pocess has handle the msg, it return HAL_OK, else return HAL_BUSY */
    if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
        ret = chost->hid->process(host, msg);
    }

    if ((ret != HAL_OK) && (chost->uac != NULL) && (chost->uac->process != NULL)) {
        ret = chost->uac->process(host, msg);
    }

    return ret;
}

Data Transfer Processing

Data transfer is scheduled and handled by each sub-function class driver. For detailed data transmission and reception processes, please refer to the corresponding chapters of the sub-function solutions.

HID + UAC Example

  • Audio Playback: Please refer to the Class Driver chapter of the Audio Host solution.

  • Button Event Reporting:
     /*************** HID + UAC Composite Class Driver *********************/
     /* USB Standard Host driver */
     static usbh_class_driver_t usbh_composite_driver = {
         .process = usbh_composite_hid_uac_cb_process,
     };
    
     static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_host_t *chost = &usbh_composite_host;
         int ret = HAL_BUSY;
    
          //1. HID process
         if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
             ret = chost->hid->process(host, msg);
         }
    
         //2. UAC process
         return ret;
     }
    
     /*************** HID Sub-funtion Class Driver *************************/
     /* HID user Callback Interface. */
     typedef struct {
         int(* report)(usbh_composite_hid_event_t *event);
     } usbh_composite_hid_usr_cb_t;
    
     /* USB Standard Class Driver */
     const usbh_class_driver_t usbh_composite_hid_driver = {
         .process = usbh_composite_hid_cb_process,
         .sof = usbh_composite_hid_cb_sof,
     };
    
     static int usbh_composite_hid_cb_sof(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
    
         //Regular reporting
         if (usbh_get_elapsed_ticks(host, pipe->tick) > UBSH_COMPOSITE_HID_TRIGGER_MAX_CNT) {
             usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
         }
    
         return HAL_OK;
     }
     /* Phase 1: Callback Process */
     static int usbh_composite_hid_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_event_t event;
         event.d32 = msg;
    
         // 1. Check if the event belongs to the HID pipe
         if ((hid->hid_ctrl_buf) && (event.msg.pipe_num == pipe->pipe_num)) {
             // 2. Clear transfer flag
             hid->next_xfer = 0;
             // 3. Handle Interrupt IN transfer state machine
             usbh_composite_hid_in_process(host);
             // 4. If transfer started/updated, notify the main host driver
             if (hid->next_xfer) {
                 usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
             }
             return HAL_OK;
         }
         return HAL_BUSY;
     }
    
     /* Phase 2: IN Transfer Process (Producer) */
     static void usbh_composite_hid_in_process(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_urb_state_t urb_state = USBH_URB_IDLE;
         u32 len;
    
         /* Handle Transfer State Machine */
         switch (pipe->xfer_state) {
         // State A: Ready to start new transfer
         case USBH_EP_XFER_START:
             if (usbh_get_elapsed_ticks(host, pipe->tick) > pipe->ep_interval) {
                 pipe->tick = usbh_get_tick(host);
                 pipe->xfer_state = USBH_EP_XFER_BUSY;
                 pipe->xfer_len = pipe->ep_mps;
                 usbh_transfer_data(host, pipe);//Start USB Hardware transfer
                 hid->next_xfer = 1;            //Flag to notify main state machine
             }
             break;
         // State B: Waiting for transfer completion
         case USBH_EP_XFER_BUSY:
             urb_state = usbh_get_urb_state(host, pipe);
             /* Check if hardware finished transfer */
             if (urb_state == USBH_URB_DONE) {
                 len = usbh_get_last_transfer_size(host, pipe);
                 /* Save raw HID data to ring buffer */
                 usb_ringbuf_add_tail(&(hid->report_msg), pipe->xfer_buf, len, 1);
                 pipe->xfer_state = USBH_EP_XFER_START;  //Set for next transfer
             }
             break;
         default:
             break;
         }
     }
    
     /* Phase 3: Parsing Thread (Consumer) */
     static void usbh_composite_hid_msg_parse_thread(void *param)
     {
         UNUSED(param);
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usb_ringbuf_manager_t *handle = &(hid->report_msg);
         usbh_composite_hid_event_t *event = &(hid->report_event);
         u8 report_msg[10];
         u32 read_cnt;
    
         while (hid->parse_task_exit == 0) {
             // 1. Try to read data from ring buffer
             read_cnt = usb_ringbuf_remove_head(handle, report_msg, 10, NULL);
             if (read_cnt) {
                 if (hid->hid_ctrl) {
                     // 2. Parse the raw bytes into a meaningful event
                     ret = usbh_composite_hid_parse_hid_report(report_msg, len, &(hid->vol_caps));
                     if (ret != HAL_OK) {
                         return;
                     }
    
                     // 3. Trigger User Callback
                     if ((hid->cb != NULL) && (hid->cb->report != NULL)) {
                         hid->cb->report(event);
                     }
    
                 }
             } else {
                 rtos_time_delay_ms(50);
             }
         }
     }
    
    /****************** Application *************************/
     static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
         .report = usbh_hid_cb_report,
     };
    
     static int usbh_hid_cb_report(usbh_composite_hid_event_t *event)
     {
         switch (event->type) {
         case VOLUME_EVENT_CONSUMER_UP:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Up (Consumer) ===\n");
             break;
         case VOLUME_EVENT_CONSUMER_DOWN:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Down (Consumer) ===\n");
             break;
         //Other event
         default:
             break;
         }
         return HAL_OK;
     }
    

API Reference

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

Application Example

This section takes USB Composite (HID + UAC) as an example to introduce the complete application implementation and the method of running example application.

Application Design

This section provides a detailed introduction to the complete development and design process of composite host drivers, covering driver initialization, hotplug management, and resource release.

Driver Initialization

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

Step Description:

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

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

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

  • Class Driver Initialization: Call usbh_composite_init() to initialize the Composite class driver.

HID + UAC Example

static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_FULL,
    .ext_intr_enable = USBH_SOF_INTR,
    .isr_priority = INT_PRI_MIDDLE,
    .main_task_priority = USBH_UAC_MAIN_THREAD_PRIORITY,
    .tick_source = USBH_SOF_TICK,
};

static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .init = usbh_uac_cb_init,
    .deinit = usbh_uac_cb_deinit,
    .attach = usbh_uac_cb_attach,
    .detach = usbh_uac_cb_detach,
    .setup = usbh_uac_cb_setup,
    .isoc_transmitted = usbh_uac_cb_isoc_transmitted,
    .isoc_in_frm_cnt = USBH_UAC_FRAME_CNT,
    .isoc_out_frm_cnt = USBH_UAC_FRAME_CNT,
};

static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
    .report = usbh_hid_cb_report,
};

static usbh_user_cb_t usbh_usr_cb = {
    .process = usbh_uac_cb_process
};

int ret = 0;

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

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

Hot-Plug Event Handling

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

Processing Logic:

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

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

HID + UAC Example

static rtos_sema_t usbh_uac_detach_sema;

/* USB detach callback */
static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .detach = usbh_uac_detach_sema,
};

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

static void usbh_uac_hotplug_thread(void *param)
{
    int ret = 0;

    UNUSED(param);

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

            /* 1. Clean up resources */
            usbh_composite_deinit();
            /* 2. De-initialize USB core */
            usbh_deinit();
            rtos_time_delay_ms(10);
            RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());
            /* 3. Re-initialize for next connection */
            ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
            if (ret != HAL_OK) {
                break;
            }

            ret = usbh_composite_init(&usbh_hid_cfg, &usbh_uac_cfg);
            if (ret < 0) {
                usbh_deinit();
                break;
            }
        }
    }
    RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
    rtos_task_delete(NULL);
}

Data Transfer Processing

After the device is connected, the application layer can call application-oriented APIs to perform parameter configuration, data transmission scheduling, and other tasks. The device status and transmission results are obtained through the callback functions registered by the application layer during initialization, including usbh_composite_cb_t, usbh_user_cb_t, and the sub-function driver’s callback function, namely usbh_composite_function_class_xx_usr_cb_t. For detailed data transmission and reception processes, please refer to the corresponding chapters of the sub-function solution.

Driver Deinitialization

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

HID + UAC Example

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

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

Operation method

This example demonstrates how to configure the Ameba development board as a host with both USB HID (Human Interface Device) and UAC (Audio Device Class) functions through a composite host protocol stack.

When the development board is connected to a standard USB headset, the system will recognize two separate logical devices. The development board will send audio data to the headset, and if a button on the headset is pressed, host will recognize the command.

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

Configuration and Compilation

  • Compilation and Flashing

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

    # Initialize environment (required for every new terminal)
    source env.sh or env.bat(Windows system)
    
    # Select Target SoC (replace xxx with your specific SoCs)
    ameba.py soc xxx
    
    ameba.py build -a usbh_composite_hid_uac -p
    
  • Confirmation of Menuconfig configuration

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

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] Composite
                Select Composite Class (HID + UAC)  --->
                    (X) HID + UAC
                        Select UAC Version (UAC 1.0)  --->
    

Verification

  • Host Startup

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

    [COMP-I] USBH UAC&HID composite demo start
    
  • Connect to Device

    Connect the development board to a USB Type-C wired headphone using a USB cable.

  • Auto Play Test

    The Ameba development board will recognize the headphones and automatically perform an audio playback test.

  • Audio Recording Test

    If the headset includes a microphone, connect a speaker to the board’s audio output. Speak into the microphone, the captured audio will be played back in real time through the speaker.

  • Volume Control Test

    If the headphones also have a volume control button, when press the button, the volume change information will be output on the serial port:

    ```
    Volume Up
    
    Volume Down
    ```