SAM3S8/SD8 Software Package: USB Device HID Mouse Driver

SAM3S8/SD8 Software Package 1.2

# USB Device HID Mouse Driver

This page describes how to implement a mouse device using the HID class and the AT91 USB device framework. Details about the USB and the HID class can be found in the USB specification 2.0 and the HID specification 1.11 documents, respectively.

## References

- AT91 USB device framework
- USB Device Enumeration
- Universal Serial Bus Revision 2.0 specification (.zip file format, size 9.80 MB)
- Device Class Definition for HID 1.11
- HID Usage Tables 1.12

This page describes how to use the AT91 USB device framework to produce a USB HID Mouse driver, which appears as a USB Mouse on host.

Details about the USB and the HID class can be found in the USB specification 2.0 and the HID specification 1.11, respectively.

## References

- AT91 USB device framework
- USB Device Enumeration
- Universal Serial Bus Revision 2.0 specification (.zip file format, size 9.80 MB)
- Device Class Definition for HID 1.11
- HID Usage Tables 1.12

## HID Basic

See USB HID Basic.

## Architecture

See USB Device Framework Architecture.

## Descriptors

### Device Descriptor

The Device descriptor of an HID device is very basic, since the HID class code is only specified at the Interface level. Thus, it only contains standard values, as shown below:

    static const USBDeviceDescriptor deviceDescriptor = {

        sizeof(USBDeviceDescriptor),
        USBGenericDescriptor_DEVICE,
        USBDeviceDescriptor_USB2_00,
        HIDDeviceDescriptor_CLASS,
        HIDDeviceDescriptor_SUBCLASS,
        HIDDeviceDescriptor_PROTOCOL,
        BOARD_USB_ENDPOINTS_MAXPACKETSIZE(0),
        HIDDMouseDriverDescriptors_VENDORID,
        HIDDMouseDriverDescriptors_PRODUCTID,
        HIDDMouseDriverDescriptors_RELEASE,
        1, // Index of manufacturer description
        2, // Index of product description
        3, // Index of serial number description
        1  // One possible configuration
    };

Note that the Vendor ID is a special value attributed by the USB-IF organization. The product ID can be chosen freely by the vendor.

### Configuration Descriptor

Since one interface is required by the HID specification, this must be specified in the Configuration descriptor. There is no other value of interest to put here.

    // Configuration descriptor
    {
        sizeof(USBConfigurationDescriptor),
        USBGenericDescriptor_CONFIGURATION,
        sizeof(HIDDMouseDriverConfigurationDescriptors),
        1, // One interface in this configuration
        1, // This is configuration #1
        0, // No associated string descriptor
        BOARD_USB_BMATTRIBUTES,
        USBConfigurationDescriptor_POWER(100)
    },

When the Configuration descriptor is requested by the host (by using the GET_DESCRIPTOR command), the device must also sent all the related descriptors, i.e. Interface, Endpoint and Class-Specific descriptors. It is convenient to create a single structure to hold all this data, for sending everything in one chunk. In the example software, a HIDDMouseDriverConfigurationDescriptors structure has been declared for that.

### HID Class Interface Descriptor

Since a Mouse device needs to transmit as well as receive data, two Interrupt (IN & OUT) endpoints are needed. This must be indicated in the Interface descriptor. Conversely to the mouse example, the Boot protocol is not implemented here, since there are more constraints on a Mouse device.

    // Interface descriptor
    {
        sizeof(USBInterfaceDescriptor),
        USBGenericDescriptor_INTERFACE,
        0, // This is interface #0
        0, // This is alternate setting #0
        2, // Two endpoints used
        HIDInterfaceDescriptor_CLASS,
        HIDInterfaceDescriptor_SUBCLASS_NONE,
        HIDInterfaceDescriptor_PROTOCOL_NONE,
        0  // No associated string descriptor
    },

### HID Descriptor

While a HID Mouse produces two different reports, one Input and one Output, only one Report descriptor can be used to describe them. Since having Physical descriptors is also useless for a Mouse, there will only be one HID class descriptor specified here.

For a Mouse, the }bCountryCode} field can be used to specify the language of the key caps. As this is optional, it is simply set to 00h in the example:

    // HID descriptor
    {
        sizeof(HIDDescriptor),
        HIDGenericDescriptor_HID,
        HIDDescriptor_HID1_11,
        0, // Device is not localized, no country code
        1, // One HID-specific descriptor (apart from this one)
        HIDGenericDescriptor_REPORT,
        HIDDMouseDriverDescriptors_REPORTSIZE
    },

### Report Descriptor

Two current reports are defined in the Report descriptor. The first one is used to notify the host of which keys are pressed, with both modifier keys (alt, ctrl, etc.) and alphanumeric keys. The second report is necessary for the host to send the LED (num lock, caps lock, etc.) states.

The Report descriptor starts with the global device functionality, described with a Usage Page and a Usage items:

    const unsigned char hiddReportDescriptor[] = {

        HIDReport_GLOBAL_USAGEPAGE + 1, HIDGenericDesktop_PAGEID,
        HIDReport_LOCAL_USAGE + 1, HIDGenericDesktop_Mouse,

As in the mouse example, the }Generic Desktop} page is used. This time, the specific usage is the }Mouse} one. An Application collection is then defined to group the reports together:

        HIDReport_COLLECTION + 1, HIDReport_COLLECTION_APPLICATION,

The first report to be defined is the modifier keys. They are represented as a bitmap field, indicating whether or not each key is pressed. A single byte is used to map keys #224-231 defined in the }HID Usage Tables} document: LeftControl, LeftShift, LeftAlt, LeftGUI (e.g. Windows key), RightControl, RightShift, RightAlt and RightGUI. The }Keypad} usage page must be specified for this report, and since this is a bitmap value, the data is flagged as }Variable}:

            // Input report: modifier keys
            HIDReport_GLOBAL_REPORTSIZE + 1, 1,
            HIDReport_GLOBAL_REPORTCOUNT + 1, 8,
            HIDReport_GLOBAL_USAGEPAGE + 1, HIDKeypad_PAGEID,
            HIDReport_LOCAL_USAGEMINIMUM + 1,
                HIDDMouseDriverDescriptors_FIRSTMODIFIERKEY,
            HIDReport_LOCAL_USAGEMAXIMUM + 1,
                HIDDMouseDriverDescriptors_LASTMODIFIERKEY,
            HIDReport_GLOBAL_LOGICALMINIMUM + 1, 0,
            HIDReport_GLOBAL_LOGICALMAXIMUM + 1, 1,
            HIDReport_INPUT + 1, HIDReport_VARIABLE,

Then, the actual alphanumeric key report is described. This is done by defining several bytes of data, one for each pressed key. In the example, up to three keys can be pressed at the same time (and detected) by the user. Once again, the usage page is set to }Keypad}. This time however, the data must be specified as an }Array}, since the same control (the keypad) produces several values:

            // Input report: standard keys
            HIDReport_GLOBAL_REPORTCOUNT + 1, 3,
            HIDReport_GLOBAL_REPORTSIZE + 1, 8,
            HIDReport_GLOBAL_LOGICALMINIMUM + 1,
                HIDDMouseDriverDescriptors_FIRSTSTANDARDKEY,
            HIDReport_GLOBAL_LOGICALMAXIMUM + 1,
                HIDDMouseDriverDescriptors_LASTSTANDARDKEY,
            HIDReport_GLOBAL_USAGEPAGE + 1, HIDKeypad_PAGEID,
            HIDReport_LOCAL_USAGEMINIMUM + 1,
                HIDDMouseDriverDescriptors_FIRSTSTANDARDKEY,
            HIDReport_LOCAL_USAGEMAXIMUM + 1,
                HIDDMouseDriverDescriptors_LASTSTANDARDKEY,
            HIDReport_INPUT + 1, 0 /* Data array */,

The LED array is then defined, with the associated usage page. The Report descriptor is formatted in this order to avoid redefining unchanged Global items, in order to save memory. This time again, the LED status is reported as a bitmap field. Three LEDs are used here: Num Lock, Caps Lock and Scroll Lock (IDs 01h to 03h). It is important to note that this is an Output report:

            // Output report: LEDs
            HIDReport_GLOBAL_REPORTCOUNT + 1, 3,
            HIDReport_GLOBAL_REPORTSIZE + 1, 1,
            HIDReport_GLOBAL_USAGEPAGE + 1, HIDLeds_PAGEID,
            HIDReport_GLOBAL_LOGICALMINIMUM + 1, 0,
            HIDReport_GLOBAL_LOGICALMAXIMUM + 1, 1,
            HIDReport_LOCAL_USAGEMINIMUM + 1, HIDLeds_NUMLOCK,
            HIDReport_LOCAL_USAGEMAXIMUM + 1, HIDLeds_SCROLLLOCK,
            HIDReport_OUTPUT + 1, HIDReport_VARIABLE,

Since the previous report only contains 3 bits, the data must be padded to a multiple of one byte. This is done by using constant Output data, as follows:

            // Output report: padding
            HIDReport_GLOBAL_REPORTCOUNT + 1, 1,
            HIDReport_GLOBAL_REPORTSIZE + 1, 5,
            HIDReport_OUTPUT + 1, HIDReport_CONSTANT,

The last item, End Collection, is necessary to close the previously opened Application Collection.

        HIDReport_ENDCOLLECTION
    };

The Input and Output reports defined by this descriptor can be modeled by the following structures:

    // HID Input Report
    typedef struct {

      // State of modifier keys.
      unsigned char bmModifierKeys:8;
      // Key codes of pressed keys.
      unsigned char pressedKeys[HIDDMouseInputReport_MAXKEYPRESSES];

    } __attribute__ ((packed)) HIDDMouseInputReport; // GCC
    // HID Output Report
    typedef struct {

      unsigned char numLockStatus:1, // State of the num. lock LED.
        capsLockStatus:1,   // State of the caps lock LED.
        scrollLockStatus:1, // State of the scroll lock LED.
        padding:5;          // Padding bits.

    } __attribute__ ((packed)) HIDDMouseOutputReport; // GCC

An instance of each one of the reports is stored in a HIDDMouseDriver structure, which holds the standard class driver and HID Mouse-specific data.

### Physical Descriptor

A Physical descriptor is useless for a Mouse device, so none are defined in this example.

### Endpoint Descriptor

Following the Interface and HID-specific descriptors, the two necessary endpoints are defined.

    // Interrupt IN endpoint descriptor
    {
        sizeof(USBEndpointDescriptor),
        USBGenericDescriptor_ENDPOINT,
        USBEndpointDescriptor_ADDRESS(
            USBEndpointDescriptor_IN,
            HIDDMouseDriverDescriptors_INTERRUPTIN),
        USBEndpointDescriptor_INTERRUPT,
        sizeof(HIDDMouseInputReport),
        HIDDMouseDriverDescriptors_INTERRUPTIN_POLLING
    },
    // Interrupt OUT endpoint descriptor
    {
        sizeof(USBEndpointDescriptor),
        USBGenericDescriptor_ENDPOINT,
        USBEndpointDescriptor_ADDRESS(
            USBEndpointDescriptor_OUT,
            HIDDMouseDriverDescriptors_INTERRUPTOUT),
        USBEndpointDescriptor_INTERRUPT,
        sizeof(HIDDMouseOutputReport),
        HIDDMouseDriverDescriptors_INTERRUPTIN_POLLING
    }

### String Descriptors

Please refer to Usage: USBD VID, PID & Strings.

## Class-specific requests

A driver request handler should first differentiate between class-specific and standard requests using the corresponding bits in the bmRequestType field. In most cases, standard requests can be immediately forwarded to the standard request handler method; class-specific methods must be decoded and treated by the custom handler.

### GetDescriptor

Three values have been added by the HID specification for the GET_DESCRIPTOR request. The high byte of the wValue field contains the type of the requested descriptor; in addition to the standard types, the HID specification adds the HID descriptor (21h), the Report descriptor (22h) and the Physical descriptor (23h) types.

There is no particular action to perform besides sending the descriptor. This can be done by using the USBD_Write() method, after the requested descriptor has been identified:

    switch (USBGenericRequest_GetRequest(request)) {

      case USBGenericRequest_GETDESCRIPTOR:
        // Check if this is a HID descriptor,
        // otherwise forward it to
        // the standard driver
        if (!HIDDMouseDriver_GetDescriptor(
            USBGetDescriptorRequest_GetDescriptorType(request),
            USBGenericRequest_GetLength(request))) {

          USBDDriver_RequestHandler(&(hiddMouseDriver.usbdDriver),
                                  request);
        }
        break;

      default:
        USBDDriver_RequestHandler(&(hiddMouseDriver.usbdDriver),
                                      request);
    }

A slight complexity of the GET_DESCRIPTOR and SET_DESCRIPTOR requests is that those are standard requests, but the standard request handler (USBDDriver_RequestHandler()) must not always be called to treat them (since they may refer to HID descriptors). The solution is to first identify GET/SET_DESCRIPTOR requests, treat the HID-specific cases and, finally, forward any other request to the standard handler.

In this case, a GET_DESCRIPTOR request for the Physical descriptor is first forwarded to the standard handler, and STALLed there because it is not recognized. This is done because the device does not have any Physical descriptors, and thus, does not need to handle the associated request.

### SetDescriptor

This request is optional and is never issued by most hosts. It is not implemented in this example.

### GetReport

Since the HID Mouse defines two different reports, the Report Type value specified by this request (upper byte of the wValue field) must be examined to decide which report to send. If the type value is 01h, then the Input report must be returned; if it is 02h, the Output report is requested:

    case HIDGenericRequest_GETREPORT:
    //-------------------------------
      type = HIDReportRequest_GetReportType(request);
      length = USBGenericRequest_GetLength(request);
      switch (type) {

          case HIDReportRequest_INPUT:

            // Adjust size and send report
            if (length > sizeof(HIDDMouseInputReport)) {

              length = sizeof(HIDDMouseInputReport);
            }
            USBD_Write(0, // Endpoint #0
               &(hiddMouseDriver.inputReport),
               length,
               0, // No callback
               0);
            break;

          case HIDReportRequest_OUTPUT:

            // Adjust size and send report
            if (length > sizeof(HIDDMouseOutputReport)) {

              length = sizeof(HIDDMouseOutputReport);
            }
            USBD_Write(0, // Endpoint #0
               &(hiddMouseDriver.outputReport),
               length,
               0, // No callback
               0);
            break;

          default:
            USBD_Stall(0);
      }
    break;

### SetReport

For an HID Mouse, the SET_REPORT command can be sent by the host to change the state of the LEDs. Normally, the dedicated Interrupt OUT endpoint will be used for this; but in some cases, using the default Control endpoint can save some bandwidth on the host side.

Note that the SET_REPORT request can be directed at the Input report of the Mouse; in this case, it can be safely discarded, according to the HID specification. Normally, most host drivers only target the Output report. The Report Type value is stored in the upper byte of the wValue field.

The length of the data phase to follow is stored in the wLength field of the request. It should be equal to the total length of the Output report. If it is different, the report status must still be updated with the received data as best as possible.

When the reception of the new data is completed, some processing must be done to enable/disable the corresponding LEDs. This is done in the callback function passed as an argument to USBD_Read():

    case HIDGenericRequest_SETREPORT:
    //-------------------------------
      type = HIDReportRequest_GetReportType(request);
      length = USBGenericRequest_GetLength(request);
      switch(type) {

        case HIDReportRequest_INPUT:
          // SET_REPORT requests on input reports are ignored
          USBD_Stall(0);
          break;

        case HIDReportRequest_OUTPUT:
          // Check report length
          if (length != sizeof(HIDDMouseOutputReport)) {

            USBD_Stall(0);
          }
          else {

            USBD_Read(0, // Endpoint #0
              &(hiddMouseDriver.outputReport),
              length,
              (TransferCallback) HIDDMouseDriver_ReportReceived,
              0); // No argument to the callback function
          }
          break;

        default:
          USBD_Stall(0);
      }
    break;

### SetIdle

In this case study, the SET_IDLE request is used to set a delay before a key is repeated. This is common behavior on Mouse devices. Usually, this delay is set to about 500 ms by the host.

The only action here is to store the new Idle rate. The management of this setting must be done in the main function, since Interrupt IN reports are sent from there.

In practice, it is not necessary to perform any action, apart from sending a zero-length packet to acknowledge it. The main application however has to make sure that only new reports are sent by the device.

    case HIDGenericRequest_SETIDLE:
    //-----------------------------
      hiddMouseDriver.inputReportIdleRate =
               HIDIdleRequest_GetIdleRate(request);
      USBD_Write(0, 0, 0, 0, 0);
    break;

### GetIdle

The only necessary operation for this request is to send the previously saved Idle rate. This is done by calling the USBD_Write() method with the one-byte variable as its parameter:

    case HIDGenericRequest_GETIDLE:
    //-----------------------------
      USBD_Write(0, &(hiddMouseDriver.inputReportIdleRate), 1, 0, 0);
    break;

### GetProtocol, SetProtocol

This HID Mouse example does not support the Boot protocol, so there is no need to implement the SET_PROTOCOL and GET_PROTOCOL requests. This means they can be safely STALLed when received.

## Main Application

Like the mouse example, the main program must perform two different operations. First, it has to monitor the physical inputs used as keys. In the example software, the buttons present on the evaluation boards are used to produce several modifier and alphanumeric keys.

Also, the main program is in charge of sending reports as they are modified, taking into account the Idle rate specified by the host. Idle rate management can be carried out by firing/resetting a timer once a new report is sent; if the timer expires, this means the Input report has not changed since. According to the HID specification, a single instance of the report must be sent in this case.

Finally, the HID specification also defines that if too many keys are pressed at the same time, the device should report an ErrorRollOver usage value (01h) in every byte of the key array. This has to be handled by the main application as well.

All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines