HID keyboard/mouse combo report ID

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
Post Reply
noperator
Posts: 2
Joined: Mon Mar 27, 2017 1:33 pm

HID keyboard/mouse combo report ID

Post by noperator » Mon Mar 27, 2017 1:53 pm

I am trying to create a device which acts as a keyboard and mouse at the same time. After some research I've concluded that it should be possible to combine both the mouse and keyboard HID descriptors, and separate them with Report IDs. I've successfully implemented the keyboard and mouse separately, but when I try to assign a report ID to the keyboard, no output is produced.

Here is the offending keyboard descriptor:

Code: Select all

PROGMEM const char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {   
   //45 without report ID
   //47 with report ID
   0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
   0x09, 0x06,                    // USAGE (Keyboard)
   0xa1, 0x01,                    // COLLECTION (Application)
   //0x85, (uint8_t)ID_KEYBOARD,    //   REPORT_ID (2)
   0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
   0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
   0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
   0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
   0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
   0x95, 0x08,                    //   REPORT_COUNT (8)
   0x75, 0x01,                    //   REPORT_SIZE (1)
   0x81, 0x02,                    //   INPUT (Data,Var,Abs)
   0x95, 0x01,                    //   REPORT_COUNT (1)
   0x75, 0x08,                    //   REPORT_SIZE (8)
   0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
   0x95, 0x06,                    //   REPORT_COUNT (6)
   0x75, 0x08,                    //   REPORT_SIZE (8)
   0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
   0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
   0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
   0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
   0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
   0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
   0xc0,                           // END_COLLECTION
}


and the code I'm testing it with

Code: Select all

typedef struct{
   //uint8_t   reportID;
   uint8_t   modifier;
   uint8_t   reserved;
   uint8_t   keycode[6];
}keyboard_report_t;

//static keyboard_report_t keyboardReportBuffer = {(uint8_t)ID_KEYBOARD, 0, 0, {0, 0, 0, 0, 0, 0}};
static keyboard_report_t keyboardReportBuffer = {0, 0, {0, 0, 0, 0, 0, 0}};
static uchar    idleRate;   /* repeat rate for keyboards, never used for mice */

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    /* The following requests are never used. But since they are required by
     * the specification, we implement them in this example.
     */
   
   if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
      switch(rq->bRequest) {
      case USBRQ_HID_GET_REPORT: // send "no keys pressed" if asked here
         
         usbMsgPtr = (usbMsgPtr_t)(void *)&keyboardReportBuffer; // we only have this one
         keyboardReportBuffer.modifier = 0;
         keyboardReportBuffer.keycode[0] = 0;
         return sizeof(keyboardReportBuffer);      
         
      case USBRQ_HID_SET_REPORT: // if wLength == 1, should be LED state
         return (rq->wLength.word == 1) ? USB_NO_MSG : 0;
         
      case USBRQ_HID_GET_IDLE: // send idle rate to PC as required by spec
         usbMsgPtr = (usbMsgPtr_t)&idleRate;
         return 1;
         
      case USBRQ_HID_SET_IDLE: // save idle rate as required by spec
         idleRate = rq->wValue.bytes[1];
         return 0;
      }
   }
   return 0;
}

usbMsgLen_t usbFunctionWrite(uint8_t * data, uchar len) {
   return 1; // Data read, not expecting more
}

int __attribute__((noreturn)) main(void)
{
    usbInit();
    usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */
    _delay_ms(255);
    usbDeviceConnect();
    sei();
   
    for(;;){                /* main event loop */

        usbPoll();
        if(usbInterruptIsReady()){

         keyboardReportBuffer.keycode[0] = 5;
         usbSetInterrupt((void *)&keyboardReportBuffer, sizeof(keyboardReportBuffer));
         while(!usbInterruptIsReady())usbPoll();
         
         keyboardReportBuffer.keycode[0] = 0;
         usbSetInterrupt((void *)&keyboardReportBuffer, sizeof(keyboardReportBuffer));
        }
    }
}


The code simply sends the letter 'b' over and over. With the report ID lines commented out in the descriptor and the code, it works fine, but when not commented the device not produce any output, but is still recognized by Windows. I also tried adding a report ID to the mouse HID descriptor and it worked without problems.

Does anyone know why this might be happening?

noperator
Posts: 2
Joined: Mon Mar 27, 2017 1:33 pm

Re: HID keyboard/mouse combo report ID

Post by noperator » Mon Mar 27, 2017 3:39 pm

Ok the problem was that usbSetInterrupt can only send 8 bytes at a time and my keyboard descriptor took up 9 bytes (report ID + modifier + reserved + 6 keycodes). I reduced the number of keycodes to 5 and can now send mouse and keyboard data at the same time!
I discovered that endpoint 3 can also be used to implement a separate HID device. I tried that briefly without success before stumbling upon the 8 byte limit. I may revisit that path at a later time...

The full HID Descriptor for reference

Code: Select all

PROGMEM const char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
   //45 47
   0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
   0x09, 0x06,                    // USAGE (Keyboard)
   0xa1, 0x01,                    // COLLECTION (Application)
   0x85, (uint8_t)ID_KEYBOARD,    //   REPORT_ID (2)
   0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
   0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
   0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
   0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
   0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
   0x95, 0x08,                    //   REPORT_COUNT (8)
   0x75, 0x01,                    //   REPORT_SIZE (1)
   0x81, 0x02,                    //   INPUT (Data,Var,Abs)
   0x95, 0x01,                    //   REPORT_COUNT (1)
   0x75, 0x08,                    //   REPORT_SIZE (8)
   0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
   0x95, 0x05,                    //   REPORT_COUNT (6)
   0x75, 0x08,                    //   REPORT_SIZE (8)
   0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
   0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
   0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
   0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
   0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
   0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
   0xc0,                           // END_COLLECTION
   
   //52 54
   0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
   0x09, 0x02,                    // USAGE (Mouse)
   0xa1, 0x01,                    // COLLECTION (Application)
   0x85, (uint8_t)ID_MOUSE,       //   REPORT_ID (1)
   0x09, 0x01,                    //   USAGE (Pointer)
   0xA1, 0x00,                    //   COLLECTION (Physical)
   0x05, 0x09,                    //     USAGE_PAGE (Button)
   0x19, 0x01,                    //     USAGE_MINIMUM
   0x29, 0x03,                    //     USAGE_MAXIMUM
   0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
   0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
   0x95, 0x03,                    //     REPORT_COUNT (3)
   0x75, 0x01,                    //     REPORT_SIZE (1)
   0x81, 0x02,                    //     INPUT (Data,Var,Abs)
   0x95, 0x01,                    //     REPORT_COUNT (1)
   0x75, 0x05,                    //     REPORT_SIZE (5)
   0x81, 0x03,                    //     INPUT (Const,Var,Abs)
   0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
   0x09, 0x30,                    //     USAGE (X)
   0x09, 0x31,                    //     USAGE (Y)
   0x09, 0x38,                    //     USAGE (Wheel)
   0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
   0x25, 0x7F,                    //     LOGICAL_MAXIMUM (127)
   0x75, 0x08,                    //     REPORT_SIZE (8)
   0x95, 0x03,                    //     REPORT_COUNT (3)
   0x81, 0x06,                    //     INPUT (Data,Var,Rel)
   0xC0,                          //   END_COLLECTION
   0xC0,                     // END COLLECTION   
};


and structures

Code: Select all

typedef struct{
   uint8_t   reportID;
    uint8_t   buttonMask;
    int8_t   dx;
    int8_t   dy;
    int8_t   dWheel;
}mouse_report_t;

typedef struct{
   uint8_t   reportID;
   uint8_t   modifier;
   uint8_t   reserved;
   uint8_t   keycode[5];
}keyboard_report_t;

Post Reply