SAMV71 Xplained Ultra Software Package 1.4

USB Device CDC Serial Driver

Purpose

This page describes how to use the USB framework to produce a USB CDC Serial Device driver, which appears as a virtual COM port on host.

References

Communication Device Class

Purpose

CDC is used to connect communication devices, such as modems (digital or analog), telephones or networking devices. Its generic framework supports a wide variety of physical layers (xDSL, ATM, etc.) and protocols.

In this document, CDC is used to implement a USB to a serial data converter. A USB to serial converter can be used in this case to bridge a legacy RS-232 interface with a USB port.

Architecture

Communication Class Interface

The Communication Class Interface is used for device management. It includes requests to manage the device state, its responses, as well as event notifications. This interface can also be optionally used for call management, i.e., setting up and terminating calls as well as managing their parameters.

The interface requires at least one endpoint (Default EP0) to used for device management. Optionally, another endpoint can be dedicated to event notification. This will usually be an Interrupt IN endpoint.

Data Class Interface

The Data Class Interface is used for generic data transmissions. It provides a means for a communication device to actually transfer data to and from the host. In addition, it also enables the multiplexing of data and commands on the same interface, through the use of wrappers.

endpoints for this interface must exist in pairs of the same type. This is necessary to allow both IN and OUT communication. Only the Bulk and Isochronous types can be used for these endpoints.

CDCArchitecture.png

CDC Class Driver Architecture

Models

To account for the wide variety of existing communication devices, several models have been defined, for more details you can refer to CDC spec. 1.1.

Class-specific Descriptors

CDC-specific information is described using Functional Descriptors. They define various parameters of an interface, such as how the device handles call management, or model-specific attributes.

Since the CDC specification defines quite a number of functional descriptors, they are not detailed here. Instead, they are presented in the various case studies of this document in which they are used.

Host Drivers

Most Operating Systems (OS) now include generic drivers for a wide variety of USB classes. This makes developing a device simpler, since the host complexity is now handled by the OS. Manufacturers can thus concentrate on the device itself, not on developing specific host drivers.

Here is a brief list of the various CDC implementations supported by several OS:

USB to Serial Converter

This section describes the implementation of the USB to serial converter using the CDC class and the AT91 USB Device Framework.

Bridging a Legacy Device and a Host with USB-Serial Converter

USB-SerialConverter.png

Model

The CDC specification defines a model which suits this application perfectly: the Abstract Control Model (ACM). It implements the requests and notifications necessary to communicate with an RS-232 interface.

The Abstract Control Model requires two interfaces, one Communication Class Interface and one Data Class Interface. Each of them must have two associated endpoints. The former shall have one endpoint dedicated to device management (default Control endpoint 0) and one for events notification (additional Interrupt IN endpoint).

The Data Class Interface needs two endpoints through which to carry data to and from the host. Depending on the application, these endpoints can either be Bulk or Isochronous. In the case of a USB to serial converter, using Bulk endpoints is probably more appropriate, since the reliability of the transmission is important and the data transfers are not time-critical.

Descriptors

The descriptors are modtly standard ones. The following code examples thus use the structures described in the AT91 USB device framework.

For CDC-specific descriptors, some new types are defined:

All the descriptors can be found in CDCDSerialDriverDescriptors.c.

Device Descriptor

const USBDeviceDescriptor deviceDescriptor = {
    sizeof(USBDeviceDescriptor),
    USBGenericDescriptor_DEVICE,
    USBDeviceDescriptor_USB2_00,
    CDCDeviceDescriptor_CLASS,
    CDCDeviceDescriptor_SUBCLASS,
    CDCDeviceDescriptor_PROTOCOL,
    BOARD_USB_ENDPOINTS_MAXPACKETSIZE(0),
    CDCDSerialDriverDescriptors_VENDORID,
    CDCDSerialDriverDescriptors_PRODUCTID,
    CDCDSerialDriverDescriptors_RELEASE,
    0, // No string descriptor for manufacturer
    1, // Index of product string descriptor is #1
    0, // No string descriptor for serial number
    1 // Device has 1 possible configuration
};

The Vendor ID and Product ID fields are used to determine which driver to use when the device is enumerated. The Vendor ID is provided by the USB-IF organization after registration; the product ID is completely vendor-specific. In the example implementation provided with this document, the Atmel vendor ID (03EBh) is used along with a custom product ID (6119h).

The configuration descriptor is followed by interface, endpoint and class- specific descriptors.

const CDCDSerialDriverConfigurationDescriptors configurationDescriptors[];

Configuration Descriptor

    {
        sizeof(USBConfigurationDescriptor),
        USBGenericDescriptor_CONFIGURATION,
        sizeof(CDCDSerialDriverConfigurationDescriptors),
        2, // There are two interfaces in this configuration
        1, // This is configuration #1
        0, // No string descriptor for this configuration
        BOARD_USB_BMATTRIBUTES,
        USBConfigurationDescriptor_POWER(100)
    },

Communication Class Interface Descriptor

The bInterfaceClass should be set to 0x02 and bInterfaceSubClass should be set to 0x02.

    {
        sizeof(USBInterfaceDescriptor),
        USBGenericDescriptor_INTERFACE,
        0, // This is interface #0
        0, // This is alternate setting #0 for this interface
        1, // This interface uses 1 endpoint
        CDCCommunicationInterfaceDescriptor_CLASS,
        CDCCommunicationInterfaceDescriptor_ABSTRACTCONTROLMODEL,
        CDCCommunicationInterfaceDescriptor_NOPROTOCOL,
        0  // No string descriptor for this interface
    },

Functional - Header Descriptor

Functional - Call Management Descriptor

    {
        sizeof(CDCCallManagementDescriptor),
        CDCGenericDescriptor_INTERFACE,
        CDCGenericDescriptor_CALLMANAGEMENT,
        CDCCallManagementDescriptor_SELFCALLMANAGEMENT,
        0 // No associated data interface
    },

Functional - Abstract Control Management Descriptor

Functional - Union Descriptor

    {
        sizeof(CDCUnionDescriptor),
        CDCGenericDescriptor_INTERFACE,
        CDCGenericDescriptor_UNION,
        0, // Number of master interface is #0
        1 // First slave interface is #1
    },

Notification Endpoint Descriptor

The EP is defined as CDCDSerialDriverDescriptors_NOTIFICATION.

    {
        sizeof(USBEndpointDescriptor), 
        USBGenericDescriptor_ENDPOINT,
        USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_IN,
                     CDCDSerialDriverDescriptors_NOTIFICATION),
        USBEndpointDescriptor_INTERRUPT,
        MIN(BOARD_USB_ENDPOINTS_MAXPACKETSIZE(
                     CDCDSerialDriverDescriptors_NOTIFICATION),
            USBEndpointDescriptor_MAXINTERRUPTSIZE_FS),
        10 // Endpoint is polled every 10ms
    },

Data Class Interface Descriptor

    {
        sizeof(USBInterfaceDescriptor),
        USBGenericDescriptor_INTERFACE,
        1, // This is interface #1
        0, // This is alternate setting #0 for this interface
        2, // This interface uses 2 endpoints
        CDCDataInterfaceDescriptor_CLASS,
        CDCDataInterfaceDescriptor_SUBCLASS,
        CDCDataInterfaceDescriptor_NOPROTOCOL,
        0  // No string descriptor for this interface
    },

Data Endpoint Descriptors

The EPs are defined as CDCDSerialDriverDescriptors_DATAOUT & CDCDSerialDriverDescriptors_DATAIN.

    // Bulk-OUT endpoint standard descriptor
    {
        sizeof(USBEndpointDescriptor), 
        USBGenericDescriptor_ENDPOINT,
        USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_OUT,
                           CDCDSerialDriverDescriptors_DATAOUT),
        USBEndpointDescriptor_BULK,
        MIN(BOARD_USB_ENDPOINTS_MAXPACKETSIZE(
            CDCDSerialDriverDescriptors_DATAOUT),
            USBEndpointDescriptor_MAXBULKSIZE_FS),
        0 // Must be 0 for full-speed bulk endpoints
    },
    // Bulk-IN endpoint descriptor
    {
        sizeof(USBEndpointDescriptor),
        USBGenericDescriptor_ENDPOINT,
        USBEndpointDescriptor_ADDRESS(USBEndpointDescriptor_IN,
                           CDCDSerialDriverDescriptors_DATAIN),
        USBEndpointDescriptor_BULK,
        MIN(BOARD_USB_ENDPOINTS_MAXPACKETSIZE(
            CDCDSerialDriverDescriptors_DATAIN),
            USBEndpointDescriptor_MAXBULKSIZE_FS),
        0 // Must be 0 for full-speed bulk endpoints
    },

String Descriptors

Several descriptors (device, configuration, interface, etc.) can specify the index of a string descriptor to comment their use.

The actual string code is defined: stringDescriptors.

Class-specific requests

The CDC specification defines a set of class-specific requests for devices implementing the ACM. This section details these requests. Please refer to section 3.6.2.1 of the CDC spec. 1.1 for more information.

SetLineCoding, GetLineCoding

These requests are sent by the host to modify or retrieve the configuration of the serial line, which includes:

When the terminal application (such as HyperTerminal) on the host (PC) side changes the setting of the COM port, a SetLineCoding request is sent with the new parameters. The host may also retrieve the current setting using GetLineCoding, not modifying them if they are correct.

When a SET_LINE_CODING request is received, the device should read the new parameters. Then program the new parameters in the USART. A callback must be provided to the USBD_Read function. See CDCDSerialDriver_SetLineCoding.

The code handling GET_LINE_CODING shall simply invoke the USBD_Write function to send the current settings of the USART to the host. See CDCDSerialDriver_GetLineCoding.

SetControlLineState

This request is sent by the host to notify the device of two state changes. The first bit (D0) of the wValue field of the request indicates whether or not a terminal is connected to the virtual COM port. Bit D1 indicates that the USART should enable/disable its carrier signal to start/stop receiving and transmitting data.

In practice, the USB to serial converter should operate only when those two bits are set. Otherwise, it should not transmit or receive data.

Since the SET_CONTROL_LINE_STATE request does not have a data payload, the device only has to acknowledge the request by sending a ZLP (zero-length packet), using the USBD_Write method. See CDCDSerialDriver_SetControlLineState.

Before that, the wValue field should be parsed to retrieve the new control line state. A single boolean variable can be used to keep track of the connection state. If both the D0 and D1 bits are set, then the converter should operate normally, i.e., forward data between the USART and the USB host. Otherwise, it should stop its activity.

Notifications

Notifications are sent by the device when an event, such as a serial line state change, has occurred. In this example, they are transmitted through a dedicated Interrupt IN endpoint. A special header must precede the data payload of each notification. This header has the same format of a SETUP request, so the USBGenericRequest structure defined in the AT91 USB device framework can be used.

Note that the device should only send a notification when there is a state change, and not continuously. This does not really matter in practice, but only sending notifications sporadically will reduce the stress on the device.

When the serial state is changed by CDCDSerialDriver_SetSerialState, the notification is sent to the host.

CDC Serial Driver API

Main Application

The job of the main application is to bridge the USART and the USB. This means that data read from one end must be forwarded to the other end. This section describes several possibilities to do this.

USB Operation

Reading data coming from the host is done using the CDCDSerialDriver_Read(). Since this is an asynchronous function, it does not block the execution flow. This means that other actions (like reading data from the USART) can be performed while the transfer is going on. Whenever some data is sent by the host, the transfer terminates and the associated callback function is invoked. This callback (UsbDataReceived()) can be programmed to forward the received data through the USART.

Likewise, the CDCDSerialDriver_Write() function can be called as soon as there is data to transmit, again without block the program flow. However, there cannot be two write operations at the same time, so the program must check whether or not the last transfer is complete. This can be done by checking the result code of the CDCDSerialDriver_Write() method. If USBD_STATUS_LOCKED is returned, then there is already another operation in progress. The device will have to buffer the data retrieved from the USART until the endpoint becomes free again.

USART Operation

The USART peripheral present on AT91 chips can be used in two different ways. The classic way is to read and write one byte at a time in the correct registers to send and receive data.

A more powerful method is available on AT91SAM chips, by using the embedded Peripheral DMA Controller (PDC). The PDC can take care of transfers between the processor, memory and peripherals, thus freeing the processor to perform other tasks. Since the PDC interrupt happens on the buffer full, Some timer can be used to check if there is any data frags input from the USART.

Using a Generic Host Driver

See USB CDC Serial Host Driver.

Add two or more ports in one USB device

See USB Dual Port CDC Serial Device.

 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines