earlephilhower / arduino-pico

Raspberry Pi Pico Arduino core, for all RP2040 and RP2350 boards
GNU Lesser General Public License v2.1
2.03k stars 422 forks source link

Increase HID Joystick Polling Rate #1769

Closed joshhead closed 1 year ago

joshhead commented 1 year ago

I am trying to add support to RP2040 for an Arduino project that is commonly run on Teensy and Pro Micro boards. It's a gamepad firmware meant to run with 1000hz polling and allow configuration over a serial port. I was able to get it running on a Pi Pico using Arduino-Pico with only minor changes, but I had some performance issues. I determined that Joystick.send_now() was blocking the main loop for around 8ms at a time, limiting the polling rate to 125hz and also causing problems with responsiveness if I tried to call it too often. I was able to work around some of the issues by checking tid_hid_ready() before calling Joystick.send_now(), but the polling rate was still capped.

After some digging I found where the polling rate is specified in the descriptor, and after decreasing it from a 10ms requested polling interval to 1ms, Joystick.send_now() was now only blocking for 10-20ms or so a majority of the time, but sometimes over 2000ms. I found there is also a loop that delays for 1ms per iteration if USB is not ready. After decreasing the delay from 1 millisecond to 1 microsecond, everything ran smoothly for me. I don't know if 1 microsecond is actually the right number to pick here, but I think it can be less than 1ms.

Would it be possible to decrease the HID polling rate to 1ms? The changes I made to my local copy are included below..

diff --git a/cores/rp2040/RP2040USB.cpp b/cores/rp2040/RP2040USB.cpp
index a0811c5..58d12e3 100644
--- a/cores/rp2040/RP2040USB.cpp
+++ b/cores/rp2040/RP2040USB.cpp
@@ -263,7 +263,7 @@ void __SetupUSBDescriptor() {
         uint8_t hid_itf = __USBInstallSerial ? 2 : 0;
         uint8_t hid_desc[TUD_HID_DESC_LEN] = {
             // Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
-            TUD_HID_DESCRIPTOR(hid_itf, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10)
+            TUD_HID_DESCRIPTOR(hid_itf, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 1)
         };

         uint8_t msd_itf = interface_count - 1;
@@ -400,7 +400,7 @@ bool __USBHIDReady() {

     while (((millis() - start) < timeout) && tud_ready() && !tud_hid_ready()) {
         tud_task();
-        delay(1);
+        delayMicroseconds(1);
     }
     return tud_hid_ready();
 }
earlephilhower commented 1 year ago

The delayMicroseconds bit seems fine, but I'm concerned about upping the refresh rate to 1khz for all USB HIDs. Since USB is all software controlled on the Pico that means 10x the CPU cycles spend on USB alone.

Let me see if we can add a weak variable individual apps like yours could override. Same idea as the separate 8k stack for core1...

earlephilhower commented 1 year ago

@joshhead give #1771 a try and report back, please. Just add int usb_hid_poll_interval = 1; to your main app and it should automatically adjust the rate for you.

joshhead commented 1 year ago

@earlephilhower Thank you, this looks great. I will try it out as soon as I can, it might be a day or two until I have enough time to sit down and test it properly.

joshhead commented 1 year ago

@earlephilhower I tried the usmee branch and everything seems to be working as intended. I am able to adjust the polling rate by setting usb_hid_poll_interval to the desired interval in my sketch. Seemingly the host (a Windows 11 PC) only wants to poll in power-of-2 intervals. With a request for 10ms, for example, I'm able to successfully complete Joystick.send_now() approximately every 8ms, or 32ms when I request 50ms polling. Regardless, 1ms works fine!