feat(class/gamepad): add gamepad device
Signed-off-by: sakumisu <1203593632@qq.com>
This commit is contained in:
@@ -10,6 +10,7 @@ if(BL_SDK_BASE)
|
|||||||
set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
|
set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
|
||||||
set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
|
set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
|
||||||
set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
|
set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
|
||||||
|
set(CONFIG_CHERRYUSB_DEVICE_GAMEPAD 1)
|
||||||
|
|
||||||
set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
|
set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
|
||||||
set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)
|
set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)
|
||||||
@@ -182,6 +183,7 @@ elseif(HPM_SDK_BASE)
|
|||||||
set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
|
set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
|
||||||
set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
|
set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
|
||||||
set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
|
set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
|
||||||
|
set(CONFIG_CHERRYUSB_DEVICE_GAMEPAD 1)
|
||||||
|
|
||||||
set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
|
set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
|
||||||
set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)
|
set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ list(
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/class/vendor/net
|
${CMAKE_CURRENT_LIST_DIR}/class/vendor/net
|
||||||
${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi
|
${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi
|
||||||
${CMAKE_CURRENT_LIST_DIR}/class/aoa
|
${CMAKE_CURRENT_LIST_DIR}/class/aoa
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/class/gamepad
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CONFIG_CHERRYUSB_DEVICE)
|
if(CONFIG_CHERRYUSB_DEVICE)
|
||||||
@@ -85,6 +86,9 @@ if(CONFIG_CHERRYUSB_DEVICE)
|
|||||||
if(CONFIG_CHERRYUSB_DEVICE_ADB)
|
if(CONFIG_CHERRYUSB_DEVICE_ADB)
|
||||||
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/adb/usbd_adb.c)
|
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/adb/usbd_adb.c)
|
||||||
endif()
|
endif()
|
||||||
|
if(CONFIG_CHERRYUSB_DEVICE_GAMEPAD)
|
||||||
|
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/gamepad/usbd_gamepad.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CONFIG_CHERRYUSB_DEVICE_FSDEV_ST)
|
if(CONFIG_CHERRYUSB_DEVICE_FSDEV_ST)
|
||||||
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/fsdev/usb_dc_fsdev.c)
|
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/fsdev/usb_dc_fsdev.c)
|
||||||
|
|||||||
224
class/gamepad/usb_gamepad.h
Normal file
224
class/gamepad/usb_gamepad.h
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026, sakumisu
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#ifndef USB_GAMEPAD_H
|
||||||
|
#define USB_GAMEPAD_H
|
||||||
|
|
||||||
|
#include "usb_hid.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GAMEPAD BUTTON LAYOUT
|
||||||
|
*
|
||||||
|
* ____________________________ __
|
||||||
|
* / [__L2__] [__R2__] \ |
|
||||||
|
* / [__ L1 __] [__ R1 __] \ | Triggers
|
||||||
|
* __/________________________________\__ __|
|
||||||
|
* / _ \ |
|
||||||
|
* / /\ __ (B4) \ |
|
||||||
|
* / || __ |A1| __ _ _ \ | Main Pad
|
||||||
|
* | <===DP===> |S1| |S2| (B3) -|- (B2)| |
|
||||||
|
* \ || ¯¯ ¯¯ _ / |
|
||||||
|
* /\ \/ / \ / \ (B1) /\ __|
|
||||||
|
* / \________ | LS | ____ | RS | _______/ \ |
|
||||||
|
* | / \ \___/ / \ \___/ / \ | | Sticks
|
||||||
|
* | / \_____/ \_____/ \ | __|
|
||||||
|
* | / L3 R3 \ |
|
||||||
|
* \_____/ \_____/
|
||||||
|
*
|
||||||
|
* |________|______| |______|___________|
|
||||||
|
* D-Pad Left Right Face
|
||||||
|
* Stick Stick Buttons
|
||||||
|
*
|
||||||
|
* Extended: A2=Touchpad/Capture A3=Mute L4/R4=Paddles
|
||||||
|
*/
|
||||||
|
|
||||||
|
// W3C Gamepad API standard button order
|
||||||
|
// Bit position = W3C button index (trivial conversion: 1 << index)
|
||||||
|
//
|
||||||
|
// Gamepad XInput Switch PS3/4/5 DInput
|
||||||
|
// ------ ------ ------ ------- ------
|
||||||
|
|
||||||
|
// Face buttons (right cluster)
|
||||||
|
#define USB_GAMEPAD_BUTTON_B1 (1 << 0) // A B Cross 2
|
||||||
|
#define USB_GAMEPAD_BUTTON_B2 (1 << 1) // B A Circle 3
|
||||||
|
#define USB_GAMEPAD_BUTTON_B3 (1 << 2) // X Y Square 1
|
||||||
|
#define USB_GAMEPAD_BUTTON_B4 (1 << 3) // Y X Triangle 4
|
||||||
|
|
||||||
|
// Shoulder buttons
|
||||||
|
#define USB_GAMEPAD_BUTTON_L1 (1 << 4) // LB L L1 5
|
||||||
|
#define USB_GAMEPAD_BUTTON_R1 (1 << 5) // RB R R1 6
|
||||||
|
#define USB_GAMEPAD_BUTTON_L2 (1 << 6) // LT ZL L2 7
|
||||||
|
#define USB_GAMEPAD_BUTTON_R2 (1 << 7) // RT ZR R2 8
|
||||||
|
|
||||||
|
// Center cluster
|
||||||
|
#define USB_GAMEPAD_BUTTON_S1 (1 << 8) // Back - Select 9
|
||||||
|
#define USB_GAMEPAD_BUTTON_S2 (1 << 9) // Start + Start 10
|
||||||
|
|
||||||
|
// Stick clicks
|
||||||
|
#define USB_GAMEPAD_BUTTON_L3 (1 << 10) // LS LS L3 11
|
||||||
|
#define USB_GAMEPAD_BUTTON_R3 (1 << 11) // RS RS R3 12
|
||||||
|
|
||||||
|
// D-pad
|
||||||
|
#define USB_GAMEPAD_BUTTON_DU (1 << 12) // D-Up D-Up D-Up Hat
|
||||||
|
#define USB_GAMEPAD_BUTTON_DD (1 << 13) // D-Down D-Down D-Down Hat
|
||||||
|
#define USB_GAMEPAD_BUTTON_DL (1 << 14) // D-Left D-Left D-Left Hat
|
||||||
|
#define USB_GAMEPAD_BUTTON_DR (1 << 15) // D-Right D-Right D-Right Hat
|
||||||
|
|
||||||
|
// Auxiliary
|
||||||
|
#define USB_GAMEPAD_BUTTON_A1 (1 << 16) // Guide Home PS 13
|
||||||
|
#define USB_GAMEPAD_BUTTON_A2 (1 << 17) // - Capture Touchpad 14
|
||||||
|
#define USB_GAMEPAD_BUTTON_A3 (1 << 18) // - - Mute -
|
||||||
|
#define USB_GAMEPAD_BUTTON_A4 (1 << 19) // - - - -
|
||||||
|
|
||||||
|
// Paddles (extended)
|
||||||
|
#define USB_GAMEPAD_BUTTON_L4 (1 << 20) // P1 - - -
|
||||||
|
#define USB_GAMEPAD_BUTTON_R4 (1 << 21) // P2 - - -
|
||||||
|
|
||||||
|
#define XINPUT_VID 0x045E // Microsoft
|
||||||
|
#define XINPUT_PID 0x028E // Xbox 360 Controller
|
||||||
|
#define XINPUT_BCD_DEVICE 0x0114 // v1.14
|
||||||
|
|
||||||
|
/* XInput (Xbox 360) USB */
|
||||||
|
|
||||||
|
// XInput Interface Class/Subclass/Protocol
|
||||||
|
#define XINPUT_INTERFACE_CLASS 0xFF
|
||||||
|
#define XINPUT_INTERFACE_SUBCLASS 0x5D
|
||||||
|
#define XINPUT_INTERFACE_PROTOCOL 0x01
|
||||||
|
|
||||||
|
#define XINPUT_BUTTON_MASK_UP (1U << 0)
|
||||||
|
#define XINPUT_BUTTON_MASK_DOWN (1U << 1)
|
||||||
|
#define XINPUT_BUTTON_MASK_LEFT (1U << 2)
|
||||||
|
#define XINPUT_BUTTON_MASK_RIGHT (1U << 3)
|
||||||
|
#define XINPUT_BUTTON_MASK_START (1U << 4)
|
||||||
|
#define XINPUT_BUTTON_MASK_BACK (1U << 5)
|
||||||
|
#define XINPUT_BUTTON_MASK_L3 (1U << 6)
|
||||||
|
#define XINPUT_BUTTON_MASK_R3 (1U << 7)
|
||||||
|
#define XINPUT_BUTTON_MASK_LB (1U << 8)
|
||||||
|
#define XINPUT_BUTTON_MASK_RB (1U << 9)
|
||||||
|
#define XINPUT_BUTTON_MASK_GUIDE (1U << 10)
|
||||||
|
//#define XINPUT_BUTTON_MASK_UNUSED (1U << 11)
|
||||||
|
#define XINPUT_BUTTON_MASK_A (1U << 12)
|
||||||
|
#define XINPUT_BUTTON_MASK_B (1U << 13)
|
||||||
|
#define XINPUT_BUTTON_MASK_X (1U << 14)
|
||||||
|
#define XINPUT_BUTTON_MASK_Y (1U << 15)
|
||||||
|
|
||||||
|
// LED patterns for report_id 0x01
|
||||||
|
#define XINPUT_LED_OFF 0x00
|
||||||
|
#define XINPUT_LED_BLINK 0x01
|
||||||
|
#define XINPUT_LED_FLASH_1 0x02
|
||||||
|
#define XINPUT_LED_FLASH_2 0x03
|
||||||
|
#define XINPUT_LED_FLASH_3 0x04
|
||||||
|
#define XINPUT_LED_FLASH_4 0x05
|
||||||
|
#define XINPUT_LED_ON_1 0x06
|
||||||
|
#define XINPUT_LED_ON_2 0x07
|
||||||
|
#define XINPUT_LED_ON_3 0x08
|
||||||
|
#define XINPUT_LED_ON_4 0x09
|
||||||
|
#define XINPUT_LED_ROTATE 0x0A
|
||||||
|
#define XINPUT_LED_BLINK_SLOW 0x0B
|
||||||
|
#define XINPUT_LED_BLINK_SLOW_1 0x0C
|
||||||
|
#define XINPUT_LED_BLINK_SLOW_2 0x0D
|
||||||
|
|
||||||
|
struct xinput_in_report {
|
||||||
|
uint8_t report_id; /* Always 0x00 */
|
||||||
|
uint8_t report_size; /* Always 0x14 (20) */
|
||||||
|
uint16_t buttons; /* DPAD, Start, Back, L3, R3, LB, RB, Guide, A, B, X, Y */
|
||||||
|
uint8_t lt; /* Left trigger (0-255) */
|
||||||
|
uint8_t rt; /* Right trigger (0-255) */
|
||||||
|
int16_t lx; /* Left stick X (-32768 to 32767) */
|
||||||
|
int16_t ly; /* Left stick Y (-32768 to 32767) */
|
||||||
|
int16_t rx; /* Right stick X (-32768 to 32767) */
|
||||||
|
int16_t ry; /* Right stick Y (-32768 to 32767) */
|
||||||
|
uint8_t reserved[6]; /* Reserved/padding */
|
||||||
|
} __PACKED;
|
||||||
|
|
||||||
|
struct xinput_out_report {
|
||||||
|
uint8_t report_id; // 0x00 = rumble, 0x01 = LED
|
||||||
|
uint8_t report_size; // 0x08
|
||||||
|
uint8_t led; // LED pattern (0x00 for rumble)
|
||||||
|
uint8_t rumble_l; // Left motor (large, 0-255)
|
||||||
|
uint8_t rumble_r; // Right motor (small, 0-255)
|
||||||
|
uint8_t reserved[3]; // Padding
|
||||||
|
} __PACKED;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define XINPUT_DESCRIPTOR_LEN (9 + 16 + 7 + 7)
|
||||||
|
|
||||||
|
#define XINPUT_DESCRIPTOR_INIT(bInterfaceNumber, out_ep, in_ep) \
|
||||||
|
USB_INTERFACE_DESCRIPTOR_INIT(bInterfaceNumber, 0x00, 0x02, 0xff, 0x5d, 0x01, 0x00), /* XInput proprietary descriptor (0x21) */ \
|
||||||
|
16, 0x21, 0x00, 0x01, 0x01, 0x24, in_ep, 0x14, 0x03, 0x00, 0x03, 0x13, out_ep, 0x00, 0x03, 0x00, \
|
||||||
|
USB_ENDPOINT_DESCRIPTOR_INIT(in_ep, 0x03, 32, 0x01), \
|
||||||
|
USB_ENDPOINT_DESCRIPTOR_INIT(out_ep, 0x03, 32, 0x08)
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#define SWITCH_VID 0x0F0D // 0x057E Nintendo Pro Controller
|
||||||
|
#define SWITCH_PID 0x0092 // 0x2009
|
||||||
|
#define SWITCH_BCD_DEVICE 0x0100 // v1.00
|
||||||
|
|
||||||
|
// Button masks (16-bit)
|
||||||
|
#define SWITCH_MASK_Y (1U << 0)
|
||||||
|
#define SWITCH_MASK_B (1U << 1)
|
||||||
|
#define SWITCH_MASK_A (1U << 2)
|
||||||
|
#define SWITCH_MASK_X (1U << 3)
|
||||||
|
#define SWITCH_MASK_L (1U << 4)
|
||||||
|
#define SWITCH_MASK_R (1U << 5)
|
||||||
|
#define SWITCH_MASK_ZL (1U << 6)
|
||||||
|
#define SWITCH_MASK_ZR (1U << 7)
|
||||||
|
#define SWITCH_MASK_MINUS (1U << 8)
|
||||||
|
#define SWITCH_MASK_PLUS (1U << 9)
|
||||||
|
#define SWITCH_MASK_L3 (1U << 10)
|
||||||
|
#define SWITCH_MASK_R3 (1U << 11)
|
||||||
|
#define SWITCH_MASK_HOME (1U << 12)
|
||||||
|
#define SWITCH_MASK_CAPTURE (1U << 13)
|
||||||
|
|
||||||
|
// D-pad / Hat switch values
|
||||||
|
#define SWITCH_HAT_UP 0x00
|
||||||
|
#define SWITCH_HAT_UP_RIGHT 0x01
|
||||||
|
#define SWITCH_HAT_RIGHT 0x02
|
||||||
|
#define SWITCH_HAT_DOWN_RIGHT 0x03
|
||||||
|
#define SWITCH_HAT_DOWN 0x04
|
||||||
|
#define SWITCH_HAT_DOWN_LEFT 0x05
|
||||||
|
#define SWITCH_HAT_LEFT 0x06
|
||||||
|
#define SWITCH_HAT_UP_LEFT 0x07
|
||||||
|
#define SWITCH_HAT_CENTER 0x08
|
||||||
|
|
||||||
|
// Analog stick range
|
||||||
|
#define SWITCH_JOYSTICK_MIN 0x00
|
||||||
|
#define SWITCH_JOYSTICK_MID 0x80
|
||||||
|
#define SWITCH_JOYSTICK_MAX 0xFF
|
||||||
|
|
||||||
|
struct switch_in_report {
|
||||||
|
uint16_t buttons; // 16 button bits
|
||||||
|
uint8_t hat; // D-pad (hat switch, 0-8)
|
||||||
|
uint8_t lx; // Left stick X (0-255, 128 = center)
|
||||||
|
uint8_t ly; // Left stick Y (0-255, 128 = center)
|
||||||
|
uint8_t rx; // Right stick X (0-255, 128 = center)
|
||||||
|
uint8_t ry; // Right stick Y (0-255, 128 = center)
|
||||||
|
uint8_t vendor; // Vendor-specific byte
|
||||||
|
} __PACKED;
|
||||||
|
|
||||||
|
struct switch_out_report {
|
||||||
|
uint8_t data[8]; // Vendor-specific rumble data
|
||||||
|
} __PACKED;
|
||||||
|
|
||||||
|
#define HID_SWITCH_REPORT_DESC_SIZE 86
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define SWITCH_DESCRIPTOR_LEN HID_CUSTOM_INOUT_DESCRIPTOR_LEN
|
||||||
|
|
||||||
|
#define SWITCH_DESCRIPTOR_INIT(bInterfaceNumber, out_ep, in_ep) \
|
||||||
|
HID_CUSTOM_INOUT_DESCRIPTOR_INIT(bInterfaceNumber, 0x00, HID_SWITCH_REPORT_DESC_SIZE, out_ep, in_ep, 64, 0x01)
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
struct usb_gamepad_report {
|
||||||
|
uint32_t buttons;
|
||||||
|
uint8_t lt;
|
||||||
|
uint8_t rt;
|
||||||
|
uint8_t lx;
|
||||||
|
uint8_t ly;
|
||||||
|
uint8_t rx;
|
||||||
|
uint8_t ry;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* USB_GAMEPAD_H */
|
||||||
218
class/gamepad/usbd_gamepad.c
Normal file
218
class/gamepad/usbd_gamepad.c
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026, sakumisu
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include "usbd_core.h"
|
||||||
|
#include "usbd_hid.h"
|
||||||
|
#include "usbd_gamepad.h"
|
||||||
|
|
||||||
|
extern int hid_class_interface_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len);
|
||||||
|
|
||||||
|
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t gamepad_report_buffer[64];
|
||||||
|
|
||||||
|
static int xinput_vendor_class_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
|
||||||
|
{
|
||||||
|
struct xinput_in_report xinput_report;
|
||||||
|
|
||||||
|
memset(&xinput_report, 0, sizeof(xinput_report));
|
||||||
|
xinput_report.report_size = 20;
|
||||||
|
|
||||||
|
memcpy(*data, &xinput_report, sizeof(xinput_report));
|
||||||
|
*len = sizeof(xinput_report);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usbd_gamepad_xinput_send_report(uint8_t ep, struct usb_gamepad_report *report)
|
||||||
|
{
|
||||||
|
struct xinput_in_report *xinput_report;
|
||||||
|
|
||||||
|
xinput_report = (struct xinput_in_report *)gamepad_report_buffer;
|
||||||
|
memset(xinput_report, 0, sizeof(xinput_report));
|
||||||
|
xinput_report->report_size = 20;
|
||||||
|
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_DU)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_UP;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_DD)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_DOWN;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_DL)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_LEFT;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_DR)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_RIGHT;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_S2)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_START;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_S1)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_BACK;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_L3)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_L3;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_R3)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_R3;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_L1)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_LB;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_R1)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_RB;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_A1)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_GUIDE;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B1)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_A;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B2)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_B;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B3)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_X;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B4)
|
||||||
|
xinput_report->buttons |= XINPUT_BUTTON_MASK_Y;
|
||||||
|
|
||||||
|
// Analog triggers (0-255), fall back to digital if analog is 0 but button pressed
|
||||||
|
xinput_report->lt = report->lt;
|
||||||
|
xinput_report->rt = report->rt;
|
||||||
|
if (xinput_report->lt == 0 && (report->buttons & USB_GAMEPAD_BUTTON_L2))
|
||||||
|
xinput_report->lt = 0xFF;
|
||||||
|
if (xinput_report->rt == 0 && (report->buttons & USB_GAMEPAD_BUTTON_R2))
|
||||||
|
xinput_report->rt = 0xFF;
|
||||||
|
|
||||||
|
return usbd_ep_start_write(0, ep, gamepad_report_buffer, sizeof(struct xinput_in_report));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert gamepad dpad mask to switch hat value
|
||||||
|
static uint8_t convert_dpad_to_switch_hat(uint32_t buttons)
|
||||||
|
{
|
||||||
|
// Joypad uses active-high (1 = pressed)
|
||||||
|
uint8_t up = (buttons & USB_GAMEPAD_BUTTON_DU) ? 1 : 0;
|
||||||
|
uint8_t down = (buttons & USB_GAMEPAD_BUTTON_DD) ? 1 : 0;
|
||||||
|
uint8_t left = (buttons & USB_GAMEPAD_BUTTON_DL) ? 1 : 0;
|
||||||
|
uint8_t right = (buttons & USB_GAMEPAD_BUTTON_DR) ? 1 : 0;
|
||||||
|
|
||||||
|
if (up && right)
|
||||||
|
return SWITCH_HAT_UP_RIGHT;
|
||||||
|
if (up && left)
|
||||||
|
return SWITCH_HAT_UP_LEFT;
|
||||||
|
if (down && right)
|
||||||
|
return SWITCH_HAT_DOWN_RIGHT;
|
||||||
|
if (down && left)
|
||||||
|
return SWITCH_HAT_DOWN_LEFT;
|
||||||
|
if (up)
|
||||||
|
return SWITCH_HAT_UP;
|
||||||
|
if (down)
|
||||||
|
return SWITCH_HAT_DOWN;
|
||||||
|
if (left)
|
||||||
|
return SWITCH_HAT_LEFT;
|
||||||
|
if (right)
|
||||||
|
return SWITCH_HAT_RIGHT;
|
||||||
|
|
||||||
|
return SWITCH_HAT_CENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usbd_gamepad_switch_send_report(uint8_t ep, struct usb_gamepad_report *report)
|
||||||
|
{
|
||||||
|
struct switch_in_report *switch_report;
|
||||||
|
|
||||||
|
switch_report = (struct switch_in_report *)gamepad_report_buffer;
|
||||||
|
memset(switch_report, 0, sizeof(switch_report));
|
||||||
|
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_S1)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_MINUS;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_S2)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_PLUS;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_L1)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_L;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_R1)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_R;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_L2)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_ZL;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_R2)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_ZR;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_L3)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_L3;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_R3)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_R3;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_A1)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_HOME;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_A2)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_CAPTURE;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B1)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_B;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B2)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_A;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B3)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_Y;
|
||||||
|
if (report->buttons & USB_GAMEPAD_BUTTON_B4)
|
||||||
|
switch_report->buttons |= SWITCH_MASK_X;
|
||||||
|
|
||||||
|
switch_report->hat = convert_dpad_to_switch_hat(report->buttons);
|
||||||
|
|
||||||
|
// Analog sticks (HID convention: 0=up, 255=down - no inversion needed)
|
||||||
|
switch_report->lx = report->lx;
|
||||||
|
switch_report->ly = report->ly;
|
||||||
|
switch_report->rx = report->rx;
|
||||||
|
switch_report->ry = report->ry;
|
||||||
|
|
||||||
|
switch_report->vendor = 0;
|
||||||
|
|
||||||
|
return usbd_ep_start_write(0, ep, gamepad_report_buffer, sizeof(struct switch_in_report));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct usbd_interface *usbd_gamepad_xinput_init_intf(struct usbd_interface *intf)
|
||||||
|
{
|
||||||
|
intf->class_interface_handler = NULL;
|
||||||
|
intf->class_endpoint_handler = NULL;
|
||||||
|
intf->vendor_handler = xinput_vendor_class_request_handler;
|
||||||
|
intf->notify_handler = NULL;
|
||||||
|
|
||||||
|
return intf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t hid_switch_report_desc[HID_SWITCH_REPORT_DESC_SIZE] = {
|
||||||
|
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||||
|
0x09, 0x05, // Usage (Game Pad)
|
||||||
|
0xA1, 0x01, // Collection (Application)
|
||||||
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
|
0x25, 0x01, // Logical Maximum (1)
|
||||||
|
0x35, 0x00, // Physical Minimum (0)
|
||||||
|
0x45, 0x01, // Physical Maximum (1)
|
||||||
|
0x75, 0x01, // Report Size (1)
|
||||||
|
0x95, 0x10, // Report Count (16)
|
||||||
|
0x05, 0x09, // Usage Page (Button)
|
||||||
|
0x19, 0x01, // Usage Minimum (Button 1)
|
||||||
|
0x29, 0x10, // Usage Maximum (Button 16)
|
||||||
|
0x81, 0x02, // Input (Data,Var,Abs)
|
||||||
|
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||||
|
0x25, 0x07, // Logical Maximum (7)
|
||||||
|
0x46, 0x3B, 0x01, // Physical Maximum (315)
|
||||||
|
0x75, 0x04, // Report Size (4)
|
||||||
|
0x95, 0x01, // Report Count (1)
|
||||||
|
0x65, 0x14, // Unit (Eng Rot:Angular Pos)
|
||||||
|
0x09, 0x39, // Usage (Hat switch)
|
||||||
|
0x81, 0x42, // Input (Data,Var,Abs,Null)
|
||||||
|
0x65, 0x00, // Unit (None)
|
||||||
|
0x95, 0x01, // Report Count (1)
|
||||||
|
0x81, 0x01, // Input (Const) - 4-bit padding
|
||||||
|
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||||
|
0x46, 0xFF, 0x00, // Physical Maximum (255)
|
||||||
|
0x09, 0x30, // Usage (X) - Left Stick X
|
||||||
|
0x09, 0x31, // Usage (Y) - Left Stick Y
|
||||||
|
0x09, 0x32, // Usage (Z) - Right Stick X
|
||||||
|
0x09, 0x35, // Usage (Rz) - Right Stick Y
|
||||||
|
0x75, 0x08, // Report Size (8)
|
||||||
|
0x95, 0x04, // Report Count (4)
|
||||||
|
0x81, 0x02, // Input (Data,Var,Abs)
|
||||||
|
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined)
|
||||||
|
0x09, 0x20, // Usage (0x20)
|
||||||
|
0x95, 0x01, // Report Count (1)
|
||||||
|
0x81, 0x02, // Input (Data,Var,Abs) - Vendor byte
|
||||||
|
0x0A, 0x21, 0x26, // Usage (0x2621)
|
||||||
|
0x95, 0x08, // Report Count (8)
|
||||||
|
0x91, 0x02, // Output (Data,Var,Abs) - Rumble
|
||||||
|
0xC0, // End Collection
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usbd_interface *usbd_gamepad_switch_init_intf(struct usbd_interface *intf)
|
||||||
|
{
|
||||||
|
intf->class_interface_handler = hid_class_interface_request_handler;
|
||||||
|
intf->class_endpoint_handler = NULL;
|
||||||
|
intf->vendor_handler = NULL;
|
||||||
|
intf->notify_handler = NULL;
|
||||||
|
|
||||||
|
intf->hid_report_descriptor = hid_switch_report_desc;
|
||||||
|
intf->hid_report_descriptor_len = HID_SWITCH_REPORT_DESC_SIZE;
|
||||||
|
return intf;
|
||||||
|
}
|
||||||
22
class/gamepad/usbd_gamepad.h
Normal file
22
class/gamepad/usbd_gamepad.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026, sakumisu
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#ifndef USBD_GAMEPAD_H
|
||||||
|
#define USBD_GAMEPAD_H
|
||||||
|
|
||||||
|
#include "usb_gamepad.h"
|
||||||
|
|
||||||
|
#define USBD_GAMEPAD_MODE_XINPUT 0
|
||||||
|
#define USBD_GAMEPAD_MODE_SWITCH 1
|
||||||
|
#define USBD_GAMEPAD_MODE_XBOXONE 2
|
||||||
|
#define USBD_GAMEPAD_MODE_PS4 3
|
||||||
|
|
||||||
|
struct usbd_interface *usbd_gamepad_xinput_init_intf(struct usbd_interface *intf);
|
||||||
|
struct usbd_interface *usbd_gamepad_switch_init_intf(struct usbd_interface *intf);
|
||||||
|
|
||||||
|
int usbd_gamepad_xinput_send_report(uint8_t ep, struct usb_gamepad_report *report);
|
||||||
|
int usbd_gamepad_switch_send_report(uint8_t ep, struct usb_gamepad_report *report);
|
||||||
|
|
||||||
|
#endif /* USBD_GAMEPAD_H */
|
||||||
250
demo/gamepad_template.c
Normal file
250
demo/gamepad_template.c
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026, sakumisu
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include "usbd_core.h"
|
||||||
|
#include "usbd_gamepad.h"
|
||||||
|
|
||||||
|
#define GAMEPAD_IN_EP 0x81
|
||||||
|
#define GAMEPAD_OUT_EP 0x02
|
||||||
|
|
||||||
|
#define USBD_MAX_POWER 500
|
||||||
|
|
||||||
|
static const uint8_t xinput_device_descriptor[] = {
|
||||||
|
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, XINPUT_VID, XINPUT_PID, XINPUT_BCD_DEVICE, 0x01)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t switch_device_descriptor[] = {
|
||||||
|
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, SWITCH_VID, SWITCH_PID, SWITCH_BCD_DEVICE, 0x01)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t xinput_config_descriptor[] = {
|
||||||
|
USB_CONFIG_DESCRIPTOR_INIT((9 + XINPUT_DESCRIPTOR_LEN), 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
|
||||||
|
XINPUT_DESCRIPTOR_INIT(0x00, GAMEPAD_OUT_EP, GAMEPAD_IN_EP)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t switch_config_descriptor[] = {
|
||||||
|
USB_CONFIG_DESCRIPTOR_INIT((9 + SWITCH_DESCRIPTOR_LEN), 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
|
||||||
|
SWITCH_DESCRIPTOR_INIT(0x00, GAMEPAD_OUT_EP, GAMEPAD_IN_EP)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *xinput_string_descriptors[] = {
|
||||||
|
(const char[]){ 0x09, 0x04 }, /* Langid */
|
||||||
|
"Microsoft", /* Manufacturer */
|
||||||
|
"XInput STANDARD GAMEPAD", /* Product */
|
||||||
|
"1.0", /* Serial Number */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *switch_string_descriptors[] = {
|
||||||
|
(const char[]){ 0x09, 0x04 }, /* Langid */
|
||||||
|
"HORI", /* Manufacturer */
|
||||||
|
"Switch Pro Controller", /* Product */
|
||||||
|
"1.0", /* Serial Number */
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t gamepad_mode = USBD_GAMEPAD_MODE_XINPUT;
|
||||||
|
bool gamepad_init_flag = false;
|
||||||
|
|
||||||
|
static const uint8_t *device_descriptor_callback(uint8_t speed)
|
||||||
|
{
|
||||||
|
switch (gamepad_mode) {
|
||||||
|
case USBD_GAMEPAD_MODE_XINPUT:
|
||||||
|
return xinput_device_descriptor;
|
||||||
|
case USBD_GAMEPAD_MODE_SWITCH:
|
||||||
|
return switch_device_descriptor;
|
||||||
|
case USBD_GAMEPAD_MODE_XBOXONE:
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_PS4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t *config_descriptor_callback(uint8_t speed)
|
||||||
|
{
|
||||||
|
switch (gamepad_mode) {
|
||||||
|
case USBD_GAMEPAD_MODE_XINPUT:
|
||||||
|
return xinput_config_descriptor;
|
||||||
|
case USBD_GAMEPAD_MODE_SWITCH:
|
||||||
|
return switch_config_descriptor;
|
||||||
|
case USBD_GAMEPAD_MODE_XBOXONE:
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_PS4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
|
||||||
|
{
|
||||||
|
if (index > 3) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gamepad_mode) {
|
||||||
|
case USBD_GAMEPAD_MODE_XINPUT:
|
||||||
|
return xinput_string_descriptors[index];
|
||||||
|
case USBD_GAMEPAD_MODE_SWITCH:
|
||||||
|
return switch_string_descriptors[index];
|
||||||
|
case USBD_GAMEPAD_MODE_XBOXONE:
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_PS4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct usb_descriptor gamepad_descriptor = {
|
||||||
|
.device_descriptor_callback = device_descriptor_callback,
|
||||||
|
.config_descriptor_callback = config_descriptor_callback,
|
||||||
|
.device_quality_descriptor_callback = device_quality_descriptor_callback,
|
||||||
|
.string_descriptor_callback = string_descriptor_callback
|
||||||
|
};
|
||||||
|
|
||||||
|
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t gamepad_read_buffer[64];
|
||||||
|
struct usb_gamepad_report gamepad_report;
|
||||||
|
|
||||||
|
#define GAMEPAD_STATE_IDLE 0
|
||||||
|
#define GAMEPAD_STATE_BUSY 1
|
||||||
|
|
||||||
|
volatile uint8_t gamepad_state = GAMEPAD_STATE_IDLE;
|
||||||
|
|
||||||
|
static void usbd_event_handler(uint8_t busid, uint8_t event)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case USBD_EVENT_RESET:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_CONNECTED:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_DISCONNECTED:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_RESUME:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_SUSPEND:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_CONFIGURED:
|
||||||
|
usbd_ep_start_read(busid, GAMEPAD_OUT_EP, gamepad_read_buffer, usbd_get_ep_mps(busid, GAMEPAD_OUT_EP));
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_SET_REMOTE_WAKEUP:
|
||||||
|
break;
|
||||||
|
case USBD_EVENT_CLR_REMOTE_WAKEUP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbd_gamepad_int_in_callback(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||||||
|
{
|
||||||
|
gamepad_state = GAMEPAD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbd_gamepad_int_out_callback(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||||||
|
{
|
||||||
|
usbd_ep_start_read(busid, GAMEPAD_OUT_EP, gamepad_read_buffer, usbd_get_ep_mps(busid, GAMEPAD_OUT_EP));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!< endpoint call back */
|
||||||
|
static struct usbd_endpoint gamepad_in_ep = {
|
||||||
|
.ep_cb = usbd_gamepad_int_in_callback,
|
||||||
|
.ep_addr = GAMEPAD_IN_EP
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct usbd_endpoint gamepad_out_ep = {
|
||||||
|
.ep_cb = usbd_gamepad_int_out_callback,
|
||||||
|
.ep_addr = GAMEPAD_OUT_EP
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct usbd_interface intf0;
|
||||||
|
|
||||||
|
void gamepad_init(uint8_t busid, uintptr_t reg_base)
|
||||||
|
{
|
||||||
|
if (gamepad_init_flag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gamepad_init_flag = true;
|
||||||
|
|
||||||
|
usbd_desc_register(busid, &gamepad_descriptor);
|
||||||
|
|
||||||
|
switch (gamepad_mode) {
|
||||||
|
case USBD_GAMEPAD_MODE_XINPUT:
|
||||||
|
usbd_add_interface(busid, usbd_gamepad_xinput_init_intf(&intf0));
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_SWITCH:
|
||||||
|
usbd_add_interface(busid, usbd_gamepad_switch_init_intf(&intf0));
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_XBOXONE:
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_PS4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
usbd_add_endpoint(busid, &gamepad_in_ep);
|
||||||
|
usbd_add_endpoint(busid, &gamepad_out_ep);
|
||||||
|
usbd_initialize(busid, reg_base, usbd_event_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gamepad_change_mode(uint8_t mode, uintptr_t reg_base)
|
||||||
|
{
|
||||||
|
gamepad_mode = mode;
|
||||||
|
|
||||||
|
if (gamepad_init_flag) {
|
||||||
|
usbd_deinitialize(0);
|
||||||
|
}
|
||||||
|
gamepad_init_flag = false;
|
||||||
|
gamepad_init(0, reg_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gamepad_test(uint8_t busid)
|
||||||
|
{
|
||||||
|
static uint32_t test_counter = 0;
|
||||||
|
|
||||||
|
if (usb_device_is_configured(busid) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gamepad_state = GAMEPAD_STATE_BUSY;
|
||||||
|
memset(&gamepad_report, 0, sizeof(gamepad_report));
|
||||||
|
|
||||||
|
gamepad_report.buttons = (1 << (test_counter % 18));
|
||||||
|
|
||||||
|
switch (gamepad_mode) {
|
||||||
|
case USBD_GAMEPAD_MODE_XINPUT:
|
||||||
|
usbd_gamepad_xinput_send_report(GAMEPAD_IN_EP, &gamepad_report);
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_SWITCH:
|
||||||
|
usbd_gamepad_switch_send_report(GAMEPAD_IN_EP, &gamepad_report);
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_XBOXONE:
|
||||||
|
break;
|
||||||
|
case USBD_GAMEPAD_MODE_PS4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_counter++;
|
||||||
|
while (gamepad_state == GAMEPAD_STATE_BUSY) {
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user