Forked to make modifications to bring the USBHID into USB compliance and add additional features.

Dependents:   HW4_AudioControl

Fork of USBDevice by mbed official

As of Revision 18 everything in the USBHID specification is now implemented, except Multi-reports.

Revision comments for changelist 18

USBHID.cpp

  • Added SET_PROTOCOL support
  • Added GET_PROTOCOL support
  • protocolSate is set to 1 by default to match USB HID specification. This variable should be checked to determine which format theinput report should have. 1 - Use the user specified report format. 0 - Use the BOOT protocol report format.

Revision comments for changelist 16

  • HID_REPORT transformed from structure to class. This was done for several reasons.
  1. When multiple reports are used the 64 byte size for every report becomes a problem.
  2. The length value should always remain the same for a report, Make the constructor set the vale at the same time it allocates memory for the DATA area.
  • By default the data will be an array of MAX_HID_REPORT_SIZE like the structure,
  • When given a length argument, the hid_report.length will be set, and hid_report.data will be an array of the size given.
  • Length zero causes data to be NULL
  • Mostly backwards compatible. The definition of a destructor caused a compiler error in USBMouse::update and USBMousekeyboard::update. This error was caused by instatiation of HID_REPORT in the middle of an IF logic statement. These files have been modified. The error complained that the logic skipped object initialization. The HID_REPORT has been moved to the class definition. Since both ABSOLUTE and RELATIVE modes used the HID_REPORT, this seems to make more sense. Previously the hid_report would be instatiated in <class>::mousesend and <class>::update.

Revision comments for changelist 14

USBdevice.cpp

  • Modified USB device state to change from Configure when disconnect is called.
  • Modified the call back function for when the suspend state changes. This should be used to turn off peripherals to conserve power.

Revision comments for changelist 13

USBdevice.cpp

  • ) Changed DEBUG messages to be more descriptive for string descriptor
  • ) Bug fix: Control Transfers did not actually transfer the data from Buffer to transfer->ptr

USBHIDTypes.h

  • ) Added ALL CLASS request to KEYWORD list
  • ) Added KEYWORDS for report type

USBHID.h

  • ) Added a new constructor to specify size of feature report
  • ) Added HID_REPORT inputReport and featureReport
  • ) Added data structures to support IDLE rate
  • ) Added data structures to support callback functions

USBHID.cpp

  • ) Changed constructor to initialize new feature data structures
  • ) Implemented Set_IDLE/GET_IDLE and the periodic resend of non-changed data
  • ) Implemented HID specification required control transfer GET_REPORT
  • ) Fixed issue where Intreput transfers and control transfers did not access the same data structures.
  • ) Implemented Feature reports
  • ) Implemented Callback Hooks for get_report/set_report actions.
  • ) Added callback hooks for interupt actions in the new functions.
  • ) interupt transfer can now write to outputReport
  • ) Modified SET_REPORT code to function for multiple types.
  • ) Refactored some code in preperation to add multi report support
Test NumberTest DescriptionTest ResultNotes
1Use USBmouse to verify backward compatibility of constructor and methodsPass
2Test SET_REPORT can set a feature reportPass
3Test GET_REPORT can retrieve a feature reportPass
4Test SET_IDLE sets up a reoccuring triggerPassIOCTL_SET_POLL_FREQUENCY_MSEC does not function for the windows HID driver. A Special test program is used to rearm the IDLE rate after windows sets it to zero
5Test SET_IDLE disables a triggerPassWindows automatically sends this command to a HID device when it is inserted.
6Enabled DEBUG in USBDevice.cpp and generated str descriptor requests.Pass
7Test SET_REPORT can set an output reportPass
8Test GET_REPORT can retrieve an output reportPass
9ReadFile, accesses the input_reportPass
10WriteFile accesses the output_report, via interupt transfer when ep1_out is used.Pass
11WriteFile accesses the output_report, via control transfer when ep1_out is NOT used.Not Tested
12Callback hooks trigger independently for each type of set_report/get_reportPass
13New constructor sets feature_report sizePass
14Control transfer SET_REPORT and writeFile access the same data structureBUGThe same data structure is accessed, but the data transfer size is different. The writeFile strips the leading byte which is the report ID, The Control transfer keeps the byte.
15Control transfer GET_REPORT and readFile access the same data structureBUGThe same dtat structure is accessed, but the data transfer size is different. The readFile strips the leading byte which is the report ID, The Control transfer keeps the byte.
16Test GET_IDLE retrieves the IDLE rateUnknownWindows HID driver does not implement IOCTL_HID_GET_POLL_FREQUENCY_MSEC

USBHID/USBHID.cpp

Committer:
jakowisp
Date:
2013-08-08
Revision:
17:cf5d77f1026a
Parent:
15:2b2a28dc6ed5
Child:
18:cb3afa532fcd

File content as of revision 17:cf5d77f1026a:

/* Copyright (c) 2010-2011 mbed.org, MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "stdint.h"
#include "USBHAL.h"
#include "USBHID.h"

/* new constructor
  This constructor adds the ability toset the feature_report length.  This constructor is not the default constructor to maintain backward compatibility.
*/
USBHID::USBHID(uint8_t output_report_length, uint8_t input_report_length, uint8_t feature_report_length, uint16_t vendor_id, uint16_t product_id, uint16_t product_release, bool connect): USBDevice(vendor_id, product_id, product_release)
{
    output_length = output_report_length;
    input_length = input_report_length;
    feature_length = feature_report_length;
    inputReport.length=input_length;
    outputReport.length=output_length;
    featureReport.length=feature_length;
    
    callbackSetInputReport = &HID_callbackSetReport;
    callbackGetInputReport = &HID_callbackGetReport;
    callbackSetOutputReport = &HID_callbackSetReport;
    callbackGetOutputReport = &HID_callbackGetReport;
    callbackSetFeatureReport = &HID_callbackSetReport;
    callbackGetFeatureReport = &HID_callbackGetReport;

    reportIdleRateInt=0x0;
    reportIdleRate=0.0;
    
    protocolState=0;
    
    if(connect) {
        USBDevice::connect();
    }
}

/*Original constructor 
    The original constructor, modified to set the defautls for new features
    Feature report will be disabled if the old/default constructor is used.
*/

USBHID::USBHID(uint8_t output_report_length, uint8_t input_report_length,  uint16_t vendor_id, uint16_t product_id, uint16_t product_release, bool connect): USBDevice(vendor_id, product_id, product_release)
{
    output_length = output_report_length;
    input_length = input_report_length;
    feature_length = 0;
    inputReport.length=input_length;
    outputReport.length=output_length;
    featureReport.length=feature_length+1;
    
    callbackSetInputReport = &HID_callbackSetReport;
    callbackGetInputReport = &HID_callbackGetReport;
    callbackSetOutputReport = &HID_callbackSetReport;
    callbackGetOutputReport = &HID_callbackGetReport;
    callbackSetFeatureReport = &HID_callbackSetReport;
    callbackGetFeatureReport = &HID_callbackGetReport;

    reportIdleRateInt=0x0;
    reportIdleRate=0.0;
    
    if(connect) {
        USBDevice::connect();
    }
}

/* IdlePeriodReSend is called when the IDLE rate is non-zero and the ticker period expires.
   By default: Windows will issue a set-idle rate to Zero when a device is connected.
   WARNING: The deviceIOcontrol commands for the poll rate get/set do not seem to be implemented in the windows hid driver.
   When the set_idle rate is greater than zero an input report will be resent every idle period even if there is no change. If the input report 
   changes via the FillReport command, the change is sent and the Ticker is reset.
*/
void USBHID::IdlePeriodReSend(void) { 
      //Disable the Tickers during the function kicked off from the ticker. 
     NVIC_DisableIRQ(TIMER3_IRQn);
     SendReport();
     NVIC_EnableIRQ(TIMER3_IRQn);    
}

/* ResetIdleTicker
   Arms the ticker function in the idle period is greater than zero, otherwise the ticker is disarmed.
   If a changed report is sent, this command is used to Arm the ticker agian before it has triggered, effectively reseting the countdown.
*/
void USBHID::ResetIdleTicker() {
   if(  reportIdleRateInt > 0 )
         countDownToReportTrigger.attach(this,&USBHID::IdlePeriodReSend,reportIdleRate);
     else
         countDownToReportTrigger.detach();
}

/* SetReportIdle handles the SET_IDLE controlTransfer.  The idle rate is a minimum of 4ms when enabled.
   The float conversion to seconds is computerd and the Ticker is armed or disarmed.
*/
void USBHID::SetReportIdle(uint16_t wValue){
     reportIdleRateInt=(wValue>>8);
     reportIdleRate= reportIdleRateInt * 0.004;
     
     #ifdef DEBUG
     printf("IDLE: %f\r\n",reportIdleRate);
     #endif
     ResetIdleTicker();
}


/* Legacy method --> No change */
bool USBHID::send(HID_REPORT *report)
{   
    return write(EPINT_IN, report->data, report->length, MAX_HID_REPORT_SIZE);
}

/* Legacy method --> No change */
bool USBHID::sendNB(HID_REPORT *report)
{
    return writeNB(EPINT_IN, report->data, report->length, MAX_HID_REPORT_SIZE);
}

/* Legacy method --> No change */
bool USBHID::read(HID_REPORT *report)
{
    uint32_t bytesRead = 0;
    bool result;
    result = USBDevice::readEP(EPINT_OUT, report->data, &bytesRead, MAX_HID_REPORT_SIZE);
    if(!readStart(EPINT_OUT, MAX_HID_REPORT_SIZE))
        return false;
    report->length = bytesRead;
    return result;
}


/* Legacy method --> No change */
bool USBHID::readNB(HID_REPORT *report)
{
    uint32_t bytesRead = 0;
    bool result;
    result = USBDevice::readEP_NB(EPINT_OUT, report->data, &bytesRead, MAX_HID_REPORT_SIZE);
    report->length = bytesRead;
    if(!readStart(EPINT_OUT, MAX_HID_REPORT_SIZE))
        return false;
    return result;
}

/* FillInputReport should be the main function that HID developers call. The develop sets a HID report with thier data. And pass it to FillInputReport
  The procedure will 1) Copy the HID report data to the input_report 2) check to see if the data has changed.
  Action #1 is important due to USB HID conformance.HID devices are required to implement the GET_REPORT control transfer. This transfer can be used to obtain the input report at any time.
      With the legacy HID methods, GET_REPORT was not implemented and SET_REPORT always wrote into the output_report. this means the contents of input_report would not be read by a control transfer.
  Action #2 is important for the IDLE RATE. Windows will set the IDLE rate to 0 for HID devices. Which means that reports should only be sent when the data changes.
      With the legacy HID methods, A send would always send a HID_REPORT even if it had not changed.     
*/
bool USBHID::FillInputReport(HID_REPORT *report)
{
    bool changed=false;
    
    for(int i =0; i<input_length; i++)
       if(inputReport.data[i] != report->data[i]) {
          inputReport.data[i] = report->data[i];
          changed=true;
        }
      
    if (changed)    
        return SendReport();
    else
        return true;
}  


bool USBHID::FillFeatureReport(HID_REPORT *report)
{
    for(int i =0; i<feature_length; i++)
          featureReport.data[i] = report->data[i];
    return true;
}  
/* SendReport is called by FillReport if the data has changed. It performs 3 action
 *   1) Trigger an exposed CallBack  This call back is called if a control transfer or interrupt transfer wants to get a INPUT_REPORT
 *       This functions may be useful to notify your device when a transfer of the input_report is executed. NOTE: The call back occurs BEFORE the actual transfer.
 *   2) Reset the IDLE ticker. IF the IDEL rate is not zero, the tickerneeds to be reset since we are sending a changed report.
 *   3) Send the Report via NB transfer on the interupt endpoint.
*/
bool USBHID::SendReport()
{
    bool result=false;
 
    if ((*callbackGetInputReport)(&inputReport)) {
        ResetIdleTicker();   
        result=writeNB(EPINT_IN, inputReport.data, inputReport.length, MAX_HID_REPORT_SIZE);
    }    
    return result;
}  



/* Legacy method --> No change */
uint16_t USBHID::reportDescLength() {
//TODO: Is this a bug? reportDesc() is called and a value that isn't changed is returned.  Why execute the function
    reportDesc();
    return reportLength;
}

/* GetReportTargetPointer: HID class control transfers Set_report and Get_report require a pointer to the report to write and read data
   The actual read and write logic is the same only the target report is different. Given the wvalue which  has a value of MSB(TYPE) LSB(reportID), a point to a report can be returned.
   Multi report is not implemented in this release, but a small change in this function will allow support of multiple reports via control transfers.
   
   If a value of 0 for a report was entered when the HID class was instantiated, a NULL report pointer is return which will cause a E0STALL for get/set report action on that type.
*/

HID_REPORT * USBHID::GetReportTargetPointer(uint16_t wValue){
   //TODO: Add support for multiple reports.
    HID_REPORT *targetPtr=NULL;
    switch(wValue>>8) {
      case 0x01:  if (input_length >0)
                     targetPtr=&inputReport;
                   break;
      case 0x03:  if (feature_length >0)
                     targetPtr=&featureReport; 
                   break;
      case 0x02:  
      default: if (output_length >0)
                   targetPtr=&outputReport; 
                break;
    }
   return targetPtr;
}


//
//  Route callbacks from lower layers to class(es)
//

// Called in ISR context
// Called by USBDevice on Endpoint0 request
// This is used to handle extensions to standard requests
// and class specific requests
// Return true if class handles this request
//Legacy method-->modified to include GET_REPORT, GET_IDLE, SET_IDLE transfers
//               modified the function of SET_REPORT for multiple types(INPUT,OUTPUT,FEATURE)
bool USBHID::USBCallback_request() {
    bool success = false;
    CONTROL_TRANSFER * transfer = getTransferPtr();
    uint8_t *hidDescriptor;

    // Process additional standard requests

    if ((transfer->setup.bmRequestType.Type == STANDARD_TYPE))
    {
        switch (transfer->setup.bRequest)
        {
            case GET_DESCRIPTOR:
                switch (DESCRIPTOR_TYPE(transfer->setup.wValue))
                {
                    case REPORT_DESCRIPTOR:
                        if ((reportDesc() != NULL) \
                            && (reportDescLength() != 0))
                        {
                            transfer->remaining = reportDescLength();
                            transfer->ptr = reportDesc();
                            transfer->direction = DEVICE_TO_HOST;
                            success = true;
                        }
                        break;
                    case HID_DESCRIPTOR:
                            // Find the HID descriptor, after the configuration descriptor
                            hidDescriptor = findDescriptor(HID_DESCRIPTOR);
                            if (hidDescriptor != NULL)
                            {
                                transfer->remaining = HID_DESCRIPTOR_LENGTH;
                                transfer->ptr = hidDescriptor;
                                transfer->direction = DEVICE_TO_HOST;
                                success = true;
                            }
                            break;
                     
                    default:
                        break;
                }
                break;
            default:
                break;
        }
    }

    // Process class-specific requests

    if (transfer->setup.bmRequestType.Type == CLASS_TYPE)
    {
        
        HID_REPORT *targetPtr=NULL;
//        printf("ReportID: %x\r\n", transfer->setup.wValue & 0xff );
        switch (transfer->setup.bRequest)
        {
             case SET_REPORT:
                targetPtr=GetReportTargetPointer(transfer->setup.wValue);
                if (targetPtr==NULL)
                    break;
                // First byte will be used for report ID
                // BUG: In a control transfer the first byte is the reportid and deviceiocontrol does transmit this byte
                //      But the interupt transfer strips the reportid byte
                targetPtr->data[0] = transfer->setup.wValue & 0xff;
                targetPtr->length = transfer->setup.wLength + 1;
                if(transfer->remaining >= sizeof(targetPtr->data) - 1) 
                   transfer->remaining = sizeof(targetPtr->data) - 1;
                else
                   transfer->remaining = transfer->setup.wLength;
                transfer->ptr = &targetPtr->data[1];
                transfer->direction = HOST_TO_DEVICE;
                transfer->notify = true;
                success = true;
                break;
             case GET_REPORT:
                // Required by section 7.2 of the HID Device Class Definition: Mandatory implementation by all devices
                targetPtr=GetReportTargetPointer(transfer->setup.wValue);
                if (targetPtr==NULL)
                    break;
                // First byte will be used for report ID
                // BUG: In a control transfer the first byte is the reportid and deviceiocontrol does transmit this byte
                //      But the interupt transfer strips the reportid byte
                transfer->setup.wLength = targetPtr->length  - 1;
                if(transfer->remaining >= sizeof(targetPtr->data) - 1) 
                   transfer->remaining = sizeof(targetPtr->data) - 1;
                else
                   transfer->remaining = transfer->setup.wLength;
                transfer->ptr = &targetPtr->data[1];
                transfer->direction = DEVICE_TO_HOST;
                transfer->notify = true;
                //Provide a hook for developers to provide thier own call back for Get_Report, prior to the report data is sentin response to a 
                //   Control IN.
                success =HIDCallback_GetReportHandler(targetPtr);
                break;
            
            case SET_IDLE:
               //SET_IDLE works very well, Microsoft is able to use this to set the IDLE rate to 0. Durring testing the default rate was set to 0x80. 
               //   Normal default setting is zero.
               //   The microsoft HID driver will throw an invlid function error when IOCTL_HID_GET_POLL_FREQUENCY_MSEC is used.
               //   wValue {duration:ReportID}
               //TODO:  To support multipe reports, a HID_REPORT Class should be defined. The ticker and idle methods and objects should be included.
                SetReportIdle(transfer->setup.wValue);
                transfer->setup.wLength = 0;
                transfer->direction = HOST_TO_DEVICE;
                transfer->remaining = transfer->setup.wLength;
                transfer->notify = true;
                success=true;
            case GET_IDLE:
                //This functionality has not been tested. The microsoft HID driver will throw an invlid function error when IOCTL_HID_GET_POLL_FREQUENCY_MSEC is used.
                transfer->remaining = 1;
                transfer->ptr = getReportIdlePtr(transfer->setup.wValue & 0xff);
                transfer->direction = DEVICE_TO_HOST;
                transfer->notify = true;
                success=true;
                break;
            case SET_PROTOCOL:                
               //This functionality has not been tested. 
                protocolState=transfer->setup.wValue;
                success=true;
                break;
            case GET_PROTOCOL:
               //This functionality has not been tested. 
                transfer->remaining = 1;
                transfer->ptr = &protocolState;
                transfer->direction = DEVICE_TO_HOST;
                transfer->notify = true;
                success=true;
                break;
            default:
                break;
        }
    }

    return success;
}

/* HIDCallback_GetReportHandler provides a useful  set of callbacks for the getting of each type in a report.
   This can be used to generate the data on the fly(Useful for a feature report) or notification that a report was sent for the device.
   NOTE: CALLBACKS happen before the actuall data for the report is transmitted.
*/
bool USBHID::HIDCallback_GetReportHandler(HID_REPORT *targetPtr) {
      bool (*callbackGetReport)(HID_REPORT *report);
      CONTROL_TRANSFER * transfer = getTransferPtr();
      
      switch(transfer->setup.wValue>>8) {
      case HID_INPUT_REPORT_TYPE:
           callbackGetReport=callbackGetInputReport;
           break;
      case HID_OUTPUT_REPORT_TYPE:
           callbackGetReport=callbackGetOutputReport;
           break;
      case HID_FEATURE_REPORT_TYPE:
           callbackGetReport=callbackGetFeatureReport;
           break;
      default:
        return false;
      }
      return (*callbackGetReport)(targetPtr); 
}

/* HIDCallback_SetReportHandler provides a useful  set of callbacks for the setting of each type in a report.
   This can be used to update displays after a value is change(Example:NUM lock LED change).
   NOTE: CALLBACKS happen after the actuall data for the report is recieved.
*/

bool USBHID::HIDCallback_SetReportHandler(HID_REPORT *targetPtr) {
      void (*callbackSetReport)(HID_REPORT *report);
      CONTROL_TRANSFER * transfer = getTransferPtr();
      
      switch(transfer->setup.wValue>>8) {
      case HID_INPUT_REPORT_TYPE:
           callbackSetReport=callbackSetInputReport;
           break;
      case HID_OUTPUT_REPORT_TYPE:
           callbackSetReport=callbackSetOutputReport;
           break;
      case HID_FEATURE_REPORT_TYPE:
           callbackSetReport=callbackSetFeatureReport;
           break;
      default:
           return false;
      }
      (*callbackSetReport)(targetPtr);
      return true;
       
}

/* USBCallback_requestCompleted To implement the set_report call back ability we need to override the requestCompleted function to provide the callback handler hook
*/
void USBHID::USBCallback_requestCompleted(uint8_t * buf, uint32_t length){
     CONTROL_TRANSFER * transfer;
     HID_REPORT *targetPtr;
      
     transfer = getTransferPtr();
     targetPtr = GetReportTargetPointer(transfer->setup.wValue);
     USBDevice::USBCallback_requestCompleted(buf, length);
     //Provide a callback hook for developer use of set_report, called after the completion of the control transfer.
     if(transfer->setup.bRequest==SET_REPORT) {
        HIDCallback_SetReportHandler(targetPtr);
     }      
}

/* To ensure that the output report is updated on a writeFile interupt transfer, the EP1_OUT_callback is overridden.
 * additionally a callback for setOutputReport type is trigger
*/
bool USBHID::EP1_OUT_callback(){
    uint32_t bytesRead = 0;
    bool result;
    //TODO: Is there a buffer over run issue here? 
    //read to Buffer and copy just the report data to the HID_REPORT.
    result = endpointReadResult(EPINT_OUT, outputReport.data, &bytesRead);
//    outputReport.length = bytesRead;
    (*callbackSetOutputReport)(&outputReport);
    return result;
}


#define DEFAULT_CONFIGURATION (1)


// Called in ISR context
// Set configuration. Return false if the
// configuration is not supported
bool USBHID::USBCallback_setConfiguration(uint8_t configuration) {
    if (configuration != DEFAULT_CONFIGURATION) {
        return false;
    }

    // Configure endpoints > 0
    addEndpoint(EPINT_IN, MAX_PACKET_SIZE_EPINT);
    addEndpoint(EPINT_OUT, MAX_PACKET_SIZE_EPINT);

    // We activate the endpoint to be able to recceive data
    readStart(EPINT_OUT, MAX_PACKET_SIZE_EPINT);
    return true;
}


uint8_t * USBHID::stringIinterfaceDesc() {
    static uint8_t stringIinterfaceDescriptor[] = {
        0x08,               //bLength
        STRING_DESCRIPTOR,  //bDescriptorType 0x03
        'H',0,'I',0,'D',0,  //bString iInterface - HID
    };
    return stringIinterfaceDescriptor;
}

uint8_t * USBHID::stringIproductDesc() {
    static uint8_t stringIproductDescriptor[] = {
        0x16,                                                       //bLength
        STRING_DESCRIPTOR,                                          //bDescriptorType 0x03
        'H',0,'I',0,'D',0,' ',0,'D',0,'E',0,'V',0,'I',0,'C',0,'E',0 //bString iProduct - HID device
    };
    return stringIproductDescriptor;
}


/* legacy method .. Modified
 *  1) Added feature report
 *  2) Used KEYWORDS for added user readability.
 */
uint8_t * USBHID::reportDesc() {
    static uint8_t reportDescriptor[] = {
        USAGE_PAGE(2), LSB(0xFFAB), MSB(0xFFAB),
        USAGE(2), LSB(0x0200), MSB(0x0200),
        COLLECTION(1), 0x01,                     // Collection 0x01
        REPORT_SIZE(1), 0x08,                    // report size = 8 bits
        LOGICAL_MINIMUM(1), 0x00,                // logical minimum = 0
        LOGICAL_MAXIMUM(2), 0xFF, 0x00,          // logical maximum = 255
        REPORT_COUNT(1), input_length,           // report count
        USAGE(1), 0x01,                          // usage
        INPUT(1), 0x02,                          // Input (array)
        REPORT_COUNT(1), output_length,          // report count
        USAGE(1), 0x02,                          // usage
        OUTPUT(1), 0x02,                         // Output (array)
        REPORT_COUNT(1), feature_length,
        USAGE(1), 0x03,
        FEATURE(2), 0x02, 0x01,
        END_COLLECTION(0)                        // end collection
    };
    reportLength = sizeof(reportDescriptor);
    return reportDescriptor;
}

#define DEFAULT_CONFIGURATION (1)
#define TOTAL_DESCRIPTOR_LENGTH ((1 * CONFIGURATION_DESCRIPTOR_LENGTH) \
                               + (1 * INTERFACE_DESCRIPTOR_LENGTH) \
                               + (1 * HID_DESCRIPTOR_LENGTH) \
                               + (2 * ENDPOINT_DESCRIPTOR_LENGTH))

uint8_t * USBHID::configurationDesc() {
    static uint8_t configurationDescriptor[] = {
        CONFIGURATION_DESCRIPTOR_LENGTH,// bLength
        CONFIGURATION_DESCRIPTOR,       // bDescriptorType
        LSB(TOTAL_DESCRIPTOR_LENGTH),   // wTotalLength (LSB)
        MSB(TOTAL_DESCRIPTOR_LENGTH),   // wTotalLength (MSB)
        0x01,                           // bNumInterfaces
        DEFAULT_CONFIGURATION,          // bConfigurationValue
        0x00,                           // iConfiguration
        C_RESERVED | C_SELF_POWERED,    // bmAttributes
        C_POWER(0),                     // bMaxPower

        INTERFACE_DESCRIPTOR_LENGTH,    // bLength
        INTERFACE_DESCRIPTOR,           // bDescriptorType
        0x00,                           // bInterfaceNumber
        0x00,                           // bAlternateSetting
        0x02,                           // bNumEndpoints
        HID_CLASS,                      // bInterfaceClass
        HID_SUBCLASS_NONE,              // bInterfaceSubClass
        HID_PROTOCOL_NONE,              // bInterfaceProtocol
        0x00,                           // iInterface

        HID_DESCRIPTOR_LENGTH,          // bLength
        HID_DESCRIPTOR,                 // bDescriptorType
        LSB(HID_VERSION_1_11),          // bcdHID (LSB)
        MSB(HID_VERSION_1_11),          // bcdHID (MSB)
        0x00,                           // bCountryCode
        0x01,                           // bNumDescriptors
        REPORT_DESCRIPTOR,              // bDescriptorType
        LSB(this->reportDescLength()),  // wDescriptorLength (LSB)
        MSB(this->reportDescLength()),  // wDescriptorLength (MSB)

        ENDPOINT_DESCRIPTOR_LENGTH,     // bLength
        ENDPOINT_DESCRIPTOR,            // bDescriptorType
        PHY_TO_DESC(EPINT_IN),          // bEndpointAddress
        E_INTERRUPT,                    // bmAttributes
        LSB(MAX_PACKET_SIZE_EPINT),     // wMaxPacketSize (LSB)
        MSB(MAX_PACKET_SIZE_EPINT),     // wMaxPacketSize (MSB)
        1,                             // bInterval (milliseconds)

        ENDPOINT_DESCRIPTOR_LENGTH,     // bLength
        ENDPOINT_DESCRIPTOR,            // bDescriptorType
        PHY_TO_DESC(EPINT_OUT),          // bEndpointAddress
        E_INTERRUPT,                    // bmAttributes
        LSB(MAX_PACKET_SIZE_EPINT),     // wMaxPacketSize (LSB)
        MSB(MAX_PACKET_SIZE_EPINT),     // wMaxPacketSize (MSB)
        1,                             // bInterval (milliseconds)
    };
    return configurationDescriptor;
}