Transparent Communication Host Solution

Overview

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

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

Ameba USB CDC ACM Host

Features

  • Supports hot plugging

  • Automatically parse descriptors and adapt to speed modes

  • Batch transmission length and other parameters are configurable

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

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

Application Scenarios

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

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

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

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

Protocol Introduction

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

  • Set baud rate (such as 9600, 115200)

  • Configure data bits, stop bits, and parity bits

  • Control DTR/RTS and other line status signals

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

Protocol Document

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

Specification type

Document

CDC (Communication Device Class Basic Protocol)

Class definitions for Communication Devices

PSTN (PSTN Subclass)

PSTN specification is included in the aforementioned CDC zip file.

Protocol Framework

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

  • Communication Class Interface (CCI)

    Responsible for device management control and signaling interaction.

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

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

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

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

  • Data Class Interface (DCI)

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

Example of protocol interaction:

../../../_images/usb_cdc_acm_class.svg

Descriptor Structure

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

CDC ACM Descriptor Topology

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

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

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

Functional Descriptor

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

  • Header Functional Descriptor: Indicates the CDC version.

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

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

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

  • Header Functional Descriptor

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

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

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

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

Note

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

Class-Specific Requests

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

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

Request Name

Requirement

Description

SEND_ENCAPSULATED_COMMAND

Required

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

GET_ENCAPSULATED_RESPONSE

Required

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

SET_COMM_FEATURE

Optional

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

GET_COMM_FEATURE

Optional

Queries the current setting status of a specific communication feature.

CLEAR_COMM_FEATURE

Optional

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

SET_LINE_CODING

Optional (+)

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

GET_LINE_CODING

Optional (+)

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

SET_CONTROL_LINE_STATE

Optional

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

SEND_BREAK

Optional

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

Note

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

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

Class Driver

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

Driver Architecture

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

  • Application Layer

    Contains business logic and data processing callbacks.

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

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

  • CDC ACM Class Driver

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

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

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

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

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

  • USB Core Driver

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

Class Driver Implementation

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

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

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

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

Driver Callback Mechanism

../../../_images/usb_host_cdc_acm_callback.png

Note

The figure above illustrates the execution flow of callback functions at different levels and does not list all calling scenarios.

Class Driver Callback Functions

The class driver needs to define a standard usbh_class_driver_t structure. This serves as a unified entry point registered with the USB Core and is the primary method for the Core layer to notify the Class layer that “an event has occurred.”

  • id_table: Supported device ID list. The core layer uses this table to match with the inserted device to determine whether to load this driver.

  • attach: Called after device connection and successful matching.

  • detach: Called when the device is disconnected.

  • setup: Called when enumeration is complete and the class request phase begins. Used to send class-specific standard control requests to complete necessary configuration before the device enters the data transfer state.

  • process: The state machine processing function executed after the class driver is ready.

  • sof: Called during SOF interrupts. Used to handle logic with strict timing requirements, primarily for isochronous transfers.

  • completed: Called when a transfer on a pipe completes.

Application Layer Callback Functions

The CDC ACM class driver application layer callback structure usbh_cdc_acm_cb_t is implemented by the user layer. Generally, the following APIs can be implemented:

API

Description

init

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

deinit

Called during class driver de-initialization to release application-related resources.

attach

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

detach

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

setup

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

receive

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

transmit

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

notify

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

Loading and Unloading Class Driver

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

Init:

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

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

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

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

Example:

int usbh_cdc_acm_init(usbh_cdc_acm_cb_t *cb)
{
    /* 1. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
      cdc->cb = cb;
      if (cb->init != NULL) {
        ret = cb->init();
        if (ret != HAL_OK) {
          RTK_LOGS(TAG, RTK_LOG_ERROR, "User init err %d\n", ret);
          return ret;
        }
      }
    }
    /* 2. Allocate memory */
    cdc->line_coding = (usb_cdc_line_coding_t *)usb_os_malloc(sizeof(usb_cdc_line_coding_t));
    /* ... */

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

Connection and Disconnection Handling

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

Attach:

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

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

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

  • Initialize the state machine to IDLE state.

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

Example:

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

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

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

Class Driver State Machine

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

State Machine Management and Scheduling

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

State Enum

Description

Key Actions

IDLE

Idle state

Waits for user commands or data transmission requests.

SET_LINE_CODING

Send baud rate setting request

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

GET_LINE_CODING

Get/Verify baud rate

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

SET_CONTROL_LINE_STATE

Control handshake signals

Configures RTS/DTR level states.

TRANSFER

Data transferring

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

ERROR

Error state

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

Line Coding Configuration (Set & Verify)

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

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

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

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

Example:

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

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

Transfer Processing Distribution

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

Example:

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

Error Recovery

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

Example:

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

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

Class-Specific Request Processing

Setup Phase Processing

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

Example:

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

APIs for Application

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

  • Set Control State

  • Set/Get Line Coding

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

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

Data Transfer Processing

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

  • CDC ACM Data Interface:

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

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

  • CDC ACM Communication Interface:

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

Transmission Logic and Zero Length Packet (ZLP) Processing

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

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

Example:

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

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

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

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

Class-Specific Request Implementation

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

Class-Specific Request

Description

SetLineCoding

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

GetLineCoding

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

SetControlLineState

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

Pipe Configuration

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

Count

Description

2

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

1

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

1

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

1

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

API Reference

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

Application Example

Application Design

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

Driver Initialization

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

Step Description:

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

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

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

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

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

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

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

int status = 0;

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

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

Hot-Plug Event Handling

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

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

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

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

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

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

    cdc_acm_attach_status = status;
    rtos_sema_give(cdc_acm_attach_status_changed_sema);
}

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

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

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

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

Data Transfer Processing

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        cdc_acm_total_rx_len = 0;

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

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

    return HAL_OK;
}

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

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

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

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

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

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

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

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

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

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

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

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

    rtos_time_delay_ms(2000);

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

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

Note

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

Driver Deinitialization

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

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

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

Operation method

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

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

Configuration and Compilation

  • Compilation and Flashing

    Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board:

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

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

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

Result Verification

  • Start Host

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

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

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

  • Serial Communication Test

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

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