The USB composite device class allows classes that are already defined in the USB library to be combined into a single composite device. The device configuration descriptors for the included device classes are merged at run time and returned to the USB host controller during device enumeration as a single composite USB device. Since each device class requires some unique initialization, the device classes provide a separate initialization API that does not touch the USB controller but does perform all other initialization. The initialization of the USB controller is deferred until the USB composite device is initialized and has merged the multiple device configuration de- scriptors into a single configuration descriptor so that it can properly initialize the USB controller. The endpoint numbers, interface numbers, and string indexes that are included in the device con- figuration descriptors are modified by the USB composite device class so that the values are valid in the composite device configuration descriptor.
2.9.1
Defining a Composite Device
The USB composite device class is defined at the top level in thetUSBDCompositeDevicestruc- ture which is used to describe the class to the USB library. In order for the USB composite device to enumerate and function properly, all members of this structure must be filled with valid infor- mation. The usVID and usPID values should have valid Vendor ID and Product ID values for the
composite device. The power requirements for the device as specified in the usMaxPowermA and ucPwrAttributes and should take into account the power requirements and settings for all devices classes that the composite device is using. The only truly optional member of thetUSBDCompos- iteDevicestructure is the pfnCallback function which provides notifications to the application that are not handled by the individual device classes. The device specific strings should be included in the ppui8StringDescriptors andui32NumStringDescriptorsmembers. This list of strings should include the following three strings in the following order: Manufacturer, Product, and Product serial number. All other strings used by the classes are specified and are sourced from the included device classes. The psPrivateData should be set to point to a tCompositeInstance structure which provides the composite class with memory for its instance data.
Note: It is important to insure that the microcontroller has enough endpoints to satisfy the number of devices included in the composite class.
Example: uint32_t g_pui32CompWorkspace[NUM_DEVICES]; tUSBDCompositeDevice g_sCompDevice = { // // Vendor ID. // VENDOR_ID, // // Product ID. // VENDOR_PRODUCT_ID, //
// This is in 2mA increments or 500mA. //
250,
//
// Bus powered device. //
USB_CONF_ATTR_BUS_PWR,
//
// Generic USB handler for the composite device. //
CompositeHandler,
//
// The string table. //
g_pStringDescriptors, NUM_STRING_DESCRIPTORS,
//
// The number of device classes in the composite entry array. //
NUM_DEVICES, g_psCompDevices };
2.9.2
Allocating Memory
The USB composite device class requires three different types of memory allocated to properly enumerate and function with the included device classes. The main allocation is a block of memory that is used to build up the combined device configuration descriptor for the combination of the desired device classes. The individual device classes provides a size in aCOMPOSITE_∗_SIZE
macro that indicates the size in bytes required to hold the configuration descriptor for the device class. This allows the application to provide a large enough buffer to the USBDCompositeInit()
function for merging the device descriptors.
2.9.2.1
Defining Device Class Instances
When defining a composite device the application must determine the size of the buffer that is passed into theUSBDCompositeInit()function. For example, if a composite device is made up of two serial devices then a buffer of size (COMPOSITE_DCDC_SIZE∗2) should be passed into the initialization routine and an array of that size should be declared in the application.
uint8_t pucDesciptorData[COMPOSITE_DCDC_SIZE*2];
The application must also allocate separate serial device structure for each instance of the devices that are included in a composite device. This is true even when including two devices classes of the same type are added so that the instances can be differentiated by the USB library. The USB composite device class can determine which instance to use based on the interface number that is accessed by the host controller. The application initializes the data in the array oftCompositeEntry
structures passed into the composite initialization for the class.
Example: Two serial instances and the composite device array.
extern tUSBDCDCDevice g_sCDCDeviceA; extern tUSBDCDCDevice g_sCDCDeviceB;
tCompositeEntry g_psDevices[2];
2.9.2.2
Interface Handling
The device class interfaces are merged into the composite device descriptor and the composite class modifies the default interface assignments to insure monotonically increasing indexes for all of the included interfaces. In the example above for the two serial ports, the first serial device would be interface 0 and the second would enumerate as interface 1.
2.9.2.3
String Handling
The device class strings are merged into the composite device descriptor which requires that the composite class modify the default string indexes. In doing this it always ignores the three default string indexes in the device descriptor. The remaining string indexes are modified to match in the configuration descriptor.
2.9.3
Example Composite Device
This section continues with the example above that used two USB device serial classes in a single device. This includes more detailed examples and code that demonstrate the configuration and setup needed for a composite serial device.
2.9.3.1
Composite Device Instance
The application must first allocate two serial device structures and pass them into the composite initialization function for the USB serial CDC device. The allocation and initialization are shown below:
//
// Buffers for serial device A. //
const tUSBBuffer g_sTxBufferA; const tUSBBuffer g_sRxBufferA;
//
// Buffers for serial device B. //
const tUSBBuffer g_sTxBufferB; const tUSBBuffer g_sRxBufferB;
//
// Device description for Serial Device A. //
const tUSBDCDCDevice g_sCDCDeviceA = { USB_VID_TI_1CBE, USB_PID_SERIAL, 0, USB_CONF_ATTR_SELF_PWR, ControlHandler, (void *)&g_sCDCDeviceA, USBBufferEventCallback, (void *)&g_sRxBufferA, USBBufferEventCallback, (void *)&g_sTxBufferA, 0, 0 }; //
// Device description for Serial Device B. //
const tUSBDCDCDevice g_sCDCDeviceB = { USB_VID_TI_1CBE, USB_PID_SERIAL, 0, USB_CONF_ATTR_SELF_PWR, ControlHandler, (void *)&g_sCDCDeviceB, USBBufferEventCallback, (void *)&g_sRxBufferB, USBBufferEventCallback, (void *)&g_sTxBufferB, 0, 0 };
Now the application must allocate the device array so that it is provided to the USB composite device class. The following code shows the allocation of the composite device array that holds the data for the two serial devices.
tCompositeEntry g_psDevices[2];
Once the array of devices has been allocated, this array is included in the USB composite de- vice structure when the device structure is allocated and initialized. The code below shows this allocation:
//
// Initialize the USB composite device structure. // tUSBDCompositeDevice g_sCompDevice = { // // TI USBLib VID. // USB_VID_TI_1CBE, //
// PID for the composite serial device. //
USB_PID_COMP_SERIAL,
//
// This is in 2mA increments so 500mA. //
250,
//
// Bus powered device. //
USB_CONF_ATTR_BUS_PWR,
//
// Generic USB handler for the composite device. //
CompositeHandler,
//
// The string table. //
g_pStringDescriptors, NUM_STRING_DESCRIPTORS,
//
// Include the array of composite devices. //
NUM_DEVICES, g_psCompDevices };
The last bit of memory that needs to be allocated is the USB composite device descriptor workspace which is provided at Initialization time. The allocation for two serial devices is shown below:
uint8_t pucDesciptorData[COMPOSITE_DCDC_SIZE*2];
Once all of the memory has been initialized and the appropriate memory allocated, the application must call the initialization functions for each device instance. In the case of the serial ports, the USB buffers used must also first be initialized before completing initialization.
//
// Initialize the transmit and receive buffers. // USBBufferInit((tUSBBuffer *)&g_sTxBufferA); USBBufferInit((tUSBBuffer *)&g_sRxBufferA); USBBufferInit((tUSBBuffer *)&g_sTxBufferB); USBBufferInit((tUSBBuffer *)&g_sRxBufferB); //
// Initialize the two serial port instances that are part of this composite // device.
//
pvSerialDeviceA =
USBDCDCCompositeInit(0, &g_sCDCDeviceA, &g_psCompDevices[0]); pvSerialDeviceB =
USBDCDCCompositeInit(0, &g_sCDCDeviceB, &g_psCompDevices[1]);
//
// Pass the device information to the USB library and place the device // on the bus.
//
USBDCompositeInit(0, &g_sCompDevice, COMPOSITE_DCDC_SIZE*2, pucDesciptorData);
When calling the USB device classes that are included with a composite device, the instance data for that class should be passed into the API. In the composite serial example that is being de- scribed in this section, the USB serial device classes provide the same callback function, Control- Handler(). The callback information for this was the device class structure which was specified as g_sCDCDeviceA or g_sCDCDeviceB for the serial devices. Since the device instance is different for each serial device, the application can simply cast the pointer to a pointer of typetUSBDCDCDevice
and use the data directly as shown below and only access the requested device:
uint32_t
ControlHandler(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgValue, void *pvMsgData) {
tUSBDCDCDevice pCDCDevice;
pCDCDevice = (tUSBDCDCDevice *)pvCBData; //
// Which event are we being asked to process? // switch(ui32Event) { ... } }