Hello everyone,
I've been using Teensy's as joysticks for a while now and they work great. I recently came upon a project which requires a single Teensy 4.1 to present itself as multiple distinct joysticks in Windows. I was able to modify the USB descriptor by introducing unique Report IDs on each joystick like so:
When compiling against this, Windows indeed sees 2 distinct joysticks with the same name.
USBView shows only one device connected to the controller which is normal, since there is indeed only one physical device connected.
In usb_desc.h, I kept only one endpoint with JOYSTICK_INTERFACE and this is what USBView is reporting also, with the correct bInterfaceNumber.
The problem is I can't send data to a specific joystick using its Report ID, since none of the functions take a report ID as a parameter (which makes sense, since the default use case is a single joystick - which I believe has a report ID of 0 by default). Also, using the standard Joystick functions does not send the data to ANY of the connected joysticks.
What I need is to modify the usb_joystick_send() function with a report ID so I can populate the buffer as per usual (using the standard Joystick.X(), Joystick.button() etc functions) and then send the buffer to a specific joystick (using something like Joystick.send_now(REPORT_ID)).
Any help or ideas would be greatly appreciated.
I've been using Teensy's as joysticks for a while now and they work great. I recently came upon a project which requires a single Teensy 4.1 to present itself as multiple distinct joysticks in Windows. I was able to modify the USB descriptor by introducing unique Report IDs on each joystick like so:
static uint8_t joystick_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x20, // Report Count (32)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button #1)
0x29, 0x05, // Usage Maximum (Button #5)
0x81, 0x02, // Input (variable,absolute)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x07, // Logical Maximum (7)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x65, 0x14, // Unit (20)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x39, // Usage (Hat switch)
0x81, 0x42, // Input (variable,absolute,null_state)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection ()
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x04, // Report Count (4)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x35, // Usage (Rz)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x02, // Report Count (2)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x85, 0x02, // Report ID (2)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x20, // Report Count (32)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button #1)
0x29, 0x05, // Usage Maximum (Button #5)
0x81, 0x02, // Input (variable,absolute)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x07, // Logical Maximum (7)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x65, 0x14, // Unit (20)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x39, // Usage (Hat switch)
0x81, 0x42, // Input (variable,absolute,null_state)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection ()
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x04, // Report Count (4)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x35, // Usage (Rz)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x0A, // Report Size (10)
0x95, 0x02, // Report Count (2)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x81, 0x02, // Input (variable,absolute)
0xC0 // End Collection
When compiling against this, Windows indeed sees 2 distinct joysticks with the same name.
USBView shows only one device connected to the controller which is normal, since there is indeed only one physical device connected.
In usb_desc.h, I kept only one endpoint with JOYSTICK_INTERFACE and this is what USBView is reporting also, with the correct bInterfaceNumber.
The problem is I can't send data to a specific joystick using its Report ID, since none of the functions take a report ID as a parameter (which makes sense, since the default use case is a single joystick - which I believe has a report ID of 0 by default). Also, using the standard Joystick functions does not send the data to ANY of the connected joysticks.
What I need is to modify the usb_joystick_send() function with a report ID so I can populate the buffer as per usual (using the standard Joystick.X(), Joystick.button() etc functions) and then send the buffer to a specific joystick (using something like Joystick.send_now(REPORT_ID)).
Any help or ideas would be greatly appreciated.