USB Combodevice (Keyboard & Joystick) - problems with report

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
Post Reply
MrOnak

USB Combodevice (Keyboard & Joystick) - problems with report

Post by MrOnak » Sat Sep 13, 2014 7:52 pm

Hi all,

this is more or less a followup question to my previous thread http://forums.obdev.at/viewtopic.php?f=8&t=9311.
How does a combo-device that registers itself as keyboard (REPORT_ID = 1) and joystick (REPORT_ID = 2) have to send the usb reports in order to be working properly?

A bit of background: My goal is to develop a custom control panel (for flight simulators and the like) that is recognized by the OS as keyboard and as joystick.
The hardware part for the "keyboard" consists of 32 hardware-debounced buttons read into the microcontroller (ATMega328p) through four shift registers (74HC165N). I have the C code for a USB-keyboard working.
The hardware part for the "joystick" consists of 6 analog axis (ADC0-ADC5 on the ATMega), plus four hardware-debounced buttons (PB0, PD7, PD6, PD5). I have the C code for a USB-joystick working as well.

Now, combining these two software parts (the hardware is a single circuit anyway) I do get a device that is recognized by Windows correctly and the keyboard part works correctly. However sending the joystick report (URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER) yields an URB_FUNCTION_ABORT_PIPE from the OS to the device. Naturally, Windows fails to recognize the inputs from the joystick report.

Inside main() my report-sending routine looks like this:

Code: Select all

    while (1) {
        wdt_reset(); // keep the watchdog happy
        usbPoll();
      
        keyboardUpdateNeeded |= readKeys(); // keyboard key presses
        joystickUpdateNeeded |= readButtons(); // joystick buttons
        // joystick analogue axis are being updated via ISR(ADC_vect) below
      
        if (keyboardUpdateNeeded && usbInterruptIsReady()) {
            usbSetInterrupt((void *) &keyboardReport, 8);
            while ( !usbInterruptIsReady() ) {usbPoll();}
            usbSetInterrupt((void *) &keyboardReport + 8, sizeof(keyboardReport) - 8);
            keyboardUpdateNeeded = 0;
        }
        if (joystickUpdateNeeded && usbInterruptIsReady()) {
            usbSetInterrupt((void *) &joystickReport, sizeof(joystickReport));
            joystickUpdateNeeded = 0;
        }
    }


My USB sniffer tells me that the joystick report (REPORT_ID=2) is indeed send correctly but as said above, Windows doesn't recognize it properly.

I tried to always send both reports when any one of them required sending like this:

Code: Select all

    while (1) {
        wdt_reset(); // keep the watchdog happy
        usbPoll();
      
        keyboardUpdateNeeded |= readKeys(); // keyboard key presses
        joystickUpdateNeeded |= readButtons(); // joystick buttons
        // joystick analogue axis are being updated via ISR(ADC_vect) below
      
        if ((keyboardUpdateNeeded || joystickUpdateNeeded) && usbInterruptIsReady()) {
            usbSetInterrupt((void *) &keyboardReport, 8);
            while ( !usbInterruptIsReady() ) {usbPoll();}
            usbSetInterrupt((void *) &keyboardReport + 8, sizeof(keyboardReport) - 8);
            keyboardUpdateNeeded = 0;
         
            while ( !usbInterruptIsReady() ) {usbPoll();}
            usbSetInterrupt((void *) &joystickReport, sizeof(joystickReport));
            joystickUpdateNeeded = 0;
        }
    }


But doing so yields an incorrectly recognized device in Windows (yellow exclamation mark).

Any ideas? Thanks a ton :)

ulao
Rank 4
Rank 4
Posts: 481
Joined: Mon Aug 25, 2008 8:45 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by ulao » Mon Sep 15, 2014 1:40 am

My goal is to develop a custom control panel
I really hope you figure this out, because I would absolutely love to see that work. I had to give up on it.

Are you setting the report id in your out ID report? 0 for Keyboard 1 for joystick? Also when dealing with reading a joystick there really is no point in checking for data change. Just sample the joystick wait for usb not ready and report, then loop. This is not your issue but helps to reduce code when things are not going right.

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Mon Sep 15, 2014 2:53 pm

I will figure this out, and share the whole thing including sources and EAGLE file on github, don't worry :). The world does need more custom controllers. Literally the USB reports are the only thing that's missing aside from building the enclosure and mounting pretty buttons so... I'm too stubborn to give up now hehe.

But about your questions / suggestions:
Are you setting the report id in your out ID report? 0 for Keyboard 1 for joystick?

Well I'm setting report-id 1 for the keyboard and 2 for the joystick. I don't think they need to be zero-indexed?
My report descriptor and data structures is this:

Code: Select all

PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
   //---- keyboard (65 byte)  ------------------------------------------------
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x06, // USAGE (Keyboard)
    0xa1, 0x01, // COLLECTION (Application)
    0x85, 0x01, //   REPORT_ID (1)
    0x75, 0x01, //   REPORT_SIZE (1)
    0x95, 0x08, //   REPORT_COUNT (8)
    0x05, 0x07, //   USAGE_PAGE (Keyboard)(Key Codes)
    0x19, 0xe0, //   USAGE_MINIMUM (Keyboard LeftControl)(224)
    0x29, 0xe7, //   USAGE_MAXIMUM (Keyboard Right GUI)(231)
    0x15, 0x00, //   LOGICAL_MINIMUM (0)
   0x25, 0x01, //   LOGICAL_MAXIMUM (1)
    0x81, 0x02, //   INPUT (Data,Var,Abs) ; Modifier byte
    0x95, 0x01, //   REPORT_COUNT (1)
    0x75, 0x08, //   REPORT_SIZE (8)
    0x81, 0x03, //   INPUT (Cnst,Var,Abs) ; Reserved byte
   0x95, 0x05, //   REPORT_COUNT (5)
    0x75, 0x01, //   REPORT_SIZE (1)
    0x05, 0x08, //   USAGE_PAGE (LEDs)
    0x19, 0x01, //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05, //   USAGE_MAXIMUM (Kana)
   0x91, 0x02, //   OUTPUT (Data,Var,Abs) ; LED report
    0x95, 0x01, //   REPORT_COUNT (1)
    0x75, 0x03, //   REPORT_SIZE (3)
    0x91, 0x03, //   OUTPUT (Cnst,Var,Abs) ; LED report padding
    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)(Key Codes)
    0x19, 0x00, //   USAGE_MINIMUM (Reserved (no event indicated))(0)
   0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)(101)
    0x81, 0x00, //   INPUT (Data,Ary,Abs)
    0xc0, // END_COLLECTION
   //---- joystick (84 byte) ------------------------------------------------
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x32,                    //     USAGE (Z)
   0x09, 0x33,                    //     USAGE (Rx)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x09, 0x34,                    //   USAGE (Ry)
    0x09, 0x35,                    //   USAGE (Rz)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //   REPORT_COUNT (2)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x09,                    //   USAGE_PAGE (Button)
    0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
    0x29, 0x04,                    //   USAGE_MAXIMUM (Button 4)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x04,                    //   REPORT_COUNT (4)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
    0xc0                           // END_COLLECTION
};

typedef struct {
   uint8_t reportId;
   uint8_t x;
   uint8_t y;
   uint8_t z;
   uint8_t rx;
   uint8_t ry;
   uint8_t rz;
   uint8_t buttons;
} joystick_report_t;

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


My usbFunctionSetup() is this (relevant part only):

Code: Select all

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

    if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
        switch (rq->bRequest) {
            case USBRQ_HID_GET_REPORT:
         // check report ID requested
         if (rq->wValue.bytes[0] == 1) {
            usbMsgPtr = (void *) &keyboardReport;
            keyboardReport.modifier    = 0;
            keyboardReport.keycode[0]    = 0;
            return sizeof(keyboardReport);
         } else if (rq->wValue.bytes[0] == 2) {
            usbMsgPtr = (void *) &joystickReport;
            return sizeof(joystickReport);
         }   


and last not least I do this in my init() function which in turn is called early in main():

Code: Select all

void init(void) {
    //---- clear reports initially --------------------------------------------
   keyboardReport.reportId = 1;
   keyboardReport.modifier = 0;
   keyboardReport.reserved = 0;
   keyboardReport.keycode[0] = KEY_NONE;
   keyboardReport.keycode[1] = KEY_NONE;
   keyboardReport.keycode[2] = KEY_NONE;
   keyboardReport.keycode[3] = KEY_NONE;
   keyboardReport.keycode[4] = KEY_NONE;
   keyboardReport.keycode[5] = KEY_NONE;

   joystickReport.reportId = 2;
   joystickReport.x       = 127;
   joystickReport.y       = 127;
   joystickReport.z       = 127;
   joystickReport.rx       = 127;
   joystickReport.ry       = 127;
   joystickReport.rz       = 127;
   joystickReport.buttons    = 0;


I'm sure I don't see the forest for the trees at the moment but it seems fine for me?

I had found two other projects that implement USB combo devices on AVR microcontrollers. They both repeatedly set the individual reportId parameters of the data structures inside the while(1) {} loop in main(). I'm not doing that at the moment since
a) the data being sent is correct according to my USB sniffer (report id 1 for keyboard, 2 for joystick) and
b) because, well nothing in the code is altering the report ids in the first place.

That said, USB being the funny customer that it is, do you see any point in trying that? The direct test of course is quickly done but in case that doesn't immediately work that is another test for each and every change that I do after that...


Also when dealing with reading a joystick there really is no point in checking for data change. Just sample the joystick wait for usb not ready and report, then loop.

Of course you're right that checking for data change for the joystick is a bit futile but I'm doing that at the moment to make protocol analysis easier - less packets to scan through - I might remove that later.

question though: Any idea if there is a requirement to always send all reports, and in the right order, whenever any data has changed?
I.e. if the joystick data changes (report id 2) do I need to send a keyboard report (report id 1) before the joystick one is being accepted? Those two other combo device projects always send all reports, in the right order. I did try that without success - see my second code snippet in the previous post of this thread - but that doesn't mean it isn't "better".

Another thought experiment: When I alter my code to always send report-id 1 first, and then report-id 2 whenever any of the data has changed, Windows fails to register the device on connect (see original post). What I did notice is that the device is "spamming" windows with URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER packets even while windows is still registering the device. Maybe that causes the registration to give up / fail? Is there any packet send from the host to device to tell the device that registration of the device is done and it's "safe" to send the reports? If so, I could alter the code relatively easy to stay quiet until the host is ready.

As always, any help / suggestions are greatly appreciated :)

ulao
Rank 4
Rank 4
Posts: 481
Joined: Mon Aug 25, 2008 8:45 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by ulao » Tue Sep 16, 2014 2:33 pm

You are correct about not need to 0 index it, here is an example I use and its works. No, I also do not believe its order dependent but in my case I do send in order. Every time I send my report I send report 1 then 9. I can not say what is going on there with windows, it should not matter what is sent until the initialization is over?

this is the top of my report

Code: Select all

const char  analog_usbHidReportDescriptor[] PROGMEM = {
///// gamepad
0x05,0x01,  //    Usage Page Generic Desktop
0x09,0x05,  //    USAGE (Game Pad)
0xA1,0x01,  //    Collection Application
0x85,0x01,  //     Report ID 1
   //axis
   0xa1, 0x00,                    //   COLLECTION (Physical) ( mudst be in a collection for open-emu )
      0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
      0x26, 0xFF, 0x00,              //     LOGICAL_MAXIMUM (255)
      0x35, 0x00,                    //     Physical Minimum (0)
      0x46, 0xFF, 0x00,              //     Physical Minimum (255)
      0x09, 0x30,                    //     USAGE (X)
      0x09, 0x31,                    //     USAGE (Y)
      0x09, 0x32,                    //     USAGE (Z)
      0x09, 0x33,                    //     USAGE (Rx)
      0x09, 0x34,                    //     USAGE (Ry)
      0x09, 0x35,                    //     USAGE (Rz)
      0x09, 0x36,                    //     USAGE slider
      0x09, 0x37,                    //     USAGE  dial
      //0x09, 0x38,                  //     wheel ( cant figure this out )
      0x75, 0x08,                    //     REPORT_SIZE (8)
      0x95, 0x08,                    //     REPORT_COUNT (8)
      0x81, 0x02,                    //     INPUT
   0xc0,//end pointer phys.
   
   //hat
   0x09,0x39,                     //    Usage (Hat switch)
   0x15,0x00,                     //    Logical_Minimum (0)
   0x25,0x07,                     //    Logical_Maximum (7)
   0x75,0x05,                     //    Report_Size (4)
   0x95,0x01,                     //    Report_Count (1)
   0x81,0x03,                     //    Input (Data, Var, Abs)
   //padding
   0x75,0x03,                     //    Report_Size (4)
   0x95,0x01,                     //    Report_Count (1)
   0x81,0x03,                     //    Input (Data, Var, Abs)
   
   //button
   0x05, 0x09,                    //      USAGE_PAGE (Button)
   0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
   0x29, 0x18,                    //     USAGE_MAXIMUM (Button 24)
   0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
   0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
   0x75, 0x01,                    //     REPORT_SIZE (1)
   0x95, 0x18,                    //     REPORT_COUNT (24)
   0x81, 0x02,                    //       INPUT


   //this is an input for FFB, data out.
   0x06,0x01,0xFF,                 //    Usage Page Generic Desktop
   0x09,0x49,                      //    Usage Undefined
   0x75,0x01,                      //    Report Size 1
   0x95,0x01,                      //    Report Count 1
   0x81,0x02,                      //    Input (Variable)
   0x75,0x07,                      //    Report Size 7
   0x81,0x03,                      //    Input (Constant, Variable)


then I have 3 pages of FFB crap.

this is the bottom (mouse)

Code: Select all

   0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
      0x85, 0x09,                // Report ID (9)
      0x09, 0x01,                    //   USAGE (Pointer)
         0xA1, 0x00,                    //   COLLECTION (Physical)
         0x05, 0x09,                    //     USAGE_PAGE (Button)
         0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
         0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
         0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
         0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
         0x75, 0x01,                    //     REPORT_SIZE (1)
         0x95, 0x03,                    //     REPORT_COUNT (3)
         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
         0x75, 0x01,                    //     REPORT_SIZE (1)
         0x95, 0x05,                    //     REPORT_COUNT (5)
         0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)

         0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
         0x09, 0x30,                    //     USAGE (X)
         0x09, 0x31,                    //     USAGE (Y)
         //0x09, 0x38,                    //     USAGE (Wheel)
         0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
         0x25, 0x00, 0x04,              //     LOGICAL_MAXIMUM (1k)
         0x75, 0x08,                    //     REPORT_SIZE (16)
         0x95, 0x02,                    //     REPORT_COUNT (2)
         0x81, 0x06,                    //     INPUT (Data,Var,Rel)
      0xC0,                          //   END_COLLECTION
    0xC0                          // END COLLECTION




This is my config if it helps any. Mind you it has FFB pipes in there.

Code: Select all

uchar my_usbDescriptorConfiguration[] = {    /* USB configuration descriptor */
     9,          /* sizeof(usbDescriptorConfiguration): length of descriptor in bytes */
    USBDESCR_CONFIG,    /* descriptor type */
    18 + 7 * USB_CFG_HAVE_INTRIN_ENDPOINT + /*7 * USB_CFG_HAVE_INTRIN_ENDPOINT3*/ + 7 + 9, 0,
                /* total length of data returned (including inlined descriptors) */
    1,          /* number of interfaces in this configuration */
    1,          /* index of this configuration */
    0,          /* configuration name string index */
    USB_CFG_IS_SELF_POWERED,  /* attributes */

    USB_CFG_MAX_BUS_POWER/2,            /* max USB current in 2mA units */
/* interface descriptor follows inline: */
    9,          /* sizeof(usbDescrInterface): length of descriptor in bytes */
    USBDESCR_INTERFACE, /* descriptor type */
    0,          /* index of this interface */
    0,          /* alternate setting for this interface */
    1 + USB_CFG_HAVE_INTRIN_ENDPOINT ,//+ USB_CFG_HAVE_INTRIN_ENDPOINT3,   /* endpoints excl 0: number of endpoint descriptors to follow */
    USB_CFG_INTERFACE_CLASS,
    USB_CFG_INTERFACE_SUBCLASS,
    USB_CFG_INTERFACE_PROTOCOL,
    0,          /* string index for interface */

    9,          /* sizeof(usbDescrHID): length of descriptor in bytes */
    USBDESCR_HID,   /* descriptor type: HID */
    0x10, 0x01, /* BCD representation of HID version */
    0x00,       /* target country code */
    0x01,       /* number of HID Report (or other HID class) Descriptor infos to follow */
    0x22,       /* descriptor type: report */
    USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH, 0,  /* total length of report descriptor *///
//#endif


#if USB_CFG_HAVE_INTRIN_ENDPOINT    /* endpoint descriptor for endpoint 1 */
    7,          /* sizeof(usbDescrEndpoint) */
    USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
   0x81,       // bulk IN endpoint number 1
    0x03,       /* attrib: Interrupt endpoint */
    8, 0,       /* maximum packet size */
    0x04, /* in ms*/

//the output.

    7,          // sizeof(usbDescrEndpoint)
    5,  // descriptor type = endpoint
    0x02,      // out endpoint number 2
    0x03,       // attrib: Interrupt endpoint
    8, 0,       // maximum packet size
    USB_CFG_INTR_POLL_INTERVAL, // in ms
#endif   
};


my report is

Code: Select all

      reportBuffer[REPORT_ID]=1;//report ID (packet 1)
      reportBuffer[X_MAIN_STICK]=128;//X-Axis ( main stick )  ( packet 2 )
      reportBuffer[Y_MAIN_STICK]=128;//Y-Axis                 ( packet 3 )
      reportBuffer[Z_AXIS_1]=128;//Z-Axis                 ( packet 4 )
      reportBuffer[X_SECONDARY_STICK]=128;//X-rotate               ( packet 5 )
      reportBuffer[Y_SECONDARY_STICK]=128;//Y-rotate               ( packet 6)
      reportBuffer[Z_AXIS_2]=128;//Z-rotate               ( packet 7)
      reportBuffer[SLIDER]=128;//slider                 ( packet 8 )
      reportBuffer[DIAL]=128;//dial                   ( packet 9 )
      reportBuffer[HAT]=-1;//d-pad                  ( packet 10 )
      reportBuffer[BUTTON_ROW_1]=0;//buttons 1               ( packet 11 )
      reportBuffer[BUTTON_ROW_2]=0;//buttons 2               ( packet 12 )
      //FFB is 13

      while (!usbInterruptIsReady()){usbPoll(); } usbSetInterrupt( reportBuffer, 8);
      while (!usbInterruptIsReady()){usbPoll(); } usbSetInterrupt((void *)&reportBuffer + 8, 5);



and mouse is

Code: Select all

         reportBuffer[Z_AXIS_1]= 0;
         reportBuffer[Y_MAIN_STICK]=reportBuffer[X_MAIN_STICK]+128;
         reportBuffer[X_MAIN_STICK]=0;//clear gamepad data
         reportBuffer[REPORT_ID]=0x09;//report ID
while (!usbInterruptIsReady()) usbPoll();   usbSetInterrupt( reportBuffer,4);

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Tue Sep 16, 2014 6:45 pm

*grrrrrr* :evil: I'm starting to loose my mind here.

Thanks for your post ulao, but although educational, I still can't figure out what's wrong with my implementation.

I've narrowed it down to the joystickReport though.If I omit that, the device registers fine and the keyboard works. keyboard and joystick together results in endless re-enumerations, connect/disconnect sequences until the OS finally gives up. Only the joystick report active (usbSetInterrupt() for the keyboard commented) is ultimately the same.

I've changed my endless-loop into this:

Code: Select all

    while (1) {
        usbPoll(); wdt_reset(); // keep the watchdog happy
        readKeys();         // keyboard
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &keyboardReport, 8);
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &keyboardReport + 8, sizeof(keyboardReport) - 8);

        readButtons();      // joystick buttons
//        while (!usbInterruptIsReady()){usbPoll();}
//        usbSetInterrupt((void *) &joystickReport, sizeof(joystickReport));
    }


And as I said, that works, with the joystick inputs not being transmitted.
Uncomment the two lines and all hell breaks loose ;)

My report structs for completeness:

Code: Select all

typedef struct {
    uint8_t reportId;
    uint8_t x;
    uint8_t y;
    uint8_t z;
    uint8_t rx;
    uint8_t ry;
    uint8_t rz;
    uint8_t buttons;
} joystick_report_t;

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

static keyboard_report_t keyboardReport;
keyboardReport.reportId = 1;
keyboardReport.modifier = 0;
keyboardReport.reserved = 0;
keyboardReport.keycode[0] = KEY_NONE;
keyboardReport.keycode[1] = KEY_NONE;
keyboardReport.keycode[2] = KEY_NONE;
keyboardReport.keycode[3] = KEY_NONE;
keyboardReport.keycode[4] = KEY_NONE;
keyboardReport.keycode[5] = KEY_NONE;

static joystick_report_t joystickReport;
joystickReport.reportId = 2;
joystickReport.x       = 127;
joystickReport.y       = 127;
joystickReport.z       = 127;
joystickReport.rx       = 127;
joystickReport.ry       = 127;
joystickReport.rz       = 127;
joystickReport.buttons    = 0;


One very confusing observation was made when I wanted to comment-out the keyboard report, but forgot to comment the 2nd report for the keyboard, resulting in this:

Code: Select all

    while (1) {
        usbPoll(); wdt_reset(); // keep the watchdog happy
        readKeys();         // keyboard
//        while (!usbInterruptIsReady()){usbPoll();}   
//        usbSetInterrupt((void *) &keyboardReport, 8);
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &keyboardReport + 8, sizeof(keyboardReport) - 8);

        readButtons();      // joystick buttons
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &joystickReport, sizeof(joystickReport));
    }


Contrary to what I thought would happen, the joystick works with this configuration. What is going through the USB connection is actually the first 8 bytes being the joystick report and the last byte from the keyboard report being sent as last byte on the report, not the first.

I've run out of ideas now. Any further ideas from anyone?

If anyone would be interested in it, I can put the whole codebase including an EAGLE schematic for the circuitry up on github but without some new ideas I'm not sure whether I can get it to work :(

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Tue Sep 16, 2014 6:55 pm

Hmmmm.... quick update. I just thought "ok I'll test this one more thing":

Code: Select all

    while (1) {
        usbPoll(); wdt_reset(); // keep the watchdog happy
      
        readKeys();         // keyboard   
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &keyboardReport, 8);
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &keyboardReport + 8, sizeof(keyboardReport) - 8);

        readButtons();      // joystick buttons
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt((void *) &joystickReport, sizeof(joystickReport));
        while (!usbInterruptIsReady()){usbPoll();}   
        usbSetInterrupt(0x00, 1); // no idea why we need to send this byte
    }


Note the last 0x00 byte after sending the correct amount of data for all reports.
IT WORKS

<visualize me doing the jigg here>

Now... Can anyone explain to me why the hell I need to send that additional byte?!
By the way when I sniff the USB report being sent then my 0x00 is actually sent as 0x14 - another mystery.

ulao
Rank 4
Rank 4
Posts: 481
Joined: Mon Aug 25, 2008 8:45 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by ulao » Tue Sep 16, 2014 7:48 pm

Note the last 0x00 byte after sending the correct amount of data for all reports.
IT WORKS
Now... Can anyone explain to me why the hell I need to send that additional byte?


Sounds to me like your count is off, I bet your device description has 9 bytes total instead of 8. You could fix the descriptor or just add the bit on the first go.
usbSetInterrupt((void *) &joystickReport,9);


I suck at counting reports but I think I count 9 here

Code: Select all

   0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //     REPORT_COUNT (2) ///////////// 1,2
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x32,                    //     USAGE (Z)
   0x09, 0x33,                    //     USAGE (Rx)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //     REPORT_COUNT (2)  ///////////// 3,4
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x09, 0x34,                    //   USAGE (Ry)
    0x09, 0x35,                    //   USAGE (Rz)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
    0x95, 0x02,                    //   REPORT_COUNT (2) ///////////// 5,6
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x09,                    //   USAGE_PAGE (Button)
    0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
    0x29, 0x04,                    //   USAGE_MAXIMUM (Button 4)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x04,                    //   REPORT_COUNT (4)  ///////////// 7,8 (4x4, 2 bytes)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1) ///////////// 9
    0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
    0xc0                           // END_COLLECTION
};

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Tue Sep 16, 2014 8:25 pm

Hmmm yeah well generally I agree it'll have to be something like that but your counting is off. The last two INPUTs are 4 bits each, so thats one byte overall. My own counting here:

Code: Select all

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
// 1 byte for REPORT_ID
    0x85, 0x02,                    //   REPORT_ID (2)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
// 2 bytes for X & Y (3 total)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x32,                    //     USAGE (Z)
    0x09, 0x33,                    //     USAGE (Rx)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
// 2 bytes for Z and Rx (5 byte total)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //     END_COLLECTION
    0x09, 0x34,                    //   USAGE (Ry)
    0x09, 0x35,                    //   USAGE (Rz)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
// 2 byte for Ry and Rz (7 total)
    0x95, 0x02,                    //   REPORT_COUNT (2)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x05, 0x09,                    //   USAGE_PAGE (Button)
    0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
    0x29, 0x04,                    //   USAGE_MAXIMUM (Button 4)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x04,                    //   REPORT_COUNT (4)
// 4 bits for buttons (7 byte, 4 bits)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x75, 0x04,                    //   REPORT_SIZE (4)
    0x95, 0x01,                    //   REPORT_COUNT (1)
// 4 bits as fillers (8 byte total)
    0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
    0xc0                           // END_COLLECTION




If anyone has another explanation as to what the hell is going on I'd really want to know :D.
In the meantime I'll be waiting for the last buttons to arrive, build the sucker, wrap it all up and get back to you all once it is on github.

Cheers for all the help folks!

ulao
Rank 4
Rank 4
Posts: 481
Joined: Mon Aug 25, 2008 8:45 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by ulao » Tue Sep 16, 2014 8:57 pm

Right I see where I added that up wrong. Though its expecting something there... Glad it works well enough to get your work done.

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Tue Sep 16, 2014 9:21 pm

Well I know I'm not going to touch that usbSetInterrupt() sequence unless I really really really have to ;)

Thanks for all the support. I'll be back with the results of this build in a few weeks... delivery of some parts from China will take a while.

blargg
Rank 3
Rank 3
Posts: 102
Joined: Thu Nov 14, 2013 10:01 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by blargg » Tue Sep 16, 2014 10:20 pm

Nice you've made progress. I plan on doing a combo device for a keyboard/media keys adapter and this will be useful to refer to.

delivery of some parts from China will take a while.


Heh, I've discovered that stuff on eBay about a year ago and it's been an interesting experience in patience and planning ahead of what projects I might like to work on in a few weeks.

MrOnak
Posts: 7
Joined: Mon Sep 15, 2014 2:29 pm

Re: USB Combodevice (Keyboard & Joystick) - problems with re

Post by MrOnak » Wed Sep 17, 2014 12:06 am

blargg wrote:Nice you've made progress. I plan on doing a combo device for a keyboard/media keys adapter and this will be useful to refer to.


I'll probably have the code and the EAGLE file up on github by this weekend. I'll send you an update if you're interested.

delivery of some parts from China will take a while.


Heh, I've discovered that stuff on eBay about a year ago and it's been an interesting experience in patience and planning ahead of what projects I might like to work on in a few weeks.


It is true, things take a while to get here but some of it is so much cheaper that I just buy a bit more (spares, shorts, ...) and its usually worth it. Some things seem to be as cheap here though (here being Germany) so its actually worth checking national sellers occasionally. Globalization is a funny thing sometimes.

Post Reply