Example application#

This section contains a fully worked example of implementing a USB mouse device compliant to the Human Interface Device (HID) Class mouse device.

The application operates as a simple mouse which when running moves the mouse pointer on the host machine. This demonstrates the simple way in which PC peripheral devices can easily be deployed using an xcore device.

Note

This application note provides a standard USB HID class device and as a result does not require drivers to run on Windows, macOS or Linux.

The full source for this application is provided along side the lib_xud software download in the examples/app_hid_house directory.

Note

The example code provides implementations in C and XC. This section concentrates on the XC version.

Required hardware#

This application note is designed to run on XMOS xcore-200 or xcore.ai series devices.

The example code provided has been implemented and tested on the XK-EVK-XU316 board but there is no dependency on this board and it can be modified to run on any development board which uses an xcore-200 or xcore.ai series device.

Declarations#

#include "xud_device.h"
#include "hid_descs.h"

/* Number of Endpoints used by this app */
#define EP_COUNT_OUT   1
#define EP_COUNT_IN    2

/* Endpoint type tables - informs XUD what the transfer types for each Endpoint in use and also
 * if the endpoint wishes to be informed of USB bus resets
 */
XUD_EpType epTypeTableOut[EP_COUNT_OUT] = {XUD_EPTYPE_CTL | XUD_STATUS_ENABLE};
XUD_EpType epTypeTableIn[EP_COUNT_IN] =   {XUD_EPTYPE_CTL | XUD_STATUS_ENABLE, XUD_EPTYPE_BUL};

main()#

The main() function creates three tasks: the XUD Io task, endpoint 0, and a task for handling the HID endpoint. An array of channels is used for both IN and OUT endpoints, endpoint 0 requires both, the HID task simply implements an IN endpoint sending mouse data to the host.

int main()
{
    chan c_ep_out[EP_COUNT_OUT];
    chan c_ep_in[EP_COUNT_IN];

    par
    {
        on tile[0]: XUD_Main(c_ep_out, EP_COUNT_OUT, c_ep_in, EP_COUNT_IN, null,
                             epTypeTableOut, epTypeTableIn, XUD_SPEED_HS, XUD_PWR_BUS);

        on tile[0]: Endpoint0(c_ep_out[0], c_ep_in[0]);

        on tile[0]: hid_mouse(c_ep_in[1]);
    }

    return 0;
} //

Since this example does not require SOF notifications null is passed into the c_sof parameter. XUD_SPEED_HS is passed for the desiredSpeed parameter such that the device attempts to run as a high-speed device.

HID endpoint task#

This function responds to the HID requests - it moves the mouse cursor in square by moving 40 pixels in each direction in sequence every 100 requests using a basic state-machine. This function could be easily changed to feed other data back (for example based on user input).

void hid_mouse(chanend chan_ep_hid)
{
    int counter = 0;
    enum {RIGHT, DOWN, LEFT, UP} state = RIGHT;

    XUD_ep ep_hid = XUD_InitEp(chan_ep_hid);

    for(;;)
    {
        /* Move the pointer around in a square (relative) */
        if(counter++ >= 500)
        {
            int x;
            int y;

            switch(state)
            {
                case RIGHT:
                    x = 40;
                    y = 0;
                    state = DOWN;
                    break;

                case DOWN:
                    x = 0;
                    y = 40;
                    state = LEFT;
                    break;

                case LEFT:
                    x = -40;
                    y = 0;
                    state = UP;
                    break;

                case UP:
                default:
                    x = 0;
                    y = -40;
                    state = RIGHT;
                    break;
            }

            /* Unsafe region so we can use shared memory. */
            unsafe
            {
                /* global buffer 'g_reportBuffer' defined in hid_defs.h */
                char * unsafe p_reportBuffer = g_reportBuffer;

                p_reportBuffer[1] = x;
                p_reportBuffer[2] = y;

                /* Send the buffer to the host.  Note this will return when complete */
                XUD_SetBuffer(ep_hid, (char *) p_reportBuffer, sizeof(g_reportBuffer));
                counter = 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_Main() task 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 to be declared as arrays of characters. Descriptors are looked at in depth in this section.

Note

null values and length 0 are 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.

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 this 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 replicated in hidDescriptor[] */
    0x21,                 /* 1  bDescriptorType (HID) */
    0x11,                 /* 2  bcdHID */
    0x01,                 /* 3  bcdHID */
    0x00,                 /* 4  bCountryCode */
    0x01,                 /* 5  bNumDescriptors */
    0x22,                 /* 6  bDescriptorType[0] (Report) */
    0x32,                 /* 7  wDescriptorLength */
    0x00,                 /* 8  wDescriptorLength */

    0x07,                 /* 0  bLength */
    0x05,                 /* 1  bDescriptorType */
    0x81,                 /* 2  bEndpointAddress */
    0x03,                 /* 3  bmAttributes */
    0x40,                 /* 4  wMaxPacketSize */
    0x00,                 /* 5  wMaxPacketSize */
    0x0a                  /* 6  bInterval */
};

Other Speed Configuration descriptor#

An Other Speed Configuration descriptor is used 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 the same Configuration descriptors are uses regardless of bus-speed (i.e. 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.). The string at index 0 must always contain the Language ID Descriptor. This descriptor indicates the languages that the device supports for string descriptors.

The USB_StandardRequests() function deals with requests for strings using the table of strings passed to it. It handles the conversion of the raw strings to valid USB string descriptors.

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
    };

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

See the HID Class Specification and related documentation for full details of all HID requests.

The HID report descriptor informs the host of the contents of the HID reports that the device sending to the host periodically. For a mouse this could include X/Y axis values, button presses etc. A tool for building these descriptors is 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 (Generic Desktop) */
    0x09, 0x02,   /* Usage (Mouse) */
    0xA1, 0x01,   /* Collection (Application) */
    0x09, 0x01,   /* Usage (Pointer) */
    0xA1, 0x00,   /* Collection (Physical) */
    0x05, 0x09,   /* Usage Page (Buttons) */
    0x19, 0x01,   /* Usage Minimum (01) */
    0x29, 0x03,   /* Usage Maximum (03) */
    0x15, 0x00,   /* Logical Minimum (0) */
    0x25, 0x01,   /* Logical Maximum (1) */
    0x95, 0x03,   /* Report Count (3) */
    0x75, 0x01,   /* Report Size (1) */
    0x81, 0x02,   /* Input (Data,Variable,Absolute); 3 button bits */
    0x95, 0x01,   /* Report Count (1) */
    0x75, 0x05,   /* Report Size (5) */
    0x81, 0x01,   /* Input(Constant); 5 bit padding */
    0x05, 0x01,   /* Usage Page (Generic Desktop) */
    0x09, 0x30,   /* Usage (X) */
    0x09, 0x31,   /* Usage (Y) */
    0x15, 0x81,   /* Logical Minimum (-127) */
    0x25, 0x7F,   /* Logical Maximum (127) */
    0x75, 0x08,   /* Report Size (8) */
    0x95, 0x02,   /* Report Count (2) */
    0x81, 0x06,   /* Input (Data,Variable,Relative); 2 position bytes (X & Y) */
    0xC0,         /* End Collection */
    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 finalise.

The complete code listing for the main endpoint 0 task is shown 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 ((bmRequestType == USB_BMREQ_H2D_STANDARD_DEV) &&
                (sp.bRequest == USB_SET_ADDRESS))
            {
              // Host has set device address, value contained in 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);
        }
    }
} /* Endpoint0 */

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];

    switch(sp.bRequest)
    {
        case HID_GET_REPORT:

            /* Mandatory. Allows sending of report over control pipe */
            /* Send a hid report - note the use of unsafe due to shared mem */
            unsafe {
              char * unsafe p_reportBuffer = g_reportBuffer;
              buffer[0] = p_reportBuffer[0];
            }

            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;
} /* HidInterfaceClassRequests */

If the HID request is not handled, the function returns XUD_RES_ERR. This results in USB_StandardRequests() being called, and eventually the endpoint responding to the host with a STALL to indicate an unsupported request.