USBD ROM Stack  1.0
ROM based USB device stack
Defining USB Descriptors

A USB device provides information about itself in data structures called USB descriptors. This section provides information about various descriptors that a USB device should provide to host during enumeration process.

Defining USB Descriptors

The host obtains descriptors from an attached device by sending various standard control requests (GET_DESCRIPTOR requests) to the default endpoint. Those requests specify the type of descriptor to retrieve. In response to such requests, the device sends descriptors that include information about the device, its configurations, interfaces and the related endpoints. Device descriptors contain information about the whole device. Configuration descriptors contain information about each device configuration. String descriptors contain Unicode text strings. The USB ROM stack handles all these standard requests eliminating the complexity from user application, as long as the user application provides the proper descriptor arrays to stack initialization routine.

Every USB device exposes a device descriptor that indicates the device's class information, vendor and product identifiers, and number of configurations. Each configuration exposes its configuration descriptor that indicates number of interfaces and power characteristics. Each interface exposes an interface descriptor for each of its alternate settings that contain information about the class and the number of endpoints. Each endpoint within each interface exposes endpoint descriptors that indicate the endpoint type and the maximum packet size. User application should provide these descriptors to the ROM stack for proper handling of standard requests.

Defining USB Device Descriptor

The device descriptor contains information about a USB device as a whole. The pointer to the memory containing device descriptor should be provided by application through device_desc field of USB_CORE_DESCS_T structure, whose address is passed to stack initialization routine usbapi->hw->init(). The user application can create an instance of _USB_DEVICE_DESCRIPTOR structure in global scope (defined in memory accessible by USB bus master) and pass the address of the instance in device_desc field. Or the application can define the descriptor as character array as shown below:

/* USB Standard Device Descriptor */
ALIGNED(4) const uint8_t USB_DeviceDescriptor[] =
{
        USB_DEVICE_DESC_SIZE,              /* bLength */
        USB_DEVICE_DESCRIPTOR_TYPE,        /* bDescriptorType */
        WBVAL(0x0200), /* 2.00 */          /* bcdUSB */
        0x00,                              /* bDeviceClass */
        0x00,                              /* bDeviceSubClass */
        0x00,                              /* bDeviceProtocol */
        USB_MAX_PACKET0,                   /* bMaxPacketSize */
        WBVAL(0x1FC9),                     /* idVendor: Vendor ID (assigned by the USB-IF) */
        WBVAL(0x0001),                     /* idProduct Product ID (assigned by the manufacturer)*/
        WBVAL(0x0100), /* 1.00 */          /* bcdDevice */
        0x01,                              /* iManufacturer */
        0x02,                              /* iProduct */
        0x03,                              /* iSerialNumber */
        0x01                               /* bNumConfigurations */
};
  • bLength: Size of this descriptor in bytes. Always set this field to 18.
  • bDescriptorType: DEVICE descriptor type. Always set this field to 0x01.
  • bcdUSB: Set to USB specification version the device and its descriptors are compliant. Setting the value to WBVAL(0x0200) implies the device is compliant with USB specification version 2.00.
  • bDeviceClass: Class code. If this field is reset to zero, each interface within a configuration specifies its own class information and the various interfaces operate independently. If this field is set to a value between 1 and FEH, the device supports different class specifications on different interfaces and the interfaces may not operate independently. This value identifies the class definition used for the aggregate interfaces. If this field is set to FFH, the device class is vendor-specific.
  • bDeviceSubClass: Set the subclass code of the device as assigned by the USB specification group.
  • bDeviceProtocol: Set the protocol code of the device as assigned by the USB specification group.
  • bMaxPacketSize: Maximum packet size, in bytes, for endpoint zero of the device. Current ROM stack implementation assumes 64 bytes, hence set this field to 64.
  • idVendor: Set the USB Vendor ID assigned to the manufacturer of this product, by the USB-IF.
  • idProduct: Set the product ID assigned by the manufacturer for this product.
  • bcdDevice: Device release number in binary-coded decimal.
  • iManufacturer: Set index of the string descriptor that provides a string containing the name of the manufacturer of this device. Check Defining USB String Descriptor for more details in defining string descriptors.
  • iProduct: Set index of the string descriptor that provides a string that contains a description of the device. Check Defining USB String Descriptor for more details in defining string descriptors.
  • iSerialNumber: Set index of the string descriptor that provides a string that contains a manufacturer-determined serial number for the device. Check Defining USB String Descriptor for more details in defining string descriptors.
  • bNumConfigurations: Set the total number of possible configurations for the device.

Defining USB Device Qualifier Descriptor

The device_qualifier descriptor describes information about a high-speed capable device that would change if the device were operating at the other speed (see _USB_DEVICE_QUALIFIER_DESCRIPTOR structure). For example, if the device is currently operating at full-speed, the device_qualifier returns information about how it would operate at high-speed and vice-versa. The pointer to the memory containing device qualifier descriptor should be provided by application through device_qualifier field of USB_CORE_DESCS_T structure, whose address is passed to stack initialization routine usbapi->hw->init().

Note:
If application is implementing full-speed only device this field should be set to 0. And also the field high_speed_desc and full_speed_desc should point to full-speed configuration descriptor array.
/* USB Device Qualifier */
ALIGNED(4) const uint8_t USB_DeviceQualifier[] = {
  USB_DEVICE_QUALI_SIZE,              /* bLength */
  USB_DEVICE_QUALIFIER_DESCRIPTOR_TYPE, /* bDescriptorType */
  WBVAL(0x0200), /* 2.00 */          /* bcdUSB */
  0x00,                              /* bDeviceClass */
  0x00,                              /* bDeviceSubClass */
  0x00,                              /* bDeviceProtocol */
  USB_MAX_PACKET0,                   /* bMaxPacketSize0 */
  0x01,                              /* bNumOtherSpeedConfigurations */
  0x00                               /* bReserved */
};

Defining USB Configuration Descriptors Array

A USB device exposes its capabilities in the form of a series of interfaces called a USB configuration. A USB configuration is described in a configuration descriptor (see _USB_CONFIGURATION_DESCRIPTOR structure). A configuration descriptor contains information about the configuration and its interfaces, alternate settings, and their endpoints. Each interface descriptor or alternate setting is described in a _USB_INTERFACE_DESCRIPTOR structure. In a configuration, each interface descriptor is followed in memory by all of the endpoint descriptors for the interface and alternate setting. Each endpoint descriptor is stored in a _USB_ENDPOINT_DESCRIPTOR structure.

The ROM stack assumes that all the descriptors associated with the configuration are arranged as a consecutive byte array in memory. The address to this array is passed to the stack through full_speed_desc and high_speed_desc field of USB_CORE_DESCS_T structure, whose address is passed to usbapi->hw->init() routine. The following diagram illustrates how configuration information should be laid out in memory.


config_des.png


The following example shows the configuration descriptor for the USB Mass storage class device:

/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
ALIGNED(4)  uint8_t USB_FsConfigDescriptor[] = {
/* Configuration 1 */
  USB_CONFIGUARTION_DESC_SIZE,       /* bLength. Set to 0x09. */
  USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType. Set to 0x02.*/
  WBVAL(                             /* wTotalLength: contains total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration.  */
    1*USB_CONFIGUARTION_DESC_SIZE +
    1*USB_INTERFACE_DESC_SIZE     +
    2*USB_ENDPOINT_DESC_SIZE
  ),
  0x01,                              /* bNumInterfaces: total number of interfaces the device supports */
  0x01,                              /* bConfigurationValue: This field indicates the index for the configuration defined in the firmware of the device. The host functional driver uses that index value to select an active configuration. */
  0x00,                              /* iConfiguration */

  USB_CONFIG_SELF_POWERED,           /* bmAttributes: contains a bitmask that indicates whether the configuration supports the remote wake-up feature, and whether the device is bus-powered or self-powered. */
  
  USB_CONFIG_POWER_MA(100),          /* bMaxPower: specifies the maximum power (in milliamp units) that the device can draw from the host, when the device is bus-powered.  */

/* Interface 0, Alternate Setting 0, MSC Class */
  USB_INTERFACE_DESC_SIZE,           /* bLength */
  USB_INTERFACE_DESCRIPTOR_TYPE,     /* bDescriptorType */
  0x00,                              /* bInterfaceNumber */
  0x00,                              /* bAlternateSetting */
  0x02,                              /* bNumEndpoints */
  USB_DEVICE_CLASS_STORAGE,          /* bInterfaceClass */
  MSC_SUBCLASS_SCSI,                 /* bInterfaceSubClass */
  MSC_PROTOCOL_BULK_ONLY,            /* bInterfaceProtocol */
  0x04,                              /* iInterface: Index to string descriptor containing interface description. */

/* Bulk In Endpoint */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  MSC_EP_IN,                         /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(64),                         /* wMaxPacketSize */
  0,                                 /* bInterval */
/* Bulk Out Endpoint */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  MSC_EP_OUT,                        /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(64),                         /* wMaxPacketSize */
  0,                                 /* bInterval */
/* Terminator */
  0                                  /* bLength: Indicates to ROM stack the end of descriptor array. */
};
Note:
For devices implementing multiple configurations the second configuration descriptors should follow immediately after the first configuration descriptors with NULL descriptor at the end of the second configuration array.

Defining USB Other Speed Configuration

The other_speed_configuration describes a configuration of a high-speed capable device if it were operating at its other possible speed. The structure _USB_OTHER_SPEED_CONFIGURATION is identical to a configuration descriptor. Except the bDescriptorType field which is set to 0x07. To optimize application data memory usage the ROM stack reuses the meory pointed by full_speed_desc and high_speed_desc fields when reporting other speed configuration descriptor.

Note:
The stack updates bDescriptorType field before sending the descriptor data to host. Hence the applications should have the configuration descriptors in read-write memory area such as ISRAM and not declare the descriptor array with const qualifier.

Defining USB String Descriptor

Device, configuration, and interface descriptors may contain references to string descriptors. String descriptors are referenced by their one-based index number. A string descriptor contains one or more Unicode strings; each string is a translation of the others into another language.

A string descriptor contains:

  • bLength: Size of this descriptor in bytes. This field is at offset 0 and occupies 1 byte. The size of the descriptor is size of Unicode string + 2.
  • bDescriptorType: STRING descriptor Type. Always set this field to 0x03. This field is at offset 1 and occupies 1 byte.
  • bString: UNICODE encoded string.

String index zero for all languages should return a string descriptor that contains an array of two-byte LANGID codes supported by the device. Current implementation of ROM stack assumes single language support. For applications using US English strings this descriptor should be:

        0x04,          /* bLength */
        0x03,          /* bDescriptorType */
        WBVAL(0x0409), /* wLANGID: US English */    

The ROM stack assumes that all USB strings referenced in various descriptors are provided to stack as a single character array containing multiple string descriptors. ROM stack traverses to the next descriptor in array by adding the value of bLength field to current index. Hence it is important to construct this descriptor array properly with bLength fields reflecting the exact size of its string descriptor.

/* USB String Descriptor (optional) */
ALIGNED(4) uint8_t USB_StringDescriptor[] =
{
        /* Index 0x00: LANGID Codes */
        0x04,                        /* bLength */
        USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType */
        WBVAL(0x0409),               /* wLANGID : US English */         

/* Index 0x01: Manufacturer */
        (2*2 + 2),                   /* bLength (3 Char + Type + length) */
        USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType */
        'N', 0,
        'X', 0,
        'P', 0,

        /* Index 0x02: Product */
        (20*2 + 2),                  /* bLength */
        USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType */
        'N', 0,
        'X', 0,
        'P', 0,
        ' ', 0,

        /* Index 0x03: Serial Number */
        (13*2 + 2),                  /* bLength (13 Char + Type + length)*/
        USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType */
        'A', 0,
        'B', 0,
        'C', 0,
        'D', 0,
        '1', 0,
        '2', 0,
        '3', 0,
        '4', 0,
        '5', 0,
        '6', 0,
        '7', 0,
        '8', 0,
        '9', 0,
};