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