Skip to content
This repository was archived by the owner on May 18, 2021. It is now read-only.

Added HID Gamepad hat support #20

Merged
merged 8 commits into from
Feb 5, 2021
Merged

Added HID Gamepad hat support #20

merged 8 commits into from
Feb 5, 2021

Conversation

nekuneko
Copy link
Contributor

@nekuneko nekuneko commented Jan 31, 2021

Changes needed to add basic gamepad support. See PR #622 from adafruit/Adafruit_nRF52_Arduino repo.

@nekuneko nekuneko changed the title Added Gamepad support Added HID Gamepad hat support Feb 3, 2021
Copy link
Member

@hathach hathach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superb !! Thank you very much for this PR. People is gonna to love this. The hat/Dpad with predefined buttons are genius. However, you need to change the descriptor to HID_PHYSICAL_MAX_N ( 315 ,2) since 1 byte is not enough to describe the 315 value.

GAMEPAD_BUTTON_MODE = TU_BIT(12), ///< Mode button
GAMEPAD_BUTTON_THUMBL = TU_BIT(13), ///< L3 button
GAMEPAD_BUTTON_THUMBR = TU_BIT(14), ///< R3 button
GAMEPAD_BUTTON_ = TU_BIT(15), ///< Undefined button
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could remove GAMEPAD_BUTTON_ undefined button, this can be used by user as custom as they want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's keep it commented to inform the user the TU_BIT(15) is available if needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is even better

@hathach
Copy link
Member

hathach commented Feb 4, 2021

For reference, I just pulled out my PS4 controller and here is its descriptor. Seem like they use hat with unit of degree for analog pad. To be honest, I am not too family with gamepad. I am glad to receive this PR, this will help lots of other people if we could get it work out of the box with most OS.

I also found the linux gamepad specification as well, which is very useful https://www.kernel.org/doc/html/latest/input/gamepad.html

Usage Page (Desktop),               ; Generic desktop controls (01h)
Usage (Gamepad),                    ; Gamepad (05h, application collection)
Collection (Application),
    Report ID (1),
    Usage (X),                      ; X (30h, dynamic value)
    Usage (Y),                      ; Y (31h, dynamic value)
    Usage (Z),                      ; Z (32h, dynamic value)
    Usage (Rz),                     ; Rz (35h, dynamic value)
    Logical Minimum (0),
    Logical Maximum (255),
    Report Size (8),
    Report Count (4),
    Input (Variable),
    Usage (Hat Switch),             ; Hat switch (39h, dynamic value)
    Logical Minimum (0),
    Logical Maximum (7),
    Physical Minimum (0),
    Physical Maximum (315),
    Unit (Degrees),
    Report Size (4),
    Report Count (1),
    Input (Variable, Null State),
    Unit,
    Usage Page (Button),            ; Button (09h)
    Usage Minimum (01h),
    Usage Maximum (0Eh),
    Logical Minimum (0),
    Logical Maximum (1),
    Report Size (1),
    Report Count (14),
    Input (Variable),
    Usage Page (FF00h),             ; FF00h, vendor-defined
    Usage (20h),
    Report Size (6),
    Report Count (1),
    Logical Minimum (0),
    Logical Maximum (127),
    Input (Variable),
    Usage Page (Desktop),           ; Generic desktop controls (01h)
    Usage (Rx),                     ; Rx (33h, dynamic value)
    Usage (Ry),                     ; Ry (34h, dynamic value)
    Logical Minimum (0),
    Logical Maximum (255),
    Report Size (8),
    Report Count (2),
    Input (Variable),
    Usage Page (FF00h),             ; FF00h, vendor-defined
    --- lots of Sony vendor feature ----
End Collection

@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 4, 2021

@hathach Do you think it would better to follow the PS4 Gamepad HID descriptor and adding the missing Rx and Ry variables? If so I will do it. I don't have many idea about HID descriptors and bluetooth but I make this work following the mouse example already written. xD

I think it could be interesting to add aswell the battery status descriptor, but I don't know how to do it right now.
Thanks a lot for your feedback :D

By the way, which software did you used for extracting the PS4 descriptor? It looks very cool.

@hathach
Copy link
Member

hathach commented Feb 4, 2021

@hathach Do you think it would better to follow the PS4 Gamepad HID descriptor and adding the missing Rx and Ry variables? If so I will do it.

To be honest, I don't use gampepad much and don't fully understand the usage of Rx and Ry. If they are common used then we can surely add it.

I think it could be interesing to add aswell the battery status descriptor, but I don't know how to do it right now.
Thanks a lot for your feedback :D

By the way, which software did you used for extracting the PS4 descriptor? It looks very cool.

HID control page (0x06) does have and battery strength. Though seem like PS4 just use its own vendor defined feature. Though HID usage is rather complicated. It may need a bit of testing, and checking to see how other implement battery status.

image

LED page does has low battery indicator, but it doesn't show the percentage like the above battery strength but look like much easier and could be found more common on consumer device to reverse engineer.

image

Though I think we could leave this battery for another PR I guess.

@hathach
Copy link
Member

hathach commented Feb 4, 2021

I just pulled out another generic/cheap gamepad that laying around just for the reference. They all seem to have X, Y, Z, Rz, Hat before buttons. Maybe it is a good idea to change ours to match the std as well. We could just add Rx, Ry as well, after buttons (again seem to be common layout for gamepad).

Usage Page (Desktop),                   ; Generic desktop controls (01h)
Usage (Joystick),                       ; Joystick (04h, application collection)
Collection (Application),
    Collection (Logical),
        Report Size (8),
        Report Count (5),
        Logical Minimum (0),
        Logical Maximum (255),
        Physical Minimum (0),
        Physical Maximum (255),
        Usage (X),                      ; X (30h, dynamic value)
        Usage (Y),                      ; Y (31h, dynamic value)
        Usage (Z),                      ; Z (32h, dynamic value)
        Usage (Z),                      ; Z (32h, dynamic value)
        Usage (Rz),                     ; Rz (35h, dynamic value)
        Input (Variable),
        Report Size (4),
        Report Count (1),
        Logical Maximum (7),
        Physical Maximum (315),
        Unit (Degrees),
        Usage (Hat Switch),             ; Hat switch (39h, dynamic value)
        Input (Variable, Null State),
        Unit,
        Report Size (1),
        Report Count (12),
        Logical Maximum (1),
        Physical Maximum (1),
        Usage Page (Button),            ; Button (09h)
        Usage Minimum (01h),
        Usage Maximum (0Ch),
        Input (Variable),
        Usage Page (FF00h),             ; FF00h, vendor-defined
        Report Size (1),
        Report Count (8),
        Logical Maximum (1),
        Physical Maximum (1),
        Usage (01h),
        Input (Variable),
    End Collection,
    Collection (Logical),
        Report Size (8),
        Report Count (7),
        Physical Maximum (255),
        Logical Maximum (255),
        Usage (02h),
        Output (Variable),
    End Collection,
End Collection

@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 4, 2021

You are aweseome hathach, thank you very much for all that information. I will modify all to add the rX and rY support and match the "standard" PS4 report. I'm agree to left the battery level feature for another PR.

@hathach
Copy link
Member

hathach commented Feb 5, 2021

I have done a bit of testing with my PS4 controller:

  • 1st stick is X, Y
  • 2nd stick is for Z and RZ
  • LT2 is both RY and trigger
  • RT2 is both RY and trigger

combined Microsoft Xbox controller here that is organized as X,Y, RX, RY, Z, RZ, Hat, Button. I think it make more sense for us to change the layout. I much prefer the layout of PS4 since it is more popular (at least where I live). I will make the changes myself :)

image

https://stackoverflow.com/questions/56432430/xbox-one-s-wireless-controller-possibly-invalid-hid-descriptor

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x05,        // Usage (Game Pad)
0xA1, 0x01,        // Collection (Application)
0x85, 0x01,        //   Report ID (1)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00,  //     Logical Maximum (65534)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x33,        //     Usage (Rx)
0x09, 0x34,        //     Usage (Ry)
0x15, 0x00,        //     Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00,  //     Logical Maximum (65534)
0x95, 0x02,        //     Report Count (2)
0x75, 0x10,        //     Report Size (16)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x32,        //   Usage (Z)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
0x95, 0x01,        //   Report Count (1)
0x75, 0x0A,        //   Report Size (10)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x35,        //   Usage (Rz)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
0x95, 0x01,        //   Report Count (1)
0x75, 0x0A,        //   Report Size (10)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x39,        //   Usage (Hat switch)
0x15, 0x01,        //   Logical Minimum (1)
0x25, 0x08,        //   Logical Maximum (8)
0x35, 0x00,        //   Physical Minimum (0)
0x46, 0x3B, 0x01,  //   Physical Maximum (315)
0x66, 0x14, 0x00,  //   Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x81, 0x42,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x75, 0x04,        //   Report Size (4)
0x95, 0x01,        //   Report Count (1)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x35, 0x00,        //   Physical Minimum (0)
0x45, 0x00,        //   Physical Maximum (0)
0x65, 0x00,        //   Unit (None)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09,        //   Usage Page (Button)
0x19, 0x01,        //   Usage Minimum (0x01)
0x29, 0x0A,        //   Usage Maximum (0x0A)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x0A,        //   Report Count (10)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x00,        //   Logical Maximum (0)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x80,        //   Usage (Sys Control)
0x85, 0x02,        //   Report ID (2)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x85,        //     Usage (Sys Main Menu)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x95, 0x01,        //     Report Count (1)
0x75, 0x01,        //     Report Size (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x75, 0x07,        //     Report Size (7)
0x95, 0x01,        //     Report Count (1)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x05, 0x0F,        //   Usage Page (PID Page)
0x09, 0x21,        //   Usage (0x21)
0x85, 0x03,        //   Report ID (3)
0xA1, 0x02,        //   Collection (Logical)
0x09, 0x97,        //     Usage (0x97)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x75, 0x04,        //     Report Size (4)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x00,        //     Logical Maximum (0)
0x75, 0x04,        //     Report Size (4)
0x95, 0x01,        //     Report Count (1)
0x91, 0x03,        //     Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x70,        //     Usage (0x70)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x64,        //     Logical Maximum (100)
0x75, 0x08,        //     Report Size (8)
0x95, 0x04,        //     Report Count (4)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0x50,        //     Usage (0x50)
0x66, 0x01, 0x10,  //     Unit (System: SI Linear, Time: Seconds)
0x55, 0x0E,        //     Unit Exponent (-2)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x09, 0xA7,        //     Usage (0xA7)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x65, 0x00,        //     Unit (None)
0x55, 0x00,        //     Unit Exponent (0)
0x09, 0x7C,        //     Usage (0x7C)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x01,        //     Report Count (1)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              //   End Collection
0x85, 0x04,        //   Report ID (4)
0x05, 0x06,        //   Usage Page (Generic Dev Ctrls)
0x09, 0x20,        //   Usage (Battery Strength)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0xFF, 0x00,  //   Logical Maximum (255)
0x75, 0x08,        //   Report Size (8)
0x95, 0x01,        //   Report Count (1)
0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection

@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 5, 2021

I have done a bit of testing with my PS4 controller:

* 1st stick is X, Y

* 2nd stick is for Z and RZ

* LT2 is both RY and trigger

* RT2 is both RY and trigger

combined Microsoft Xbox controller here that is organized as X,Y, RX, RY, Z, RZ, Hat, Button. I think it make more sense for us to change the layout. I much prefer the layout of PS4 since it is more popular (at least where I live). I will make the changes myself :)

The hid reports' order doesn't matters, it's the same to have Buttons, X, Y, Z, RX, RY, RZ, Hat and X,Y, RX, RY, Z, RZ, Hat, Button. What changes is the representation, as PS4 uses uint8_t for X, Y, Z meanwhile Linux/Windows uses int8_t. That's may why we need parsers or external drivers in order to use de PS4/Xbox Controllers on PC. I preferred do it as is to simplify the code because PS4 / Xbox splits the Rx / Ry from the X, Y, in different collection descriptors. Meanwhile the ble controller and the examples works I'm happy with the order you choose. xD

From the reports you present here I extract the following representation:

PS4 Controller representation
uint8_t X, Y, Z, Rz    (0, 255)
uint8_t hat            (0, 7)
uint816_t buttons      (1, 15)
uint8_t Rx, Ry         (0, 255)
Xbox Controller representation
uint16_t X, Y    (0, 65534)
uint16_t Rx, Ry  (0, 65534)
uint16_t Z       (0, 1023)
uint16_t Rz      (0, 1023)
uint8_t  hat     (1, 8)
uint16_t button  (1, 10)
Generic Joystick
uint8_t X, Y, Z, Z, Rz (0, 255)
uint8_t hat            (?, 7)
uint16_t button        (1, 12)
Current Adafruit BLE Controller representation
uint16_t button               (1, 16)
int8_t X, Y, Z, Rx, Ry, Rz    (-127, 127)
uint8_t  hat                  (1, 8)

ESP32-BLE-Gamepad (https://github.com/lemmingDev/ESP32-BLE-Gamepad/blob/master/BleGamepad.cpp)
uint64_t button                (1, 64)
int16_t X, Y, Z, Rz            (-32767, 32767)
uint16_t Rx, Ry                (0, 65535)
uint16_t Slider1, Slider2      (0, 65535)
uint8_t hat4, hat3, hat2, hat1 (1, 8)

@hathach
Copy link
Member

hathach commented Feb 5, 2021

The hid reports' order doesn't matters, it's the same to have Buttons, X, Y, Z, RX, RY, RZ, Hat and X,Y, RX, RY, Z, RZ, Hat, Button. What changes is the representation, as PS4 uses uint8_t for X, Y, Z meanwhile Linux/Windows uses int8_t. That's may why we need parsers or external drivers in order to use de PS4/Xbox Controllers on PC. I preferred do it as is to simplify the code because PS4 / Xbox splits the Rx / Ry from the X, Y, in different collection descriptors. Meanwhile the ble controller and the examples works I'm happy with the order you choose. xD

Thanks for the sum up, yeah, I know the field order is not important and is defined by hid descriptors. Though I still want to stick with most common format. All x,y,z and its rotate along with hat is mostly fixed, button has potential to grow bigger, therefore moving it to last make sense for me. Also x,y,z,rx seem to stick to each other. So my intended order would be

int8_t X, Y, Z, Rz  (-127, 127)
int8_t Rx, Ry        (-127, 127)    
uint8_t  hat           (1, 8)
uint16_t button     (1, 16)

@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 5, 2021

Ok, let's do it.

@hathach
Copy link
Member

hathach commented Feb 5, 2021

Ok, let's do it.

wow, look like I push a bit late than you. was doing the same thing :D. I do a merge and push to add the D-pad explanation from linux site, which I found very useful for new user, Hope you don't mind :)

Copy link
Member

@hathach hathach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for this great PR, it will definitely helps lots of user with gamepad usage for both USB and BLE.

@hathach hathach merged commit 3e907b3 into adafruit:master Feb 5, 2021
@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 5, 2021

Ok, let's do it.

wow, look like I push a bit late than you. was doing the same thing :D. I do a merge and push to add the D-pad explanation from linux site, which I found very useful for new user, Hope you don't mind :)

yes ahahahahaa thank you for adding the extra information

@hathach
Copy link
Member

hathach commented Feb 5, 2021

@nekuneko This got merged to the core, though actually this repo tinyusb/src folder is a local mirror copy of the main tinyusb repo here https://github.com/hathach/tinyusbo. This PR should be also made toward the main stack repo to help other user on other platform (esp32s2, pico etc..) as well. Would you mind doing a PR with this changes so that you got into the contributor list as well. If you don't have time, no problem at all, I will do the PR, and reference to this as well. There is actually more people following main repo, that could provide helpful comment as well.

Note: the local copy here is a bit older than the main repo.

@nekuneko
Copy link
Contributor Author

nekuneko commented Feb 5, 2021

It's ok @hathach, if you can make the changes faster than me, just do it. :) Please don't forget to review adafruit/Adafruit_nRF52_Arduino#622 and adafruit/Adafruit_TinyUSB_Arduino#72 PR.

@hathach
Copy link
Member

hathach commented Feb 5, 2021

It's ok @hathach, if you can make the changes faster than me, just do it. :) Please don't forget to review adafruit/Adafruit_nRF52_Arduino#622 and adafruit/Adafruit_TinyUSB_Arduino#72 PR.

Ok, thanks. Those are next PRs I will review 👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants