USB HID Joystick: one device two interfaces conundrum

D

Dmitry Borets

Hello!


I've buit an LUFA/Arduino-based joystick HID device that, due to a ridiculous 32-button max limitation, has got two distinct interfaces in it: first one with 32 buttons, the second with the other 14 buttons and 8 axes. The device itself works allright, Windows Device Manager correctly sees one USB composite device, two USB Input Devices and two HID-compliant game controllers, correctly recognises both interfaces and all the controls on each of them.


But I can't, for the life of me, make Windows - and the other apps that rely on Windows drivers stack (or DirectX input, I really don't know which), see these two interfaces as separate joysticks. Why? Because, I believe (this gotta be it, for I have turned over every page on the internet trying to find a solution), Windows feeds those progams with the device ID based on VID&PID&Revison, that is - either a Manufacturer string, or a Descriptor string of the first interface only.

Hence, the apps and games that rely on the info provided by Windows, see only one joystick (one interface) and I'm getting only half of the functionality.

Some of the apps, e.g. X-Plane, that, I believe, work with the HID devices directly, bypassing Windows drivers, correctly distinguish the interfaces by their string descriptors.

I even tried Interface Association Descriptors (one IAD - one interface) in a futile attempt to make it work; it didn't work without IADs either.


Now, to the question: is there an official documented way of making Windows enumerate the interfaces based on their individual distictive IDs, and feed the other apps and games with distinctive joustick names/IDs so that the apps would see two joysticks instead of one?


Help me, guys. I'm really desperate. There has got to be a way.


Thanks,

Dmitry.


Here's the descriptors (but again, everything's working just fine if not for this thing with the same name/ID for the two interfaces):


const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickAReport[] =
{
/* First 32 buttons */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x04, /* Usage (Joystick) */
0xa1, 0x01, /* Collection (Application) */
0x09, 0x01, /* Usage (Pointer) */
0xa1, 0x00, /* Collection (Physical) */
0x05, 0x09, /* Usage Page (Button) */
0x19, 0x01, /* Usage Minimum (Button 1) */
0x29, 0x20, /* Usage Maximum (Button 32) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x20, /* Report Count (32) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0xc0, /* End Collection */
0xc0 /* End Collection */
};

const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickBReport[] =

{

/* Last 14 buttons and 8 axes */

0x05, 0x01, /* Usage Page (Generic Desktop) */

0x09, 0x04, /* Usage (Joystick) */

0xa1, 0x01, /* Collection (Application) */

0x09, 0x01, /* Usage (Pointer) */

0xa1, 0x00, /* Collection (Physical) */

0x05, 0x09, /* Usage Page (Button) */

0x19, 0x01, /* Usage Minimum (Button 1) */

0x29, 0x0E, /* Usage Maximum (Button 14) */

0x15, 0x00, /* Logical Minimum (0) */

0x25, 0x01, /* Logical Maximum (1) */

0x75, 0x01, /* Report Size (1) */

0x95, 0x0E, /* Report Count (14) */

0x81, 0x02, /* Input (Data, Variable, Absolute) */

0x75, 0x02, /* Report Size (2) */

0x95, 0x01, /* Report Count (1) */

0x81, 0x01, /* Input (Constant) for padding */

0x05, 0x01, /* Usage Page (Generic Desktop) */

0x09, 0x30, /* Usage (X) Four pairs of coupled axes */

0x09, 0x31, /* Usage (Y) */

0x09, 0x30, /* Usage (X) */

0x09, 0x31, /* Usage (Y) */

0x09, 0x30, /* Usage (X) */

0x09, 0x31, /* Usage (Y) */

0x09, 0x30, /* Usage (X) */

0x09, 0x31, /* Usage (Y) */

0x16, 0x00, 0x00, /* Logical Minimum (double byte zero) */

0x26, 0xff, 0x03, /* Logical Maximum (1023 - 5V-based Arduino AD converter's maximum) */

0x75, 0x10, /* Report Size (16) */

0x95, 0x08, /* Report Count (8) */

0x81, 0x82, /* Input (Data, Variable, Absolute, Volatile) */

0xc0, /* End Collection */

0xc0 /* End Collection */

};




const USB_Descriptor_Device_t PROGMEM DeviceDescriptor =

{

.Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device},


.USBSpecification = VERSION_BCD(2,0,0),

.Class = 0xEF,

.SubClass = 0x02,

.Protocol = 0x01,


.Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE,


.VendorID = 0x03EB,

.ProductID = 0x2043,

.ReleaseNumber = VERSION_BCD(1,4,1),


.ManufacturerStrIndex = STRING_ID_Manufacturer,

.ProductStrIndex = STRING_ID_Product,

.SerialNumStrIndex = STRING_ID_Serial,


.NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS

};


const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =

{

.Config =

{

.Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration},



.TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t),

.TotalInterfaces = 0x02,


.ConfigurationNumber = 0x01,

.ConfigurationStrIndex = NO_DESCRIPTOR,


.ConfigAttributes = (USB_CONFIG_ATTR_RESERVED | USB_CONFIG_ATTR_SELFPOWERED),


.MaxPowerConsumption = USB_CONFIG_POWER_MA(100)

},


.IAD_A =

{

.Header = {.Size = sizeof(USB_Descriptor_Interface_Association_t), .Type = DTYPE_InterfaceAssociation},


.FirstInterfaceIndex = INTERFACE_ID_Joystick_A,

.TotalInterfaces = 0x01,


.Class = 0x03,

.SubClass = 0x00,

.Protocol = HID_CSCP_NonBootProtocol,


.IADStrIndex = STRING_ID_IAD_A

},


.HIDA_Interface =

{

.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},



.InterfaceNumber = INTERFACE_ID_Joystick_A,

.AlternateSetting = 0x00,


.TotalEndpoints = 0x01,


.Class = 0x03,

.SubClass = 0x00,

.Protocol = HID_CSCP_NonBootProtocol,


.InterfaceStrIndex = STRING_ID_Interface_A

},



.HIDA_JoystickHID =

{

.Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID},


.HIDSpec = VERSION_BCD(1,11,0),

.CountryCode = 0x00,

.TotalReportDescriptors = 0x01,

.HIDReportType = HID_DTYPE_Report,

.HIDReportLength = sizeof(JoystickAReport)

},



.HIDA_ReportINEndpoint =

{

.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},


.EndpointAddress = (ENDPOINT_DIR_IN | JOYSTICK_A_EPNUM),

.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),

.EndpointSize = JOYSTICK_A_EPSIZE,

.PollingIntervalMS = 0x02

},



.IAD_B =

{

.Header = {.Size = sizeof(USB_Descriptor_Interface_Association_t), .Type = DTYPE_InterfaceAssociation},


.FirstInterfaceIndex = INTERFACE_ID_Joystick_B,

.TotalInterfaces = 0x01,


.Class = 0x03,

.SubClass = 0x00,

.Protocol = HID_CSCP_NonBootProtocol,


.IADStrIndex = STRING_ID_IAD_B

},


.HIDB_Interface =

{

.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},



.InterfaceNumber = INTERFACE_ID_Joystick_B,

.AlternateSetting = 0x00,


.TotalEndpoints = 1,


.Class = 0x03,

.SubClass = 0x00,

.Protocol = HID_CSCP_NonBootProtocol,


.InterfaceStrIndex = STRING_ID_Interface_B

},



.HIDB_JoystickHID =

{

.Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID},


.HIDSpec = VERSION_BCD(1,11,0),

.CountryCode = 0x00,

.TotalReportDescriptors = 0x01,

.HIDReportType = HID_DTYPE_Report,

.HIDReportLength = sizeof(JoystickBReport)

},



.HIDB_ReportINEndpoint =

{

.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},


.EndpointAddress = (ENDPOINT_DIR_IN | JOYSTICK_B_EPNUM),

.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),

.EndpointSize = JOYSTICK_B_EPSIZE,

.PollingIntervalMS = 0x02

}

};

Continue reading...
 
Back
Top Bottom