NicoHood/HID

Gamepad + Keyboard/Mouse doesn't seem to be working properly

Opened this issue · 17 comments

I have a simple sketch where I initialize one gamepad, mouse and keyboard. (attached)

If i try to use all three at the same time, mouse and keyboard send events, but gamepad does not.
If i switch gamepad to gamepad1, then only gamepad1 sends events, but mouse and keyboard do not. Also for some reason, three joysticks are detected instead of the one I have initialized.

hidproject_example.txt

Thats a known issue. Use the single report mouse/keyboard/gamepad. (aka Gamepad1, Bootkeyboard, Bootmouse)

Unfortunately this is not sufficient, as even using (= calling .begin() on) only Gamepad1 in a sketch causes all the 4 instances (well, 3 actually, as endpoints get exhausted after that) of SingleGamepad declared in SingleGamepad.hpp to show up:

[ven ago 28 23:35:41 2020] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
[ven ago 28 23:35:41 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.2/0003:04D8:EB64.0088/input/input164
[ven ago 28 23:35:41 2020] hid-generic 0003:04D8:EB64.0088: input,hidraw1: USB HID v1.01 Joystick [Arduino Leonardo] on usb-0000:00:14.0-1/input2
[ven ago 28 23:35:41 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:04D8:EB64.0089/input/input165
[ven ago 28 23:35:41 2020] hid-generic 0003:04D8:EB64.0089: input,hidraw4: USB HID v1.01 Joystick [Arduino Leonardo] on usb-0000:00:14.0-1/input3
[ven ago 28 23:35:41 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.4/0003:04D8:EB64.008A/input/input166
[ven ago 28 23:35:41 2020] hid-generic 0003:04D8:EB64.008A: input,hidraw5: USB HID v1.01 Joystick [Arduino Leonardo] on usb-0000:00:14.0-1/input4

Commenting out two of those instances allows the BootKeyboard to show up:

[ven ago 28 23:40:50 2020] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
[ven ago 28 23:40:50 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.2/0003:04D8:EB64.008D/input/input169
[ven ago 28 23:40:50 2020] hid-generic 0003:04D8:EB64.008D: input,hidraw1: USB HID v1.01 Joystick [Arduino Leonardo] on usb-0000:00:14.0-1/input2
[ven ago 28 23:40:50 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.3/0003:04D8:EB64.008E/input/input170
[ven ago 28 23:40:50 2020] hid-generic 0003:04D8:EB64.008E: input,hidraw4: USB HID v1.01 Joystick [Arduino Leonardo] on usb-0000:00:14.0-1/input3
[ven ago 28 23:40:50 2020] input: Arduino Leonardo as /devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.4/0003:04D8:EB64.008F/input/input171
[ven ago 28 23:40:50 2020] hid-generic 0003:04D8:EB64.008F: input,hidraw5: USB HID v1.01 Keyboard [Arduino Leonardo] on usb-0000:00:14.0-1/input4

I'm not sure how this can be fixed. I'm not a big fan of object instances defined at library level, but you went for this approach everywhere, and that's what all the official Arduino libraries do, so I guess it's ok. Maybe the default instances can be reduced to one or two and if a user needs more, they can just declare as many extra as they need.

Maybe just calling BootKeyboard.begin () (and BootMouse.begin()) before Gamedpad1.begin() will work?

IMPORTANT: It should be noted that I have the Multiple Input USB “quirk” enabled on my system, maybe this is influencing the way the devices show up.

Could this having anything to do with it? This output is from some USB debugging software I picked up on the Interwebz so I can't really comment beyond what this says, however, this is a SAMD powered device as you can see, operating in Gamepad mode per example.

There are other factors in the report that are "off" too but this one seems to be something that could actually trip everything up. I'm going to have a look through Nico's code but my C++ skills are so poor that it's unlikely I'll be able to find it.

Perhaps this will help someone else, even Nico - although I understand that he has pretty much left this project and I can understand why, given the lack of contributions from outside.

        ===>Device Descriptor<===
[Port1]  :  USB Composite Device

Is Port User Connectable:         yes

Is Port Debug Capable:            no

Companion Port Number:            13

Companion Hub Symbolic Link Name: USB#ROOT_HUB30#4&6618f79&0&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}

Protocols Supported:

 USB 1.1:                         yes

 USB 2.0:                         yes

 USB 3.0:                         no

Device Power State:               PowerDeviceD0

       ---===>Device Information<===---
English product name: "Seeed XIAO M0"

ConnectionStatus:                  
Current Config Value:              0x01  -> Device Bus Speed: Full (is not SuperSpeed or higher capable)
Device Address:                    0x06
Open Pipes:                           4

bLength:                           0x12

bDescriptorType:                   0x01

bcdUSB:                          0x0200

bDeviceClass:                      0x00 *!*ERROR: device class should be Multi-interface Function 0xEF When IAD descriptor is used

bDeviceSubClass:                   0x00 *!*ERROR: device SubClass should be USB Common Sub Class 2 When IAD descriptor is used

bDeviceProtocol:                   0x00 *!*ERROR: device Protocol should be USB IAD Protocol 1 When IAD descriptor is used

bMaxPacketSize0:                   0x40 = (64) Bytes

idVendor:                        0x2886 = Vendor ID not listed with USB.org as of 02-15-2012

idProduct:                       0x802F

bcdDevice:                       0x0104

I really recommend using LUFA if you want a reliably working USB Core. The arduino core is not flexible enough for all usecases. It is made to abstract the basics, not the complex things. USB is such kind of ultra complex thing, and I did not use it for a long time, so I am so so so sorry that I am really no help (anymore) :-/

Hey Nico, you've done us all a solid man! Don't sweat it... doing stuff for free is expensive (if you get my drift)!

I'm itching to try Hoodloader 2 on my new "Uno" clone though! 👍

Reading your recommendation Nico (https://www.beyondlogic.org/usbnutshell/) which is a great reference for the terrified, I'm starting to think this is a USB2/3 issue in the underlying HID software/firmware. I don't have a Windows 10 box with USB2 handy, but I have one I can test this on. Be nice to rule that out as our suspect!

I'm not sure what we're talking about here. In my experience, commenting out 2 instances of SingleGamepad and using the two remaining ones together with BootKeyboard works perfectly on Linux. Both gamepads and the keyboard will work fine. I haven't had the occasion to test on Windows, but I surely will.

On a side note, the initialization order doesn't seem to change anything: even calling BootKeyboard.begin () before Gamepad1.begin() will still occupy all the 4 endpoints with Gamepad instances.

It works fine on Linux - that's the issue, it fails on Windows - and it doesn't look like Nico's code that's the problem, I think it's in the underlying Arduino HID implementation which hasn't been touched in years.

Per Nico's suggestion, I've switched to a different library, which doesn't use the Arduino base a works flawlessly across several different boards.

Understood, but I'll try to make a test to confirm this.

What library did you switch to, @marcdraco?

Dean Camera's LUFA - although I've also switched from an Arduino to a Teensy - I think on Dean's suggestion. It's primarily because I had a spare Teensy 3.1 in stock gathering dust (soldered to a board) and I figured... how hard can it be (to remove). Harder than I thought. Dang those SMT chips are close to the header soldering!

I think (and I could be wrong - I'm working on something else right now - that the Teensy also uses LUFA). It's a very well-used library with commercial and free tiers. I don't blame Nico for dropping out of this - it's all fine and well debugging our own code but when a library we rely on has issues we end up taking the heat for something that's (often) outside of our control. USB is stupidly complex so it's something I'm happy to leave to experts and just hope it sends/returns what I expect it to.

I have 4 gamepads, bootmouse, and bootkeyboard on Windows 10 and Linux. I have not done much testing though.

The trick is to remove the Arduino CDC ACM port (frees 3 endpoints) and clean up the device class, subclass, and protocol fields. The IDE auto upload will not work without the CDC ACM port but double clicking the reset button before uploading works.

#include "HID-Project.h"

void setup()
{
  Gamepad1.begin();
  Gamepad2.begin();
  Gamepad3.begin();
  Gamepad4.begin();
  BootKeyboard.begin();
  BootMouse.begin();
}

void loop()
{
}

All my testing has been done with an Adafruit M0 board. Patches to the Adafruit SAMD board files are required. Also use version 1.5.14. Higher versions do not work for me.

Patches in diff format to disable Arduino CDC ACM port and fix device descriptor.

diff --git a/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBCore.cpp b/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBCore.cpp
index 2154fe5..6f2cef6 100644
--- a/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBCore.cpp
+++ b/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBCore.cpp
@@ -204,7 +204,7 @@ bool USBDeviceClass::sendDescriptor(USBSetup &setup)
 {
 	uint8_t t = setup.wValueH;
 	uint8_t desc_length = 0;
-	bool _cdcComposite;
+	bool _cdcComposite = false;
 	int ret;
 	const uint8_t *desc_addr = 0;
 
@@ -222,8 +222,12 @@ bool USBDeviceClass::sendDescriptor(USBSetup &setup)
 
 	if (t == USB_DEVICE_DESCRIPTOR_TYPE)
 	{
+#ifdef CDC_ENABLED
 		if (setup.wLength == 8)
 			_cdcComposite = 1;
+#endif
 
 		desc_addr = _cdcComposite ?  (const uint8_t*)&USB_DeviceDescriptorB : (const uint8_t*)&USB_DeviceDescriptor;
 
diff --git a/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBDesc.h b/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBDesc.h
index 1962be3..eb3e82f 100644
--- a/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBDesc.h
+++ b/packages/adafruit/hardware/samd/1.5.14/cores/arduino/USB/USBDesc.h
@@ -20,7 +20,7 @@
 #define __USBDESC_H__
 
 // CDC or HID can be enabled together.
-#define CDC_ENABLED
+//#define CDC_ENABLED
 #define PLUGGABLE_USB_ENABLED
 
 #ifdef CDC_ENABLED

Class, subclass, protocol before patches.

  bDeviceClass          239 Miscellaneous Device
  bDeviceSubClass         2 ?
  bDeviceProtocol         1 Interface Association

After patches.

  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 

Nice! Have you filed a Pull Request for those? I'm still very much splodging around after a >30 year (!) hiatus from any serious programming and a lot of this is new... Y'know long before Stroustrup dreamed up C++ and even then, I was a middle-road 6502/6809/Z80 assembly-language and BASIC (ugh) programmer! Oh, the shame... but BASIC was all we had for high-level stuff on some of those machines.

None of the changes are in the the HID library. The changes are in the Adafruit SAMD board package but they cannot be made permanent. Doing so would remove the CDC ACM port on all Adafruit SAMD boards. Use the Arduino IDE portable feature to create a sandboxed build environment for these kinds of changes. https://www.arduino.cc/en/Guide/PortableIDE

I forgot to mention if using SAMD boards, use the latest version of the HID library from github. The last release does not include the latest SAMD board fixes.

Actually the HID library is working pretty well here. I'm typing this message on a Commodore 16 keyboard connected via USB to a Dell Laptop running Win10Pro through a custom ATmega32U4 board. Two joysticks are connected at the same time, they have been detected and are working perfectly. HID serial is in use as well.

Yes, I do not see any problems with the HID library. Removing the USB serial is optional. Very handy if you want to want to free up endpoints for more devices.

Ok, I was mainly referring to @marcdraco's comment above.

The only mod i did was commenting out SingleGamepad3 and 4. So I'm using SingleGamepad1, 2 and a multi-report combination of Keyboard, Consumer and System. Everything seems to work fine on both Linux and Windows.

Well I'm totally confused but I'm dug in like a tick developing something different for the Arduino Uno so I'm off the map right now. Hopefully, I'll get back to this and try these changes.