The Abstract Control Model (ACM) under the USB Communication Device Class (CDC) is based on the USB Bulk Transfer mechanism and defines a set of universal data interaction standards.
In Host mode, the system utilizes this protocol to establish a high-speed data channel with external devices, enabling bidirectional transparent transmission of raw data streams.
The Ameba platform integrates a USB-IF compliant CDC ACM protocol stack, capable of efficient data transparent transmission capabilities.
As a USB CDC ACM host, Ameba can establish a point-to-point communication link with devices through the USB interface, and combine its wireless capability to expand various data transparent transmission applications, such as:
Wireless Data Transparent Transfer Bridge: Ameba, as a gateway, can be mounted with an external proprietary protocol dongle or acquisition module to transparently forward the raw data stream received on the USB side to a Wi-Fi or Bluetooth network, achieving seamless bridging and protocol conversion of wired data streams to wireless networks.
Empowering Traditional Industrial Equipment: Ameba connects to PLCs, CNC machines, or precision instruments via a USB-to-serial adapter, enabling protocol conversion and cloud access for traditional RS-232/485 devices. This facilitates the digital and intelligent upgrade of old production lines at a low cost.
High-speed data acquisition: Ameba connects to high-frequency sensors or acquisition cards equipped with USB interfaces, utilizing a bulk transfer mechanism to process large volumes of raw data in real-time. This breaks through the bandwidth bottleneck of traditional low-speed interfaces, ensuring data integrity and real-time performance in high-frequency sampling scenarios.
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”.
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:
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.
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 。
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.
This section details the internal implementation of the CDC ACM host driver, including the driver architecture, support for class-specific requests, and the allocation scheme for transmission resources.
The CDC ACM host protocol stack adopts a layered architecture design, decoupling the USB transport layer from the upper-layer data stream. From top to bottom, the driver architecture is divided into the following layers:
Application Layer
Contains business logic and data processing callbacks.
Business Implementation: Calls interfaces and processes transmitted/received data based on actual scenarios (such as wireless bridges, data acquisition).
Data Buffering: The application layer directly acquires data packets uploaded from the lower layer by registering user callback functions.
CDC ACM Class Driver
Strictly adhering to the USB CDC ACM protocol specification, this layer implements the core business logic for the interaction between the host and the ACM device. Its main responsibilities include:
Enumeration and Interface Binding: Responsible for identifying the communication interface class (0x02, CDC_COMM) and data interface class (0x0A, CDC_DATA), automatically parsing interface descriptors, and applying for corresponding pipe resources.
Transmission Parameter Negotiation: During the initial connection phase, it is responsible for sending requests such as SetLineCoding to initialize link parameters, ensuring configuration synchronization between the host and device.
Notification Event Handling: Listens for status notifications reported by the device (such as SerialState) via interrupt transfers and triggers corresponding event callbacks.
Data Transmission: Encapsulates data streams from the upper layer into USB Bulk Transfer Requests (URB) and passes received USB data packets to the application layer via callback functions.
USB Core Driver
Responds to hardware interrupts in real-time, responsible for handling USB standard enumeration, transfer management, and underlying physical data transmission scheduling.
The CDC ACM class driver plays a connecting role in the system architecture. Its implementation logic mainly revolves around the following three core interaction interfaces:
Host Class Driver Callback API: The class driver interacts with the underlying USB Core by defining and registering a standard usbh_class_driver_t structure.
Application-Facing Callback API: The class driver provides an asynchronous event notification mechanism to the upper-layer application via the usbh_cdc_acm_cb_t callback structure.
Application-Facing API: After the application layer calls these APIs, the driver switches the internal state machine status and initiates data transmission scheduling.
The figure above illustrates the execution flow of callback functions at different levels and does not list all calling scenarios.
Class Driver Callback Functions
The class driver needs to define a standard usbh_class_driver_t structure.
This serves as a unified entry point registered with the USB Core and is the primary method for the Core layer to notify the Class layer that “an event has occurred.”
id_table: Supported device ID list. The core layer uses this table to match with the inserted device to determine whether to load this driver.
attach: Called after device connection and successful matching.
detach: Called when the device is disconnected.
setup: Called when enumeration is complete and the class request phase begins. Used to send class-specific standard control requests to complete necessary configuration before the device enters the data transfer state.
process: The state machine processing function executed after the class driver is ready.
sof: Called during SOF interrupts. Used to handle logic with strict timing requirements, primarily for isochronous transfers.
completed: Called when a transfer on a pipe completes.
Application Layer Callback Functions
The CDC ACM class driver application layer callback structure usbh_cdc_acm_cb_t is implemented by the user layer. Generally, the following APIs can be implemented:
API
Description
init
Called during class driver initialization to initialize application-related resources.
deinit
Called during class driver de-initialization to release application-related resources.
attach
Called when the class driver executes the attach callback to handle device connection
events at the application layer.
detach
Called when the class driver executes the detach callback to handle device disconnection
events at the application layer.
setup
Called when the class driver executes the setup callback, indicating that the application
layer class driver is ready for data transfer.
receive
Called when the class driver receives BULK IN data, used by the application layer to
process data reported by the device.
transmit
Called when the class driver BULK OUT data transfer completes, used by the application
layer to obtain the OUT transfer status.
notify
Called when the class driver receives INTR IN data, used by the application layer to
process data reported by the device.
These two functions are responsible for the allocation and release of memory resources, as well as the registration and deregistration of the class driver to the USB core.
usbh_cdc_acm_init() is the top-level function used to load the CDC ACM host class driver. It primarily completes the following tasks:
Saves the user-provided callback functions and calls the user init callback.
Allocates memory for line_coding (current device parameters) and user_line_coding (user expected parameters).
Calls usbh_register_class() to register the CDC ACM class driver with the USB host core.
Example:
intusbh_cdc_acm_init(usbh_cdc_acm_cb_t*cb){/* 1. Save the user callback and call the user's ``init`` callback */if(cb!=NULL){cdc->cb=cb;if(cb->init!=NULL){ret=cb->init();if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"User init err %d\n",ret);returnret;}}}/* 2. Allocate memory */cdc->line_coding=(usb_cdc_line_coding_t*)usb_os_malloc(sizeof(usb_cdc_line_coding_t));/* ... *//* 3. Register class driver*/usbh_register_class(&usbh_cdc_acm_driver);returnret;}
usbh_cdc_acm_deinit() is the top-level function used to unload the CDC ACM host class driver, responsible for cleaning up resources:
Calls the user deinit callback to inform the application layer.
If the device is in a connected state, forcibly closes all open pipes (Interrupt IN, Bulk IN, Bulk OUT).
Frees the previously allocated memory.
Deregisters the class driver.
Example:
intusbh_cdc_acm_deinit(void){/* 1. Call the user's ``deinit`` callback */if((cdc->cb!=NULL)&&(cdc->cb->deinit!=NULL)){cdc->cb->deinit();}/* 2. If the device is connected, forcefully close all open pipes */if((host!=NULL)&&(host->connect_state==USBH_STATE_SETUP)){usbh_close_pipe(host,&cdc->intr_in);/* Close other pipes */}/* 3. Free memory */if(cdc->line_coding!=NULL){usb_os_mfree(cdc->line_coding);}/* ... *//* 4. Unregister class driver*/usbh_unregister_class(&usbh_cdc_acm_driver);returnret;}
When the USB core detects the insertion or removal of a device matching the CDC ACM class, the following callback functions are called.
usbh_cdc_acm_attach is a key step in device enumeration, responsible for parsing interface descriptors and allocating pipe resources:
Locate Communication Interface (Comm Interface): If found, parses its interrupt endpoint and opens the Interrupt IN pipe.
Locate Data Interface (Data Interface): If found, parses its bulk endpoints and opens Bulk IN and Bulk OUT pipes.
Initialize the state machine to IDLE state.
Call the user attach callback to inform the application layer of the connection status.
Example:
staticintusbh_cdc_acm_attach(usb_host_t*host){/* 1. Get the Communication interface and open the interrupt pipe */dev_id.bInterfaceClass=USB_CDC_COMM_INTERFACE_CLASS_CODE;itf_data=usbh_get_interface_descriptor(host,&dev_id);if(itf_data){usbh_open_pipe(host,intr_in,ep_desc);}/* 2. Get the Data interface and open the Bulk IN & Bulk OUT pipe */dev_id.bInterfaceClass=USB_CDC_DATA_INTERFACE_CLASS_CODE;itf_data=usbh_get_interface_descriptor(host,&dev_id);/* Open bulk_in / bulk_out pipes *//* 3. Initialize the state machine */cdc->state=USBH_CDC_ACM_STATE_IDLE;/* 4. Notify the user layer */if((cdc->cb!=NULL)&&(cdc->cb->attach!=NULL)){cdc->cb->attach();}returnHAL_OK;}
usbh_cdc_acm_detach is called when the device is removed. It is responsible for notifying the upper-layer application that the device has been removed and closing all relevant pipes to release hardware channels.
Example:
staticintusbh_cdc_acm_detach(usb_host_t*host){/* 1. Notify the user layer */if((cdc->cb!=NULL)&&(cdc->cb->detach!=NULL)){cdc->cb->detach();}/* 2. Close pipes */if(intr_in->pipe_num){usbh_close_pipe(host,intr_in);}/* Close bulk_in / bulk_out pipes */returnHAL_OK;}
The usbh_cdc_acm_process callback function is the core state machine handler for the host-side CDC ACM class.
Unlike the passive response mechanism on the device side, the host-side driver must actively maintain the device state.
Its core responsibilities include maintaining the class driver lifecycle states (e.g., IDLE, TRANSFER, ERROR), handling the setting and verification of Line Coding, and distributing data transmission tasks.
State Machine Management and Scheduling
usbh_cdc_acm_process manages the scheduling of control transfers (such as baud rate configuration) and bulk/interrupt data transfers based on the current class driver state.
State Enum
Description
Key Actions
IDLE
Idle state
Waits for user commands or data transmission requests.
SET_LINE_CODING
Send baud rate setting request
Sends a SET_LINE_CODING request (EP0). Upon success, it automatically transitions to GET_LINE_CODING for read-back verification.
GET_LINE_CODING
Get/Verify baud rate
Sends a GET_LINE_CODING request. After acquisition, it compares the value with the user setting. If consistent, it triggers the user line_coding_changed callback.
SET_CONTROL_LINE_STATE
Control handshake signals
Configures RTS/DTR level states.
TRANSFER
Data transferring
Distributes tasks to specific TX/RX processing functions based on the pipe number.
ERROR
Error state
Attempts to clear endpoint features (Clear Feature) to restore communication.
Line Coding Configuration (Set & Verify)
The CDC ACM host driver implements a “Set-Readback-Verify” closed-loop process to ensure the device has correctly accepted parameters like the baud rate.
Set: Sends the SET_LINE_CODING request. Upon success, the state automatically transitions to GET_LINE_CODING.
Readback (Get): Sends the GET_LINE_CODING request to read the current configuration from the device, ensuring serial parameters are synchronized.
Verify: Compares the configuration requested by the user with the actual configuration returned by the device. If they match, the configuration is successful. The line_coding_changed callback is called to notify the application layer of the successful modification.
Example:
caseUSBH_CDC_ACM_STATE_SET_LINE_CODING:/* 1. Send a setting request */req_status=usbh_cdc_acm_process_set_line_coding(host,cdc->user_line_coding);if(req_status==HAL_OK){/* 2. After successful setup, immediately goto to get the setting for verification */cdc->state=USBH_CDC_ACM_STATE_GET_LINE_CODING;}/* ... error handling ... */break;caseUSBH_CDC_ACM_STATE_GET_LINE_CODING:req_status=usbh_cdc_acm_process_get_line_coding(host,cdc->line_coding);if(req_status==HAL_OK){cdc->state=USBH_CDC_ACM_STATE_IDLE;/* 3. Verify whether the readback data is consistent with the user settings */if((cdc->line_coding->b.dwDteRate==cdc->user_line_coding->b.dwDteRate)&&/* ... compare other fields ... */){/* 4. Config Match, notify the application layer that the setting has been changed. */if((cdc->cb!=NULL)&&(cdc->cb->line_coding_changed!=NULL)){cdc->cb->line_coding_changed(cdc->line_coding);}}}break;
Transfer Processing Distribution
When in the TRANSFER state, processing is distributed to specific TX (transmit), RX (receive), or INTR (interrupt) handler functions based on the pipe number (Pipe ID) that triggered the event.
Example:
caseUSBH_CDC_ACM_STATE_TRANSFER:/* Distribute transmission tasks according to pipe numbers */if(event.msg.pipe_num==cdc->bulk_out.pipe_num){usbh_cdc_acm_process_tx(host);// Handle BULK OUT transfer}elseif(event.msg.pipe_num==cdc->bulk_in.pipe_num){usbh_cdc_acm_process_rx(host);// Handle BULK IN transfer}elseif(event.msg.pipe_num==cdc->intr_in.pipe_num){usbh_cdc_acm_process_intr_rx(host);// Handle Interrupt IN transfer(e.g. Serial State)}break;
Error Recovery
When an error occurs during other state processing, the driver attempts to clear endpoint features (Clear Feature) and restore to the IDLE state.
Example:
switch(cdc->state){/* ... IDLE state ... */caseUSBH_CDC_ACM_STATE_ERROR:/* Error recovery mechanism */req_status=usbh_ctrl_clear_feature(host,0x00U);if(req_status==HAL_OK){cdc->state=USBH_CDC_ACM_STATE_IDLE;}break;}
The usbh_cdc_acm_setup callback function is invoked after the basic enumeration is completed.
It initiates the first class-specific request: Get Line Coding, to synchronize the state between the host and the device.
Example:
staticintusbh_cdc_acm_setup(usb_host_t*host){/* Initiate a Get Line Coding request */status=usbh_cdc_acm_process_get_line_coding(host,cdc->line_coding);returnstatus;}
APIs for Application
Interfaces provided to the upper-layer application for triggering configuration or data transmission.
Set Control State
usbh_cdc_acm_set_control_line_state(): Switches the state to SET_CONTROL_LINE_STATE and schedules the initiation of the control request within process.
Set/Get Line Coding
usbh_cdc_acm_set_line_coding(): Assembles the Setup packet; the driver switches the state to SET_LINE_CODING and schedules the initiation of the control request within process.
usbh_cdc_acm_get_line_coding(): Returns the device parameters read back for verification after the SET_LINE_CODING state.
The following three APIs for application serve as the interface layer between the USB driver and the application. The driver switches the state to TRANSFER and schedules the distribution of data transmission within the process callback.
CDC ACM Data Interface:
usbh_cdc_acm_transmit(): The application layer passes in data to initiate transmission. It handles Zero Length Packet (ZLP) logic and notifies the application layer of the transmission result via the user transmit callback function.
usbh_cdc_acm_receive(): Initiates reception and passes data through to the application layer via the user receive callback function.
CDC ACM Communication Interface:
usbh_cdc_acm_notify_receive(): Initiates interrupt transfer reception and passes data through to the application layer via the user notify callback function.
Transmission Logic and Zero Length Packet (ZLP) Processing
In the bulk data transmission function usbh_cdc_acm_transmit(), the driver must handle the Zero Length Packet (ZLP) issue defined in the USB protocol.
If the length of the transmitted data is exactly an integer multiple of the endpoint’s Maximum Packet Size (MPS), the host must send an additional ZLP to inform the device that the transmission has ended. This is a standard requirement for USB bulk transfers.
Example:
intusbh_cdc_acm_transmit(u8*buf,u32len){usbh_cdc_acm_host_t*cdc=&usbh_cdc_acm_host;usb_host_t*host=cdc->host;usbh_pipe_t*pipe=&cdc->bulk_out;if(pipe->xfer_state==USBH_EP_XFER_IDLE){pipe->xfer_buf=buf;pipe->xfer_len=len;/* If the data length is greater than 0 and an integer multiple of MPS, a ZLP needs to be sent */if((pipe->xfer_len>0)&&(pipe->xfer_len%pipe->ep_mps)==0){pipe->trx_zlp=1;}else{pipe->trx_zlp=0;}cdc->state=USBH_CDC_ACM_STATE_TRANSFER;pipe->xfer_state=USBH_EP_XFER_START;/* ... notify state change ... */}}
This driver stack follows the USB CDC ACM specification, encapsulating the implementation and transmission process of core Class-Specific Requests.
Although the specification marks some requests as optional, the host driver implements the following requests to support standard virtual serial port applications.
Developers can refer to the source code path {SDK}/component/usb/host/cdc_acm for extensions.
Class-Specific Request
Description
SetLineCoding
Configures line coding. Sends parameters such as baud rate, stop bits, and parity bits to the device. In pure passthrough mode, this request is mainly used to complete the protocol handshake process.
GetLineCoding
Gets line coding. Reads the current line configuration parameters from the device, used to verify if the configuration has taken effect or to synchronize with the device’s default state.
SetControlLineState
Sets control signal state. Used to control RTS (Request To Send) and DTR (Data Terminal Ready) signal levels, commonly used for flow control handshakes or to notify the device that the host side is ready.
The CDC ACM host driver parses the configuration descriptor during the device enumeration phase (usbh_cdc_acm_attach callback function), automatically finding and allocating corresponding transmission resources based on interface subclasses to establish complete data and control channels.
Count
Description
2
For default control transfer 0. Used for sending standard USB requests and CDC class-specific control requests (e.g., SetLineCoding).
1
For Interrupt IN transfer. Belongs to the Communication Interface (CCI), used to receive notification events (Notify) actively reported by the device. The maximum timeout is set to 1000 ticks.
1
For Bulk IN transfer. Belongs to the Data Interface (DCI), used to receive raw data streams (RX Data) uploaded by the device, supporting large packet transmission and ZLP processing.
1
For Bulk OUT transfer. Belongs to the Data Interface (DCI), used to send raw data streams (TX Data) to the device. The driver layer optimizes DMA scheduling to ensure high-bandwidth transmission.
This section details the complete development process for the CDC ACM Host driver, covering driver initialization, hot-plug management, data transmission and reception mechanisms, and resource release.
Before using the CDC ACM Host driver, it is necessary to define the configuration structure and register callback functions, followed by sequentially calling core interfaces to start the USB host controller and the ACM class driver.
Step Description:
Hardware Configuration: Set the USB speed mode (High Speed/Full Speed) and related interrupt priorities.
Callback Registration: Define the usbh_cdc_acm_cb_t structure and mount handler functions for each phase (connection, disconnection, data transmission, notification).
Core Initialization: Call usbh_init() to initialize the USB core stack.
Class Driver Initialization: Call usbh_cdc_acm_init() to initialize the CDC ACM class driver.
/* 1. Configure USB speed (High Speed or Full Speed), isr priority and main task priority. */staticusbh_config_tusbh_cfg={.speed=USB_SPEED_HIGH,.ext_intr_enable=USBH_SOF_INTR,.isr_priority=INT_PRI_MIDDLE,//...};/* 2. Define USB user-level callbacks. */staticusbh_user_cb_tusbh_usr_cb={.process=cdc_acm_cb_process/* USB callback to handle class-independent events in the application */};/* 3. Define user callbacks for CDC ACM events. */staticusbh_cdc_acm_cb_tcdc_acm_usr_cb={.init=cdc_acm_cb_init,/* Class init callback */.deinit=cdc_acm_cb_deinit,/* Class deinit callback */.attach=cdc_acm_cb_attach,/* Device attach callback */.detach=cdc_acm_cb_detach,/* Device detach callback */.setup=cdc_acm_cb_setup,/* Class setup callback */.transmit=cdc_acm_cb_transmit,/* Data transmit callback */.receive=cdc_acm_cb_receive,/* Data receive callback */.notify=cdc_acm_cb_notify,/* Interrupt notify callback */};intstatus=0;/*4. Initialize USB host core driver with configuration. */status=usbh_init(&usbh_cfg,&usbh_usr_cb);if(status!=HAL_OK){return;}/* 5. Initialize CDC ACM class driver. */status=usbh_cdc_acm_init(&cdc_acm_usr_cb);if(status!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();return;}
Monitor the connection and disconnection of CDC ACM devices by registering the attach and detach callback functions in usbh_cdc_acm_cb_t.
In the example code, semaphore mechanisms are used to synchronize states:
Attach: When a device is inserted and enumerated successfully, the attach callback is triggered, releasing the cdc_acm_attach_sema to notify the main thread to start data testing.
Detach: When a device is removed, the detach callback is triggered, releasing the cdc_acm_detach_sema to trigger the hot-plug management thread for resource cleanup and re-initialization.
/* 1. Configure USB status change callback */staticusbd_cdc_acm_cb_tcdc_acm_cb={.status_changed=cdc_acm_cb_status_changed};/* 2. Configure Callback executed in ISR context */staticvoidcdc_acm_cb_status_changed(u8old_status,u8status){RTK_LOGS(TAG,RTK_LOG_INFO,"Status change: %d -> %d \n",old_status,status);cdc_acm_attach_status=status;rtos_sema_give(cdc_acm_attach_status_changed_sema);}/* 3. Create thread handling the state machine */staticvoidcdc_acm_hotplug_thread(void*param){intret=0;UNUSED(param);for(;;){if(rtos_sema_take(cdc_acm_attach_status_changed_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){if(cdc_acm_attach_status==USBD_ATTACH_STATUS_DETACHED){RTK_LOGS(TAG,RTK_LOG_INFO,"DETACHED\n");/* 1.Clean up resources */usbd_cdc_acm_deinit();ret=usbd_deinit();if(ret!=0){break;}/* 2.Re-initialize for next connection */ret=usbd_init(&cdc_acm_cfg);if(ret!=0){break;}ret=usbd_cdc_acm_init(CONFIG_CDC_ACM_BULK_OUT_XFER_SIZE,CONFIG_CDC_ACM_BULK_IN_XFER_SIZE,&cdc_acm_cb);if(ret!=0){usbd_deinit();break;}}elseif(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);}
Example: Set the baud rate to 38400 and perform readback verification.
staticvoidcdc_acm_request_test(void){intret;usbh_cdc_acm_line_coding_tline_coding;usbh_cdc_acm_line_coding_tnew_line_coding;/* Wait for device attach */....../* 1. Get current line coding from device */ret=usbh_cdc_acm_get_line_coding(&line_coding);if(ret!=HAL_OK){/* Get failed */}/* 2. Modify configuration parameters *//* Baudrate: 38400, Stop bits: 1, Parity: None, Data bits: 8*/line_coding.b.dwDteRate=38400;line_coding.b.bCharFormat=CDC_ACM_LINE_CODING_CHAR_FORMAT_1_STOP_BITS;line_coding.b.bParityType=CDC_ACM_LINE_CODING_PARITY_NO;line_coding.b.bDataBits=8;/* 3. Set new line coding to device */ret=usbh_cdc_acm_set_line_coding(&line_coding);if(ret!=HAL_OK){/* Set failed */}/* Wait for the setting to take effect */rtos_time_delay_ms(10);/* 4. Get line coding again to verify */ret=usbh_cdc_acm_get_line_coding(&new_line_coding);/* 5. Compare set values with read-back values */......}
Bulk Transfer
The driver uses an asynchronous transmission mechanism, combined with semaphores to implement synchronization logic:
Send: Call usbh_cdc_acm_transmit() to send data. Upon completion, the driver calls back the transmit function and releases cdc_acm_send_sema.
Receive: Call usbh_cdc_acm_receive() to prepare to receive data. Upon receiving data, the driver calls back the receive function and releases cdc_acm_receive_sema.
/* RX Callback: Handle Reception Completion */staticintcdc_acm_cb_receive(u8*buf,u32len,u8status){if(status==HAL_OK){u16cdc_acm_bulk_in_mps=usbh_cdc_acm_get_bulk_ep_mps();/* 1. Copy data to internal buffer (Logic for buffer overflow check included) */if((len>0)&&((cdc_acm_total_rx_len+len)<=USBH_CDC_ACM_LOOPBACK_BUF_SIZE)){/* memcpy(cdc_acm_loopback_rx_buf + cdc_acm_total_rx_len, buf, len); */}cdc_acm_total_rx_len+=len;/* 2. Check for ZLP, short packet, or buffer full to complete the transfer */if((len==0)||(len%cdc_acm_bulk_in_mps)||((len%cdc_acm_bulk_in_mps==0)&&(len<USBH_CDC_ACM_LOOPBACK_BUF_SIZE))||(cdc_acm_total_rx_len>USBH_CDC_ACM_LOOPBACK_BUF_SIZE)){cdc_acm_total_rx_len=0;/* 3. Signal the receive semaphore */rtos_sema_give(cdc_acm_receive_sema);}}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"RX fail: %d\n",status);}returnHAL_OK;}/* TX Callback: Handle Transmission Completion */staticintcdc_acm_cb_transmit(u8status){if(status==HAL_OK){/* TX done, signal semaphore */rtos_sema_give(cdc_acm_send_sema);}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"TX fail: %d\n",status);}returnHAL_OK;}/* Task thread: Handle Loopback Test Logic */staticvoidcdc_acm_loopback_test(void){inti;intret;/* Initialize TX buffer *///...for(i=0;i<USBH_CDC_ACM_LOOPBACK_CNT;i++){/* Prepare RX buffer and check device status *///.../* 1. Transmit Data */ret=usbh_cdc_acm_transmit(cdc_acm_loopback_tx_buf,USBH_CDC_ACM_LOOPBACK_BUF_SIZE);if(ret<0){RTK_LOGS(TAG,RTK_LOG_ERROR,"TX fail: %d\n",ret);return;}/* 2. Wait for TX Complete signal */if(rtos_sema_take(cdc_acm_send_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){/* 3. Prepare to Receive Data */usbh_cdc_acm_receive(cdc_acm_loopback_rx_buf,USBH_CDC_ACM_LOOPBACK_BUF_SIZE);}/* 4. Wait for RX Complete signal and Verify Data */if(rtos_sema_take(cdc_acm_receive_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){/* Check rx data integrity *///...}}RTK_LOGS(TAG,RTK_LOG_INFO,"Bulk loopback test PASS\n");}
Interrupt Notify
Use usbh_cdc_acm_notify_receive() to listen for interrupt transfers from the device (such as Serial State changes). When a notification is received, the notify callback is triggered.
/* Callback: Handle Interrupt Notify */staticintcdc_acm_cb_notify(u8*buf,u32len,u8status){/* Handle buf and len *///...if(status==HAL_OK){/* Notify received, signal semaphore to notify the task */rtos_sema_give(cdc_acm_notify_sema);}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"Notify fail: %d\n",status);}returnHAL_OK;}/* Task Thread: Execute Notify Test Logic */staticvoidcdc_acm_notify_test_thread(void*param){/* Wait for device ready *///.../* 1. Optional: Change control line state to trigger device notification */usbh_cdc_acm_set_control_line_state();rtos_time_delay_ms(2000);for(inti=0;i<USBH_CDC_ACM_LOOPBACK_CNT;i++){/* 2. Submit Interrupt IN Request to listen for notification */usbh_cdc_acm_notify_receive(cdc_acm_notify_rx_buf,USBH_CDC_ACM_NOTIFY_BUF_SIZE);/* 3. Wait for Notify Callback */if(rtos_sema_take(cdc_acm_notify_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){/* Notification received. Check data (e.g. Serial State at offset 8) */RTK_LOGS(TAG,RTK_LOG_INFO,"Intr rx success(0x%02x 0x%02x)\n",cdc_acm_notify_rx_buf[9],cdc_acm_notify_rx_buf[8]);}}RTK_LOGS(TAG,RTK_LOG_INFO,"Intr notify test PASS\n");rtos_task_delete(NULL);}
Note
For complete data testing logic, please refer to the SDK example code: {SDK}/component/example/usb/usbh_cdc_acm/example_usbh_cdc_acm.c.
When the device is disconnected or the USB host function needs to be disabled, the class driver and the host core driver must be unloaded in sequence, and related system resources must be released.
/* 1. Deinitialize CDC ACM class driver. */usbh_cdc_acm_deinit();/* 2. Deinitialize USB host core driver */usbh_deinit();
This section introduces a complete USB CDC ACM Host example, demonstrating how Ameba, acting as a host, identifies a CDC device and performs baud rate setting, data loopback testing, and interrupt notification testing.
The example code path: {SDK}/component/example/usb/usbh_cdc_acm.
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board:
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_cdc_acm-p
Confirmation of Menuconfig configuration
If compilation fails, please execute ameba.pymenuconfig and confirm that USBHCDCACM has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Host) --->
[*] CDC ACM
Restart the development board and observe the serial log; the following startup information should be displayed:
[ACM-I] USBD CDC ACM demo start
Connect Device
Use a USB cable to connect a specific USB CDC ACM device (e.g., another Ameba board running the usbd_cdc_acm example code) to the USB port of this development board.
Serial Communication Test
If the operation is normal after connection, the control transmission test and bulk transmission loopback test will be conducted in sequence.
The serial port log will display the following success message:
```
[ACM-I] Ctrl test PASS
[ACM-I] Bulk loopback test start, loopback times:100, size: 1024
[ACM-I] Bulk loopback test PASS
```
The Ethernet Control Model (ECM) within the USB Communication Device Class (CDC) defines a standard protocol for transmitting Ethernet frames over a USB interface. In Host mode, the system uses this protocol to identify and drive external USB network devices—such as USB-to-Ethernet adapters and LTE cellular modules—abstracting them into local network interfaces.
The Ameba platform integrates a USB-IF compliant CDC ECM Host protocol stack, capable of seamless integration with the system’s built-in LwIP network stack. This solution provides flexible network expansion capabilities, enabling devices to easily access wired LANs or 4G cellular networks via the USB interface.
Acting as a USB Host, Ameba is responsible for enumerating, configuring, and loading drivers for connected CDC ECM devices. This allows users to flexibly extend connectivity without modifying board-level hardware designs. For example,
Wired Ethernet Expansion (USB-to-Ethernet): Ameba drives external standard USB-to-Ethernet adapters (supporting mainstream chip solutions like Realtek RTL815x) via the USB interface. This provides plug-and-play wired access for hardware platforms lacking native Ethernet ports.
4G Cellular Access (LTE Dongle): Ameba connects to ECM-enabled 4G modules or USB dongles. The protocol stack converts USB data streams into standard network packets, enabling internet access via cellular networks. This is ideal for outdoor IoT gateways or vehicular terminals requiring mobile backhaul or Wi-Fi link backup.
Dual-Interface Routing and Bridging: By combining Ameba’s native Wi-Fi interface with the USB-expanded physical channel, the platform acts as a network hub. It can perform data forwarding, load balancing, or failover between interfaces, functioning as a simple wireless router or dual-port bridge.
The Communication Device Class (CDC) is a general standard defined by the USB specification for communication devices. As a subclass, the Ethernet Control Model (ECM) specifically defines how to encapsulate and transmit Ethernet data frames over a USB interface, enabling a USB device to be recognized by the operating system as a standard Network Interface Controller (NIC).
The USB-IF officially publishes the base CDC class protocol and the ECM subclass specification. Please refer to the following core documents during development:
Common CDC ECM technical terms used in this document are defined as follows:
Term
Description
CCI (Communication Class Interface)
The interface used for managing device connection status, sending control commands, and receiving network status notifications (e.g., cable plug/unplug). It typically utilizes Control and Interrupt endpoints.
DCI (Data Class Interface)
The interface used for the actual transmission of Ethernet packets. It typically utilizes Bulk IN and Bulk OUT endpoints.
Ethernet Frame
The data payload transmitted by CDC ECM, usually following the IEEE 802.3 standard (containing Destination MAC, Source MAC, EtherType, and Payload).
Notification
Asynchronous events actively reported by the device to the host via the Interrupt endpoint, such as NETWORK_CONNECTION (link status change) or CONNECTION_SPEED_CHANGE.
Functional Descriptor
CDC-specific descriptors embedded within the standard configuration descriptor. They describe the specific capabilities of the network device (e.g., MAC address, maximum segment size).
Located at the top of the architecture; represents specific network applications.
LwIP/Network Stack
The network stack is unaware of underlying USB details. It operates on an abstract standard network interface (Netif) to provide network services to upper layers, handling TCP/IP, UDP, DHCP, and other network layer logic.
CDC ECM Class Driver
The core middleware implementing the behavior defined by the ECM specification:
Enumeration Parsing: Parses CDC functional descriptors to retrieve the MAC address.
Control Management: Configures the Packet Filter and handles network status interrupt notifications.
Data Tx/Rx: Encapsulates pbufs from the network stack into USB transfer requests (URB/Transfer) and restores received USB packets into Ethernet frames for LwIP.
USB Core & HCD (Host Controller Driver)
The low-level driver responsible for standard USB enumeration, endpoint management, and physical data transmission scheduling (e.g., DMA operations), abstracting hardware controller differences from the class driver.
A standard CDC ECM device typically consists of two USB Interfaces, associated via a Union Functional Descriptor:
Communication Interface (CCI) - Control & Notification
Control Transfer
Mapped Endpoint: Default Control Endpoint 0.
Enumeration & Configuration: Transmits standard USB descriptors and CDC-specific functional descriptors.
Class-Specific Requests: Handles ECM protocol control commands. The most critical is SetEthernetPacketFilter, used to configure the device to receive broadcast, multicast, or unicast packets.
Interrupt Transfer
Interrupt IN: Mandatory. Used by the device to send Notifications to the host.
Typical Applications:
NETWORK_CONNECTION: Reports cable insertion (Link Up) or removal (Link Down) status.
CONNECTION_SPEED_CHANGE: Reports current upstream and downstream connection speeds.
Data Interface (DCI) - Bulk Pipes
The Data Interface typically contains two Alternate Settings, which is a key design point of the ECM protocol:
Alt Setting 0: Idle Mode. Contains no endpoints. When the network is disconnected or the driver is not loaded, the host should switch the interface to this mode to save bus bandwidth.
Alt Setting 1: Active Mode**. Contains a pair of Bulk endpoints (Bulk IN / Bulk OUT).
Bulk IN: Device -> Host. Uploads received network data packets.
Bulk OUT: Host -> Device. Sends Ethernet frames to be forwarded to the network.
In the standard configuration descriptor, CDC ECM devices use Class-Specific Interface Descriptors to define network capabilities in detail. These are collectively referred to as Functional Descriptors.
CDC ECM Descriptor Topology
Device Descriptor
└── Identifies basic device information (USB Version 2.00)
Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│ ├── Header Functional Descriptor
│ ├── Union Functional Descriptor
│ ├── Ethernet Networking Functional Descriptor
│ └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (control transfer only)
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
├── Endpoint Descriptor(Bulk In)
└── Endpoint Descriptor(Bulk Out)
Device Qualifier Descriptor
└── Device information while running in another speed mode
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── CDC Control (CDC Control) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│ ├── Header Functional Descriptor
│ ├── Union Functional Descriptor
│ ├── Ethernet Networking Functional Descriptor
│ └── Endpoint Descriptor(Interrupt IN)
│
└── CDC Data (CDC-Data) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (control transfer only)
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
├── Endpoint Descriptor(Bulk In)
└── Endpoint Descriptor(Bulk Out)
Data transmission in CDC ECM is straightforward. The USB Payload is the raw Ethernet Frame, without additional header encapsulation (unlike RNDIS or NCM). The figure below shows a full Ethernet frame (1514 bytes) being split into 3 USB packets for transmission.
Note
ZLP (Zero Length Packet)
If the length of the transmitted Ethernet frame is exactly a multiple of the endpoint’s wMaxPacketSize (e.g., 512 bytes in High Speed), the host or device must send a Zero Length Packet (ZLP) to indicate the end of the transfer.
This section details the internal implementation of the CDC ECM Host driver, covering the driver framework, supported class-specific requests, and endpoint resource allocation schemes.
The USB Host CDC ECM driver stack adopts a modular design, enabling efficient interaction between the network protocol stack and the USB hardware controller via a layered architecture. This architecture ensures data transmission stability and system scalability.
The system is divided into several core modules based on functional responsibilities:
Responsible for Encapsulation & Decapsulation of Network Layer Packets.
Acting as the interface layer between the USB driver and the TCP/IP stack (LwIP), it functions effectively as the Data Link Layer:
TX (Data Producer): Receives PBUF data packets from LwIP and converts them into USB transfer requests.
RX (Data Consumer): Receives Ethernet frames reported by the USB lower layer, encapsulates them into PBUFs, and submits them to LwIP for upper-layer protocol processing.
This is the core component of the CDC ECM class driver. It is fully compliant with the USB CDC ECM specification and implements the essential logic for interaction between the Host and the ECM device. Key responsibilities include:
Enumeration & Configuration Parsing: Identifies CDC communication and data interfaces, parses device descriptors, and extracts key parameters such as the MAC address.
Network Control Management: Manages device behavior via the Control Endpoint, such as configuring Packet Filters and multicast lists.
Data Tx/Rx Abstraction: Provides standardized data transmission/reception APIs, serving as a bridge between the upper network stack (LwIP) and the underlying USB transport.
Self-Healing Mechanism: Immediately triggers recovery processes upon detecting transmission anomalies (e.g., timeouts or blocking), ensuring automatic network connection restoration and uninterrupted communication.
The ECM Host class driver serves as a crucial bridge in the system architecture. Its implementation logic revolves around three core interaction interfaces:
Host Class Driver Callback API: The class driver interacts with the underlying USB Core by defining and registering a standard usbh_class_driver_t structure.
Application Callback API: The class driver provides an asynchronous event notification mechanism to upper-layer applications via the usbh_cdc_ecm_state_cb_t callback structure.
Application API: When the application layer invokes these APIs, the driver transitions its internal state machine, thereby triggering and scheduling data transfers.
These two functions are responsible for allocating and freeing memory resources, as well as registering and unregistering the class driver with the USB Core.
usbh_cdc_ecm_init() is the top-level function for loading the CDC ECM host class driver. It primarily performs the following tasks:
Saves the user-provided callback functions and invokes the user’s init callback.
Allocates the necessary memory resources for control transfers.
Parses the private data passed from the application layer and stores it internally for subsequent specific use cases.
Calls usbh_register_class() to register the cdc_ecm class driver with the USB Host Core.
Example:
intusbh_cdc_ecm_init(usbh_cdc_ecm_state_cb_t*cb,usbh_cdc_ecm_priv_data_t*priv){/* 1. Save the frame count param */if(priv==NULL){USBH_ECM_FREE_MEM(cdc->led_array);cdc->led_cnt=0;}else{if(priv->mac_value){usbh_cdc_ecm_set_dongle_mac(priv->mac_value);}if((priv->led_array!=NULL)&&(priv->led_cnt>0)){usbh_cdc_ecm_set_dongle_led_array(priv->led_array,priv->led_cnt);}}/* 2. Allocate memory */cdc->dongle_ctrl_buf=(u8*)usb_os_malloc(CDC_ECM_MAC_STRING_LEN);/* 3. Save the user callback and call the user's ``init`` callback */if(cb!=NULL){cdc->cb=cb;if(cb->init!=NULL){ret=cb->init();if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"User init err %d\n",ret);returnret;}}}/* 4. Register class driver*/usbh_register_class(&usbh_cdc_ecm_driver);returnHAL_OK;}
usbh_cdc_ecm_deinit() is the top-level function for unloading the cdc_ecm host class driver. It is strictly responsible for resource cleanup:
Forcefully closes all open pipes if the device remains connected.
Frees all memory resources allocated during the initialization phase.
Inspects the TX status to guarantee the safe return of upper-layer data read operations.
Invokes the user’s deinit callback to notify the application layer of the uninstallation event.
Example:
intusbh_cdc_ecm_deinit(void){/* 1.Reset ethernet connect status*/cdc->eth_hw_connect=0;/* 2. Unregister class driver*/usbh_unregister_class(&usbh_cdc_ecm_driver);/* 3. Close all open pipes */usbh_cdc_ecm_deinit_all_pipe();/* 4. Free memory */usbh_cdc_ecm_deinit_ep_buf();USBH_ECM_FREE_MEM(cdc->dongle_ctrl_buf);USBH_ECM_FREE_MEM(cdc->led_array);/* 5. Release the tx lock to prevent the Tx API from blocking. */usbh_cdc_ecm_tx_status_check();usb_os_sema_delete(cdc->bulk_tx_sema);/* 6. Call the user's ``deinit`` callback */if((cdc->cb!=NULL)&&(cdc->cb->deinit!=NULL)){cdc->cb->deinit();}returnret;}
When the USB Core detects the attachment or detachment of a device matching the cdc_ecm class, it automatically triggers the corresponding callback functions.
usbh_cdc_ecm_attach is a critical step in the device enumeration phase. It parses interface descriptors and allocates pipe resources:
Find CDC Control Interface: Parses and retrieves the MAC address string index, interrupt endpoint information, etc.
Find CDC Data Interface: Parses and retrieves the interface index and pipe information for network data.
Open Pipes: Allocates and opens status detection pipes and data transmission/reception pipes based on the acquired descriptor information.
Initialize State Machine: Sets the device state to CDC_ECM_STATE_IDLE, indicating readiness to receive data.
Notify Application Layer: Invokes the user’s attach callback to report the connection status to the application layer.
staticintusbh_cdc_ecm_attach(usb_host_t*host){/* 1. Parse descriptors to get Ctrl and Data information */status=usbh_cdc_ecm_parse_interface_desc(host);if(status){returnstatus;}/* 2. Open the pipe for steaming transfer *//* control in */pipe_info=&(cdc->intr_rx);if(pipe_info->valid){usbh_open_pipe(host,&(pipe_info->pipe),&(pipe_info->ep_desc));pipe_info->buf=(u8*)usb_os_malloc(pipe_info->pipe.ep_mps);pipe_info->buf_len=pipe_info->pipe.ep_mps;pipe_info->pipe.xfer_state=USBH_EP_XFER_START;cdc->intr_check_tick=pipe_info->pipe.ep_interval;}/* bulk out */pipe_info=&(cdc->bulk_tx);if(pipe_info->valid){usbh_open_pipe(host,&(pipe_info->pipe),&(pipe_info->ep_desc));}/* bulk in */pipe_info=&(cdc->bulk_rx);if(pipe_info->valid){usbh_open_pipe(host,&(pipe_info->pipe),&(pipe_info->ep_desc));/* ecm use bulk, the max ethernet packet size is 1542, malloc (512*3) to rx a whole ethernet packet*/pipe_info->buf=(u8*)usb_os_malloc(USBH_CDC_ECM_BULK_BUF_MAX_SIZE);pipe_info->buf_len=USBH_CDC_ECM_BULK_BUF_MAX_SIZE;pipe_info->pipe.xfer_state=USBH_EP_XFER_START;}/* 3. Initialize the state machine */cdc->state=CDC_ECM_STATE_IDLE;/* 4. Notify the user layer */if((cdc->cb!=NULL)&&(cdc->cb->attach!=NULL)){cdc->cb->attach();}returnHAL_OK;}
usbh_cdc_ecm_detach is invoked when the device is unplugged. Its core responsibility is to clean up the connection state and notify the upper-layer application that the device has been removed.
staticintusbh_cdc_ecm_detach(usb_host_t*host){/* 1. Reset xfer status */cdc->state=CDC_ECM_STATE_IDLE;/* 3. Deinit all pipes */usbh_cdc_ecm_deinit_all_pipe();/* 3. Notify the user layer */if((cdc->cb!=NULL)&&(cdc->cb->detach!=NULL)){cdc->cb->detach();}returnHAL_OK;}
The usbh_cdc_ecm_process callback function acts as the central state machine processor for the host-side CDC ECM class.
Unlike the device side, which passively responds to requests, the host-side driver must proactively maintain the device state. Its core duty is to manage the lifecycle of the class driver and orchestrate transfer tasks.
State Machine Management and Scheduling
usbh_cdc_ecm_process coordinates control transfers (e.g., retrieving the MAC address) and data transfers based on the current state of the class driver.
State Enum
Description
Key Actions
CDC_ECM_STATE_IDLE
Idle State
Waits for user commands or data transfer requests.
CDC_ECM_STATE_PRE_SETTING
Control Transfer
Invokes corresponding control processing functions based on the current stage (e.g., Get MAC, Set MAC).
CDC_ECM_STATE_TRANSFER
Data Transfer
Dispatches tasks to specific TX/RX processing functions based on the Pipe ID.
CDC_ECM_STATE_ERROR
Error State
Attempts to send a Clear Feature request to recover communication.
Control Transfer Dispatching Example
When in the CDC_ECM_STATE_PRE_SETTING state, the state machine dispatches tasks to specific control transfer processing functions according to the triggering event.
Example:
caseCDC_ECM_STATE_PRE_SETTING:/* Ctrl Request */req_status=usbh_cdc_ecm_ctrl_setting(host);if(req_status==HAL_OK){RTK_LOGS(TAG,RTK_LOG_INFO,"ECM alt setting finish %d\n",cdc->intr_rx.pipe.pipe_num);cdc->state=CDC_ECM_STATE_TRANSFER;usbh_notify_class_state_change(host,cdc->intr_rx.pipe.pipe_num);}else{usbh_notify_class_state_change(host,0);}break;
Data Transfer Dispatching Example
When in the CDC_ECM_STATE_TRANSFER state, the state machine dispatches data processing tasks to specific TX/RX handling functions based on the triggered Pipe ID.
Should an error occur during other state processing operations, the driver will attempt to send a Clear Feature request to the device in an effort to restore communication and return to the IDLE state.
Example:
/* ... Error state ... */caseCDC_ECM_STATE_ERROR:/* Error recovery mechanism */ret=usbh_ctrl_clear_feature(host,0x00U);if(ret==HAL_OK){cdc->xfer_state=CDC_ECM_STATE_IDLE;}break;
CDC ECM data interaction is broadly divided into two categories: CDC Control Transfer and CDC Data Transfer.
CDC Control Transfer
Responsible for managing and configuring the functional behavior of the CDC ECM device. The driver implements the following state machine flows to process various configuration requests:
caseCDC_ECM_STATE_GET_MAC_STR:/* get Mac String */state=usbh_cdc_ecm_get_mac_str(host);if(state==HAL_OK){cdc->sub_status++;}elseif(state!=HAL_BUSY){RTK_LOGS(TAG,RTK_LOG_INFO,"Get MAC fail error[%d]\n",state);usb_os_sleep_ms(10);}break;
caseCDC_ECM_STATE_CTRL_ALT_SETTING:/* Set Interface */state=usbh_cdc_ecm_process_set_alt(host);if(state==HAL_OK){cdc->sub_status++;}elseif(state!=HAL_BUSY){RTK_LOGS(TAG,RTK_LOG_ERROR,"ECM alt setting err\n");usb_os_sleep_ms(100);}break;
CDC-DATA Transfer:
CDC ECM network data stream transmission encompasses the reporting of network status and the actual transmission/reception of network data. Network status transfer utilizes Interrupt (INTR) transfers, whereas network data transfer relies on Bulk (BULK) transfers.
Code example(Logic for retrieving network status via INTR transfer):
staticintcdc_ecm_cb_intr_receive(u8*buf,u32length){if(buf&&length>=8){if(length==8&&buf[0]==0xA1&&buf[1]==CDC_ECM_NOTIFY_NETWORK_CONNECTION){/* parse to get the value */}elseif(length==16&&buf[0]==0xA1&&buf[1]==CDC_ECM_NOTIFY_CONNECTION_SPEED_CHANGE){/*/* parse to get the value */}}returnHAL_OK;}staticvoidusbh_cdc_ecm_process_intr_in(usb_host_t*host){usbh_pipe_t*ep=&(cdc->intr_rx->pipe);switch(ep->xfer_state){caseUSBH_EP_XFER_START:/* 1. Do Transfer */ep->xfer_buf=pipe_info->buf;ep->xfer_len=pipe_info->buf_len;usbh_transfer_data(host,ep);ep->tick=usbh_get_tick(host);pipe_info->busy_tick=usbh_get_tick(host);ep->xfer_state=USBH_EP_XFER_BUSY;break;caseUSBH_EP_XFER_BUSY:/* 2. Check the urb state */urb_state=usbh_get_urb_state(host,ep);switch(urb_state){caseUSBH_URB_DONE:/* 2.1. Xfer Complete */len=usbh_get_last_transfer_size(host,ep);if(len>0){cdc_ecm_cb_intr_receive(ep->xfer_buf,len);}break;caseUSBH_URB_BUSY:/* 2.2 Handle urb busy status */if(usbh_get_elapsed_ticks(host,pipe_info->busy_tick)>=cdc->intr_check_tick){ep->xfer_state=USBH_EP_XFER_IDLE;}break;caseUSBH_URB_ERROR:caseUSBH_URB_STALL:/* 2.3 Handle error status */ep->xfer_state=USBH_EP_XFER_IDLE;break;caseUSBH_URB_IDLE:/* 2.4 Check idle time */if(usbh_get_elapsed_ticks(host,ep->tick)>=cdc->intr_check_tick){ep->xfer_state=USBH_EP_XFER_IDLE;}break;}break;}}
Code example (Logic for receiving data via BULK transfer):
staticvoidusbh_cdc_ecm_process_bulk_in_req(usb_host_t*host,usbh_cdc_ecm_pipe_info_t*pipe_info){ep->xfer_buf=pipe_info->buf;ep->xfer_len=pipe_info->buf_len;usbh_transfer_data(host,ep);ep->xfer_state=USBH_EP_XFER_BUSY;tick=usbh_get_tick(host);ep->tick=tick;pipe_info->busy_tick=tick;}staticvoidusbh_cdc_ecm_process_bulk_in(usb_host_t*host){usbh_pipe_t*ep=&(cdc->bulk_rx->pipe);switch(ep->xfer_state){caseUSBH_EP_XFER_START:/* 1. Do Transfer */usbh_cdc_ecm_process_bulk_in_req(host,pipe_info);break;caseUSBH_EP_XFER_BUSY:/* 2. Check the urb state */urb_state=usbh_get_urb_state(host,ep);switch(urb_state){caseUSBH_URB_DONE:/* 2.1. Xfer Complete */len=usbh_get_last_transfer_size(host,ep);cdc_ecm_cb_bulk_receive(ep->xfer_buf,len);usbh_cdc_ecm_process_bulk_in_req(host,pipe_info);break;caseUSBH_URB_BUSY:/* 2.2 Handle urb busy status */if(usbh_get_elapsed_ticks(host,pipe_info->busy_tick)>=USB_BULK_IN_NAK_CHECK_MAX_CNT){if(usbh_check_nak_timeout(host,ep->pipe_num,USB_BULK_IN_NAK_CHECK_CNT)==HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"BULK in 1\n");usbh_prepare_retransfer(host,ep->pipe_num);ep->xfer_state=USBH_EP_XFER_START;}else{usbh_increase_busy_cnt(host,ep->pipe_num,1);usbh_enable_nak_interrupt(host,ep->pipe_num);pipe_info->busy_tick=usbh_get_tick(host);}}break;caseUSBH_URB_ERROR:/* 2.3 Handle error status */ep->xfer_state=USBH_EP_XFER_START;pipe_info->busy_tick=usbh_get_tick(host);;ep->tick=usbh_get_tick(host);break;caseUSBH_URB_STALL:/* 2.4 Handle stall status */usbh_cdc_ecm_process_bulk_in_req(host,pipe_info);break;caseUSBH_URB_IDLE:/* 2.5 Check idle time */if(usbh_get_elapsed_ticks(host,ep->tick)>=(USB_BULK_IN_IDLE_MAX_CNT)){usbh_prepare_retransfer(host,ep->pipe_num);usbh_cdc_ecm_process_bulk_in_req(host,pipe_info);}break;default:ep->xfer_state=USBH_EP_XFER_START;break;}}
Code example (Logic for transmitting data via BULK transfer):
staticvoidusbh_cdc_ecm_process_bulk_out(usb_host_t*host){usbh_pipe_t*ep=&(cdc->bulk_tx->pipe);switch(ep->xfer_state){caseUSBH_EP_XFER_START:/* 1. Do Transfer */ep->xfer_buf=pipe_info->buf;ep->xfer_len=pipe_info->buf_len;ep->xfer_state=USBH_EP_XFER_BUSY;usbh_transfer_data(host,ep);ep->tick=usbh_get_tick(host);pipe_info->busy_tick=ep->tick;break;caseUSBH_EP_XFER_BUSY:/* 2. Check the urb state */urb_state=usbh_get_urb_state(host,ep);switch(urb_state){caseUSBH_URB_DONE:/* 2.1. Xfer Complete */if(ep->trx_zlp){//ZLPep->trx_zlp=0U;ep->xfer_len=0U;ep->xfer_buf=NULL;ep->xfer_state=USBH_EP_XFER_START;}else{ep->xfer_state=USBH_EP_XFER_IDLE;cdc_ecm_cb_bulk_send(urb_state);}break;caseUSBH_URB_BUSY:/* 2.2 Handle urb busy status */if(usbh_get_elapsed_ticks(host,pipe_info->busy_tick)>=USB_BULK_OUT_BUSY_MAX_CNT){///timeout 5msif(usbh_check_nak_timeout(host,ep->pipe_num,USB_BULK_OUT_BUSY_CHECK_CNT)==HAL_OK){// loong time not get the nakusbh_prepare_retransfer(host,ep->pipe_num);ep->xfer_state=USBH_EP_XFER_START;}else{usbh_increase_busy_cnt(host,ep->pipe_num,1);usbh_enable_nak_interrupt(host,ep->pipe_num);pipe_info->busy_tick=usbh_get_tick(host);}}break;caseUSBH_URB_ERROR:/* 2.3 Handle error status */ep->xfer_state=USBH_EP_XFER_START;break;caseUSBH_URB_STALL:/* 2.4 Handle stall status */ep->xfer_state=USBH_EP_XFER_IDLE;cdc_ecm_cb_bulk_send(urb_state);break;caseUSBH_URB_IDLE:/* 2.4 Check idle time */if(usbh_get_elapsed_ticks(host,ep->tick)>=(USB_BULK_OUT_IDLE_MAX_CNT)){usbh_prepare_retransfer(host,ep->pipe_num);ep->xfer_state=USBH_EP_XFER_START;}break;}break;}}
These interfaces are exposed for upper-layer applications to retrieve device information, configure operational parameters, and initiate data transfers.
The driver stack strictly adheres to the USB CDC ECM 1.2 specification, encapsulating the composition and transmission processes for core Class-Specific Requests.
The driver layer implements default logic for SetEthernetPacketFilter, SetEthernetMulticastFilters, and GetEthernetStatistic. Developers can refer to the source code to implement extensions for other request types.
Source Path: {SDK}/component/usb/host/cdc_ecm
Request Type
Notes
SetEthernetPacketFilter
Sets the Ethernet Packet Filter
Used to control which types of network packets the device receives (e.g., Unicast, Broadcast, Multicast, or Promiscuous Mode).
SetEthernetMulticastFilters
Sets the Multicast Address Filter
When the upper network stack (e.g., IGMP) needs to listen to specific multicast groups, this request pushes the list of Multicast MAC addresses down to the device.
GetEthernetStatistic
Retrieves Ethernet Statistics
Used to read internal statistical counters maintained by the device (e.g., successful TX/RX frames, CRC errors, dropped packets). This is typically used for network diagnostics.
During the device enumeration phase, the CDC ECM Host driver parses the configuration descriptor and automatically looks up and allocates corresponding endpoint resources based on interface types to establish complete communication pipes.
Pipe Type
Description
Control IN/OUT
Default Control Endpoint 0 (EP0).
Used for sending Standard USB Requests and CDC Class-Specific Control Requests (e.g., SetEthernetPacketFilter).
Interrupt IN
Interrupt Input Endpoint.
Belongs to the Communication Interface (CCI). Used to receive active network notifications reported by the device (e.g., NetworkConnection for cable plug/unplug status).
Bulk IN
Bulk Input Endpoint.
Belongs to the Data Interface (DCI). Used to receive network data packets uploaded by the device (RX Data).
Bulk OUT
Bulk Output Endpoint.
Belongs to the Data Interface (DCI). Used to send network data packets to the device (TX Data).
This section provides a detailed guide to the full development process for the CDC ECM Host driver, covering driver initialization, hotplug management, network data transmission/reception mechanisms, and resource release.
Before using the CDC ECM Host driver, developers must define the configuration structure, register callback functions, and then sequentially call the core interfaces to start the USB Host Controller and the ECM Class Driver.
Step-by-Step Description:
Hardware Configuration: Set the USB speed mode (High Speed/Full Speed) and configure interrupt/task priorities.
Callback Registration: Define user callback structures to hook processing functions for various stages (Connect, Disconnect, Data Transfer).
Core Initialization: Call usbh_init() to initialize the USB Core stack.
Class Driver Loading: Call usbh_cdc_ecm_init() to initialize the CDC ECM Class Driver.
/* * 1. Configure USB speed (High Speed or Full Speed) ,isr priority and main task priority. */staticusbh_config_tusbh_ecm_cfg={.speed=USB_SPEED_HIGH,.isr_priority=USBH_ECM_ISR_PRIORITY,.main_task_priority=USBH_ECM_MAIN_THREAD_PRIORITY,};/* * Define USB user-level callbacks. */staticusbh_user_cb_tusbh_ecm_usr_cb={.process=cdc_ecm_cb_process,/* USB callback to handle class-independent events in the application */.validate=cdc_ecm_cb_device_check,/* USB callback to validate a device when multiple devices are connected to a hub */};/* * 2. Define user callbacks for CDC ECM events. */staticusbh_cdc_ecm_state_cb_tcdc_ecm_usb_cb={.init=cdc_ecm_cb_init,/* USB init callback */.deinit=cdc_ecm_cb_deinit,/* USB deinit callback */.attach=cdc_ecm_cb_attach,/* USB attach callback */.detach=cdc_ecm_cb_detach,/* USB detach callback */.setup=cdc_ecm_cb_setup,/* USB setup callback */.bulk_send=cdc_ecm_cb_bulk_send,/* Data transmission complet callback */.bulk_received=cdc_ecm_cb_bulk_receive,/* Data transmission complet callback */.intr_received=cdc_ecm_cb_intr_receive,/* Data transmission complet callback */};intret=0;/** * 3. Initialize USB host core driver with configuration. */ret=usbh_init(&usbh_ecm_cfg,&usbh_ecm_usr_cb);if(ret!=HAL_OK){return;}/* * 4. Initialize CDC ECM class driver. */ret=usbh_cdc_ecm_init(&cdc_ecm_usb_cb);if(ret!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();return;}
As a host, the system must robustly handle the dynamic insertion and removal of USB network adapters. The SDK natively supports hotplug mechanisms and coordinates with the LwIP protocol stack to manage network interface states (e.g., DHCP restart, default route switching).
Handling Logic:
Device Detach: Triggers a callback to release a semaphore. The application thread captures this signal, executes de-initialization, and frees heap memory.
Device Attach: The USB Core detects the device and re-executes the enumeration and driver loading process.
Link Status Maintenance: Monitors the physical link (Link Up/Down) status. Based on the status, it triggers DHCP requests or releases the IP and configures routing.
/* USB detach callback */staticusbh_cdc_ecm_state_cb_tcdc_ecm_usb_cb={.detach=cdc_ecm_cb_detach,};/* Callback executed in main task */staticvoidcdc_ecm_cb_detach(void){cdc_ecm_is_ready=0;usb_os_sema_give(cdc_ecm_detach_sema);}staticintusbh_cdc_ecm_doinit(void){intstatus;status=usbh_init(&usbh_ecm_cfg,&usbh_ecm_usr_cb);if(status!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"Host init fail %d\n",status);returnUSBH_CORE_INIT_FAIL;}status=usbh_cdc_ecm_init(&cdc_ecm_usb_cb,params);if(status!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"Init driver fail %d\n",status);returnUSBH_CLASS_INIT_FAIL;}returnHAL_OK;}/* Thread Context: Handle the state machine */staticvoidecm_hotplug_thread(void*param){intret=0;UNUSED(param);for(;;){usb_os_sema_take(cdc_ecm_detach_sema,USB_OS_SEMA_TIMEOUT);RTK_LOGS(TAG,RTK_LOG_INFO,"Hot plug\n");/* Stop transfer, release resource */usbh_cdc_ecm_deinit();usbh_deinit();usbh_cdc_ecm_tx_status_check();usbh_ecm_timer_unregister();RTK_LOGS(TAG,RTK_LOG_INFO,"Free heap size: 0x%08x\n",usb_os_get_free_heap_size());/* Re-init */ret=usbh_cdc_ecm_doinit();if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"Init fail %d\n",ret);break;}}}/** * Get ecm device connect status */intusbh_cdc_ecm_get_connect_status(void)//1 up{u8ret1=cdc_ecm_is_ready;u8ret2=ecm_hw_connect;intret=ret1&ret2;returnret;;}/* Link Status Task */staticvoidecm_link_change_thread(void*param){u8*mac;u32dhcp_status=0;u8link_is_up=0;eth_state_tethernet_unplug=ETH_STATUS_IDLE;UNUSED(param);RTK_LOGS(TAG,RTK_LOG_INFO,"Enter link status task!\n");cdc_ecm_do_init();while(1){link_is_up=usbh_cdc_ecm_get_connect_status();if(1==link_is_up&&(ethernet_unplug<ETH_STATUS_INIT)){// unlink -> linkRTK_LOGS(TAG,RTK_LOG_INFO,"Do DHCP\n");...dhcp_status=LwIP_IP_Address_Request(NETIF_ETH_INDEX);if(DHCP_ADDRESS_ASSIGNED==dhcp_status){netifapi_netif_set_default(pnetif_eth);//Set default gw to ether netifRTK_LOGS(TAG,RTK_LOG_INFO,"Switch to link\n");}else{RTK_LOGS(TAG,RTK_LOG_INFO,"DHCP Fail\n");}}elseif(0==link_is_up&&(ethernet_unplug>=ETH_STATUS_INIT)){// link -> unlinkethernet_unplug=ETH_STATUS_DEINIT;netif_set_default(pnetif_sta);//switch to other netifRTK_LOGS(TAG,RTK_LOG_INFO,"Swicth to unlink\n");}else{rtos_time_delay_ms(1000);}}}
When the upper LwIP protocol stack has packets to send, it calls the usbh_cdc_ecm_send_data() interface provided by the USB CDC ECM Class Driver.
Process Description:
Submit Request: LwIP calls the send function; the driver layer calls usbh_cdc_ecm_bulk_send() to submit a Bulk OUT transfer request.
Wait for Completion: The driver blocks via usb_os_sema_take(), waiting for the transfer completion semaphore.
USB Transfer: The USB Core handles the low-level DMA transfer, sending data to the device.
Callback Release: Upon transfer completion, the cdc_ecm_cb_bulk_send() callback is triggered, releasing the semaphore.
Return to Upper Layer: The function acquires the semaphore and returns to LwIP, indicating the transmission is complete.
staticusbh_cdc_ecm_state_cb_tcdc_ecm_usb_cb={.bulk_send=cdc_ecm_cb_bulk_send,};/* USB transfer finish callback */staticintcdc_ecm_cb_bulk_send(usbh_urb_state_tstate){usb_os_sema_give(cdc_ecm_tx_sema);if(state!=USBH_URB_DONE){RTK_LOGS(TAG,RTK_LOG_ERROR,"BULK TX fail %d\n",state);}returnHAL_OK;}/* Tansfer APi, used for LwIP */intusbh_cdc_ecm_send_data(u8*buf,u32len){intret;u8retry_cnt=0;while(1){ret=usbh_cdc_ecm_bulk_send(buf,len);if(ret==HAL_OK){//successbreak;}if(++retry_cnt>100){RTK_LOGS(TAG,RTK_LOG_ERROR,"TX drop(%d)\n",len);ret=HAL_ERR_UNKNOWN;break;}else{usb_os_delay_ms(1);}}/* *wait cdc_ecm_cb_bulk_send to give the sema */if(ret==HAL_OK){usb_os_sema_take(cdc_ecm_tx_sema,USB_OS_SEMA_TIMEOUT);}returnret;}
When the USB network adapter receives network data, it uploads it to the host via the Bulk IN endpoint.
Process Description:
Register Callback: During initialization, ethernetif_mii_recv() is registered as the reception handler for the Class Driver.
USB Reception: After the low-level driver completes a Bulk IN transfer, cdc_ecm_cb_bulk_receive() is triggered.
Data Delivery: The callback function invokes ethernetif_mii_recv().
PBUF Encapsulation: LwIP pbuf is allocated, and the received raw data is copied into the pbuf payload.
Submit to Stack: The netif->input() function is called to deliver the packet to the TCP/IP stack for processing.
staticusbh_cdc_ecm_state_cb_tcdc_ecm_usb_cb={.bulk_received=cdc_ecm_cb_bulk_receive,};report_data=ethernetif_mii_recv;staticintcdc_ecm_cb_bulk_receive(u8*buf,u32length){if((report_data!=NULL)&&(length>0)){report_data(buf,length);}returnHAL_OK;}voidethernetif_mii_recv(u8*buf,u32frame_len){structeth_drv_sgsg_list[MAX_ETH_DRV_SG];structpbuf*p,*q;u32total_len=frame_len;structnetif*netif=pnetif_eth;memcpy((u8*)RX_BUFFER,buf,frame_len);/* Allocate buffer to store received packet */p=pbuf_alloc(PBUF_RAW,total_len,PBUF_POOL);if(p==NULL){RTK_LOGW(TAG,"\n\r[%s]Cannot allocate pbuf to receive packet(%d)\n",__func__,total_len);return;}/* Create scatter list */for(q=p;q!=NULL&&sg_len<MAX_ETH_DRV_SG;q=q->next){sg_list[sg_len].buf=(unsignedint)q->payload;sg_list[sg_len++].len=q->len;}rltk_mii_recv(sg_list,sg_len);/* Pass received packet to the interface */if(ERR_OK!=netif->input(p,netif)){pbuf_free(p);}}
This section demonstrates how to configure the Ameba development board as a USB CDC ECM Host and access the network via an external USB-to-Ethernet Adapter.
The Ameba identifies the Dongle, obtains an IP address via DHCP, and performs Ping and iPerf tests.
Example Path: {SDK}/component/example/usb/usbh_cdc_ecm (Provides a complete design reference for developers implementing network routing features).
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_cdc_ecm-p
Confirmation of Menuconfig configuration
If compilation fails, please execute ameba.pymenuconfig and confirm that USBHCDCECM has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Host) --->
[*] CDC ECM
Select USB Ethernet (USB Ethernet) --->
Test connectivity with the gateway. See Ping Test for details.
AT+PING=192.168.31.1
[+PING]: _AT_WLAN_PING_TEST_
OK
[MEM] After do cmd, available heap 2978368
#
[ping_test] PING 192.168.31.1 32(60) bytes of data
[ping_test] 32 bytes from 192.168.31.1: icmp_seq=1 time=0 ms
[ping_test] 32 bytes from 192.168.31.1: icmp_seq=2 time=0 ms
[ping_test] 32 bytes from 192.168.31.1: icmp_seq=3 time=0 ms
[ping_test] 32 bytes from 192.168.31.1: icmp_seq=4 time=0 ms
[ping_test] 4 packets transmitted, 4 received, 0% packet loss, average 0 ms
[ping_test] min: 0 ms, max: 0 ms
Uplink Input Test (RX Test)
Ameba: Server (Receiver)
PC: Client (Sender)
See AT+IPERF for details.
EVB:
AT+IPERF=-s,-i,1
[+IPERF]: _AT_WLAN_IPERF1_TCP_TEST_
OK
[MEM] After do cmd, available heap 2977792
#
Start TCP server! id = [0]
tcp_server_func: Create socket fd = 0
tcp_server_func: Bind socket successfully
tcp_server_func: Listen port 5001
tcp_server_func: Accept connection successfully
tcp_s: id[0] Receive 9383 KBytes in 1000 ms, 76866 Kbits/sec
tcp_s: id[0] Receive 11391 KBytes in 1000 ms, 93323 Kbits/sec
tcp_s: id[0] Receive 11400 KBytes in 1000 ms, 93393 Kbits/sec
tcp_s: id[0] Receive 11393 KBytes in 1000 ms, 93334 Kbits/sec
tcp_s: id[0] Receive 11324 KBytes in 1000 ms, 92774 Kbits/sec
tcp_s: [END] id[0] Totally receive 55040 KBytes in 5013 ms, frame_num = 38604, 89943 Kbits/sec
TCP server stopped!
The USB Mass Storage Class (MSC) protocol establishes a standard communication specification between a host and mass storage devices.
Ameba has implemented a complete USB MSC host protocol stack based on the official MSC protocol standards released by the USB-IF. It supports interaction with MSC devices via the SCSI (Small Computer System Interface) command set, enabling stable and high-speed file reading and writing.
As a USB storage host, Ameba can mount external mass storage devices via the USB interface. By combining this with network technologies to implement various data interactive applications. For example,
Smart Network Storage / Private Cloud: Combines network technology to bridge local storage with cloud connectivity, supporting automatic bidirectional file synchronization and secure remote access based on DDNS/VPN, to build a lightweight home private cloud.
Industrial IoT Gateway: Caches sensor data or serial logs to USB storage devices in unstable network environments (e.g., mines, offshore), and automatically executes resume-from-break uploads to the cloud once connectivity is restored, ensuring the integrity and reliability of industrial data.
AI Media Library & Storage Expansion: Utilizes external storage to house massive media files or AI knowledge bases, overcoming internal Flash capacity limitations to enable local offline retrieval and low-cost functional expansion.
Hardware Security Token: During startup or privilege verification, the device scans a connected U disk for specific encrypted certificates or License files; upon successful validation, it activates advanced system privileges, acting as a physically isolated, low-cost hardware authorization lock.
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.
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)
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:
For the SCSI command set used in the BOT transmission process, please refer to https://www.t10.org/. The following two specifications are of primary concern:
The figure below illustrates the software and hardware layers that commands and data traverse between the host and the device.
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.
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).
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.
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.
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.
Use the following example code to define the configuration structure and callback functions, then invoke the initialization interface to initialize the USB host core and the MSC class driver.
staticusbh_config_tusbh_cfg={.speed=USB_SPEED_HIGH,.ext_intr_enable=USBH_SOF_INTR,.isr_priority=INT_PRI_MIDDLE,.main_task_priority=3U,.tick_source=USBH_SOF_TICK,}staticusbh_msc_cb_tusbh_msc_usr_cb={.attach=usbh_msc_cb_attach,/* USB device attach callback */.setup=usbh_msc_cb_setup,/* USB device setup done, indicate that device is ready for bulk transfer */};staticusbh_user_cb_tusbh_usr_cb={.process=usbh_msc_cb_process};intret=0;ret=usbh_init(&usbh_cfg,&usbh_usr_cb);/* Initialize USB host core driver with configuration and user callback. */if(ret!=HAL_OK){return;}ret=usbh_msc_init(&usbh_msc_usr_cb);/* Initializes the MSC host class with MSC class user callback. */if(ret!=HAL_OK){usbd_msc_disk_deinit();return;}
Perform file read and write operations on the device after initialization.
staticrtos_sema_tmsc_attach_sema;static__IOintmsc_is_ready=0;staticintusbh_msc_cb_attach(void){RTK_LOGS(TAG,RTK_LOG_INFO,"ATTACH\n");rtos_sema_give(msc_attach_sema);returnHAL_OK;}staticintusbh_msc_cb_setup(void){RTK_LOGS(TAG,RTK_LOG_INFO,"SETUP\n");msc_is_ready=1;returnHAL_OK;}staticintusbh_msc_cb_process(usb_host_t*host,u8msg){UNUSED(host);switch(msg){caseUSBH_MSG_DISCONNECTED:msc_is_ready=0;break;caseUSBH_MSG_CONNECTED:break;default:break;}returnHAL_OK;}staticu32filenum=0;staticu8*msc_wt_buf;staticu8*msc_rd_buf;FATFSfs;FILf;intdrv_num=0;charlogical_drv[4];charpath[64]={'0'};u32br;u32bw;rtos_sema_create(&msc_attach_sema,0U,1U);msc_wt_buf=(u8*)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);if(msc_wt_buf==NULL){RTK_LOGS(TAG,RTK_LOG_ERROR,"Fail to alloc test buf\n");gotoexit_deinit;}msc_rd_buf=(u8*)rtos_mem_zmalloc(USBH_MSC_TEST_BUF_SIZE);if(msc_rd_buf==NULL){RTK_LOGS(TAG,RTK_LOG_ERROR,"Fail to alloc test buf\n");gotoexit_deinit;}/* Register USB disk driver to fatfs*/drv_num=FATFS_RegisterDiskDriver(&USB_disk_Driver);if(drv_num<0){RTK_LOGS(TAG,RTK_LOG_ERROR,"Fail to register\n");gotoexit_deinit;}logical_drv[0]=drv_num+'0';logical_drv[1]=':';logical_drv[2]='/';logical_drv[3]=0;while(1){if(msc_is_ready){rtos_time_delay_ms(10);/* Wait for MSC device is ready for class-specific communication */break;}}/* Mount logical drive */if(f_mount(&fs,logical_drv,1)!=FR_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"Fail to mount logical drive\n");FATFS_UnRegisterDiskDriver(drv_num);gotoexit_deinit;}while(1){if(rtos_sema_take(msc_attach_sema,RTOS_SEMA_MAX_COUNT)!=RTK_SUCCESS){RTK_LOGS(TAG,RTK_LOG_ERROR,"Fail to take sema\n");continue;/* Wait for MSC device attach*/}}/* Construct the device file path */strcpy(path,logical_drv);sprintf(&path[3],"TEST%ld.DAT",filenum);/* Open test file */f_open(&f,path,FA_OPEN_ALWAYS|FA_READ|FA_WRITE);/* Write the data from a buffer to an opened file */f_write(&f,(void*)msc_wt_buf,USBH_MSC_TEST_BUF_SIZE,(UINT*)&bw);/* Move the file pointer to the file head */f_lseek(&f,0);/* Read data from an opened file to a buffer */f_read(&f,(void*)msc_rd_buf,USBH_MSC_TEST_BUF_SIZE,(UINT*)&br);/* Close source file */f_close(&f);exit_deinit:rtos_sema_delete(msc_attach_sema);if(msc_wt_buf){rtos_mem_free(msc_wt_buf);}if(msc_rd_buf){rtos_mem_free(msc_rd_buf);}usbh_msc_deinit();usbh_deinit();return;
This section introduces a complete USB Mass Storage (MSC) application example, which demonstrates how to configure the Ameba development board as a USB storage host through the MSC protocol stack.
When the development board is connected to a standard MSC device such as a U-disk supporting the FAT32 format, the host can recognize it and perform simple file read and write tests based on FatFS.
Example path: {SDK}/component/example/usb/usbh_msc. It provides a complete design reference for developers designing custom USB storage host products.
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_msc-p
Confirmation of Menuconfig configuration
If compilation fails, please execute ameba.pymenuconfig and confirm that USBHMSC has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Host) --->
[*] MSC
The USB Audio Class (UAC) specification defines the industry standard for transmitting audio data over USB interfaces. In Host mode, the Ameba platform utilizes this protocol to identify, configure, and drive external USB audio devices.
The current UAC Host stack on the Ameba platform is optimized for Audio Playback scenarios. It integrates a UAC 1.0 compliant protocol stack (verified against USB-IF standards), abstracting external USB UAC devices into local system audio output interfaces. This solution supports plug-and-play functionality and seamlessly integrates with the system’s built-in audio processing framework, providing convenient and high-quality audio output expansion capabilities for embedded devices.
The Ameba UAC Host driver is designed to deliver stable and compatible audio output. Key features include:
Broad Device Compatibility: Fully supports USB devices compliant with the UAC 1.0 standard, such as USB speakers, USB headsets, and USB-to-3.5mm audio adapters.
Automatic Enumeration & Configuration: Automatically parses device descriptors, identifies audio streaming endpoints, and establishes Isochronous transfer channels without manual intervention.
Mainstream Audio Format Support: Please refer to the Supported Audio Formats section for detailed specifications.
Deep System Integration: Exposes a unified API to upper-layer applications, effectively abstracting and masking underlying USB protocol details.
Hot-Plug Support: Supports dynamic plug-and-play and removal of USB peripherals during runtime without requiring a system reboot.
As a USB Host, the Ameba platform handles the complexity of enumerating UAC devices, parsing audio descriptors, and maintaining stable data transmission channels. This solution is ideal for embedded applications that demand high-quality audio playback with minimal development overhead, including:
Smart Audio Terminals: Devices that require voice prompts, advertisement broadcasting, or public address functionality via external UAC devices (e.g., active speakers or headphones).
Digital Signage & Kiosks: Systems that combine local storage or network streaming to play background music or multimedia commentary through high-fidelity USB audio peripherals to enhance user experience.
IoT Audio Gateways: Lightweight audio response nodes that receive audio commands or content from Wi-Fi/Cloud sources and output them through generic USB audio devices.
UAC (USB Audio Class) is a universal audio device class standard defined by USB-IF, aimed at standardizing the encapsulation and transmission of digital audio data streams over USB interfaces.
USB audio devices (such as USB speakers, headphones, and microphones) can be automatically recognized as standard audio input/output terminals in the host system through standard interfaces for data transmission and function control, without the need to install proprietary drivers.
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:
The following table defines common technical terms related to UAC used in this document:
Term
Description
AC Interface (Audio Control Interface)
Responsible for managing the topology of the audio device (e.g., Input/Output Terminals, Feature Units) and issuing control commands (such as volume adjustment and mute) via the Control Endpoint.
AS Interface (Audio Streaming Interface)
Responsible for the actual transmission of audio payload data, typically using Isochronous endpoints. An AS Interface can contain multiple Alternate Settings, each corresponding to different sample rates, bit depths, or channel configurations.
Terminal Type
Identifies the physical or logical attributes of an audio signal source or sink (e.g., USB Streaming Terminal represents a USB data stream, Speaker Terminal represents a physical speaker).
Feature Unit
A processing node within the audio topology that provides specific audio control capabilities (e.g., master volume adjustment, channel gain, mute control).
Sample Rate / Bit Resolution
Core parameters of the audio format. Common combinations in UAC 1.0 include 48 kHz / 16-bit, 44.1 kHz / 16-bit, etc.
Definitions of common audio technology terms used in this document are as follows:
Terms
Introduction
PCM
Pulse Code Modulation, audio data is a raw stream.
Channel
A channel sound is an independent audio signal captured or played in different positions, so the number of channels is the number of sound sources.
Bit Depth
Bit depth represents the bits effectively used in the process of audio signals.
Sampling depth shows the resolution of the sound.
The larger the value, the higher the resolution.
Sample rate
The audio sampling rate refers to the number of frames that the signal is sampled per second.
The higher the sampling frequency, the higher quality the sound will be.
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.
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
Descriptor Topology
Device Descriptor
└── Identifies basic device information (USB Version 1.10)
Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Control Class)
│ └── Class-Specific Descriptor Collection
│ ├── Audio Control Interface Header (declares UAC version)
│ ├── 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)
│ │ ├── Format Descriptor (audio format:channel, bit width and frequency)
│ │ ├── 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)
│ ├── Format Descriptor (audio format:channel, bit width and frequency)
│ ├── 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 (typically 9 + bInCollection × 1)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (HEADER)
├── bcdADC : 2 bytes → Audio Device Class Specification Release Number (0x0100)
├── wTotalLength : 2 byte → Total number of bytes for all AC descriptors (including this header and all Unit/Terminal descriptors)
├── baInterfaceNr(1) : 1 byte → Interface number of the first AudioStreaming or MIDIStreaming interface in the Collection.
│ ⋮
└── baInterfaceNr(N) : 1 byte → Interface number of the last AudioStreaming or MIDIStreaming interface in the Collection.
Input Terminal Descriptor
Clock Source Descriptor
├── bLength : 1 byte → Total descriptor length (fixed = 12)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x0A (Clock Source)
├── bTerminalID : 1 byte → Constant uniquely identifying the Terminal within the audio function.
├── wTerminalType : 2 bytes → Constant characterizing the type of Terminal.
├── bAssocTerminal : 1 byte → D of the Output Terminal to which this Input Terminal is associated.
├── bNrChannels : 1 byte → Number of logical output channels in the Terminal’s output audio channel cluster.
├── wChannelConfig : 2 bytes → Describes the spatial location of the logical channels.
├── iChannelNames : 1 byte → Index of a string descriptor, d
└── iTerminal : 1 byte → String descriptor index
Feature Unit Descriptor
Feature Unit Descriptor
├─ bLength : 1 byte → otal descriptor length in bytes = 7+(ch+1)*n
├─ 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
├─ bControlSize : 1 byte → Size in bytes of an element of the bmaControls() array: n
├─ bmaControls[0] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for master channel
├─ bmaControls[1] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for logical channel1
│ ⋮
├─ bmaControls[N] : n bytes → A bit set to 1 indicates that the mentioned Control is supported for logical channel ch
└─ iFeature : 1 byte → String descriptor index
Output Terminal Descriptor
Output Terminal Descriptor
├─ bLength : 1 byte → Total descriptor length (fixed = 9 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
└─ 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 0x07 (7 bytes)
├─ bDescriptorType : 1 byte → 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → 0x01(AS_GENERAL)
├─ bTerminalLink : 1 byte → Associated Terminal ID (Input or Output Terminal)
├─ bDelay : 1 bytes → Delay introduced by this interface (in number of frames)
└─ wFormatTag : 2 byte → Audio data format (e.g., 0x0001 = PCM)
Audio Streaming Format Type Descriptor
Audio Streaming Format Type Descriptor
├─ bLength : 1 byte → Total length of descriptor in bytes (8 + (num_freq × 3))
├─ bDescriptorType : 1 byte → = 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte → = 0x02(FORMAT_TYPE)
├─ bFormatType : 1 byte → = 0x01(FORMAT_TYPE_I)
├─ bNrChannels : 1 byte → Number of channels (e.g., 2)
├─ 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
├─ bSamFreqType : 1 byte → sampling frequency count
└─ tSamFreq(n) : 3×n byte → Sample rates in little-endian 3-byte format
Note
For detailed field definitions, please refer to the official USB-IF UAC protocol documentation.
The control requests issued by the UAC host to the device are categorized into Standard Requests and Class-Specific Requests.
This section primarily introduces the Class-Specific Requests unique to UAC. These requests are used to implement the distinctive features of audio devices and mainly include Audio Control Requests (targeting AC interfaces) and Audio Streaming Requests (targeting AS interfaces).
Audio Control Requests (AC Requests)
AC Requests are class-specific control transfers sent by the host over Endpoint 0 to dynamically configure and manage audio functionality within a USB audio device.
AudioControl Type
Required
Description
Mute Control Request
Optional
Manipulate the mute control in the Feature Unit of the audio function
Volume Control Request
Optional
Manipulate the volume control in the Feature Unit of the audio function
Sampling Frequency Control
Optional
Manipulate the actual sampling frequency of the clock signal.
Mixer Unit Control Request
Optional
Manipulate the control inside a Mixer Unit of the audio function
Terminal Control Request
Optional
Manipulate the control inside a Mixer Unit of the audio function
Selector Unit Control Request
Optional
Manipulate the control inside a Selector Unit of the audio function
Effect Unit Control Request
Optional
Manipulate the Controls inside an Effect Unit of the audio function
Processing Unit Control Request
Optional
Manipulate the Controls inside a Processing Unit of the audio function
Extension Unit Control Requests
Optional
Manipulate the Controls inside an Extension Unit of the audio function
Audio Streaming Requests (AS Requests)
AS Requests are class-specific control transfers issued by the host over Endpoint 0 to configure and manage parameters related to audio data streams, pecifically those associated with an Audio Streaming Interface.
AudioStreaming Type
Required
Description
Interface Control Request
Optional
Manipulate the Controls inside an AudioStreaming interface of the audio function
Encoder Control Request
Optional
Manipulate the Encoder Controls inside an AudioStreaming interface of the audio function
Decoder Control Request
Optional
Manipulate the Decoder Controls inside an AudioStreaming interface of the audio function
Endpoint Control Request
Optional
Manipulate the Controls inside an AudioStreaming endpoint of the audio function
Audio Control Requests (AC Requests)
Audio Control 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
Mixer Unit 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
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)
Audio Streaming 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
Endpoint Control Request
Optional
Describe the requests that the audio function can support for its AudioStreaming endpoint
UAC audio data streams typically use Linear PCM encoding and are encapsulated in a Multi-Channel Interleaved format. The specific data arrangement depends on the number of channels and bit depth.
An example of 2-Channel interleaved data is shown below:
An example of 4-Channel interleaved data is shown below:
An example of N-Channel interleaved data is shown below:
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.
This section details the internal architecture of the USB Host UAC 1.0 driver stack, the responsibilities of key modules, support for class-specific requests, and the allocation strategy for underlying channel resources.
The USB Host UAC 1.0 driver stack adopts a layered modular design, facilitating efficient interaction between the audio subsystem and the USB hardware controller through clearly defined interfaces. The architecture is specifically optimized for real-time Isochronous Transfer (ISOC) processing to ensure the stable output of high-fidelity audio streams.
The system is organized from top to bottom into the following core functional modules:
Acting as middleware between the USB driver and the upper-layer audio framework, this layer is primarily responsible for:
Write Operations: Functioning as a data producer, it reads PCM data from the upper-layer audio buffer and writes it into the UAC Class Driver’s ring buffer.
Control Interaction: Maps upper-layer application operations, such as volume adjustment and mute toggling, to the corresponding UAC Class Driver control APIs.
This is the core component of the UAC implementation, composed of protocol handling logic and Ringbuffer Management.
Enumeration & Configuration Parsing: Automatically identifies Audio Control (AC) and Audio Streaming (AS) interfaces, parsing parameters such as Terminal Types, Feature Units, sample rates, and bit depths.
Stream Management: Dynamically selects the appropriate Alternate Setting for the AS interface based on target audio parameters and activates the corresponding Isochronous OUT endpoint.
Buffer Scheduling: Maintains a ring buffer to handle data packetization and scheduling, ensuring a continuous, jitter-free data supply.
Protocol Encapsulation: Encapsulates standard USB requests and UAC class-specific requests (e.g., SET_CUR) to control the device.
The UAC Host Class Driver serves as a bridge within the system architecture. Its implementation logic revolves around three core interaction interfaces:
Host Class Driver Callback API: The class driver interacts with the underlying USB Core by defining and registering a standard usbh_class_driver_t structure.
Application-Facing Callback API: The class driver provides an asynchronous event notification mechanism to upper-layer applications via the usbh_uac_cb_t callback structure.
Application-Facing API: When the application layer calls these APIs, the driver switches its internal state machine to initiate data transmission scheduling.
These functions manage memory resource allocation and deallocation, as well as the registration and deregistration of the class driver with the USB Core.
usbh_uac_init() is the top-level function for loading the UAC Host Class Driver. It performs the following tasks:
Save Callbacks: Stores the user-provided callback functions and invokes the user’s init callback.
Save Parameters: Saves the user-configured frame count frame_cnt for the ring buffer.
Allocate Memory: Allocates memory buffers for control transfers and TX audio transmission.
Register Driver: Calls usbh_register_class() to register the UAC class driver with the USB Host Core.
Example:
intusbh_uac_init(usbh_uac_cb_t*cb,intframe_cnt){/* 1. Save the frame count param */uac->frame_cnt=frame_cnt;/* 2. Allocate memory */uac->audio_ctrl_buf=(u8*)usb_os_malloc(UBSH_UAC_AUDIO_CTRL_BUF_MAX_LEN);uac->isoc_tx_buf=(u8*)usb_os_malloc(USBH_UAC_ISOC_BUF_LENGTH);/* 3. Save the user callback and call the user's ``init`` callback */if(cb!=NULL){uac->cb=cb;if(cb->init!=NULL){ret=cb->init();if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"User init err %d\n",ret);returnret;}}}/* 4. Register class driver*/usbh_register_class(&usbh_uac_driver);returnHAL_OK;}
usbh_uac_deinit() is the top-level function used to unload the UAC host class driver, responsible for cleaning up resources.
Call usbh_uac_stop_play() to notify the UAC class driver to stop data transmission.
Callbacks are triggered when the USB Core detects the insertion or removal of a device matching the UAC class.
usbh_uac_cb_attach is a critical step in device enumeration, responsible for parsing interface descriptors and allocating pipe resources:
Find AC Interface: Parses and retrieves audio control information, including volume ranges and mute capabilities.
Find AS Interface: Parses and retrieves audio format information and endpoint descriptors.
Open Pipe: Allocates and opens the ISOC OUT pipe based on the retrieved descriptor information.
Initialize State Machine: Sets the initial state to UAC_STATE_GET_MUTE to prepare for fetching audio information.
Notify Application: Calls the user attach callback to inform the application layer of the connection status.
staticintusbh_uac_cb_attach(usb_host_t*host){/* 1. Parse descriptors to get AC and AS information */status=usbh_uac_parse_interface_desc(host);if(status){returnstatus;}/* 2. Open the pipe for steaming transfer */if(uac->as_isoc_out){as_itf=uac->as_isoc_out;as_itf->choose_alt_idx=0;pipe=&(as_itf->pipe);ep_desc=&(as_itf->itf_info_array[as_itf->choose_alt_idx].ep_desc);usbh_open_pipe(host,pipe,ep_desc);}/* 3. Initialize the state machine */uac->ctrl_state=UAC_STATE_GET_MUTE;/* 4. Notify the user layer */if((uac->cb!=NULL)&&(uac->cb->attach!=NULL)){uac->cb->attach();}returnHAL_OK;}
usbh_uac_cb_detach is called when the device is removed. It is responsible for notifying the upper-layer application that the device has been removed.
staticintusbh_uac_cb_detach(usb_host_t*host){/* 1. Notify the user layer */if((uac->cb!=NULL)&&(uac->cb->detach!=NULL)){uac->cb->detach();}/* 2. Reset xfer status */uac->xfer_state=UAC_STATE_IDLE;returnHAL_OK;}
The usbh_uac_cb_process callback is the core state machine handler for the UAC class on the Host side.
Unlike the Device side, which passively responds to requests, the Host driver must actively maintain the device state. Its primary responsibilities are managing the lifecycle state of the class driver and dispatching transfer tasks.
State Management and Scheduling
usbh_uac_cb_process manages the scheduling of control transfers (e.g., sample rate configuration) and data transfers based on the current class driver state.
State Enum
Description
Key Action
IDLE
Idle State
Waits for user commands or data transfer requests.
TRANSFER
Data Transferring
Dispatches tasks to specific TX/RX handlers based on pipe numbers.
ERROR
Error State
Attempts to Clear Feature on endpoints to restore communication.
Transfer Dispatch Example
When in the TRANSFER state, processing is dispatched to specific transfer handlers based on the pipe ID triggering the event.
Example:
caseUAC_STATE_TRANSFER:/* Distribute transmission tasks according to pipe numbers */if(event.msg.pipe_num==0){ret=usbh_uac_ctrl_setting(host,0);// Handle ctrl message transfer}break;
Error Recovery
If an error occurs during state processing, the driver attempts to send a Clear Feature request and reset to the IDLE state.
Example:
/* ... Error state ... */caseUAC_STATE_ERROR:/* Error recovery mechanism */ret=usbh_ctrl_clear_feature(host,0x00U);if(ret==HAL_OK){uac->xfer_state=UAC_STATE_IDLE;}break;
UAC data transfer is categorized into two types: Audio Control Transfer and Audio Streaming Transfer .
Audio Control Transfer
Responsible for managing the functional behavior of the audio device. The driver implements a state machine flow to handle configuration requests in the following sequence:
caseUAC_STATE_SET_OUT_ALT:ret=usbh_uac_process_set_out_alt(host);if(ret==HAL_OK){uac->ctrl_state=UAC_STATE_SET_OUT_FREQ;}elseif(ret!=HAL_BUSY){RTK_LOGS(TAG,RTK_LOG_ERROR,"OUT alt err\n");uac->ctrl_state=UAC_STATE_SET_OUT_FREQ;}break;
To ensure real-time performance and low latency for audio streams, the UAC Class Driver utilizes USB hardware interrupt mechanisms (Start of Frame SOF or Transfer Completed) to precisely trigger the scheduling of the next frame of data.
Completed Interrupt: Triggered when the current data has been successfully sent to the bus.
Compact Scheduling: When the endpoint transfer interval (bInterval) is 1 (transmission required every frame), the driver maximizes bandwidth utilization and minimizes latency by immediately filling and submitting the request for the next frame within the Completed callback of the previous frame.
SOF (Start Of Frame) Interrupt: Triggered at the start of each USB frame.
Interval Scheduling: When the endpoint transfer interval (bInterval) is greater than 1 (transmission every N frames), the driver uses the SOF interrupt counter to track frame numbers. Once the scheduled interval is reached, it immediately prepares and submits the next data transfer request.
Code Example (Completed Callback Logic):
staticusbh_class_driver_tusbh_uac_driver={.completed=usbh_uac_cb_completed,};staticintusbh_uac_cb_completed(usb_host_t*host,u8pipe_num){u32cur_frame=usbh_get_current_frame_number(host);if(pdata_ctrl->next_xfer==1){/* Check if this is the audio streaming pipe */if((uac->as_isoc_out)&&(pipe_num==uac->as_isoc_out->pipe.pipe_num)){pipe->xfer_state=USBH_EP_XFER_IDLE;/* If ring buffer has data, prepare next transfer */if(!usb_ringbuf_is_empty(&(pdata_ctrl->buf_manager))){/* Trigger next xfer logic: check if interval is met */if(usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame,1),pipe->frame_num)>=pipe->ep_interval){usbh_uac_isoc_out_process_xfer(host,cur_frame);}else{/* Wait for next SOF if interval not met yet */pipe->xfer_state=USBH_EP_XFER_WAIT_SOF;}}else{/* Buffer empty, go idle */pipe->xfer_state=USBH_EP_XFER_IDLE;}}}returnHAL_OK;}
Code Example (SOF Callback Logic):
staticusbh_class_driver_tusbh_uac_driver={.sof=usbh_uac_cb_sof,};/** * @brief SOF (Start of Frame) callback handler for UAC. * Responsible for scheduling ISOC transfers when bInterval > 1. */staticintusbh_uac_cb_sof(usb_host_t*host){u32cur_frame=usbh_get_current_frame_number(host);/* Check if audio streaming is active */if(pdata_ctrl->next_xfer==1){/* Check Condition 1: Standard Interval Check Has the time elapsed since the last transfer met the endpoint interval? *//* Check Condition 2: Pre-scheduling (Lookahead) If the previous transfer finished early (state is WAIT_SOF), check if the NEXT frame (cur_frame + 1) will hit the interval target. This allows preparing data slightly ahead of time. */if((usbh_get_elapsed_frame_cnt(host,pipe->frame_num)>=pipe->ep_interval)||((pipe->xfer_state==USBH_EP_XFER_WAIT_SOF)&&(usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame,1),pipe->frame_num)>=pipe->ep_interval))){/* Execute transfer if either condition is met */usbh_uac_isoc_out_process_xfer(host,cur_frame);}}returnHAL_OK;}
usbh_uac_write():Writes PCM data into the driver’s internal ring buffer. The class driver automatically fragments large data packets into smaller packets that fit the USB frame size based on the audio format.
usbh_uac_start_play():Initiates the playback process; the driver begins extracting data from the ring buffer and sending it.
The driver stack includes built-in encapsulation for core requests defined by the USB Audio Class 1.0 specification. The current implementation focuses on Audio Playback scenarios and enables control requests targeting Feature Units by default.
Developers can view the source code at {SDK}/component/usb/host/uac and extend it to support additional request types as needed.
Request Type
Note
Mute Control
Sends a SET_CUR request to the Mute Control Selector of a Feature Unit to mute/unmute audio.
Volume Control
Sends a SET_CUR request to the Volume Control Selector of a Feature Unit to set gain values.
During the device enumeration phase, the driver parses the configuration descriptor. When an Audio Streaming (AS) Interface is detected as active, the driver automatically requests the corresponding USB pipe resources.
Pipe Type
Usage Description
Control IN/OUT Pipe
Used for standard device requests (enumeration, configuration) and UAC class-specific requests (volume, sample rate settings).
Isochronous OUT Pipe
Belongs to the Active Alternate Setting (typically Alt 1) of the Audio Streaming (AS) Interface. Used by the Host to send PCM audio data streams (TX Data) to the UAC device.
This section details the process required to develop a complete USB UAC 1.0 Host application, covering driver initialization, hot-plug event handling, audio data writing mechanisms, and resource release strategies.
Before using the UAC 1.0 Host driver, hardware parameter configuration, callback registration, and core protocol stack initialization must be completed in the specified sequence.
Step-by-Step Description:
Hardware Configuration: Configure the USB speed mode (Full Speed) and interrupt/task priorities.
Callback Registration: Define user callback structures and mount handlers for various stages (connection, disconnection, data transmission).
/* * 1. Configure USB speed (Full Speed) ,isr priority and main task priority. */staticusbh_config_tusbh_cfg={.speed=USB_SPEED_FULL,.isr_priority=INT_PRI_MIDDLE,.main_task_priority=USBH_UAC_MAIN_THREAD_PRIORITY,.ext_intr_enable=USBH_SOF_INTR,};/* * Define USB user-level callbacks. */staticusbh_user_cb_tusbh_usr_cb={.process=usbh_uac_cb_process,/* USB callback to handle class-independent events in the application */};/* * 2. Define user callbacks for UAC events. */staticusbh_uac_cb_tusbh_uac_cfg={.init=usbh_uac_cb_init,/* USB init callback */.deinit=usbh_uac_cb_deinit,/* USB deinit callback */.attach=usbh_uac_cb_attach,/* USB attach callback */.detach=usbh_uac_cb_detach,/* USB detach callback */.setup=usbh_uac_cb_setup,/* USB setup callback */.isoc_transmitted=usbh_uac_cb_isoc_transmitted,/* Data transmission complet callback */};intret=0;/** * 3. Initialize USB host core driver with configuration. */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){return;}/* * 4. Initialize UAC class driver. */ret=usbh_uac_init(&usbh_uac_cfg,USBH_UAC_FRAME_CNT);if(ret!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();return;}
The system must possess a robust hot-plug handling mechanism to cope with the dynamic removal and re-insertion of UAC devices. The SDK provides standard state machine and callback mechanisms to notify the upper-layer application.
Handling Logic:
Detach: Triggers a callback to release a semaphore. The application thread captures this, executes de-initialization, and frees heap memory.
Attach: The USB Core detects the device and re-executes the enumeration and driver loading process.
/* USB detach callback */staticusbh_uac_cb_tusbh_uac_cfg={.detach=usbh_uac_cb_detach,};/* Callback executed in main task */staticintusbh_uac_cb_detach(void){RTK_LOGS(TAG,RTK_LOG_INFO,"DETACH\n");rtos_sema_give(usbh_uac_detach_sema);usbh_uac_is_ready=0;returnHAL_OK;}/* Thread Context: Handle the state machine */staticvoidusbh_uac_hotplug_thread(void*param){intret=0;UNUSED(param);for(;;){usb_os_sema_take(usbh_uac_detach_sema,USB_OS_SEMA_TIMEOUT);RTK_LOGS(TAG,RTK_LOG_INFO,"Hot plug\n");/* Stop transfer, release resource */usbh_uac_deinit();usbh_deinit();rtos_time_delay_ms(10);RTK_LOGS(TAG,RTK_LOG_INFO,"Free heap size: 0x%08x\n",usb_os_get_free_heap_size());/* Re-init */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){break;}ret=usbh_uac_init(&usbh_uac_cfg,USBH_UAC_FRAME_CNT);if(ret<0){usbh_deinit();break;}}}
The UAC Class Driver utilizes a Ring Buffer mechanism to buffer audio data generated by the upper-layer application and drives continuous data transmission via SOF (Start of Frame) or Transfer Complete interrupts.
Audio Write
The upper-layer application calls the usbh_uac_write() interface to fill PCM data into the ring buffer. If the buffer is full, this function will block and wait based on the configured timeout_ms.
staticintusbh_uac_write_ring_buf(usbh_uac_buf_ctrl_t*pdata_ctrl,u8*buffer,u32size,u32*written_len){u32written_size=handle->written;/* Fill it into the end of the data that was not completed last time to form a whole package */if(written_size){xfer_len=usbh_uac_next_packet_size(pdata_ctrl);can_copy_len=xfer_len-written_size;copy_len=size<can_copy_len?size:can_copy_len;usb_ringbuf_write_partial(handle,buffer,copy_len);offset+=copy_len;*written_len+=copy_len;if(size>=can_copy_len){size-=copy_len;usb_ringbuf_finish_write(handle);pdata_ctrl->sample_accum=pdata_ctrl->last_sample_accum;}else{return0;}}/* Fill the entire package in a cycle */do{if(usb_ringbuf_is_full(handle)){return1;}xfer_len=usbh_uac_next_packet_size(pdata_ctrl);if(size>=xfer_len){usb_ringbuf_add_tail(handle,buffer+offset,xfer_len);*written_len+=xfer_len;size-=xfer_len;offset+=xfer_len;pdata_ctrl->sample_accum=pdata_ctrl->last_sample_accum;}else{break;}}while(1);/* Write the remaining data at the end */if(size>0){if(usb_ringbuf_is_full(handle)){return1;}usb_ringbuf_write_partial(handle,buffer+offset,size);*written_len+=size;}return0;}/* Tansfer APi, used for Audio */u32usbh_uac_write(u8*buffer,u32size,u32timeout_ms){/* check usb status */if(usbh_uac_usb_status_check()!=HAL_OK){return0;}/* loop to write data to the ringbuffer */while(written_len<size&&pdata_ctrl->next_xfer){if(usb_ringbuf_is_full()){if(usbh_uac_wait_isoc_with_status_check(pdata_ctrl,timeout_ms)!=HAL_OK){break;}}try_len=size-written_len;just_written=0;usbh_uac_write_ring_buf(pdata_ctrl,buffer+written_len,try_len,&just_written);if(just_written>0){written_len+=just_written;last_zero=0;}else{//wait sema and retrylast_zero=1;}}returnwritten_len;}
Audio Output
The underlying driver automatically retrieves data from the ring buffer via interrupt callbacks and sends it to the USB bus.
SOF Interrupt: Triggered periodically (every 1ms). It checks if the current frame number reaches the transmission Interval. If so, it retrieves data from the RingBuffer and submits the transfer.
Commplete Interrupt: Triggered after the previous transfer completes. It checks if the RingBuffer has remaining data; if so, it schedules the transmission task for the next frame.
staticusbh_class_driver_tusbh_uac_driver={.sof=usbh_uac_cb_sof,.completed=usbh_uac_cb_completed,};staticvoidusbh_uac_isoc_out_process_xfer(usb_host_t*host,u32cur_frame){if(!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))){/* check valid data */pbuf=usb_ringbuf_get_head(&(pdata_ctrl->buf_list));if(pbuf&&pbuf->buf_len>0){pipe->frame_num=usbh_uac_frame_num_inc(cur_frame,1);pipe->xfer_buf=pbuf->buf;pipe->xfer_len=pbuf->buf_len;usbh_transfer_data(host,pipe);pipe->xfer_state=USBH_EP_XFER_BUSY;}}staticintusbh_uac_cb_sof(usb_host_t*host){/* this class right not just support isoc out */if(pdata_ctrl->next_xfer==1){/* check the condition for transmission */if((usbh_get_elapsed_frame_cnt(host,pipe->frame_num)>=pipe->ep_interval)||((pipe->xfer_state==USBH_EP_XFER_WAIT_SOF)&&(usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame,1),pipe->frame_num)>=pipe->ep_interval))){usbh_uac_isoc_out_process_xfer(host,cur_frame);}}returnHAL_OK;}staticintusbh_uac_cb_completed(usb_host_t*host,u8pipe_num){if(pdata_ctrl->next_xfer==1){if((uac->as_isoc_out)&&(pipe_num==uac->as_isoc_out->pipe.pipe_num)){usbh_uac_isoc_out_process_complete(host);if(!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))){/* trigger next xfer after binterval */if(usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame,1),pipe->frame_num)>=pipe->ep_interval){usbh_uac_isoc_out_process_xfer(host,cur_frame);}else{pipe->xfer_state=USBH_EP_XFER_WAIT_SOF;}}else{/* TX ISOC OUT token only when play*/pipe->xfer_state=USBH_EP_XFER_IDLE;}}}returnHAL_OK;}
When the system shuts down or a full reset of the USB stack is required, resources must be released strictly in the reverse order: Class Driver first, then Core Driver, to avoid memory leaks or pointer errors.
/* 1. Deinitialize UAC class driver first */usbh_uac_deinit();/* 2. Deinitialize USB host core driver */usbh_deinit();
This section uses the scenario of Ameba connecting to a USB Headset for audio playback to demonstrate how to configure the Ameba development board as a USB UAC 1.0 Host and output audio through an external standard USB headset.
Default Behavior: Identify UAC Device -> Configure as 48 kHz / 16-bit / 2-channels -> Loop playback of a preset PCM audio clip.
Path: {SDK}/component/example/usb/usbh_uac, This provides a complete design reference for developers creating products such as voice announcement systems or audio gateways.
Note
This example requires XDK (Extended Development Kit) support. For XDK download, please refer to SDK Download.
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_uac-p
Confirmation of Menuconfig configuration
If compilation fails, please execute ameba.pymenuconfig and confirm that USBHUAC has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Host) --->
[*] UAC
The USB Video Class (UVC) protocol defines the industry standard for transmitting video data over a USB interface. In Host mode, the Ameba platform can identify and drive external USB camera devices through this protocol.
The UVC host protocol stack of the Ameba platform focuses on the Video Capture scenario. It integrates the USB-IF compliant UVC 1.5 protocol, abstracting external USB cameras as system local video input interfaces. This solution supports Plug-and-Play, can seamlessly interface with the system’s built-in video processing framework, and provides convenient, flexible visual perception extension capabilities for devices.
The Ameba UVC Host driver aims to provide efficient and compatible video input capabilities. The main features are as follows:
Extensive Device Compatibility: Supports UVC-compliant USB camera devices (such as USB webcams, industrial cameras, USB microscopes).
Automatic Enumeration Configuration: Automatically parses device descriptors, identifies video streaming interfaces and control interfaces, and establishes Isochronous transmission channels.
Mainstream Video Format Support:
Compressed formats: MJPEG, H.264
Uncompressed formats: YUV
Deep System Integration: Exposes a unified API interface to upper-layer applications, shielding underlying USB transmission details.
Hot-Plug Support: Supports Plug-and-Play and dynamic removal of USB peripherals without restarting the system.
As a USB Host, Ameba is responsible for enumerating USB cameras, parsing video descriptors, and establishing a stable image data stream. This solution is suitable for embedded applications that have requirements for visual acquisition while pursuing low power consumption and rapid integration, such as:
Intelligent Monitoring and Security: Ameba captures images in real-time via USB cameras, combined with network transmission or local storage, used for home monitoring, doorbell peepholes, or industrial site surveillance.
Visual Recognition Terminals: As a front-end acquisition device for edge computing nodes, it acquires image data and passes it to subsequent AI algorithms for face recognition, QR code scanning, or object detection.
Video Calling Devices: Combined with Wi-Fi or cellular network modules, it captures user video streams through generic USB cameras to realize low-cost VoIP video intercom functions.
The UVC (USB Video Class) protocol defines standard interfaces within the USB specification framework for implementing Control Management and Video Stream Transmission between a host and video capture devices. The host driver establishes video data channels through this protocol to achieve real-time camera preview, recording, and parameter adjustment.
USB-IF has officially released the UVC class base protocol and specifications for multiple Payload formats. Please refer to the following core documents during development:
The definitions of general UVC (USB Video Class) technical terms used in this document are as follows:
Term
Description
VC Interface (Video Control Interface)
Video Control Interface. As the core control center of the UVC device, it manages the topology of the video device (such as the connection relationship between Units and Terminals). The host sends control requests via this interface, such as adjusting brightness, contrast, or performing Pan/Tilt/Zoom control.
VS Interface (Video Streaming Interface)
Video Streaming Interface. Responsible for the actual transmission of video payload data, usually using Isochronous or Bulk pipes. Each VS Interface contains specific video format information (such as YUV, MJPEG, H.264) and related frame descriptors.
Input Terminal (IT)
Input Terminal. The entry point where the video data stream enters the UVC function topology. Common input terminals include Camera Sensors or Composite Video Input interfaces. It represents the physical source of data.
Output Terminal (OT)
Output Terminal. The exit point where the video data stream leaves the UVC function topology. The most common output terminal is the USB Streaming Terminal, indicating that data will be sent to the host via the USB bus.
Processing Unit (PU)
Processing Unit. A processing node located after the Input Terminal, used to adjust the video image itself. It provides control capabilities over image quality, such as Brightness, Contrast, Hue, Saturation, and Sharpness.
Extension Unit (XU)
Extension Unit. A functional module that the UVC specification allows manufacturers to customize. Through XU, manufacturers can define specific control commands outside the standard UVC specification and access them on the host side via matching drivers or applications.
Probe & Commit Control
Negotiation and Commit Control. This is a critical mechanism in the process of establishing a video stream. The host first sends a “Probe” request to query the bandwidth and parameters supported by the device. After reaching an agreement, it sends a “Commit” request to lock the configuration before the video stream transmission can be started.
Payload Header
Payload Header. Header information in UVC video stream data packets, containing key synchronization information such as frame toggle (Frame ID), timestamp (PTS/SCR), and error flags.
The UVC Host protocol stack adopts a layered architecture design, aiming to decouple the USB transport layer from the upper-layer video application or multimedia framework.
Located at the top layer of the architecture, responsible for specific business logic processing. Includes video preview, recording applications, or AI algorithms and network streaming services based on video streams.
Video Middleware
Acts as an abstraction layer connecting the upper and lower layers. It provides a unified data acquisition interface upwards for the application layer, shielding underlying differences; it is responsible for video stream encoding/decoding processing, format conversion, and buffer queue management.
UVC Class Driver
The core intermediate layer, implementing behaviors defined by the UVC specification:
Topology Parsing: Parses the internal topology (Unit/Terminal) of the Video Control Interface (VC) and format descriptors of the Video Streaming Interface (VS).
Stream Negotiation: Implements the Probe and Commit processes to negotiate parameters such as resolution, frame rate, and bandwidth.
Frame Reassembly and Submission: Parses the UVC Payload Header, handles frame start/end indicators (FID/EOF), reassembles scattered USB packets into complete video frames, and submits them to the middleware layer frame by frame.
USB Core & HCD (Host Controller Driver)
Underlying drivers responsible for handling USB standard enumeration, isochronous pipe management, and scheduling of underlying physical data transmission.
In addition to standard USB descriptors (such as Device Descriptor, Configuration Descriptor, Endpoint Descriptor), UVC devices also define Class-Specific Descriptors.
These descriptors are classified into Class-Specific Video Control Interface Descriptors (VC) and Class-Specific Video Streaming Interface Descriptors (VS) based on the interface they belong to.
Descriptor Topology
Device Descriptor
└── Identifies basic device information (USB Version 2.00, Composite Device)
Configuration Descriptor
├── Contains total length, power supply (500mA), etc.
│
├── Interface Association Descriptor (IAD)
│ └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│ ├── Class-Specific VC Header (declares UVC version, clock frequency)
│ ├── Class-Specific Descriptor Collection (Topology)
│ │ ├── Input Terminal (Camera)
│ │ ├── Processing Unit
│ │ ├── Extension Unit (Vendor Specific Controls)
│ │ └── Output Terminal (USB Streaming)
│ └── Standard Endpoint Descriptor (Interrupt IN for Status)
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (negotiation only, no data endpoint)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ ├── Class-Specific VS Header
│ ├── Format Descriptor
│ │ └── Frame Descriptor (various resolutions and frame rates)
│ ├── Format Descriptor
│ │ └── Frame Descriptor (various resolutions and frame rates)
│ ├── Still Image Frame Descriptor
│ └── Color Matching Descriptor
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ └── Standard Endpoint Descriptor (ISO IN endpoint, e.g., Low Bandwidth)
│
├── Alternate Setting 2
│ ...... Can configure multiple different settings as needed (e.g., Medium to High Bandwidth)
│
└── ...... (Other Alternate Settings for different packet sizes)
Device Qualifier Descriptor
└── Device information while running in another speed mode (e.g., High Speed vs Full Speed capability)
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode
│
├── Interface Association Descriptor (IAD)
│ └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│ └── Class-Specific Descriptor Collection (Same Topology as main configuration)
│ ├── Input Terminal
│ ├── Processing Unit
│ ├── Extension Unit
│ └── Output Terminal
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (negotiation only)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ ├── Class-Specific VS Header
│ ├── Format Descriptor
│ │ └── Frame Descriptor
│ └── Color Matching Descriptor
│
├── Alternate Setting 1: Data transfer active state
│ ├── Standard Interface Descriptor (Streaming Class)
│ └── Standard Endpoint Descriptor (ISO IN endpoint)
│
└── ...... (Other alternate settings typically available in other speed modes)
UVC Video Control (VC) Interface
Video Control Interface Descriptor
Interface Header Descriptor
├── bLength : 1 byte → Total descriptor length (13 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (VC_HEADER)
├── bcdUVC : 2 bytes → Video Class Specification Release Number (0x0100 = 1.00)
├── wTotalLength : 2 bytes → Total number of bytes for all VC descriptors
├── dwClockFreq : 4 bytes → Clock frequency in Hz (e.g., 0x02DC6C00 = 48 MHz)
├── bInCollection : 1 byte → Number of VideoStreaming interfaces
└── baInterfaceNr(1) : 1 byte → Interface number of the first VideoStreaming interface (0x01)
Processing Unit Descriptor
├── bLength : 1 byte → Total descriptor length (11 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x05 (VC_PROCESSING_UNIT)
├── bUnitID : 1 byte → Unique ID of this Unit (0x02)
├── bSourceID : 1 byte → ID of the source connected to this unit (0x01 = Camera IT)
├── wMaxMultiplier : 2 bytes → Max digital zoom multiplier
├── bControlSize : 1 byte → Size of bmControls (2 bytes)
└── bmControls : 2 bytes → Bitmap of supported image controls
• D0: Brightness
• D1: Contrast
• D2: Hue
• D3: Saturation
• D4: Sharpness
• D6: White Balance Temperature
Extension Unit Descriptor
Extension Unit Descriptor
├── bLength : 1 byte → Total descriptor length (29 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x06 (VC_EXTENSION_UNIT)
├── bUnitID : 1 byte → Unique ID of this Unit (0x03)
├── guidExtensionCode : 16 bytes→ Vendor-specific GUID (e.g., {0FB885C3-...})
├── bNumControls : 1 byte → Number of controls in this XU (0x05)
├── bNrInPins : 1 byte → Number of input pins (0x01)
├── baSourceID[1] : 1 byte → ID of the source connected (0x02 = PU)
├── bControlSize : 1 byte → Size of bmControls (4 bytes)
└── bmControls : 4 bytes → Bitmap of supported vendor controls
Output Terminal Descriptor
Output Terminal Descriptor
├── bLength : 1 byte → Total descriptor length (9 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x03 (VC_OUTPUT_TERMINAL)
├── bTerminalID : 1 byte → Unique ID of this terminal (0x05)
├── wTerminalType : 2 bytes → 0x0101 (TT_STREAMING)
├── bAssocTerminal : 1 byte → Associated Input Terminal ID
├── bSourceID : 1 byte → ID of the connected Source (0x04 = XU)
└── iTerminal : 1 byte → String descriptor index
UVC Video Stream (VS) Interface
Video Stream Interface Descriptor
Class-Specific VS Input Header Descriptor
├── bLength : 1 byte → Total descriptor length (15 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (VS_INPUT_HEADER)
├── bNumFormats : 1 byte → Number of video formats supported (0x02)
├── wTotalLength : 2 bytes → Total length of all VS specific descriptors
├── bEndpointAddress : 1 byte → Address of the ISO IN endpoint (0x81)
├── bmInfo : 1 byte → Capabilities (0x00)
├── bTerminalLink : 1 byte → ID of the Output Terminal in VC interface (0x05)
├── bStillCaptureMethod: 1 byte → Method of still image capture (0x02)
├── bTriggerUsage : 1 byte → Trigger usage (0x00)
├── bControlSize : 1 byte → Size of control field (1 byte)
└── bmaControls(n) : n bytes → Controls for each format
Video Stream Format Type Descriptor
Video Streaming Format Type Descriptor(MJPEG)
├── bLength : 1 byte → Total descriptor length (11 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x06 (VS_FORMAT_MJPEG)
├── bFormatIndex : 1 byte → Index of this format (0x01)
├── bNumFrameDescriptors: 1 byte → Number of frame descriptors (0x09)
├── bmFlags : 1 byte → Characteristics (0x01 = Fixed Sample Size)
├── bDefaultFrameIndex : 1 byte → Default frame index (0x01)
├── bAspectRatioX : 1 byte → X dimension of aspect ratio
├── bAspectRatioY : 1 byte → Y dimension of aspect ratio
├── bmInterlaceFlags : 1 byte → Interlace information (0x00 = Progressive)
└── bCopyProtect : 1 byte → Duplication restrictions (0x00)
Video Streaming Format Type Descriptor(Uncompressed)
├── bLength : 1 byte → Total descriptor length (27 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x04 (VS_FORMAT_UNCOMPRESSED)
├── bFormatIndex : 1 byte → Index of this format (0x02)
├── bNumFrameDescriptors: 1 byte → Number of frame descriptors
├── guidFormat : 16 bytes→ GUID identifying the format (YUY2: {32595559-...})
└── bBitsPerPixel : 1 byte → Bits per pixel (0x10 = 16 bits)
Video Stream Frame Type Descriptor
Video Streaming Frame Type Descriptor (MJPEG)
├── bLength : 1 byte → Total descriptor length (e.g., 34 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x07 (VS_FRAME_MJPEG)
├── bFrameIndex : 1 byte → Index of this frame (0x01)
├── bmCapabilities : 1 byte → Still image support, etc.
├── wWidth : 2 bytes → Frame Width (e.g., 0x0A20 = 2592)
├── wHeight : 2 bytes → Frame Height (e.g., 0x0798 = 1944)
├── dwMinBitRate : 4 bytes → Min Bit Rate (bps)
├── dwMaxBitRate : 4 bytes → Max Bit Rate (bps)
├── dwMaxVideoFrameBuf : 4 bytes → Max Frame Buffer Size (bytes)
├── dwDefaultFrameInterval: 4 bytes→ Default frame interval in 100ns units
├── bFrameIntervalType : 1 byte → 0 = Continuous, Non-0 = Discrete
└── dwFrameInterval(n) : 4×n bytes→ Discrete frame intervals supported (e.g., 33ms for 30fps)
For detailed field definitions, please refer to the official USB-IF UVC (USB Video Class) 1.5 protocol documentation.
Note
Note: Confirm Device Capabilities at Current Speed (FS vs HS)
Please be aware that cameras often present different descriptor structures in Full Speed and High Speed modes:
Format Support Differences: Due to bandwidth limitations, certain uncompressed formats (like YUY2) may only be visible in High Speed mode. In Full Speed, the device might only support MJPEG or extremely low-resolution YUY2.
Descriptor Variations: The aforementioned VS_FRAME_UNCOMPRESSED descriptors may not exist at all when connected at Full Speed, or parameters such as resolution and frame rate may change significantly.
The UVC Host driver controls device behavior by sending the following requests via control pipe 0. These requests usually target specific Unit IDs or Interfaces.
Request Name
Requirement
Description
SET_CUR
Mandatory
Sets the current attribute value. Used for Probe/Commit negotiation, or controlling PU attributes like brightness and contrast.
GET_CUR
Mandatory
Gets the current attribute value. Reads negotiated parameters or current device status.
GET_MIN / GET_MAX
Optional
Gets the adjustable range of an attribute. The host driver uses this range to limit input values from the application layer.
Before officially starting video stream transmission (Stream On), the host and device must strictly follow the UVC state machine to negotiate parameters and bandwidth; otherwise, the device may refuse to start the video stream. According to the official spec, this process consists of two main phases: Probe (Three-step Handshake) and Commit.
Stream Negotiation Timing Diagram
Detailed Core Steps
PROBE1: Initiate Negotiation (SET_CUR)
The host sends a SET_CUR(PROBE) request to the Video Streaming Interface (VS Interface), carrying the desired video parameter structure.
Purpose: The host proposes “I want this resolution, frame rate, and format”.
PROBE2: Get Feedback (GET_CUR)
The host sends a GET_CUR(PROBE) request to read back the actual parameters supported by the device.
Device Correction: If the parameters requested by the host are not supported, the device returns the closest corrected parameters.
Key Data: In the returned structure, the dwMaxPayloadTransferSize field is calculated and filled by the device, which is the basis for subsequent bandwidth selection.
PROBE3: Final Confirmation (SET_CUR)
The host sends the parameters read back in step 2 (which may have been corrected) to the device again via SET_CUR(PROBE).
Purpose: Both sides synchronize states, the host confirms acceptance of the device’s correction suggestions, and ends the negotiation phase.
COMMIT: Commit to Effect (SET_CUR)
After parameters are agreed upon, the host sends a SET_CUR(COMMIT) request.
Purpose: Notifies the device that “parameters are determined”.
Note
Key Parameters
In the Probe/Commit process, the host mainly relies on the following three core fields to determine the video stream configuration:
bFormatIndex: Format Index
bFrameIndex: Frame Index
dwFrameInterval: Frame Interval
Other Parameters
Whether other parameters in the structure (such as wCompWindowSize, wCompQuality, etc.) are effective or adjustable depends entirely on the actual support of the specific device. For most standard devices, usually only the above three core parameters need attention, and other parameters can maintain the default values returned by the device.
For the complete definition of the Probe and Commit Control structure and details of all fields, please refer to the official spec.
Stream Bandwidth Matching and Activation (Stream Activation)
When parameter negotiation is complete, the host needs to select appropriate hardware interface settings based on the bandwidth requirements fed back by the device to officially open the transmission pipe.
Stream Bandwidth Allocation Diagram
Match Bandwidth
The driver will traverse all Alternate Settings of the video streaming interface based on the dwMaxPayloadTransferSize value obtained during the negotiation phase.
Set Interface
Once a suitable Alternate Setting is found, the host sends the standard USB request SET_INTERFACE. This step marks the formal reservation of USB Isochronous Transfer bandwidth, and the video stream transmission begins immediately.
Note
Bandwidth Matching Principle
In isochronous transfer mode, bandwidth selection is crucial. The driver needs to follow the principle of sufficient and minimal:
Search Target: Among all Alternate Settings, look for settings where wMaxPacketSize (endpoint maximum packet size) is greater than or equal to the negotiated value dwMaxPayloadTransferSize.
Optimal Choice: Among all settings that meet the above conditions, choose the one with the smallest wMaxPacketSize.
If bandwidth is insufficient, it will cause data packet truncation, resulting in screen corruption or frame loss. If bandwidth is excessive, it will occupy valuable periodic USB bus bandwidth, potentially causing other devices on the bus (such as USB audio) to fail due to insufficient bandwidth.
UVC video data is not a pure raw data stream but is encapsulated in data packets with a Payload Header. The host driver must first parse and strip this header to obtain the valid video Payload.
In the UVC specification, video data transmission formats are mainly divided into two categories: Frame-based and Stream-based. These two modes determine how the host parses video data and processes the Payload Header.
Frame-based
This is the most common mode, applicable to MJPEG, Uncompressed (YUV/NV12), and other formats.
Characteristics: Video data is strictly divided into individual independent images (frames).
Transmission Logic: The host driver focuses on “frame boundaries”, assembling a complete frame by detecting the FID toggle or EOF flag in the Payload Header.
Stream-based
Mainly used for compressed stream formats like H.264/H.265.
Characteristics: Data is treated as a continuous byte stream without a strict physical “frame” boundary concept (or the boundary is handled internally by the decoder).
Transmission Logic: The host driver is mainly responsible for transporting the data stream, usually not relying on the Payload Header to determine the start or end of a frame, but passing the data directly to the upper-layer application or decoder to parse the content.
Note
Engineering Practice: Actual Behavior of Stream-based Devices
Although the UVC specification states that Stream-based formats may not use FID and EOF to delimit boundaries, in actual engineering applications:
Device Side (Camera):
Even when outputting Stream-based formats like H.264/H.265, the vast majority of manufacturers still try to follow Frame-based rules as much as possible, marking the boundaries of data blocks (such as NAL Units or frame slices) by toggling FID or setting EOF.
Host Side (Host):
To simplify logic, most generic UVC drivers (such as Linux uvcvideo, Windows system drivers) default to uniformly slicing data packets based on FID jumps or EOF flags.
Tip
Engineering Practice: Host Driver Data Processing Mechanism
In actual engineering implementation, the UVC Host Driver adopts a unified transmission processing strategy for Frame-based and Stream-based formats, without making special distinctions for stream transmission. Its core processing logic is as follows:
Format Independence of Transport Layer:
The driver layer is only responsible for data transport and reassembly and is not aware of the specific video encoding format or stream type. Regardless of the data type declared by the device descriptor, the driver treats it as a generic data payload.
Unified Packet Assembly Mechanism:
The driver strictly relies on the FID (Frame Identifier) state toggle and EOF (End of Frame) flag bit in the Payload Header for data delimitation. The driver assembles data packets belonging to the same logical sequence into a complete Payload Buffer and submits it to the user space as one frame at the driver level.
Separation of Parsing Responsibility:
Parsing of data content is the responsibility of the Application Layer.
For example, for Stream-based formats like H.264, the Buffer submitted by the driver may contain multiple NAL Units or partial frame data, and the application layer needs to parse the stream data structure itself to obtain the actual video frames.
This section details the internal implementation details of the USB UVC host driver, including driver architecture, video stream management, support for class-specific requests, and pipe resource allocation schemes.
The USB Host UVC driver stack is based on a modular design, achieving efficient interaction between the upper-layer application and the USB hardware controller through a layered architecture. This architecture ensures stable capture and processing of high-bandwidth video data and provides a flexible frame buffer management mechanism.
Its core architecture and data flow are shown in the figure below:
Divided by functional responsibilities, it consists of the following core modules:
Responsible for buffer management and extraction of Video Frame data.
As the interface layer between the USB driver and the User Application, it manages the final output of video data:
Parameter Configuration: Provides the usbh_uvc_set_param() interface, allowing the application layer to set resolution, frame rate, and video format (MJPEG/H.264/YUV).
Data Acquisition: Provides the usbh_uvc_get_frame() interface, through which the application layer retrieves fully assembled video frames from the Ready Queue.
The UVC host-side driver uses the UVC Class Driver to handle standard protocol handshakes, descriptor topology parsing, and video stream maintenance.
This module strictly follows the USB Video Class 1.1/1.5 protocol specifications, implementing the core business logic for interaction between the host and the UVC device. Its main responsibilities include:
Enumeration and Interface Binding: Responsible for parsing the Video Control Interface (VC Interface) and Video Streaming Interface (VS Interface), and establishing the internal UVC topology (such as Unit and Terminal).
Format Negotiation (Probe & Commit): Before the video stream starts, executes the standard Probe/Commit process to negotiate resolution, frame rate, and optimal bandwidth settings with the device.
Dynamic Bandwidth Allocation: Based on negotiation results, automatically selects the best matching Alt Setting and requests corresponding Isochronous pipe resources.
Responsible for USB protocol stack core logic and state machine management.
State Machine Maintenance: Manages UVC-specific state transitions, including UVC_STATE_CTRL (control transfer processing), UVC_STATE_TRANSFER (data transfer processing), and state migration during the stream startup process.
Event Processing: Responds to underlying transfer completion events, coordinating the timing of control transfers and data stream transfers.
Dynamic Hot-Plug: Handles device connection (Attach) and removal (Detach) events, automatically releasing stream buffers and pipe resources.
Responsible for the parsing and reassembly of raw USB data packets (Stream Decoding):
URB Processing: Receives URB (USB Request Block) data packets reported by the bottom layer and parses the UVC Payload Header.
Frame Assembly: Handles FID (Frame ID) toggle logic, strips protocol headers, and fills valid video payloads into the Frame Buffer.
Error Detection: Detects packet loss or Error Bits during transmission to ensure video frame integrity. If hardware acceleration is enabled, this module interfaces directly with the hardware decoder.
This driver stack follows the USB Video Class specification, encapsulating the implementation and sending process of core Class-Specific Requests.
The driver layer mainly implements support for Video Probe Control and Commit Control. Source code path: {SDK}/component/usb/host/uvc
Class-Specific Request Type
Remarks
SET_CUR
Sets the current attribute. Mainly used in Probe and Commit phases to send desired video stream parameters (e.g., dwFrameInterval, bFormatIndex) to the device.
GET_CUR
Gets the current attribute. Reads the currently active configuration parameters from the device, used to verify settings or retrieve current status.
The UVC host driver parses the configuration descriptor during the device enumeration phase (usbh_uvc_attach), automatically identifies Video Control and Video Streaming interfaces, and requests corresponding pipe resources based on Alt Settings.
Pipe Type
Description
Control IN/OUT Pipe
Default Control Pipe 0 (EP0). Used for sending standard requests and UVC specific requests (e.g., Video Probe and Commit Control).
Isochronous IN Pipe
Belongs to the Video Streaming Interface (VS Interface). Used for receiving high-bandwidth video payload data. The driver supports automatically selecting the appropriate Packet Size (MPS) and transfer mode based on USB speed (Full/High Speed).
This section details the complete development process of the UVC (USB Video Class) Host driver, covering driver initialization, hot-plug management, video stream control and data processing, as well as example descriptions for different application scenarios.
Before using the UVC Host driver, a configuration structure must be defined and callback functions registered, followed by calling core interfaces in sequence to start the USB host controller and the UVC class driver.
Step Description:
Hardware Configuration: Set the USB speed mode and related interrupt priorities.
Callback Registration: Define the usbh_uvc_cb_t structure and mount handler functions for various stages (initialization, connection, disconnection, parameter setting completion).
Core Initialization: Call usbh_init() to initialize the USB core stack.
Class Driver Loading: Call usbh_uvc_init() to initialize the UVC class driver.
/* * 1. Configure USB speed, ISR priority, and main task priority. */staticusbh_config_tusbh_cfg={.speed=USB_SPEED_HIGH,.ext_intr_enable=USBH_SOF_INTR,.isr_priority=INT_PRI_MIDDLE,.main_task_priority=CONFIG_USBH_UVC_MAIN_THREAD_PRIORITY,.tick_source=USBH_SOF_TICK,};/* * 2. Define USB user-level callbacks. */staticusbh_user_cb_tusbh_usr_cb={.process=uvc_cb_process,};/* * 3. Configure UVC user-level HW priority. */staticusbh_uvc_ctx_tuvc_cfg={#if USBH_UVC_USE_HW.hw_isr_pri=CONFIG_USBH_UVC_HW_IRQ_PRIORITY,#endif};/* * 4. Define user callbacks for UVC events. */staticusbh_uvc_cb_tuvc_cb={.init=uvc_cb_init,.deinit=uvc_cb_deinit,.attach=uvc_cb_attach,.detach=uvc_cb_detach,.setup=uvc_cb_setup,.setparam=uvc_cb_setparam,};intret=0;/* * 5. Initialize USB host core driver with configuration. */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){gotofree_sema_exit;}/* * 6. Initialize UVC class driver. */ret=usbh_uvc_init(&uvc_cfg,&uvc_cb);if(ret!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();gotousb_deinit_exit;}
Listen for UVC camera connection and disconnection by registering attach and detach callback functions in usbh_uvc_cb_t.
In the example code, a Semaphore mechanism is used to synchronize states:
Attach: When a camera is inserted and successfully enumerated, the attach callback is triggered, releasing uvc_attach_sema to notify the main thread to create the video capture task.
Detach: When the camera is removed, the detach callback is triggered, releasing uvc_detach_sema to trigger the hot-plug management thread to perform resource cleanup and re-initialization.
/* USB detach callback */staticusbh_uvc_cb_tuvc_cb={.detach=uvc_cb_detach,};/* Callback executed when device is removed */staticintuvc_cb_detach(void){RTK_LOGS(TAG,RTK_LOG_INFO,"DETACH\n");rtos_sema_give(uvc_detach_sema);usbh_uvc_is_ready=0;returnHAL_OK;}/* Thread Context: Handle the state machine for Hotplug */staticvoiduvc_hotplug_thread(void*param){intret=0;UNUSED(param);for(;;){/* Wait for detach signal */if(rtos_sema_take(uvc_detach_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){rtos_time_delay_ms(100);/* Stop transfer, release resource */usbh_uvc_deinit();usbh_deinit();rtos_time_delay_ms(10);RTK_LOGS(TAG,RTK_LOG_INFO,"Free heap: 0x%x\n",rtos_mem_get_free_heap_size());/* Re-init Host Core */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"Init USBH fail\n");break;}/* Re-init UVC Class */ret=usbh_uvc_init(&uvc_cfg,&uvc_cb);if(ret<0){RTK_LOGS(TAG,RTK_LOG_ERROR,"Init UVC fail\n");usbh_deinit();break;}}}RTK_LOGS(TAG,RTK_LOG_ERROR,"Hotplug thread fail\n");rtos_task_delete(NULL);}/* Main entry task to initialize USB and wait for connection */staticvoidexample_usbh_uvc_task(void*param){rtos_task_tuvc_task;rtos_task_thotplug_task;intret=0;/* ... Initialization of semaphores and mutexes ... *//* Init USB Host Core */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){gotofree_sema_exit;}/* Init UVC Class Driver */ret=usbh_uvc_init(&uvc_cfg,&uvc_cb);if(ret!=HAL_OK){usbh_deinit();gotousb_deinit_exit;}/* Create Hotplug detection thread */ret=rtos_task_create(&hotplug_task,"uvc_hotplug_thread",uvc_hotplug_thread,NULL,1024U,CONFIG_USBH_UVC_HOTPLUG_THREAD_PRIORITY);if(ret!=RTK_SUCCESS){gotousbh_uvc_deinit_exit;}/* Wait for device attach callback to release semaphore */if(rtos_sema_take(uvc_attach_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){/* Create the main UVC test/streaming thread */.......}gotoexample_exit;/* ... Error handling labels ... */.......}
After the UVC device is successfully enumerated, the host needs to configure video parameters and start the video stream, then periodically acquire image frame data.
Parameter Configuration (Set Parameters)
Before starting the video stream, use usbh_uvc_set_param() to set the desired format (MJPEG/H264/YUV), resolution, and frame rate. After the setting request is issued, wait for the uvc_setparam_sema semaphore to confirm that the parameter set has been accepted by the device.
Start Video Stream (Stream On)
Call usbh_uvc_stream_on() to enable video transmission for the specified interface. The device will start sending ISOC data packets on the USB bus.
Frame Data Acquisition and Processing
The driver internally maintains a malloc-based Frame Pool with a size defined by CONFIG_USBH_UVC_FRAME_BUF_SIZE. The application layer needs to follow the “Get Frame -> Process -> Put Frame” flow in a loop:
This function internally implements a Drop Oldest Frame strategy. To ensure the application layer always gets the Newest image, if the consumer’s processing speed is slower than the production speed, the driver will automatically discard old frames in the queue that have not yet been read, ensuring the frame buffer pool can retrieve the current latest frame.
Process:
Consume the data at the application layer (e.g., copy for display, save to file, upload to network, or just perform statistical analysis).
Note
Data Processing Description:
The UVC driver, as a producer, delivers data to the consumer in units of Frames (i.e., aggregating all Payloads of an image). The driver layer does not analyze the specific data content inside the Payload, so the application layer needs to implement logic for parsing and processing the Payload itself according to the specific video format.
Put Frame:
Regardless of how the application layer processes the frame (even if it just counts the data size or decides to discard the frame), usbh_uvc_put_frame()must be called after processing is complete.
Note
This function returns the buffer to the internal frame pool of the driver. Otherwise, the frame pool will be exhausted, causing failure to acquire new data subsequently.
Stop Video Stream (Stream Off)
When video data is no longer needed or before disconnecting, call usbh_uvc_stream_off(). This notifies the device to stop ISOC transmission, releases bus bandwidth, and resets related internal states of the driver.
/* Define user callbacks for UVC events */staticusbh_uvc_cb_tuvc_cb={.setup=uvc_cb_setup,.setparam=uvc_cb_setparam,};/* Define USB user-level callbacks */staticusbh_user_cb_tusbh_usr_cb={.process=uvc_cb_process,};/* Define USB user-level setup callback */staticintuvc_cb_setup(void){RTK_LOGS(TAG,RTK_LOG_INFO,"SETUP\n");usbh_uvc_is_ready=1;rtos_sema_give(uvc_start_sema);returnHAL_OK;}/* Define USB user-level setparam callback */staticintuvc_cb_setparam(void){RTK_LOGS(TAG,RTK_LOG_INFO,"SETPARAM\n");rtos_sema_give(uvc_setparam_sema);returnHAL_OK;}/* Define USB user-level process callback */staticintuvc_cb_process(usb_host_t*host,u8msg){UNUSED(host);switch(msg){caseUSBH_MSG_DISCONNECTED:usbh_uvc_is_ready=0;break;caseUSBH_MSG_CONNECTED:break;default:break;}returnHAL_OK;}/* Main UVC Test Thread */staticvoiduvc_test(void*param){usbh_uvc_frame_t*buf;intret=0;/* Wait for the device to be ready (Enumeration Complete) */if(rtos_sema_take(uvc_start_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){/* 1. Configure UVC Parameters *//* Set the desired format, resolution, and frame rate */uvc_s_ctx.fmt_type=CONFIG_USBH_UVC_FORMAT_TYPE;// e.g., MJPEGuvc_s_ctx.width=CONFIG_USBH_UVC_WIDTH;// e.g., 1080uvc_s_ctx.height=CONFIG_USBH_UVC_HEIGHT;// e.g., 720uvc_s_ctx.frame_rate=CONFIG_USBH_UVC_FRAME_RATE;// e.g., 30uvc_s_ctx.frame_buf_size=CONFIG_USBH_UVC_FRAME_BUF_SIZE;/* Trigger the UVC parameter setting process */ret=usbh_uvc_set_param(&uvc_s_ctx,CONFIG_USBH_UVC_IF_NUM_0);if(ret!=RTK_SUCCESS){RTK_LOGS(TAG,RTK_LOG_ERROR,"Set param req failed: %d\n",ret);gotoexit;}/* Wait for the semaphore indicating parameter setting is actually completed */if(rtos_sema_take(uvc_setparam_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){RTK_LOGS(TAG,RTK_LOG_INFO,"Set params OK\n");}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"Set params timeout\n");gotoexit;}/* ... Initialize consumer tasks (e.g., VFS thread or HTTPC thread) ... *//* 2. Start Video Stream */RTK_LOGS(TAG,RTK_LOG_INFO,"Stream on\n");ret=usbh_uvc_stream_on(&uvc_s_ctx,CONFIG_USBH_UVC_IF_NUM_0);if(ret)gotoexit;/* 3. Main Capture Loop */while(/* Condition: e.g., Loop Count < MAX or Device Connected */){if(!usbh_uvc_is_ready)break;// Device disconnected// 3.1 Get Frame from USB Stack/* Retrieve a filled frame buffer from the UVC driver */buf=usbh_uvc_get_frame(CONFIG_USBH_UVC_IF_NUM_0);if(buf==NULL){/* Frame not ready yet, wait and retry */rtos_time_delay_ms(10);continue;}// 3.2 Process the Frame Data/* CRITICAL: Buffer overflow detected! *//* This means the camera sent a frame larger than our allocated buffer. *//* ACTION: Please increase 'CONFIG_USBH_UVC_FRAME_BUF_SIZE' in example_usbh_uvc.c *//* to match the camera's actual output size for the current resolution/format. */if(buf->byteused>CONFIG_USBH_UVC_FRAME_BUF_SIZE){RTK_LOGS(TAG,RTK_LOG_ERROR,"Frame %d overflow %d > %d\n",img_cnt,buf->byteused,CONFIG_USBH_UVC_FRAME_BUF_SIZE);/* Even on error, we must return the frame buffer */usbh_uvc_put_frame(buf,CONFIG_USBH_UVC_IF_NUM_0);return;}elseif(buf->byteused>0){/* CONSUMER LOGIC: *//* The actual data processing happens here. *//* - Simple Mode: Just count bytes. *//* - VFS Mode: Write `buf->buf` to SD Card. *//* - HTTPC Mode: Send `buf->buf` to Network. */usbh_uvc_img_prepare(buf);......}// 3.3 Put Frame back to USB Stack/* CRITICAL: Must return the buffer to driver for the next capture */usbh_uvc_put_frame(buf,CONFIG_USBH_UVC_IF_NUM_0);}/* 4. Stop Video Stream */if(usbh_uvc_is_ready){usbh_uvc_stream_off(CONFIG_USBH_UVC_IF_NUM_0);RTK_LOGS(TAG,RTK_LOG_INFO,"Stream off\n");}}exit:rtos_task_delete(NULL);}
When the device is disconnected or the USB host function needs to be turned off, the class driver and host core driver must be unloaded in order, and related system resources released.
/* 1. Deinitialize UVC class driver. */usbh_uvc_deinit();/* 2. Deinitialize USB host core driver */usbh_deinit();
This example demonstrates how Ameba, acting as a USB UVC host, captures video frames from a camera. To meet different application scenarios, the example provides three working modes:
USBH_UVC_APP_SIMPLE: Basic Test Mode, captures video frames only but does not process them (discards directly), used for verifying the driver path and statistical throughput.
USBH_UVC_APP_VFS: SD Card Storage Mode, writes captured video frames to an SD card via the VFS interface.
USBH_UVC_APP_HTTPC: Network Upload Mode, sends captured video frames to an HTTP server.
Users can select the currently active mode by modifying the CONFIG_USBH_UVC_APP macro definition in the code.
Software Configuration
Open the example_usbh_uvc.c file and modify the CONFIG_USBH_UVC_APP macro definition according to testing requirements:
/* Supported application example: USBH_UVC_APP_SIMPLE, USBH_UVC_APP_VFS, USBH_UVC_APP_HTTPC */#define CONFIG_USBH_UVC_APP USBH_UVC_APP_SIMPLE/* Supported formats: USBH_UVC_FORMAT_MJPEG, USBH_UVC_FORMAT_YUV, USBH_UVC_FORMAT_H264* Note: Users must verify which formats their specific camera supports and* adjust the definition below accordingly. */#define CONFIG_USBH_UVC_FORMAT_TYPE USBH_UVC_FORMAT_MJPEG/* Target resolution and compression ratio.* If the specific camera device does not support* these values, the host stack will automatically select the closest match.* Always check the logs to confirm the actual parameters applied. */#define CONFIG_USBH_UVC_WIDTH 1280#define CONFIG_USBH_UVC_HEIGHT 720#define CONFIG_USBH_UVC_FRAME_RATE 30/* Frame buffer size in bytes* Size depends on format, resolution, and scene complexity.* Please increase this value if an oversize error occurs. */#define CONFIG_USBH_UVC_FRAME_BUF_SIZE (150 * 1024)
Depending on the selected mode, pay attention to the following configuration items:
If using HTTPC mode, configure the target server’s IP address and port, and ensure the Wi-Fi connection information is correct.
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board:
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_uvc-p
If compilation fails, please execute ameba.pymenuconfig and confirm that USBDCDCACM has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (Host) --->
[*] UVC
Result Verification
General Test Steps:
Connect the USB camera to the USB interface of the development board.
Reset the development board and observe the serial logs.
Ensure there are no USB-related error messages (e.g., enumeration failure).
Check the specific verification steps and expected logs below according to the selected mode:
Test Description:
In this mode, after the system identifies the camera, it will periodically capture images (default 200 frames), only calculating throughput without saving data.
Expected Log:
```
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=20832
[UVC-I] Captured frame 1, len=20912
...
[UVC-I] Captured frame 199, len=108264
[UVC-I] TP 4126 KB/s @ 4953 ms, fps 40/s
[UVC-I] TP 4.0 MB/s-40 (0_20930888/200)
[UVC-I] Stream off
[UVC-I] Test done
```
Test Description:
Be sure to insert an SD card formatted as FAT32 before resetting.
After the test is complete, insert the SD card into a computer and check if imgX.jpeg files are generated and can be opened normally.
Expected Log:
```
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Start vfs service
...
[UVC-I] VFS-SDcard Init Success
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=25952
[UVC-I] Create image file: sdcard:img0.jpeg
[UVC-I] fwrite() ok, w 25946
[UVC-I] Captured frame 1, len=24904
[UVC-I] Create image file: sdcard:img1.jpeg
```
Environment Setup:
Before testing, set up an Apache server on the PC to receive images.
Install Apache: Download and unzip Apache24, modify SRVROOT in conf/httpd.conf to the unzipped path, and set the Listen port to 5090.
Configure the upload script: Create submit.py in Apache24/cgi-bin/ (ensure the Python path in the first line is correct):
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.
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.
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.
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.
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:
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.
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.
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.
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_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.
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.
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. */typedefstruct{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. */typedefstruct{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_tstate);/**< Callback when isochronous OUT (Play) transfer completes */int(*isoc_received)(u8*buf,u32len);/**< Callback when isochronous IN (Record) data is received */}usbh_composite_uac_usr_cb_t;
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 */staticusbh_composite_host_tusbh_composite_host;/* Composite Class Driver */staticconstusbh_class_driver_tusbh_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. */typedefstruct{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 */staticusbh_composite_host_tusbh_composite_host;/* Composite Device ID */staticconstusbh_dev_id_tcomposite_devs[]={{.mMatchFlags=USBH_DEV_ID_MATCH_ITF_INFO,.bInterfaceClass=USB_UAC1_CLASS_CODE,.bInterfaceSubClass=USB_UAC1_SUBCLASS_AUDIOSTREAMING,.bInterfaceProtocol=0x00,},{},};/* Composite Class Driver */staticusbh_class_driver_tusbh_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. */typedefstruct{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_tpipe;/**< Interrupt IN pipe handler. *///....}usbh_composite_hid_t;/* HID Host */staticusbh_composite_hid_tusbh_composite_hid;/* HID Class Driver */constusbh_class_driver_tusbh_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. */typedefstruct{usbh_composite_host_t*driver;/**< Pointer to the parent composite host structure. */usbh_composite_uac_usr_cb_t*cb;/**< Pointer to the user-registered callback structure. */usbh_uac_ac_itf_info_tac_isoc_in;/**< Audio Control Interface info (Topology) */usbh_uac_as_itf_info_t*as_isoc_out;/**< Pointer to Audio Streaming Output interface info */usbh_uac_as_itf_info_t*as_isoc_in;/**< Pointer to Audio Streaming Input interface info *///...}usbd_composite_msc_dev_t;/* UAC Host */staticusbh_composite_uac_tusbh_composite_uac;/* UAC Class Driver */constusbh_class_driver_tusbh_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,};
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.
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*/staticintusbh_composite_hid_uac_cb_process(usb_host_t*host,u32msg){usbh_composite_host_t*chost=&usbh_composite_host;intret=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);}returnret;}
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 */staticusbh_class_driver_tusbh_composite_driver={.process=usbh_composite_hid_uac_cb_process,};staticintusbh_composite_hid_uac_cb_process(usb_host_t*host,u32msg){usbh_composite_host_t*chost=&usbh_composite_host;intret=HAL_BUSY;//1. HID processif((chost->hid!=NULL)&&(chost->hid->process!=NULL)){ret=chost->hid->process(host,msg);}//2. UAC processreturnret;}/*************** HID Sub-funtion Class Driver *************************//* HID user Callback Interface. */typedefstruct{int(*report)(usbh_composite_hid_event_t*event);}usbh_composite_hid_usr_cb_t;/* USB Standard Class Driver */constusbh_class_driver_tusbh_composite_hid_driver={.process=usbh_composite_hid_cb_process,.sof=usbh_composite_hid_cb_sof,};staticintusbh_composite_hid_cb_sof(usb_host_t*host){usbh_composite_hid_t*hid=&usbh_composite_hid;usbh_pipe_t*pipe=&(hid->pipe);//Regular reportingif(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);}returnHAL_OK;}/* Phase 1: Callback Process */staticintusbh_composite_hid_cb_process(usb_host_t*host,u32msg){usbh_composite_hid_t*hid=&usbh_composite_hid;usbh_pipe_t*pipe=&(hid->pipe);usbh_event_tevent;event.d32=msg;// 1. Check if the event belongs to the HID pipeif((hid->hid_ctrl_buf)&&(event.msg.pipe_num==pipe->pipe_num)){// 2. Clear transfer flaghid->next_xfer=0;// 3. Handle Interrupt IN transfer state machineusbh_composite_hid_in_process(host);// 4. If transfer started/updated, notify the main host driverif(hid->next_xfer){usbh_notify_composite_class_state_change(host,pipe->pipe_num,USBH_COMPOSITE_HID_EVENT);}returnHAL_OK;}returnHAL_BUSY;}/* Phase 2: IN Transfer Process (Producer) */staticvoidusbh_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_turb_state=USBH_URB_IDLE;u32len;/* Handle Transfer State Machine */switch(pipe->xfer_state){// State A: Ready to start new transfercaseUSBH_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 transferhid->next_xfer=1;//Flag to notify main state machine}break;// State B: Waiting for transfer completioncaseUSBH_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) */staticvoidusbh_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);u8report_msg[10];u32read_cnt;while(hid->parse_task_exit==0){// 1. Try to read data from ring bufferread_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 eventret=usbh_composite_hid_parse_hid_report(report_msg,len,&(hid->vol_caps));if(ret!=HAL_OK){return;}// 3. Trigger User Callbackif((hid->cb!=NULL)&&(hid->cb->report!=NULL)){hid->cb->report(event);}}}else{rtos_time_delay_ms(50);}}}/****************** Application *************************/staticusbh_composite_hid_usr_cb_tusbh_hid_cfg={.report=usbh_hid_cb_report,};staticintusbh_hid_cb_report(usbh_composite_hid_event_t*event){switch(event->type){caseVOLUME_EVENT_CONSUMER_UP:RTK_LOGS(NOTAG,RTK_LOG_INFO,"=== Executing Volume Up (Consumer) ===\n");break;caseVOLUME_EVENT_CONSUMER_DOWN:RTK_LOGS(NOTAG,RTK_LOG_INFO,"=== Executing Volume Down (Consumer) ===\n");break;//Other eventdefault:break;}returnHAL_OK;}
This section takes USB Composite (HID + UAC) as an example to introduce the complete application implementation and the method of running example application.
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.
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
staticusbh_config_tusbh_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,};staticusbh_composite_uac_usr_cb_tusbh_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,};staticusbh_composite_hid_usr_cb_tusbh_hid_cfg={.report=usbh_hid_cb_report,};staticusbh_user_cb_tusbh_usr_cb={.process=usbh_uac_cb_process};intret=0;/* Initialize USB host core driver with configuration. */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){return;}/* Initialize class driver with application callback handler. */ret=usbh_composite_init(&usbh_hid_cfg,&usbh_uac_cfg,USBH_UAC_FRAME_CNT);if(ret!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();return;}
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.
This example demonstrates how to configure the Ameba development board as a host with both USB HID (Human Interface Device) and UAC (Audio Device Class) functions through a composite host protocol stack.
When the development board is connected to a standard USB headset, the system will recognize two separate logical devices. The development board will send audio data to the headset, and if a button on the headset is pressed, host will recognize the command.
The example code path is: {SDK}/component/example/usb/usbh_composite_hid_uac. It provides provides a complete reference solution for developers to design custom composite products.
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)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_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) --->
The USB Vendor Class (Vendor Specific) leverages the openness of the USB specification to allow for vendor-defined private protocols or requests, supporting developers in flexibly defining configurations and transfer types.
Ameba provides a reference example for designing a host-side vendor driver based on the underlying USB protocol stack, enabling the establishment of a highly flexible and exclusive data channel with customized USB devices.
As a Vendor host, developers can develop private command sets and specific format data based on this to interact with Vendor devices, achieving customized control and functional expansion beyond the limitations of standard Host classes.
As a USB host with high customizability, Ameba can establish a dedicated control and communication channel between the host and the device through the Vendor interface, breaking through the functional limitations of standard classes and enabling the following typical applications, such as:
Custom protocol communication: Define a private command set and data frame format for dedicated communication between devices and hosts (such as parameter configuration, fault diagnosis, and status inquiry).
Secure firmware upgrade: Utilize a customized Vendor protocol to transmit firmware, enabling a private and secure upgrade mechanism.
Special peripheral control: Controls non-standard USB devices (such as specific sensors, dongles, or custom IO expansion boards).
Please refer to the corresponding section of the vendor device solution: Protocol Introduction.
The Vendor class does not have an officially unified USB class standard specification.
Its descriptor structure, command requests, and endpoint usage are all defined by the vendor and require a host-side driver or application for parsing.
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:
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)
| ...
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.
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.
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-OnlyReset 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.,Vendortype).
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.
Command-Response Model
This scenario is suitable for lightweight control applications, such as USB to GPIO/I2C/SPI bridges and device parameter configuration tools.
Working Mechanism:
Control Channel (Vendor Request): All operations are completed on EP0.
Command as Request: Directly utilizes the bRequest and wValue fields in the SETUP packet to carry opcodes and parameters, without occupying additional endpoint resources.
Control Transfer Atomicity: Each interaction is a complete control transfer transaction, consisting of three stages: Setup, Data (optional), and Status.
Private Firmware Update
This scenario combines the flexibility of control transfers with the high throughput of bulk transfers to implement device OTA (Over-the-Air) or DFU (Device Firmware Upgrade) functions via custom protocols, featuring state machine management and reliability verification mechanisms.
Working Mechanism:
Control Channel (Vendor Request): Responsible for sending critical control commands (such as Flash erase, status inquiry, verification, reset) to ensure process controllability.
Data channel (BULK OUT): Responsible for transmitting large blocks of firmware binary data, enhancing writing efficiency.
The protocol stack provides a design reference example for a Vendor Class (vendor-defined class) host driver.
Unlike standard class drivers such as HID or MSC, it does not bind to specific industry protocols, but provides a flexible basic framework, granting developers great freedom to customize private USB communication protocols.
The driver architecture adopts a layered design, consisting of three core layers from top to bottom:
User Application Layer: Implements specific business logic. Users can interact with the driver by simply registering a callback structure, and send or receive data using encapsulated interfaces, without needing to concern themselves with the underlying USB protocol details.
Vendor Class Driver Layer: (The core implementation of this driver example) built on top of the USB host core stack, serving as an intermediary layer connecting the underlying hardware with the upper-level applications. It is responsible for managing channel resources, maintaining state machines, and handling various data transfers. It serves as the primary foundation for developers to conduct secondary development.
Core Driver Layer: Responsible for low-level USB hardware interrupt handling, bus enumeration, transfer request (URB) scheduling, and standard protocol stack management.
The Vendor Class Driver plays a pivotal role in the system architecture, connecting the upper and lower layers. Its core interaction logic mainly revolves around the following three interfaces:
Host Class Driver Callback API: The class driver interacts with the underlying USB Core by registering the standard usbh_class_driver_t structure.
Application-Facing Callback API: Provides asynchronous event notifications (such as connection, disconnection, data reception) to the upper-layer application via the usbh_vendor_cb_t registered by the application layer during initialization.
Application-Facing API: Functional interfaces provided for the application layer to call. Upon calling, the driver switches its internal state machine status and initiates data transfer scheduling.
The figure above illustrates the execution flow of callback functions at different levels and does not list all calling scenarios.
Class Driver Callback Functions
The class driver needs to define a standard usbh_class_driver_t structure.
This serves as a unified entry point registered with the USB Core and is the primary method for the Core layer to notify the Class layer that “an event has occurred.”
id_table: Supported device ID list. The core layer uses this table to match with the inserted device to determine whether to load this driver.
attach: Called after device connection and successful matching.
detach: Called when the device is disconnected.
setup: Called when enumeration is complete and the class request phase begins. Used to send class-specific standard control requests to complete necessary configuration before the device enters the data transfer state.
process: The state machine processing function executed after the class driver is ready.
sof: Called during SOF interrupts. Used to handle logic with strict timing requirements, primarily for isochronous transfers.
completed: Called when a transfer on a pipe completes.
Application Layer Callback Functions
The Vendor class driver application layer callback structure usbh_vendor_cb_t is implemented by the user layer. Generally, the following APIs can be implemented:
API
Description
init
Called during class driver initialization to initialize application-related resources.
deinit
Called during class driver de-initialization to release application-related resources.
attach
Called when the class driver executes the attach callback to handle device connection
events at the application layer.
detach
Called when the class driver executes the detach callback to handle device disconnection
events at the application layer.
setup
Called when the class driver executes the setup callback, indicating that the application
layer class driver is ready for data transfer.
receive
Called when the class driver receives IN data, used by the application layer to process
data reported by the device.
transmit
Called when the class driver OUT data transfer completes, used by the application
layer to obtain the OUT transfer status.
usbh_vendor_init() is the top-level function used to load the Vendor Class Driver.
Execute User Callback Initialization: If a user init callback is registered, it is called first, giving the upper-layer application an opportunity to execute application-specific initialization logic.
Register Class Driver: Calls usbh_register_class() to register the defined usbh_class_driver_t class driver instance with the Host Core Driver, making the host ready to respond to enumeration.
Example:
typedefstruct{usbh_vendor_xfer_tbulk_in_xfer;//BULK IN pipe xfer.// Other pipe xfer if exit.usbh_vendor_cb_t*cb;//User callback.usb_host_t*host;//USB host core.usbh_vendor_state_tstate;//Class Driver state.}usbh_vendor_host_t;/* Define Vendor host */staticusbh_vendor_host_tusbh_vendor_host;/* Define Vendor Class Driver */staticusbh_class_driver_tusbh_vendor_driver={.id_table=vendor_devs,.attach=usbh_vendor_attach,.detach=usbh_vendor_detach,.setup=usbh_vendor_setup,.process=usbh_vendor_process,.sof=usbh_vendor_sof,};intusbh_vendor_init(usbh_vendor_cb_t*cb){usbh_vendor_host_t*vendor=&usbh_vendor_host;/* 1. Execute User Init Callback */if(cb!=NULL){vendor->cb=cb;if(cb->init!=NULL){ret=cb->init();if(ret!=HAL_OK){RTK_LOGS(TAG,RTK_LOG_ERROR,"User init err %d\n",ret);returnret;}}}/* 2. Register Class Driver */usbh_register_class(&usbh_vendor_driver);returnHAL_OK;}
usbh_vendor_deinit() is the top-level function used to unload the Vendor Class Driver.
Execute User Callback Cleanup: If a user deinit callback is registered, it is called first to inform the upper-layer application to clean up user-layer private resources or logic.
Reset State Machine: Sets the driver state machine status to IDLE.
Release All Pipes and Resources: Force closes all open pipes via usbh_close_pipe() and cleans up all resources allocated during initialization to prevent memory leaks or pipe occupation.
Unregister Class Driver: Calls usbh_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:
intusbh_vendor_deinit(void){usbh_vendor_host_t*vendor=&usbh_vendor_host;/* 1. Execute User Deinit Callback */if((vendor->cb!=NULL)&&(vendor->cb->deinit!=NULL)){vendor->cb->deinit();}/* 2. Set the state of the drive state machine to IDLE. */vendor->state=VENDOR_STATE_IDLE;/* 3. Close all pipelines */usbh_vendor_deinit_all_pipe();/* 4. Release all resources */usbh_vendor_deinit_all_xfer();/*5. Unregister Class Driver */usbh_unregister_class(&usbh_vendor_driver);returnHAL_OK;}
The connection and disconnection of devices are automatically detected by the USB Core, which then calls the corresponding callbacks of the class driver for resource management.
Device Connection
When the Core layer enumerates a Vendor device matching the usbh_dev_id_t, the usbh_vendor_attach callback is invoked. The main responsibilities of this phase are:
Obtain and parse the corresponding interface descriptor.
Open the corresponding pipe via usbh_open_pipe() based on the endpoint descriptor.
Initialize the transfer management structure usbh_vendor_xfer_t corresponding to each pipe.
Call the user attach callback to inform the application layer.
Example:
staticintusbh_vendor_attach(usb_host_t*host){usbh_vendor_host_t*vendor=&usbh_vendor_host;usbh_itf_data_t*itf_data;usbh_dev_id_tdev_id={0,};/* 1. Sets up device identification parameters to match a vendor class interface */dev_id.bInterfaceClass=VENDOR_CLASS_CODE;dev_id.bInterfaceSubClass=VENDOR_SUBCLASS_CODE;dev_id.bInterfaceProtocol=VENDOR_PROTOCOL;dev_id.mMatchFlags=USBH_DEV_ID_MATCH_ITF_INFO;//Core only needs to match interface information (ignore VID/PID)/* 2. Search for interfaces that match the dev_id rule in the devices associated with the host */itf_data=usbh_get_interface_descriptor(host,&dev_id);if(itf_data==NULL){// Match failure: Although the device is connected, there is no driver supporting the InterfaceRTK_LOGS(TAG,RTK_LOG_ERROR,"Get itf desc fail\n");returnHAL_ERR_PARA;}else{/* 3. If found, initializes the vendor host with the host pointer and sets the state to transfer */vendor->host=host;vendor->state=VENDOR_STATE_XFER;/* 4. Get data in/out endpoints */usbh_vendor_get_endpoints(host,itf_data->itf_desc_array);/* 5. Calls the `attach` callback function if it exists */if((vendor->cb!=NULL)&&(vendor->cb->attach!=NULL)){vendor->cb->attach();}returnHAL_OK;}}
Device Disconnection
When the device is unplugged, the usbh_vendor_detach callback is invoked. The main responsibilities of this phase are:
Call the user detach callback to inform the application layer.
Example:
staticintusbh_vendor_detach(usb_host_t*host){UNUSED(host);usbh_vendor_host_t*vendor=&usbh_vendor_host;/* 1. Reset the state of the driver state machine to IDLE */vendor->state=VENDOR_STATE_IDLE;/* 2. Close all pipes in turn */usbh_vendor_deinit_all_pipe();/* 3. Calls the `detach` callback function if it exist */if((vendor->cb!=NULL)&&(vendor->cb->detach!=NULL)){vendor->cb->detach();}returnHAL_OK;}
In the USB host driver, the attach callback function collaborates with the usbh_dev_id_t structure to complete the critical process from “generic device connection” to “specific interface binding”.
The matching process is as follows:
Define Matching Rules: By configuring the usbh_dev_id_t structure, specify target device characteristics (such as Class Code, Protocol, etc.).
Match Flags: Use mMatchFlags for precise control over the matching strategy (e.g., matching only VID/PID, or specific Class/SubClass).
Flexibility: Precisely control whether to match the entire device or a specific interface within the device (this is especially important for composite devices).
Developers adapt according to the actual connected Vendor devices.
The USB Core iterates through all interface descriptors of the currently connected device and compares them with the passed usbh_dev_id_t rules. If a match is successful, it returns the pointer to the corresponding interface descriptor.
The usbh_vendor_process callback function is the core state machine handler for the Vendor Class on the host side. Unlike the passive response on the device side, the host-side driver needs to actively maintain device status.
The USB host class driver uses a state machine to manage data transfer processing, with the driver state updated by API calls and underlying interrupt feedback.
The state machine generally contains three core states:
VENDOR_STATE_IDLE (Idle State): Standby state after system initialization or disconnection, waiting for new transfer requests or event triggers.
VENDOR_STATE_XFER (Data Transfer State): Core data processing state; dispatches and processes corresponding transfer events based on the Pipe Number in the event message.
VENDOR_STATE_ERROR (Error Handling State): Exception handling state; attempts error recovery via standard requests (Clear Feature).
Example:
switch(vendor->state){caseVENDOR_STATE_IDLE:breakcaseVENDOR_STATE_XFER:/* Distribute and process according to the pipe number */if(event.msg.pipe_num==vendor->bulk_in_xfer.pipe.pipe_num){usbh_vendor_bulk_process_rx(host);}elseif(event.msg.pipe_num==vendor->isoc_out_xfer.pipe.pipe_num){usbh_vendor_isoc_process_tx(host);}/* Deal with other pipe */break;caseVENDOR_STATE_ERROR:/* Try to recover */if(usbh_ctrl_clear_feature(host,0x00U)==HAL_OK){vendor->state=VENDOR_STATE_IDLE;// Restoration successful, reset to IDLE}break;}
This section provides a detailed introduction to the data transfer processing flow in the Vendor class driver.
Host-side transfers are mainly divided into three stages: TRX API -> Process -> Callback:
Initiation of Transfer Request: The application layer proactively initiates OUT (transmit) or IN (receive) transfer requests by calling APIs.
State Machine Management: The driver manages data interaction with the device by maintaining a transfer state machine.
Callback Notification: Notifies the application layer via usbh_vendor_cb_t callbacks upon transfer completion or error. This callback mechanism decouples the application layer from the low-level driver, eliminating the need for the application to poll and wait.
Calls the user receive callback function to pass the data up to the application layer.
Note
BULK IN ZLP Handling
If a zero-length packet is received, the receive callback is triggered as well, so the application layer needs to check for and handle the ZLP case.
Note
USB DMA requires the data buffer address to be aligned with the Cache line, so the transmit/receive buffer addresses passed down by the application layer must be address-aligned.
Special Processing Logic
BULK ZLP Handling
In Bulk transfers, if the data length is exactly an integer multiple of the MPS (Maximum Packet Size), the driver automatically flags trx_zlp to automatically receive/send a ZLP after the data to terminate the transfer.
Isochronous transfers are time-sensitive and rely on SOF interrupts to handle timing control for isochronous transfers.
SOF Callback: The usbh_vendor_sof callback function is triggered at the beginning of each frame to track the current frame number.
Transmission Scheduling: Ensure that data packets are submitted strictly within the correct (micro)frames at regular intervals.
When the TX processing function determines that there is still transmission to be done, it will set the channel status to USBH_EP_XFER_WAIT_SOF.
Verify the frame interval during the SOF callback, and call usbh_notify_class_state_change() to trigger the core scheduling for actual data transmission when the conditions are met.
Example:
staticintusbh_vendor_sof(usb_host_t*host){usbh_vendor_host_t*vendor=&usbh_vendor_host;/* 1.Obtain the current frame number for ISOC scheduling */intcur_frame=usbh_get_current_frame_number(host);/* 2. Waiting for the correct SOF interval to start next transfer - if cur_frame - last frame_num >= interval, means we should trigger a xfer ASAP. - if xfer_state = USBH_EP_XFER_WAIT_SOF, it means that last xfer has been done, so in sof intr, we should check whether the next frame will be the xfer frame */if((usbh_get_elapsed_frame_cnt(host,pipe->frame_num)>=pipe->ep_interval)||((pipe->xfer_state==USBH_EP_XFER_WAIT_SOF)&&(cur_frame-out_xfer->cur_frame)%pipe->ep_interval==0)){//3. Set next transfer parameters: e.g. buffer and length//4. Start next transferusbh_notify_class_state_change(host,pipe->pipe_num);}returnHAL_OK;}
During the device enumeration phase (in the usbh_vendor_attach callback function), the Vendor Host Driver parses the configuration descriptor and automatically searches for and requests corresponding transfer resources based on the interface descriptor, establishing complete data and control pipes.
The pipe configuration in this example is as follows:
This section provides a detailed introduction to the complete development process of the Vendor application, covering core aspects such as driver loading, hotplug handling, how to establish transmission, and how to process data.
However, the data content transmitted (such as Loopback test data) carries no specific business meaning. Developers can base on this framework:
Define private command set: Replace the test data in the example and implement specific private protocol parsing logic.
Adjust resource allocation: Adjust the FIFO size and buffer size based on the actual business throughput.
Before using the Vendor driver, configuration structures must be defined and callback functions registered. Subsequently, initialization interfaces are called to load the USB core driver and the Vendor class driver.
Step Description:
Hardware Configuration: Configure USB speed modes (High Speed/Full Speed), interrupt priorities, etc.
Callback Registration: Define the user callback structure usbh_vendor_cb_t and usbh_user_cb_t mount processing functions for various stages.
Core Initialization: Call usbh_init() to initialize the USB core driver.
Class Driver Initialization: Call usbh_vendor_init() to initialize the Vendor class driver.
Example:
staticusbh_config_tusbh_cfg={.speed=USB_SPEED_HIGH,.ext_intr_enable=USBH_SOF_INTR,.isr_priority=INT_PRI_MIDDLE,.main_task_priority=3U,.tick_source=USBH_SOF_TICK,};staticusbh_vendor_cb_tvendor_usr_cb={.attach=vendor_cb_attach,.detach=vendor_cb_detach,.setup=vendor_cb_setup,.transmit=vendor_cb_transmit,.receive=vendor_cb_receive,};staticusbh_user_cb_tusbh_usr_cb={.process=vendor_cb_process};intret=0;/* Initialize USB host core driver with configuration. */ret=usbh_init(&usbh_cfg,&usbh_usr_cb);if(ret!=HAL_OK){return;}/* Initialize class driver with application callback handler. */ret=usbh_vendor_init(&vendor_usr_cb);if(ret!=HAL_OK){/* If class driver init fails, clean up the core driver */usbh_deinit();return;}
Once the Vendor host enumeration is successful, APIs can be called to start data transfer.
The following process applies to BULK, INTR, and ISOC transfer. BULK transfer is used as an example here.
static__IOintvendor_is_ready=0;staticrtos_sema_tvendor_bulk_send_sema;staticrtos_sema_tvendor_bulk_receive_sema;staticu8vendor_bulk_loopback_tx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE]__attribute__((aligned(CACHE_LINE_SIZE)));staticu8vendor_bulk_loopback_rx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE]__attribute__((aligned(CACHE_LINE_SIZE)));staticusbh_vendor_cb_tvendor_usr_cb={.setup=vendor_cb_setup,.transmit=vendor_cb_transmit,.receive=vendor_cb_receive,};staticintvendor_cb_setup(void){vendor_is_ready=1;returnHAL_OK;}/*** @brief USB Vendor transmit callback function* @param ep_type transmission type (BULK, INTERRUPT, ISOC)* @return HAL status*/staticintvendor_cb_transmit(u8ep_type){//Release the different semaphore according to different transmission, e.g. transmission typeswitch(ep_type){caseUSB_CH_EP_TYPE_BULK:rtos_sema_give(vendor_bulk_send_sema);break;//Other transmissiondefault:break;}returnHAL_OK;}/*** @brief USB Vendor receive callback function* @param ep_type transmission type (BULK, INTERRUPT, ISOC)* @param buf Received data buffer* @param len Length of received data* @param status Transfer status* @return HAL status*/staticintvendor_cb_receive(u8ep_type,u8*buf,u32len,intstatus){// Get endpoint maximum packet size for each typeu16vendor_bulk_in_mps=usbh_vendor_get_bulk_ep_mps();switch(ep_type){caseUSB_CH_EP_TYPE_BULK:// 1. Check if transfer transfer is complete successfully// 2. Reset total length and signal completionrtos_sema_give(vendor_bulk_receive_sema);}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"%d RX fail: %d\n",ep_type,status);}break;}returnHAL_OK;}/*---------------- BULK TRX Test --------------------*//*1. Wait for device to be ready for data transfer*/while(1){if(vendor_is_ready){/* Check if device is ready */rtos_time_delay_ms(10);break;}}/* 2. BULK TX test *//* Initiate transfer with TX configuration: transfer length and times */usbh_vendor_bulk_transmit(vendor_bulk_loopback_tx_buf,USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE,USBH_VENDOR_BULK_LOOPBACK_CNT);/* 3. Wait for transfer completion semaphore */if(rtos_sema_take(vendor_bulk_send_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){// Success indicates transfer completion}/* 4. BULK RX test *//* Initiate transfer with RX configuration: transfer length and times */usbh_vendor_bulk_receive(vendor_bulk_loopback_rx_buf,USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE,USBH_VENDOR_BULK_LOOPBACK_CNT);/* 5. Wait for transfer completion semaphore */if(rtos_sema_take(vendor_bulk_receive_sema,RTOS_SEMA_MAX_COUNT)==RTK_SUCCESS){// Success indicates transfer completion}
The handling methods in the receive callback differ for the three transfer types upon successful reception:
Processing Logic:
Data received via bulk transfer is accumulated until specific conditions are met before releasing the semaphore.
This is primarily because the protocol mandates that when a bulk transfer length is a multiple of the MPS (Max Packet Size), a Zero Length Packet (ZLP) must be appended to mark the end of the transfer.
The user layer needs to distinguish this situation, so it waits for a complete packet sequence or specific termination conditions (such as a ZLP, a short packet, or a full buffer) to confirm the completion of a full transfer.
This mechanism ensures data integrity and correctness, preventing subsequent processing before data is fully received.
Example:
staticintvendor_cb_receive(u8ep_type,u8*buf,u32len,intstatus){/* 1. Get endpoint maximum packet size of BULK IN endpoint */u16vendor_bulk_in_mps=usbh_vendor_get_bulk_ep_mps();switch(ep_type){caseUSB_CH_EP_TYPE_BULK:/* 2. Check if transfer was successful */if(status==HAL_OK){/* 3 Update total received length */vendor_bulk_total_rx_len+=len;/* 4. Determine if transfer is complete based on: Zero-length packet (ZLP)/Short packet (not multiple of MPS)/ Buffer full condition */if((len==0)||(len%vendor_bulk_in_mps)||((len%vendor_bulk_in_mps==0)&&(len<USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE))||(vendor_bulk_total_rx_len>USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE)){/* 5. Reset total length and signal completion */vendor_bulk_total_rx_len=0;rtos_sema_give(vendor_bulk_receive_sema);}}else{RTK_LOGS(TAG,RTK_LOG_ERROR,"%d RX fail: %d\n",ep_type,status);}break;}returnHAL_OK;}
Processing Logic:
Handling interrupt transfers is relatively simple, primarily based on short packet detection.
When the received data length is less than or equal to the MPS, it usually indicates the end of a complete transfer cycle.
This design aligns with the characteristics of interrupt transfers—small data volume and periodicity—allowing timely identification of transfer completion.
Example:
staticintvendor_cb_receive(u8ep_type,u8*buf,u32len,intstatus){/* 1. Get endpoint maximum packet size of Interrupt IN endpoint */u16vendor_intr_in_mps=usbh_vendor_get_intr_ep_mps();switch(ep_type){caseUSB_CH_EP_TYPE_INTR:/* 2. Check if transfer was successful */if(status==HAL_OK){/* 3. Update total received length */vendor_intr_total_rx_len+=len;/* 4. Determine if transfer is complete based on: Short packet (less than MPS) with data received */if(((len<vendor_intr_in_mps)&&(vendor_intr_total_rx_len>0))||(vendor_intr_total_rx_len>=USBH_VENDOR_INTR_LOOPBACK_BUF_SIZE)){///*5. Reset total length and signal completion */vendor_intr_total_rx_len=0;rtos_sema_give(vendor_intr_receive_sema);}}else{// Log error if transfer failedRTK_LOGS(TAG,RTK_LOG_ERROR,"%d RX fail: %d\n",ep_type,status);}break;}returnHAL_OK;}
Processing Logic:
Isochronous transfers use an immediate semaphore release mechanism.
Since isochronous transfers are typically used for real-time data transfer (e.g., audio or video streams), timeliness is more critical than data integrity.
The upper layer is notified immediately upon receiving each data packet to ensure real-time requirements are met.
Example:
staticintvendor_cb_receive(u8ep_type,u8*buf,u32len,intstatus){switch(ep_type){caseUSB_CH_EP_TYPE_ISOC:// For isochronous transfers, immediately signal completionrtos_sema_give(vendor_isoc_rxdone_sema);break;}returnHAL_OK;}
Note
Buffers allocated for transfer and reception must be Cache-line aligned.
For the complete data transfer logic, please refer to the SDK example code: {SDK}/component/example/usb/usbh_vendor/example_usbh_vendor.c.
This section introduces a complete Vendor host loopback example, demonstrating how to implement custom bidirectional data communication with the device via the Vendor protocol stack.
The example code path is: {SDK}/component/example/usb/usbh_vendor. It provides provides a complete reference solution for developers to design custom vendor products.
Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.
# Initialize environment (required for every new terminal)sourceenv.shorenv.bat(Windowssystem)# Select Target SoC (replace xxx with your specific SoCs)
ameba.pysocxxx
ameba.pybuild-ausbh_vendor-p
Confirmation of Menuconfig configuration
If compilation fails, please execute ameba.pymenuconfig and confirm that USBHVENDOR has been selected.
- Choose `CONFIG USB --->`:
[*] Enable USB
USB Mode (host) --->
[*] Vendor
Responses are provided by Realtek's AI chatbot and may contain inaccuracies. Realtek is not liable for any damages from its use and offers no warranties.