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.
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 |
|
|
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
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.).
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_tstructure.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 functionsusbh_composite_function_class_xx_usr_cb_tregistered 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_tis the callback for the entire host.Composite Application Layer Callback APIs
usbh_composite_cb_tis 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_tstructure 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_tstructure. 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_tstructure. 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 FunctionReceives 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_tstructure.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, theattachcallback is called, which internally iterates through theattachcallback function of each sub-function.The main responsibilities of each sub-function driver are:
Match the sub-function’s
usbh_dev_id_tto 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
attachcallback of the sub-function driver to notify the application layer.
Device Disconnection
When the device is unplugged, the
detachcallback is called, which internally iterates through thedetachcallback 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
detachcallback 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_tandusbh_composite_fucntion_xx_usr_cbmount 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 ```