Basic example HS device: USB HID device
This section contains a full worked example of a High Speed USB 2.0 HID Class device. The example code in this document is intended for xCORE-USB (U-Series) devices. The code would be very similar for an xCORE General Purpose (L-Series) devices with external ULPI transceiver, with only the declarations and call to XUD_Manager() being different.
The full source for this demo is released as the HID Class USB Device Demo available through the XMOS xTIMEcomposer tool. The tool can be downloaded from www.xmos.com.
Declarations
#include <xs1.h> #include "xud.h" #define XUD_EP_COUNT_OUT 1 #define XUD_EP_COUNT_IN 2 /* Endpoint type tables */ XUD_EpType epTypeTableOut[XUD_EP_COUNT_OUT] = { XUD_EPTYPE_CTL | XUD_STATUS_ENABLE }; XUD_EpType epTypeTableIn[XUD_EP_COUNT_IN] = { XUD_EPTYPE_CTL | XUD_STATUS_ENABLE, XUD_EPTYPE_BUL };
Main program
The main function creates three tasks: the XUD manager, endpoint 0, and HID process. An array of channels is used for both in and out endpoints, endpoint 0 requires both, the HID process is simply an IN endpoint sending mouse data to the host.
int main() { chan c_ep_out[XUD_EP_COUNT_OUT], c_ep_in[XUD_EP_COUNT_IN]; par { XUD_Manager(c_ep_out, XUD_EP_COUNT_OUT, c_ep_in, XUD_EP_COUNT_IN, null, epTypeTableOut, epTypeTableIn, null, null, null, XUD_SPEED_HS, null); Endpoint0(c_ep_out[0], c_ep_in[0]); hid_mouse(c_ep_in[1]); } return 0; }
Since we do not require SOF notifications null is passed into the c_sof parameter. XUD_SPEED_HS is passed for the desiredSpeed parameter as we wish to run as a high-speed device. Test mode support is not important for this example to null is also passed to the c_usb_testmode parameter.
HID response function
This function responds to the HID requests—it draws a square using the mouse moving 40 pixels in each direction in sequence every 100 requests. Change this function to feed other data back (for example based on user input). It demonstrates the use of XUD_SetBuffer.
void hid_mouse(chanend c_ep1) { char buffer[] = {0, 0, 0, 0}; int counter = 0; int state = 0; XUD_ep ep = XUD_Init_Ep(c_ep1); counter = 0; while(1) { counter++; if(counter == 100) { if(state == 0) { buffer[1] = 40; buffer[2] = 0; state+=1; } else if(state == 1) { buffer[1] = 0; buffer[2] = 40; state+=1; } else if(state == 2) { buffer[1] = -40; buffer[2] = 0; state+=1; } else if(state == 3) { buffer[1] = 0; buffer[2] = -40; state = 0; } counter = 0; } else { buffer[1] = 0; buffer[2] = 0; } XUD_SetBuffer(c_ep, buffer, 4) < 0; } }
Note, this endpoint does not receive or check for status data. It always performs IN transactions. Since it’s behaviour is not modified based on bus speed the mouse cursor will move more slowly when connected via a full-speed port. Ideally the device would either modify its required polling rate in its descriptors (bInterval in the endpoint descriptor) or the counter value it is using in the hid_mouse() function.
Should processing take longer that the host IN polls, the XUD_Manager core will simply NAK the host. The XUD_SetBuffer() function will return when the packet transmission is complete.
Device Descriptors
The USB_StandardRequests() function expects descriptors be declared as arrays of characters. Descriptors are looked at in depth in this section.
Device descriptor
The device descriptor contains basic information about the device. This descriptor is the first descriptor the host reads during its enumeration process and it includes information that enables the host to further interrogate the device. The descriptor includes information on the descriptor itself, the device (USB version, vendor ID etc.), its configurations and any classes the device implements.
For the HID Mouse example this descriptor looks like the following:
static unsigned char devDesc[] = { 0x12, /* 0 bLength */ USB_DESCTYPE_DEVICE, /* 1 bdescriptorType */ 0x00, /* 2 bcdUSB */ 0x02, /* 3 bcdUSB */ 0x00, /* 4 bDeviceClass */ 0x00, /* 5 bDeviceSubClass */ 0x00, /* 6 bDeviceProtocol */ 0x40, /* 7 bMaxPacketSize */ (VENDOR_ID & 0xFF), /* 8 idVendor */ (VENDOR_ID >> 8), /* 9 idVendor */ (PRODUCT_ID & 0xFF), /* 10 idProduct */ (PRODUCT_ID >> 8), /* 11 idProduct */ (BCD_DEVICE & 0xFF), /* 12 bcdDevice */ (BCD_DEVICE >> 8), /* 13 bcdDevice */ 0x01, /* 14 iManufacturer */ 0x02, /* 15 iProduct */ 0x00, /* 16 iSerialNumber */ 0x01 /* 17 bNumConfigurations */
Device qualifier descriptor
Devices which support both full and high-speeds must implement a device qualifier descriptor. The device qualifier descriptor defines how fields of a high speed device’s descriptor would look if that device is run at a different speed. If a high-speed device is running currently at full/high speed, fields of this descriptor reflect how device descriptor fields would look if speed was changed to high/full. Please refer to section 9.6.2 of the USB 2.0 specification for further details.
For a full-speed only device this is not required.
Typically a device qualifier descriptor is derived mechanically from the device descriptor. The USB_StandardRequest function will build a device qualifier from the device descriptors passed to it based on the speed the device is currently running at.
Configuration descriptor
The configuration descriptor contains the devices features and abilities. This descriptor includes Interface and Endpoint Descriptors. Every device must have at least one configuration, in our example there is only one configuration. The configuration descriptor is presented below:
static unsigned char cfgDesc[] = { 0x09, /* 0 bLength */ 0x02, /* 1 bDescriptortype */ 0x22, 0x00, /* 2 wTotalLength */ 0x01, /* 4 bNumInterfaces */ 0x01, /* 5 bConfigurationValue */ 0x03, /* 6 iConfiguration */ 0x80, /* 7 bmAttributes */ 0xC8, /* 8 bMaxPower */ 0x09, /* 0 bLength */ 0x04, /* 1 bDescriptorType */ 0x00, /* 2 bInterfacecNumber */ 0x00, /* 3 bAlternateSetting */ 0x01, /* 4: bNumEndpoints */ 0x03, /* 5: bInterfaceClass */ 0x00, /* 6: bInterfaceSubClass */ 0x02, /* 7: bInterfaceProtocol*/ 0x00, /* 8 iInterface */ 0x09, /* 0 bLength. Note this is currently replicated in hidDescriptor[] below */ 0x21, /* 1 bDescriptorType (HID) */ 0x10, /* 2 bcdHID */ 0x11, /* 3 bcdHID */ 0x00, /* 4 bCountryCode */ 0x01, /* 5 bNumDescriptors */ 0x22, /* 6 bDescriptorType[0] (Report) */ 0x48, /* 7 wDescriptorLength */ 0x00, /* 8 wDescriptorLength */ 0x07, /* 0 bLength */ 0x05, /* 1 bDescriptorType */ 0x81, /* 2 bEndpointAddress */ 0x03, /* 3 bmAttributes */ 0x40, /* 4 wMaxPacketSize */ 0x00, /* 5 wMaxPacketSize */ 0x01 /* 6 bInterval */
Other speed configuration descriptor
A other speed configuration for similar reasons as the device qualifier descriptor. The USB_StandardRequests() function generates this descriptor from the Configuration Descriptors passed to it based on the bus speed it is currently running at. For the HID mouse example we used the same configuration Descriptors if running on full-speed or high-speed.
String descriptors
An array of strings supplies all the strings that are referenced from the descriptors (using fields such as ‘iInterface’, ‘iProduct’ etc.). Note that String 0 is always language ID descriptor.
The USB_StandardRequests() function deals with requests for strings using the table of strings passed to it. The string table for the HID mouse example is shown below:
static char * unsafe stringDescriptors[]= { "\x09\x04", // Language ID string (US English) "XMOS", // iManufacturer "Example HID Mouse", // iProduct "Config", // iConfiguration
Note that the null values and length 0 is passed for the full-speed descriptors, this means that the same descriptors will be used whether the device is running in full or high-speed.
Application and class specific requests
Although the USB_StandardRequests() function deals with many of the requests the device is required to handle in order to be properly enumerated by a host, typically a USB device will have Class (or Application) specific requests that must be handled.
In the case of the HID mouse there are three mandatory requests that must be handled:
- GET_DESCRIPTOR
- HID Return the HID descriptor
- REPORT Return the HID report descriptor
- GET_REPORT Return the HID report data
Please refer to the HID Specification and related documentation for full details of all HID requests.
The HID report descriptor informs the hosts of the contents of the HID reports that it will be sending to the host periodically. For a mouse this could include X/Y axis values, button presses etc. Tools for building these descriptors are available for download on the usb.org website.
The HID report descriptor for the HID mouse example is shown below:
static unsigned char hidReportDescriptor[] = { 0x05, 0x01, // Usage page (desktop) 0x09, 0x02, // Usage (mouse) 0xA1, 0x01, // Collection (app) 0x05, 0x09, // Usage page (buttons) 0x19, 0x01, 0x29, 0x03, 0x15, 0x00, // Logical min (0) 0x25, 0x01, // Logical max (1) 0x95, 0x03, // Report count (3) 0x75, 0x01, // Report size (1) 0x81, 0x02, // Input (Data, Absolute) 0x95, 0x01, // Report count (1) 0x75, 0x05, // Report size (5) 0x81, 0x03, // Input (Absolute, Constant) 0x05, 0x01, // Usage page (desktop) 0x09, 0x01, // Usage (pointer) 0xA1, 0x00, // Collection (phys) 0x09, 0x30, // Usage (x) 0x09, 0x31, // Usage (y) 0x15, 0x81, // Logical min (-127) 0x25, 0x7F, // Logical max (127) 0x75, 0x08, // Report size (8) 0x95, 0x02, // Report count (2) 0x81, POSITION_TYPE, // Input (Data, Rel=0x6, Abs=0x2) 0xC0, // End collection 0x09, 0x38, // Usage (Wheel) 0x95, 0x01, // Report count (1) 0x81, 0x02, // Input (Data, Relative) 0x09, 0x3C, // Usage (Motion Wakeup) 0x15, 0x00, // Logical min (0) 0x25, 0x01, // Logical max (1) 0x75, 0x01, // Report size (1) 0x95, 0x01, // Report count (1) 0xB1, 0x22, // Feature (No preferred, Variable) 0x95, 0x07, // Report count (7) 0xB1, 0x01, // Feature (Constant) 0xC0 // End collection
The request for this descriptor (and the other required requests) should be implemented before making the call to USB_StandardRequests(). The programmer may decide not to make a call to USB_StandardRequests if the request is fully handled. It is possible the programmer may choose to implement some functionality for a request, then allow USB_StandardRequests() to finalize.
The complete code listing for the main endpoint 0 task is show below:
void Endpoint0(chanend chan_ep0_out, chanend chan_ep0_in) { USB_SetupPacket_t sp; unsigned bmRequestType; XUD_BusSpeed_t usbBusSpeed; XUD_ep ep0_out = XUD_InitEp(chan_ep0_out); XUD_ep ep0_in = XUD_InitEp(chan_ep0_in); while(1) { /* Returns XUD_RES_OKAY on success */ XUD_Result_t result = USB_GetSetupPacket(ep0_out, ep0_in, sp); if(result == XUD_RES_OKAY) { /* Set result to ERR, we expect it to get set to OKAY if a request is handled */ result = XUD_RES_ERR; /* Stick bmRequest type back together for an easier parse... */ bmRequestType = (sp.bmRequestType.Direction<<7) | (sp.bmRequestType.Type<<5) | (sp.bmRequestType.Recipient); if(USE_XSCOPE) { /* Stick bmRequest type back together for an easier parse... */ unsigned bmRequestType = (sp.bmRequestType.Direction<<7) | (sp.bmRequestType.Type<<5) | (sp.bmRequestType.Recipient); if ((bmRequestType == USB_BMREQ_H2D_STANDARD_DEV) && (sp.bRequest == USB_SET_ADDRESS)) { debug_printf("Address allocated %d\n", sp.wValue); } } switch(bmRequestType) { /* Direction: Device-to-host * Type: Standard * Recipient: Interface */ case USB_BMREQ_D2H_STANDARD_INT: if(sp.bRequest == USB_GET_DESCRIPTOR) { /* HID Interface is Interface 0 */ if(sp.wIndex == 0) { /* Look at Descriptor Type (high-byte of wValue) */ unsigned short descriptorType = sp.wValue & 0xff00; switch(descriptorType) { case HID_HID: result = XUD_DoGetRequest(ep0_out, ep0_in, hidDescriptor, sizeof(hidDescriptor), sp.wLength); break; case HID_REPORT: result = XUD_DoGetRequest(ep0_out, ep0_in, hidReportDescriptor, sizeof(hidReportDescriptor), sp.wLength); break; } } } break; /* Direction: Device-to-host and Host-to-device * Type: Class * Recipient: Interface */ case USB_BMREQ_H2D_CLASS_INT: case USB_BMREQ_D2H_CLASS_INT: /* Inspect for HID interface num */ if(sp.wIndex == 0) { /* Returns XUD_RES_OKAY if handled, * XUD_RES_ERR if not handled, * XUD_RES_RST for bus reset */ result = HidInterfaceClassRequests(ep0_out, ep0_in, sp); } break; } } /* If we haven't handled the request about then do standard enumeration requests */ if(result == XUD_RES_ERR ) { /* Returns XUD_RES_OKAY if handled okay, * XUD_RES_ERR if request was not handled (STALLed), * XUD_RES_RST for USB Reset */ unsafe{ result = USB_StandardRequests(ep0_out, ep0_in, devDesc, sizeof(devDesc), cfgDesc, sizeof(cfgDesc), null, 0, null, 0, stringDescriptors, sizeof(stringDescriptors)/sizeof(stringDescriptors[0]), sp, usbBusSpeed); } } /* USB bus reset detected, reset EP and get new bus speed */ if(result == XUD_RES_RST) { usbBusSpeed = XUD_ResetEndpoint(ep0_out, ep0_in); } } }
The skeleton HidInterfaceClassRequests() function deals with any outstanding HID requests. See the USB HID Specification for full request details:
XUD_Result_t HidInterfaceClassRequests(XUD_ep c_ep0_out, XUD_ep c_ep0_in, USB_SetupPacket_t sp) { unsigned buffer[64]; unsigned tmp; switch(sp.bRequest) { case HID_GET_REPORT: /* Mandatory. Allows sending of report over control pipe */ /* Send a hid report - note the use of asm due to shared mem */ asm("ldaw %0, dp[g_reportBuffer]": "=r"(tmp)); asm("ldw %0, %1[0]": "=r"(tmp) : "r"(tmp)); buffer[0] = tmp; return XUD_DoGetRequest(c_ep0_out, c_ep0_in, (buffer, unsigned char []), 4, sp.wLength); break; case HID_GET_IDLE: /* Return the current Idle rate - optional for a HID mouse */ /* Do nothing - i.e. STALL */ break; case HID_GET_PROTOCOL: /* Required only devices supporting boot protocol devices, * which this example does not */ /* Do nothing - i.e. STALL */ break; case HID_SET_REPORT: /* The host sends an Output or Feature report to a HID * using a cntrol transfer - optional */ /* Do nothing - i.e. STALL */ break; case HID_SET_IDLE: /* Set the current Idle rate - this is optional for a HID mouse * (Bandwidth can be saved by limiting the frequency that an * interrupt IN EP when the data hasn't changed since the last * report */ /* Do nothing - i.e. STALL */ break; case HID_SET_PROTOCOL: /* Required only devices supporting boot protocol devices, * which this example does not */ /* Do nothing - i.e. STALL */ break; } return XUD_RES_ERR; }
If the HID request is not handles, the function returns XUD_RES_ERR. This results in USB_StandardRequests() being called, and eventually the endpoint being STALLed to indicate an unknown request.