36 Commits

Author SHA1 Message Date
wiredopposite
b9038483ce update to tusb_gamepad lib 2024-05-01 22:13:43 -06:00
wiredopposite
e57de52cc4 new libs 2024-04-24 17:01:51 -06:00
wiredopposite
ee05f9516a use tusb_xinput fork 2024-04-24 16:58:05 -06:00
wiredopposite
a87ed08a63 Update issue templates 2024-04-19 22:19:40 -06:00
wiredopposite
e3acca6d5c add PowerA Enhanced controller 2024-04-19 17:07:27 -06:00
wiredopposite
2c601723f1 update readme 2024-04-17 21:54:00 -06:00
wiredopposite
d6dcbc52a7 update readme 2024-04-17 21:42:59 -06:00
wiredopposite
e210b50c1a update readme 2024-04-17 21:42:05 -06:00
wiredopposite
ae59b5e0c9 update readme 2024-04-17 21:38:43 -06:00
wiredopposite
9ebbbc3735 update readme 2024-04-17 21:27:06 -06:00
wiredopposite
f3ddd49252 xinput host 2024-04-17 20:12:05 -06:00
wiredopposite
f7da9a07d0 xinput 2024-04-17 20:08:04 -06:00
wiredopposite
e2ececd2e0 restrict input mode for 2+ players 2024-04-17 20:01:14 -06:00
wiredopposite
3604c8e112 fixed wireless adapter rumble 2024-04-17 19:40:56 -06:00
wiredopposite
86c60d76a4 switch to tusb_xinput fork 2024-04-17 19:16:17 -06:00
wiredopposite
2613221d44 new boards 2024-04-17 18:26:02 -06:00
wiredopposite
1f27c3d4b5 resolve dinput bug 2024-04-16 18:28:16 -06:00
wiredopposite
da72c91c04 resolve dinput bug 2024-04-16 18:27:10 -06:00
wiredopposite
933206398a resolve dinput bug 2024-04-16 18:25:24 -06:00
wiredopposite
0a053f8f3d modularizing device drivers 2024-04-16 18:13:13 -06:00
wiredopposite
b1535c9c75 refactoring for different hardware 2024-04-16 15:19:57 -06:00
wiredopposite
383274c83b fixed dinput bug 2024-04-16 15:09:23 -06:00
wiredopposite
54bbdf0a41 4 player support 2024-03-24 20:53:41 -06:00
wiredopposite
b0fd734194 add config header file 2024-03-24 10:10:26 -06:00
wiredopposite
49fff842f2 host side refactor 2024-03-24 10:06:05 -06:00
wiredopposite
3995fd538c fixed sys reset/tusb conflict 2024-03-11 20:47:18 -06:00
wiredopposite
2643628dac input mode switching 2024-03-11 14:23:13 -06:00
wiredopposite
8fb77437cf input mode selection 2024-03-11 14:18:01 -06:00
wiredopposite
559633010e typo 2024-03-05 19:51:58 -07:00
wiredopposite
323706929b ps3 support and cdc mode 2024-03-05 19:28:39 -07:00
wiredopposite
ff48a17750 ps3 support and cdc mode 2024-03-05 19:04:38 -07:00
wiredopposite
20fd72e86f refactor and more controllers 2024-03-04 16:27:53 -07:00
wiredopposite
2be0455d6f refactor and more controllers 2024-03-01 16:53:07 -07:00
wiredopposite
e519b337fd update license 2024-02-21 15:57:22 -07:00
wiredopposite
51ab32fe99 Create LICENSE 2024-02-20 18:25:47 -07:00
wiredopposite
8edb2a272c initial 2024-02-20 18:04:24 -07:00
96 changed files with 5657 additions and 3814 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**What type of controller do you have the issue with?**
Please include the model, even better to include the PID/VID.
You can get the PID and VID like this:
- plug the controller into your computer
- open Device Manager
- right click the controller (probably listed under Human Interface Devices)
- select Properties
- go to the Details tab
- select Hardware IDs from the dropdown menu
It will look like this: HID\VID_046D&PID_C05A
**What platform are you using the OGX-Mini on?**
OG Xbox, PS3, Switch, etc.
**What board are you using?**
Pi Pico, Adafruit Feather, RP2040-Zero...

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@@ -4,4 +4,3 @@ release
generated
tools
.ignore
src/usbh/tusb_hid/experiment

12
.gitmodules vendored Normal file
View File

@@ -0,0 +1,12 @@
[submodule "lib/Pico-PIO-USB"]
path = lib/Pico-PIO-USB
url = https://github.com/sekigon-gonnoc/Pico-PIO-USB.git
[submodule "lib/tinyusb"]
path = lib/tinyusb
url = https://github.com/hathach/tinyusb.git
[submodule "lib/tusb_gamepad"]
path = lib/tusb_gamepad
url = https://github.com/wiredopposite/tusb_gamepad.git
[submodule "lib/tusb_xinput"]
path = lib/tusb_xinput
url = https://github.com/wiredopposite/tusb_xinput.git

View File

@@ -1,17 +1,18 @@
cmake_minimum_required(VERSION 3.12)
set(CMAKE_BUILD_TYPE Release)
message("Build type: \"${CMAKE_BUILD_TYPE}\"")
# Project name
set(NAME OGX-Mini)
# Board type
set(PICO_BOARD none)
# Fixes that allow some MCH2022 badges with a slowly starting oscillator to boot properly
add_compile_definitions(PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H=1 PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64)
# add_compile_definitions(PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H=1 PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64)
# add_compile_options(-Wno-unused-parameter -Wno-unused-variable -Wno-missing-field-initializers -Wno-unused-function)
add_compile_options(-Wno-missing-field-initializers)
# SDK
include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake)
project(${NAME} C CXX ASM)
@@ -22,60 +23,48 @@ if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0")
message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()
set(PICO_PIO_USB_PATH "${CMAKE_CURRENT_LIST_DIR}/lib/Pico-PIO-USB")
set(PICO_TINYUSB_PATH "${CMAKE_CURRENT_LIST_DIR}/lib/tinyusb")
set(ROOT ${CMAKE_CURRENT_LIST_DIR})
set(PICO_PIO_USB_PATH ${ROOT}/lib/Pico-PIO-USB)
set(PICO_TINYUSB_PATH ${ROOT}/lib/tinyusb)
set(TUSB_GAMEPAD_PATH ${ROOT}/lib/tusb_gamepad)
set(XINPUT_HOST_PATH ${ROOT}/lib/tusb_xinput)
pico_sdk_init()
add_subdirectory(lib/Pico-PIO-USB)
# add_subdirectory(${ROOT}/lib)
add_subdirectory(${ROOT}/lib/CRC32 CRC32)
add_subdirectory(${XINPUT_HOST_PATH} xinput_host)
add_subdirectory(${PICO_PIO_USB_PATH})
add_subdirectory(${TUSB_GAMEPAD_PATH})
target_include_directories(tusb_gamepad PRIVATE ${ROOT}/src) # so tusb_gamepad can see tusb_config.h
set(SRC_DIR ${ROOT}/src)
file(GLOB_RECURSE SOURCES
"src/usbh/*"
"src/usbh/tusb_xinput/*"
"src/usbh/tusb_hid/*"
"src/*"
"src/usbd/*"
"src/usbd/drivers/*"
"src/usbd/drivers/astro/*"
"src/usbd/drivers/egret/*"
"src/usbd/drivers/hid/*"
"src/usbd/drivers/keyboard/*"
"src/usbd/drivers/mdmini/*"
"src/usbd/drivers/neogeo/*"
"src/usbd/drivers/net/*"
"src/usbd/drivers/pcengine/*"
"src/usbd/drivers/ps4/*"
"src/usbd/drivers/psclassic/*"
"src/usbd/drivers/shared/*"
"src/usbd/drivers/switch/*"
"src/usbd/drivers/xbone/*"
"src/usbd/drivers/xboxog/*"
"src/usbd/drivers/xinput/*")
${SRC_DIR}/main.cpp
${SRC_DIR}/input_mode.cpp
${SRC_DIR}/usbh/tusb_host_manager.cpp
${SRC_DIR}/usbh/tusb_host.cpp
${SRC_DIR}/usbh/n64usb/N64USB.cpp
${SRC_DIR}/usbh/ps3/Dualshock3.cpp
${SRC_DIR}/usbh/ps3/DInput.cpp
${SRC_DIR}/usbh/ps4/Dualshock4.cpp
${SRC_DIR}/usbh/ps5/Dualsense.cpp
${SRC_DIR}/usbh/psclassic/PSClassic.cpp
${SRC_DIR}/usbh/switch/SwitchPro.cpp
${SRC_DIR}/usbh/switch/SwitchWired.cpp
${SRC_DIR}/usbh/xinput/XInput.cpp
${SRC_DIR}/usbh/shared/hid_class_driver.c
${SRC_DIR}/usbh/shared/scaling.cpp
)
# Firmware
add_executable(${NAME}
${SOURCES}
)
add_executable(${NAME} ${SOURCES})
target_include_directories(${NAME} PUBLIC
${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/lib)
include_directories(
lib/
src/usbh
src/usbd
src/usbd/drivers)
#------- Comment out the following line if compiling for the normal Pi Pico -------#
add_compile_definitions(FEATHER_RP2040)
#------- USB host data +/- will be GPIO 0/1 for the Pico --------------------------#
#------ These are your options for platforms -----#
#------ Uncomment only one at a time -------------#
add_compile_definitions(HOST_ORIGINAL_XBOX)
# add_compile_definitions(HOST_XINPUT)
# add_compile_definitions(HOST_NINTENDO_SWITCH)
${ROOT}/src
${ROOT}/lib)
target_link_libraries(${NAME}
pico_stdlib
@@ -93,7 +82,10 @@ target_link_libraries(${NAME}
tinyusb_board
tinyusb_host
tinyusb_pico_pio_usb
CRC32
cmsis_core
xinput_host
tusb_gamepad
)
pico_add_extra_outputs(${NAME})

View File

@@ -1,6 +1,9 @@
MIT License
Copyright (c) 2023 Ryzee119
Copyright (c) 2024 wiredOpposite (wiredopposite.com)
Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
Copyright (c) 2021 Jason Skuby (mytechtoybox.com)
Copyright (c) 2020 Ryan Wendland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,40 +0,0 @@
# Copyright (c) 2022 Nicolai Electronics
# SPDX-License-Identifier: MIT
INSTALL_PREFIX := $PWD
BUILD_DIR := build
GENERATED_DIR := generated
.PHONY: all firmware flash clean install_rules $(BUILD_DIR) format
all: build flash
@echo "All tasks completed"
build:
mkdir -p $(BUILD_DIR)
mkdir -p $(GENERATED_DIR)
cd $(BUILD_DIR); cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release ..
$(MAKE) -C $(BUILD_DIR) --no-print-directory all
debug:
mkdir -p $(BUILD_DIR)
mkdir -p $(GENERATED_DIR)
cd $(BUILD_DIR); cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Debug ..
$(MAKE) -C $(BUILD_DIR) --no-print-directory all
flash:
picotool load $(BUILD_DIR)/i2c_adapter.bin
picotool reboot
clean:
rm -rf $(BUILD_DIR)
rm -rf $(GENERATED_DIR)
install_rules:
cp tools/99-pico.rules /etc/udev/rules.d/
@echo "reload rules with:"
@echo "\tudevadm control --reload-rules"
@echo "\tudevadm trigger"
format:
find . -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' | grep -v '$(BUILD_DIR)' | xargs clang-format -i

View File

@@ -1,29 +1,75 @@
# OGX-Mini
![Adafruit Feather RP2040 USB Host](images/ada_feather_rp2040_usb.jpg "Adafruit Feather RP2040 USB Host")
![OGX-Mini Boards](images/OGX-Mini-github.jpg "OGX-Mini Boards")
Firmware for the RP2040/Pico, setup for the [Adafruit Feather USB Host board](https://www.adafruit.com/product/5723), capable of emulating gamepads for
Firmware for the RP2040, capable of emulating gamepads for several consoles. The firmware now comes in 3 flavors, for the [Adafruit Feather USB Host board](https://www.adafruit.com/product/5723), the Pi Pico, and the Waveshare RP2040-Zero.
## Supported platforms
- Original Xbox
- Playstation 3
- Nintendo Switch (docked)
- XInput (not Xbox 360)
- Nintendo Switch (must be in dock mode, no rumble yet)
- Playstation Classic
Currently there's no way to switch what device is being emulated on the fly, so the firmware must be compiled specifically for whichever platform you'd like to play on. As long as that's the case, I'll provide compiled .uf2 files for each platform in [Releases](https://github.com/wiredopposite/OGX-Mini/releases).
## Supported devices
### Wired controllers
- Original Xbox Duke and S
- Xbox 360, One, Series, and Elite
- Dualshock 3 (PS3)
- Dualshock 4 (PS4)
- Dualsense (PS5, Dualsense Edge should work but it's untested)
- Nintendo Switch Pro
- Nintendo Switch wired (tested with PowerA brand)
- Nintendo 64 USB (experimental, tested with RetroLink brand)
- Playstation Classic
- Generic DInput
# Supported devices
- Original Xbox Duke and S controllers
- Xbox One, Series, and Elite controllers
- Wired Xbox 360 controllers
- Xbox 360 wireless PC adapter (Microsoft or clones, syncs 1 controller)
- Sony Dualshock 4 (PS4) controllers
- Sony Dualsense (PS5) controllers
- 8Bitdo v1 and v2 Bluetooth adapters
### Wireless adapters
- Xbox 360 PC adapter (Microsoft or clones, syncs 1 controller)
- 8Bitdo v1 and v2 Bluetooth adapters (set to XInput mode)
- Most wireless adapters that present themselves as Switch/XInput/PlayStation controllers should work
# Adding supported controllers
I am currently searching for VID and PID numbers for PS3, PS4, PS5, and Switch Pro gamepads, including knockoffs. If you have one, let me know and I'll add support for it.
Note: There are some third party controllers that can change their VID/PID, these might not work correctly.
# Compiling
You can compile this for the Pi Pico by commenting out this line in CMakeLists.txt
`add_compile_definitions(FEATHER_RP2040)`
That will set the D+ and D- host pins to GPIO 0 and 1. Below that you can uncomment whichever platform (OG Xbox, Xinput, etc.) you'd like to use.
## Changing input mode
By default the input mode is set to OG Xbox, you must hold a button combo for 3 seconds to change which platform you want to play on. Your chosen input mode will persist after powering off the device.
# Special thanks
Thank you to Ryzee119 and the Open Stick Community, without their work this project would not exist.
Start = Plus (Switch) = Options (Dualsense/DS4)
### XInput
Start + Dpad Up
### Original Xbox
Start + Dpad Right
### Switch
Start + Dpad Down
### PlayStation 3
Start + Dpad Left
### PlayStation Classic
Start + A (Cross for PlayStation and B for Switch gamepads)
After a new mode is stored, the RP2040 will reset itself so you don't need to unplug it.
## Hardware
I've designed a PCB for the RP2040-Zero so you can make a small form-factor adapter yourself. The gerber files, schematic, and BOM are in Hardware folder.
![OGX-Mini Boards](images/OGX-Mini-rpzero-int.jpg "OGX-Mini Boards")
If you would like a prebuilt unit, you can purchase one, with cable and Xbox adapter included, from my website [wiredopposite.com](https://wiredopposite.com/product/ogx-mini-controller-adapter-for-original-xbox-playstation-3-and-switch-ogx360/) or my [Etsy store](https://www.etsy.com/listing/1426992904/ogx-mini-controller-adapter-for-original).
For the Pi Pico, this is a diagram of how you'd connect the extra USB port:
![Pi Pico Wiring Diagram](images/pi_pico_diagram.png "Pi Pico Wiring Diagram]")
For the [Adafruit Feather USB Host board](https://www.adafruit.com/product/5723), no extra work is needed.
## Adding supported controllers
If your third party controller isn't working, but the original version is listed above, send me the device's VID and PID and I'll add it so it's recognized properly.
## Compiling
You can compile this for different boards by changing USBD_BOARD in the usbd_config.h file, you can also adjust USBD_MAX_GAMEPADS to enable more controllers on PlayStation 3 (this is experimental).
Choosing OGXM_PI_PICO will set the D+ and D- host pins to GPIO 0 and 1.
You can also choose OGXM_RPZERO_INTERPOSER for the RP2040-Zero and that will set D+ and D- to GPIO 10 and 11, so connecting a USB port is easier. You can still use the Pi Pico firmware on the RP2040-Zero (the other way around has not been tested though).
## Special thanks
Thank you to Ryzee119 and the OpenStickCommunity, without their work this project would not exist.

View File

@@ -0,0 +1,6 @@
References,Qty,Description,Manufacturer,MPN,Digikey,Mouser,RS,Newark,Farnell,LCSC,JLC Assembly
USB1,1,USB TYPE-A SINGLE PORT RIGHT ANG,,CU01SAH0S00,2987-CU01SAH0S00-ND,,,,,,
"R1,R2",2,RES 27 OHM 5% 1/8W 0805,,RC0805JR-0727RL,311-27ARCT-ND,,,,,,
R3,1,RES 470 OHM 1% 1/8W 0805 (Optional),,RC0805FR-07470RL,311-470CRCT-ND,,,,,,
LED1,1,LED GREEN CLEAR 0805 SMD (Optional),,150080GS75000,732-4983-1-ND,,,,,,
U1,1,Waveshare RP2040-Zero,,Waveshare RP2040-Zero,,,,,,,
1 References Qty Description Manufacturer MPN Digikey Mouser RS Newark Farnell LCSC JLC Assembly
2 USB1 1 USB TYPE-A SINGLE PORT RIGHT ANG CU01SAH0S00 2987-CU01SAH0S00-ND
3 R1,R2 2 RES 27 OHM 5% 1/8W 0805 RC0805JR-0727RL 311-27ARCT-ND
4 R3 1 RES 470 OHM 1% 1/8W 0805 (Optional) RC0805FR-07470RL 311-470CRCT-ND
5 LED1 1 LED GREEN CLEAR 0805 SMD (Optional) 150080GS75000 732-4983-1-ND
6 U1 1 Waveshare RP2040-Zero Waveshare RP2040-Zero

Binary file not shown.

5
hardware/README.md Normal file
View File

@@ -0,0 +1,5 @@
Gerber, BOM, and schematic for an RP2040-Zero interposer board you can make yourself. LED1 and R3 are both optional.
The RP2040-Zero board can be found on Amazon and AliExpress.
![OGX-Mini Boards](../images/OGX-Mini-rpzero-int.jpg "OGX-Mini Boards")

File diff suppressed because it is too large Load Diff

BIN
images/OGX-Mini-github.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

BIN
images/pi_pico_diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

2
lib/CMakeLists.txt Normal file
View File

@@ -0,0 +1,2 @@
add_subdirectory(CRC32)
add_subdirectory(hid_parser)

8
lib/CRC32/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
add_library(CRC32
src/CRC32.cpp
src/CRC32.h
)
target_include_directories(CRC32 INTERFACE
src
)

37
lib/CRC32/src/CRC32.cpp Normal file
View File

@@ -0,0 +1,37 @@
//
// Copyright (c) 2013 Christopher Baker <https://christopherbaker.net>
//
// SPDX-License-Identifier: MIT
//
#include "CRC32.h"
static const uint32_t crc32_table[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
CRC32::CRC32() {
reset();
}
void CRC32::reset() {
_state = ~0L;
}
void CRC32::update(const uint8_t &data) {
// via http://forum.arduino.cc/index.php?topic=91179.0
uint8_t tbl_idx = 0;
tbl_idx = _state ^ (data >> (0 * 4));
_state = *(uint32_t *)(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4);
tbl_idx = _state ^ (data >> (1 * 4));
_state = *(uint32_t *)(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4);
}
uint32_t CRC32::finalize() const
{
return ~_state;
}

66
lib/CRC32/src/CRC32.h Normal file
View File

@@ -0,0 +1,66 @@
//
// Copyright (c) 2013 Christopher Baker <https://christopherbaker.net>
//
// SPDX-License-Identifier: MIT
//
#pragma once
#include <stdint.h>
/// \brief A class for calculating the CRC32 checksum from arbitrary data.
/// \sa http://forum.arduino.cc/index.php?topic=91179.0
class CRC32 {
public:
/// \brief Initialize an empty CRC32 checksum.
CRC32();
/// \brief Reset the checksum claculation.
void reset();
/// \brief Update the current checksum caclulation with the given data.
/// \param data The data to add to the checksum.
void update(const uint8_t &data);
/// \brief Update the current checksum caclulation with the given data.
/// \tparam Type The data type to read.
/// \param data The data to add to the checksum.
template <typename Type>
void update(const Type &data) {
update(&data, 1);
}
/// \brief Update the current checksum caclulation with the given data.
/// \tparam Type The data type to read.
/// \param data The array to add to the checksum.
/// \param size Size of the array to add.
template <typename Type>
void update(const Type *data, uint16_t size) {
uint16_t nBytes = size * sizeof(Type);
const uint8_t *pData = (const uint8_t *)data;
for (uint16_t i = 0; i < nBytes; i++)
{
update(pData[i]);
}
}
/// \returns the caclulated checksum.
uint32_t finalize() const;
/// \brief Calculate the checksum of an arbitrary data array.
/// \tparam Type The data type to read.
/// \param data A pointer to the data to add to the checksum.
/// \param size The size of the data to add to the checksum.
/// \returns the calculated checksum.
template <typename Type>
static uint32_t calculate(const Type *data, uint16_t size = 1) {
CRC32 crc;
crc.update(data, size);
return crc.finalize();
}
private:
/// \brief The internal checksum state.
uint32_t _state = ~0L;
};

View File

@@ -0,0 +1,8 @@
add_library(hid_parser
src/hid_parser.c
src/hid_parser.h
)
target_include_directories(hid_parser INTERFACE
src
)

View File

@@ -0,0 +1,444 @@
/****************************************************************************
* Adapted from the LUFA Library:
*
* Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
* dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
****************************************************************************/
#include "hid_parser.h"
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#ifdef DYNAMIC
#include <malloc.h>
#define ACQUIRE_AND_RELEASE(structure, size) \
__attribute__ ((always_inline)) static inline structure##_t* acquire_##structure() { return malloc(sizeof(structure##_t)); } \
__attribute__ ((always_inline)) static inline void release_##structure(structure##_t* pointer) { free(pointer); }
#else
#define ACQUIRE_AND_RELEASE(structure, size) \
enum { MAX_##structure = size }; \
static structure##_t structure##s[MAX_##structure]; \
static bool structure##sAcquired[MAX_##structure]; \
static structure##_t* acquire_##structure() \
{\
for(uint8_t i=0; i < MAX_##structure; i++) \
{\
if(!structure##sAcquired[i])\
{\
structure##sAcquired[i] = true;\
return &structure##s[i];\
}\
}\
assert(false);\
return NULL;\
}\
static void release_##structure(structure##_t* pointer)\
{\
structure##sAcquired[pointer - structure##s] = false;\
}
#endif
ACQUIRE_AND_RELEASE(HID_ReportSizeInfo, 100);
ACQUIRE_AND_RELEASE(HID_CollectionPath, 25);
ACQUIRE_AND_RELEASE(HID_ReportInfo, 1);
ACQUIRE_AND_RELEASE(HID_ReportItem, 50);
void USB_FreeReportInfo(HID_ReportInfo_t *ReportInfo)
{
if (ReportInfo)
{
HID_ReportItem_t *item = ReportInfo->FirstReportItem;
while (item)
{
HID_ReportItem_t *current = item;
item = item->Next;
release_HID_ReportItem(current);
}
release_HID_ReportInfo(ReportInfo);
}
}
uint8_t USB_ProcessHIDReport(const uint8_t *ReportData,
uint16_t ReportSize,
HID_ReportInfo_t **ParserDataOut)
{
HID_ReportSizeInfo_t *FirstReportIDSize = acquire_HID_ReportSizeInfo();
HID_CollectionPath_t *FirstCollectionPath = acquire_HID_CollectionPath();
memset(FirstCollectionPath, 0, sizeof(HID_CollectionPath_t));
HID_ReportInfo_t *ParserData = acquire_HID_ReportInfo();
HID_StateTable_t StateTable[HID_STATETABLE_STACK_DEPTH];
HID_StateTable_t *CurrStateTable = &StateTable[0];
HID_CollectionPath_t *CurrCollectionPath = NULL;
HID_ReportSizeInfo_t *CurrReportIDInfo = FirstReportIDSize;
uint16_t UsageList[HID_USAGE_STACK_DEPTH];
uint8_t UsageListSize = 0;
HID_MinMax_t UsageMinMax = {0, 0};
memset(ParserData, 0x00, sizeof(HID_ReportInfo_t));
memset(CurrStateTable, 0x00, sizeof(HID_StateTable_t));
memset(CurrReportIDInfo, 0x00, sizeof(HID_ReportSizeInfo_t));
ParserData->TotalDeviceReports = 1;
uint8_t Result = HID_PARSE_Successful;
while (ReportSize)
{
uint8_t HIDReportItem = *ReportData;
uint32_t ReportItemData;
ReportData++;
ReportSize--;
switch (HIDReportItem & HID_RI_DATA_SIZE_MASK)
{
case HID_RI_DATA_BITS_32:
ReportItemData = (((uint32_t)ReportData[3] << 24) | ((uint32_t)ReportData[2] << 16) |
((uint16_t)ReportData[1] << 8) | ReportData[0]);
ReportSize -= 4;
ReportData += 4;
break;
case HID_RI_DATA_BITS_16:
ReportItemData = (((uint16_t)ReportData[1] << 8) | (ReportData[0]));
ReportSize -= 2;
ReportData += 2;
break;
case HID_RI_DATA_BITS_8:
ReportItemData = ReportData[0];
ReportSize -= 1;
ReportData += 1;
break;
default:
ReportItemData = 0;
break;
}
switch (HIDReportItem & (HID_RI_TYPE_MASK | HID_RI_TAG_MASK))
{
case HID_RI_PUSH(0):
if (CurrStateTable == &StateTable[HID_STATETABLE_STACK_DEPTH - 1])
{
Result = HID_PARSE_HIDStackOverflow;
break;
}
memcpy((CurrStateTable + 1),
CurrStateTable,
sizeof(HID_StateTable_t));
CurrStateTable++;
break;
case HID_RI_POP(0):
if (CurrStateTable == &StateTable[0])
{
Result = HID_PARSE_HIDStackUnderflow;
break;
}
CurrStateTable--;
break;
case HID_RI_USAGE_PAGE(0):
CurrStateTable->Attributes.Usage.Page = ReportItemData;
break;
case HID_RI_LOGICAL_MINIMUM(0):
CurrStateTable->Attributes.Logical.Minimum = ReportItemData;
break;
case HID_RI_LOGICAL_MAXIMUM(0):
CurrStateTable->Attributes.Logical.Maximum = ReportItemData;
break;
case HID_RI_PHYSICAL_MINIMUM(0):
CurrStateTable->Attributes.Physical.Minimum = ReportItemData;
break;
case HID_RI_PHYSICAL_MAXIMUM(0):
CurrStateTable->Attributes.Physical.Maximum = ReportItemData;
break;
case HID_RI_UNIT_EXPONENT(0):
CurrStateTable->Attributes.Unit.Exponent = ReportItemData;
break;
case HID_RI_UNIT(0):
CurrStateTable->Attributes.Unit.Type = ReportItemData;
break;
case HID_RI_REPORT_SIZE(0):
CurrStateTable->Attributes.BitSize = ReportItemData;
break;
case HID_RI_REPORT_COUNT(0):
CurrStateTable->ReportCount = ReportItemData;
break;
case HID_RI_REPORT_ID(0):
CurrStateTable->ReportID = ReportItemData;
if (ParserData->UsingReportIDs)
{
CurrReportIDInfo = NULL;
HID_ReportSizeInfo_t *iterator = FirstReportIDSize;
while (true)
{
if (iterator->ReportID == CurrStateTable->ReportID)
{
CurrReportIDInfo = iterator;
break;
}
if (!iterator->Next)
break;
iterator = iterator->Next;
}
if (CurrReportIDInfo == NULL)
{
ParserData->TotalDeviceReports++;
iterator->Next = CurrReportIDInfo = acquire_HID_ReportSizeInfo();
memset(CurrReportIDInfo, 0x00, sizeof(HID_ReportSizeInfo_t));
}
}
ParserData->UsingReportIDs = true;
CurrReportIDInfo->ReportID = CurrStateTable->ReportID;
break;
case HID_RI_USAGE(0):
if (UsageListSize == HID_USAGE_STACK_DEPTH)
{
Result = HID_PARSE_UsageListOverflow;
break;
}
if ((HIDReportItem & HID_RI_DATA_SIZE_MASK) == HID_RI_DATA_BITS_32)
CurrStateTable->Attributes.Usage.Page = (ReportItemData >> 16);
UsageList[UsageListSize++] = ReportItemData;
break;
case HID_RI_USAGE_MINIMUM(0):
UsageMinMax.Minimum = ReportItemData;
break;
case HID_RI_USAGE_MAXIMUM(0):
UsageMinMax.Maximum = ReportItemData;
break;
case HID_RI_COLLECTION(0):
if (CurrCollectionPath == NULL)
{
CurrCollectionPath = FirstCollectionPath;
}
else
{
HID_CollectionPath_t *ParentCollectionPath = CurrCollectionPath;
CurrCollectionPath = FirstCollectionPath;
while (CurrCollectionPath->Next)
{
CurrCollectionPath = CurrCollectionPath->Next;
}
HID_CollectionPath_t *NewCollectionPath = acquire_HID_CollectionPath();
CurrCollectionPath->Next = NewCollectionPath;
CurrCollectionPath = NewCollectionPath;
memset(CurrCollectionPath, 0, sizeof(HID_CollectionPath_t));
CurrCollectionPath->Parent = ParentCollectionPath;
}
CurrCollectionPath->Type = ReportItemData;
CurrCollectionPath->Usage.Page = CurrStateTable->Attributes.Usage.Page;
if (UsageListSize)
{
CurrCollectionPath->Usage.Usage = UsageList[0];
for (uint8_t i = 1; i < UsageListSize; i++)
UsageList[i - 1] = UsageList[i];
UsageListSize--;
}
else if (UsageMinMax.Minimum <= UsageMinMax.Maximum)
{
CurrCollectionPath->Usage.Usage = UsageMinMax.Minimum++;
}
break;
case HID_RI_END_COLLECTION(0):
if (CurrCollectionPath == NULL)
{
Result = HID_PARSE_UnexpectedEndCollection;
break;
}
CurrCollectionPath = CurrCollectionPath->Parent;
if (CurrCollectionPath)
{
release_HID_CollectionPath(CurrCollectionPath->Next);
CurrCollectionPath->Next = NULL;
}
break;
case HID_RI_INPUT(0):
case HID_RI_OUTPUT(0):
case HID_RI_FEATURE(0):
for (uint8_t ReportItemNum = 0; ReportItemNum < CurrStateTable->ReportCount; ReportItemNum++)
{
HID_ReportItem_t NewReportItem;
memcpy(&NewReportItem.Attributes,
&CurrStateTable->Attributes,
sizeof(HID_ReportItem_Attributes_t));
NewReportItem.ItemFlags = ReportItemData;
NewReportItem.ReportID = CurrStateTable->ReportID;
if (UsageListSize)
{
NewReportItem.Attributes.Usage.Usage = UsageList[0];
for (uint8_t i = 1; i < UsageListSize; i++)
UsageList[i - 1] = UsageList[i];
UsageListSize--;
}
else if (UsageMinMax.Minimum <= UsageMinMax.Maximum)
{
NewReportItem.Attributes.Usage.Usage = UsageMinMax.Minimum++;
}
uint8_t ItemTypeTag = (HIDReportItem & (HID_RI_TYPE_MASK | HID_RI_TAG_MASK));
if (ItemTypeTag == HID_RI_INPUT(0))
NewReportItem.ItemType = HID_REPORT_ITEM_In;
else if (ItemTypeTag == HID_RI_OUTPUT(0))
NewReportItem.ItemType = HID_REPORT_ITEM_Out;
else
NewReportItem.ItemType = HID_REPORT_ITEM_Feature;
NewReportItem.BitOffset = CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType];
CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType] += CurrStateTable->Attributes.BitSize;
ParserData->LargestReportSizeBits = MAX(ParserData->LargestReportSizeBits, CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType]);
if (!(ReportItemData & HID_IOF_CONSTANT) && CALLBACK_HIDParser_FilterHIDReportItem(&NewReportItem))
{
if (!ParserData->FirstReportItem)
{
ParserData->FirstReportItem = acquire_HID_ReportItem();
ParserData->LastReportItem = ParserData->FirstReportItem;
}
else
{
ParserData->LastReportItem->Next = acquire_HID_ReportItem();
ParserData->LastReportItem = ParserData->LastReportItem->Next;
}
memcpy(ParserData->LastReportItem, &NewReportItem, sizeof(HID_ReportItem_t));
ParserData->LastReportItem->Next = NULL;
ParserData->TotalReportItems++;
}
}
break;
default:
break;
}
if (Result != HID_PARSE_Successful)
{
break;
}
if ((HIDReportItem & HID_RI_TYPE_MASK) == HID_RI_TYPE_MAIN)
{
UsageMinMax.Minimum = 0;
UsageMinMax.Maximum = 0;
UsageListSize = 0;
}
}
if (!(ParserData->TotalReportItems))
Result = HID_PARSE_NoUnfilteredReportItems;
if (Result != HID_PARSE_Successful)
{
USB_FreeReportInfo(ParserData);
}
else
{
*ParserDataOut = ParserData;
}
HID_ReportSizeInfo_t *iteratorReportIDSizes = FirstReportIDSize;
while (iteratorReportIDSizes)
{
HID_ReportSizeInfo_t *temp = iteratorReportIDSizes->Next;
release_HID_ReportSizeInfo(iteratorReportIDSizes);
iteratorReportIDSizes = temp;
}
HID_CollectionPath_t *iteratorCollectionPaths = FirstCollectionPath;
while (iteratorCollectionPaths)
{
HID_CollectionPath_t *temp = iteratorCollectionPaths->Next;
release_HID_CollectionPath(iteratorCollectionPaths);
iteratorCollectionPaths = temp;
}
return Result;
}
bool USB_GetHIDReportItemInfo(uint16_t report_id, const uint8_t *ReportData,
HID_ReportItem_t *const ReportItem)
{
if (ReportItem == NULL)
{
return false;
}
if (ReportItem->ReportID != report_id)
{
return false;
}
uint16_t DataBitsRem = ReportItem->Attributes.BitSize;
uint16_t CurrentBit = ReportItem->BitOffset;
uint32_t BitMask = (1 << 0);
ReportItem->PreviousValue = ReportItem->Value;
ReportItem->Value = 0;
while (DataBitsRem--)
{
if (ReportData[CurrentBit / 8] & (1 << (CurrentBit % 8)))
ReportItem->Value |= BitMask;
CurrentBit++;
BitMask <<= 1;
}
return true;
}

View File

@@ -0,0 +1,424 @@
/****************************************************************************
* Adapted from the LUFA Library:
*
* Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
* dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
****************************************************************************/
/** \file
* \brief USB Human Interface Device (HID) Class report descriptor parser.
*
* This file allows for the easy parsing of complex HID report descriptors, which describes the data that
* a HID device transmits to the host. It also provides an easy API for extracting and processing the data
* elements inside a HID report sent from an attached HID device.
*/
/** \ingroup Group_USB
* \defgroup Group_HIDParser HID Report Parser
* \brief USB Human Interface Device (HID) Class report descriptor parser.
*
* \section Sec_HIDParser_Dependencies Module Source Dependencies
* The following files must be built with any user project that uses this module:
* - LUFA/Drivers/USB/Class/Host/HIDParser.c <i>(Makefile source module name: LUFA_SRC_USB)</i>
*
* \section Sec_HIDParser_ModDescription Module Description
* Human Interface Device (HID) class report descriptor parser. This module implements a parser than is
* capable of processing a complete HID report descriptor, and outputting a flat structure containing the
* contents of the report in an a more friendly format. The parsed data may then be further processed and used
* within an application to process sent and received HID reports to and from an attached HID device.
*
* A HID report descriptor consists of a set of HID report items, which describe the function and layout
* of data exchanged between a HID device and a host, including both the physical encoding of each item
* (such as a button, key press or joystick axis) in the sent and received data packets - known as "reports" -
* as well as other information about each item such as the usages, data range, physical location and other
* characteristics. In this way a HID device can retain a high degree of flexibility in its capabilities, as it
* is not forced to comply with a given report layout or feature-set.
*
* This module also contains routines for the processing of data in an actual HID report, using the parsed report
* descriptor data as a guide for the encoding.
*
* @{
*/
#pragma once
/* Includes: */
#include <stdint.h>
#include <stdbool.h>
/* Enable C linkage for C++ Compilers: */
#if defined(__cplusplus)
extern "C"
{
#endif
/* Macros: */
#if !defined(HID_STATETABLE_STACK_DEPTH) || defined(__DOXYGEN__)
/** Constant indicating the maximum stack depth of the state table. A larger state table
* allows for more PUSH/POP report items to be nested, but consumes more memory. By default
* this is set to 2 levels (allowing non-nested PUSH items) but this can be overridden by
* defining \c HID_STATETABLE_STACK_DEPTH to another value in the user project makefile, passing the
* define to the compiler using the -D compiler switch.
*/
#define HID_STATETABLE_STACK_DEPTH 2
#endif
#if !defined(HID_USAGE_STACK_DEPTH) || defined(__DOXYGEN__)
/** Constant indicating the maximum stack depth of the usage table. A larger usage table
* allows for more USAGE items to be indicated sequentially for REPORT COUNT entries of more than
* one, but requires more stack space. By default this is set to 8 levels (allowing for a report
* item with a count of 8) but this can be overridden by defining \c HID_USAGE_STACK_DEPTH to another
* value in the user project makefile, passing the define to the compiler using the -D compiler
* switch.
*/
#define HID_USAGE_STACK_DEPTH 8
#endif
/** Returns the value a given HID report item (once its value has been fetched via \ref USB_GetHIDReportItemInfo())
* left-aligned to the given data type. This allows for signed data to be interpreted correctly, by shifting the data
* leftwards until the data's sign bit is in the correct position.
*
* \param[in] ReportItem HID Report Item whose retrieved value is to be aligned.
* \param[in] Type Data type to align the HID report item's value to.
*
* \return Left-aligned data of the given report item's pre-retrieved value for the given datatype.
*/
#define HID_ALIGN_DATA(ReportItem, Type) ((Type)(ReportItem->Value << ((8 * sizeof(Type)) - ReportItem->Attributes.BitSize)))
/** Convenience macro to determine the larger of two values.
*
* \attention This macro should only be used with operands that do not have side effects from being evaluated
* multiple times.
*
* \param[in] x First value to compare
* \param[in] y First value to compare
*
* \return The larger of the two input parameters
*/
#if !defined(MAX) || defined(__DOXYGEN__)
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#endif
/** Convenience macro to determine the smaller of two values.
*
* \attention This macro should only be used with operands that do not have side effects from being evaluated
* multiple times.
*
* \param[in] x First value to compare.
* \param[in] y First value to compare.
*
* \return The smaller of the two input parameters
*/
#if !defined(MIN) || defined(__DOXYGEN__)
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#if !defined(CONCAT) || defined(__DOXYGEN__)
/** Concatenates the given input into a single token, via the C Preprocessor.
*
* \param[in] x First item to concatenate.
* \param[in] y Second item to concatenate.
*
* \return Concatenated version of the input.
*/
#define CONCAT(x, y) x##y
/** CConcatenates the given input into a single token after macro expansion, via the C Preprocessor.
*
* \param[in] x First item to concatenate.
* \param[in] y Second item to concatenate.
*
* \return Concatenated version of the expanded input.
*/
#define CONCAT_EXPANDED(x, y) CONCAT(x, y)
#endif
/* Public Interface - May be used in end-application: */
/* Enums: */
/** Enum for the possible error codes in the return value of the \ref USB_ProcessHIDReport() function. */
enum HID_Parse_ErrorCodes_t
{
HID_PARSE_Successful = 0, /**< Successful parse of the HID report descriptor, no error. */
HID_PARSE_HIDStackOverflow = 1, /**< More than \ref HID_STATETABLE_STACK_DEPTH nested PUSHes in the report. */
HID_PARSE_HIDStackUnderflow = 2, /**< A POP was found when the state table stack was empty. */
HID_PARSE_UnexpectedEndCollection = 3, /**< An END COLLECTION item found without matching COLLECTION item. */
HID_PARSE_UsageListOverflow = 4, /**< More than \ref HID_USAGE_STACK_DEPTH usages listed in a row. */
HID_PARSE_NoUnfilteredReportItems = 5, /**< All report items from the device were filtered by the filtering callback routine. */
};
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
/* Macros: */
#define HID_RI_DATA_SIZE_MASK 0x03
#define HID_RI_TYPE_MASK 0x0C
#define HID_RI_TAG_MASK 0xF0
#define HID_RI_TYPE_MAIN 0x00
#define HID_RI_TYPE_GLOBAL 0x04
#define HID_RI_TYPE_LOCAL 0x08
#define HID_RI_DATA_BITS_0 0x00
#define HID_RI_DATA_BITS_8 0x01
#define HID_RI_DATA_BITS_16 0x02
#define HID_RI_DATA_BITS_32 0x03
#define HID_RI_DATA_BITS(DataBits) CONCAT_EXPANDED(HID_RI_DATA_BITS_, DataBits)
#define _HID_RI_ENCODE_0(Data)
#define _HID_RI_ENCODE_8(Data) , (Data & 0xFF)
#define _HID_RI_ENCODE_16(Data) \
_HID_RI_ENCODE_8(Data) \
_HID_RI_ENCODE_8(Data >> 8)
#define _HID_RI_ENCODE_32(Data) \
_HID_RI_ENCODE_16(Data) \
_HID_RI_ENCODE_16(Data >> 16)
#define _HID_RI_ENCODE(DataBits, ...) CONCAT_EXPANDED(_HID_RI_ENCODE_, DataBits(__VA_ARGS__))
#define _HID_RI_ENTRY(Type, Tag, DataBits, ...) (Type | Tag | HID_RI_DATA_BITS(DataBits)) _HID_RI_ENCODE(DataBits, (__VA_ARGS__))
#endif
/* Public Interface - May be used in end-application: */
/* Macros: */
/** \name HID Input, Output and Feature Report Descriptor Item Flags */
/**@{*/
#define HID_IOF_CONSTANT (1 << 0)
#define HID_IOF_DATA (0 << 0)
#define HID_IOF_VARIABLE (1 << 1)
#define HID_IOF_ARRAY (0 << 1)
#define HID_IOF_RELATIVE (1 << 2)
#define HID_IOF_ABSOLUTE (0 << 2)
#define HID_IOF_WRAP (1 << 3)
#define HID_IOF_NO_WRAP (0 << 3)
#define HID_IOF_NON_LINEAR (1 << 4)
#define HID_IOF_LINEAR (0 << 4)
#define HID_IOF_NO_PREFERRED_STATE (1 << 5)
#define HID_IOF_PREFERRED_STATE (0 << 5)
#define HID_IOF_NULLSTATE (1 << 6)
#define HID_IOF_NO_NULL_POSITION (0 << 6)
#define HID_IOF_VOLATILE (1 << 7)
#define HID_IOF_NON_VOLATILE (0 << 7)
#define HID_IOF_BUFFERED_BYTES (1 << 8)
#define HID_IOF_BITFIELD (0 << 8)
/**@}*/
/** \name HID Report Descriptor Item Macros */
/**@{*/
#define HID_RI_INPUT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0x80, DataBits, __VA_ARGS__)
#define HID_RI_OUTPUT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0x90, DataBits, __VA_ARGS__)
#define HID_RI_COLLECTION(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xA0, DataBits, __VA_ARGS__)
#define HID_RI_FEATURE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xB0, DataBits, __VA_ARGS__)
#define HID_RI_END_COLLECTION(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xC0, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_PAGE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x00, DataBits, __VA_ARGS__)
#define HID_RI_LOGICAL_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x10, DataBits, __VA_ARGS__)
#define HID_RI_LOGICAL_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x20, DataBits, __VA_ARGS__)
#define HID_RI_PHYSICAL_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x30, DataBits, __VA_ARGS__)
#define HID_RI_PHYSICAL_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x40, DataBits, __VA_ARGS__)
#define HID_RI_UNIT_EXPONENT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x50, DataBits, __VA_ARGS__)
#define HID_RI_UNIT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x60, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_SIZE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x70, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_ID(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x80, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_COUNT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x90, DataBits, __VA_ARGS__)
#define HID_RI_PUSH(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0xA0, DataBits, __VA_ARGS__)
#define HID_RI_POP(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0xB0, DataBits, __VA_ARGS__)
#define HID_RI_USAGE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x00, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x10, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x20, DataBits, __VA_ARGS__)
/**@}*/
/* Type Defines: */
/** \brief HID Parser Report Item Min/Max Structure.
*
* Type define for an attribute with both minimum and maximum values (e.g. Logical Min/Max).
*/
typedef struct
{
uint32_t Minimum; /**< Minimum value for the attribute. */
uint32_t Maximum; /**< Maximum value for the attribute. */
} HID_MinMax_t;
/** \brief HID Parser Report Item Unit Structure.
*
* Type define for the Unit attributes of a report item.
*/
typedef struct
{
uint32_t Type; /**< Unit type (refer to HID specifications for details). */
uint8_t Exponent; /**< Unit exponent (refer to HID specifications for details). */
} HID_Unit_t;
/** \brief HID Parser Report Item Usage Structure.
*
* Type define for the Usage attributes of a report item.
*/
typedef struct
{
uint16_t Page; /**< Usage page of the report item. */
uint16_t Usage; /**< Usage of the report item. */
} HID_Usage_t;
/** \brief HID Parser Report Item Collection Path Structure.
*
* Type define for a COLLECTION object. Contains the collection attributes and a reference to the
* parent collection if any.
*/
typedef struct HID_CollectionPath
{
uint8_t Type; /**< Collection type (e.g. "Generic Desktop"). */
HID_Usage_t Usage; /**< Collection usage. */
struct HID_CollectionPath *Parent; /**< Reference to parent collection, or \c NULL if root collection. */
struct HID_CollectionPath *Next; /**< Reference to parent collection, or \c NULL if root collection. */
} HID_CollectionPath_t;
/** \brief HID Parser Report Item Attributes Structure.
*
* Type define for all the data attributes of a report item, except flags.
*/
typedef struct
{
uint8_t BitSize; /**< Size in bits of the report item's data. */
HID_Usage_t Usage; /**< Usage of the report item. */
HID_Unit_t Unit; /**< Unit type and exponent of the report item. */
HID_MinMax_t Logical; /**< Logical minimum and maximum of the report item. */
HID_MinMax_t Physical; /**< Physical minimum and maximum of the report item. */
} HID_ReportItem_Attributes_t;
/** \brief HID Parser Report Item Details Structure.
*
* Type define for a report item (IN, OUT or FEATURE) layout attributes and other details.
*/
typedef struct HID_ReportItem_s
{
uint16_t BitOffset; /**< Bit offset in the IN, OUT or FEATURE report of the item. */
uint8_t ItemType; /**< Report item type, a value in \ref HID_ReportItemTypes_t. */
uint16_t ItemFlags; /**< Item data flags, a mask of \c HID_IOF_* constants. */
uint8_t ReportID; /**< Report ID this item belongs to, or 0x00 if device has only one report */
HID_ReportItem_Attributes_t Attributes; /**< Report item attributes. */
uint32_t Value; /**< Current value of the report item - use \ref HID_ALIGN_DATA() when processing
* a retrieved value so that it is aligned to a specific type.
*/
uint32_t PreviousValue; /**< Previous value of the report item. */
struct HID_ReportItem_s* Next;
} HID_ReportItem_t;
/** \brief HID Parser Report Size Structure.
*
* Type define for a report item size information structure, to retain the size of a device's reports by ID.
*/
typedef struct HID_ReportSizeInfo_s
{
uint8_t ReportID; /**< Report ID of the report within the HID interface. */
uint16_t ReportSizeBits[3]; /**< Total number of bits in each report type for the given Report ID,
* indexed by the \ref HID_ReportItemTypes_t enum.
*/
struct HID_ReportSizeInfo_s* Next;
} HID_ReportSizeInfo_t;
/** \brief HID Parser State Structure.
*
* Type define for a complete processed HID report, including all report item data and collections.
*/
typedef struct
{
uint8_t TotalReportItems; /**< Total number of report items stored in the \c ReportItems array. */
HID_ReportItem_t* FirstReportItem; /**< Report items array, including all IN, OUT
* and FEATURE items.
*/
uint8_t TotalDeviceReports; /**< Number of reports within the HID interface */
uint16_t LargestReportSizeBits; /**< Largest report that the attached device will generate, in bits */
bool UsingReportIDs; /**< Indicates if the device has at least one REPORT ID
* element in its HID report descriptor.
*/
HID_ReportItem_t* LastReportItem;
} HID_ReportInfo_t;
/* Function Prototypes: */
/** Function to process a given HID report returned from an attached device, and store it into a given
* \ref HID_ReportInfo_t structure.
*
* \param[in] ReportData Buffer containing the device's HID report table.
* \param[in] ReportSize Size in bytes of the HID report table.
* \param[out] ParserData Pointer to a \ref HID_ReportInfo_t instance for the parser output.
*
* \return A value in the \ref HID_Parse_ErrorCodes_t enum.
*/
uint8_t USB_ProcessHIDReport(const uint8_t *ReportData,
uint16_t ReportSize,
HID_ReportInfo_t **ParserData);
void USB_FreeReportInfo(HID_ReportInfo_t *ReportInfo);
/** Extracts the given report item's value out of the given HID report and places it into the Value
* member of the report item's \ref HID_ReportItem_t structure.
*
* When called on a report with an item that exists in that report, this copies the report item's \c Value
* to its \c PreviousValue element for easy checking to see if an item's value has changed before processing
* a report. If the given item does not exist in the report, the function does not modify the report item's
* data.
*
* \param[in] ReportData Buffer containing an IN or FEATURE report from an attached device.
* \param[in,out] ReportItem Pointer to the report item of interest in a \ref HID_ReportInfo_t ReportItem array.
*
* \returns Boolean \c true if the item to retrieve was located in the given report, \c false otherwise.
*/
bool USB_GetHIDReportItemInfo(uint16_t report_id, const uint8_t *ReportData,
HID_ReportItem_t *const ReportItem);
/** Callback routine for the HID Report Parser. This callback <b>must</b> be implemented by the user code when
* the parser is used, to determine what report IN, OUT and FEATURE item's information is stored into the user
* \ref HID_ReportInfo_t structure. This can be used to filter only those items the application will be using, so that
* no RAM is wasted storing the attributes for report items which will never be referenced by the application.
*
* Report item pointers passed to this callback function may be cached by the user application for later use
* when processing report items. This provides faster report processing in the user application than would
* a search of the entire parsed report item table for each received or sent report.
*
* \param[in] CurrentItem Pointer to the current report item for user checking.
*
* \return Boolean \c true if the item should be stored into the \ref HID_ReportInfo_t structure, \c false if
* it should be ignored.
*/
bool CALLBACK_HIDParser_FilterHIDReportItem(HID_ReportItem_t *const CurrentItem);
/** Enum for the different types of HID reports. */
enum HID_ReportItemTypes_t
{
HID_REPORT_ITEM_In = 0, /**< Indicates that the item is an IN report type. */
HID_REPORT_ITEM_Out = 1, /**< Indicates that the item is an OUT report type. */
HID_REPORT_ITEM_Feature = 2, /**< Indicates that the item is a FEATURE report type. */
};
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
/* Type Defines: */
typedef struct
{
HID_ReportItem_Attributes_t Attributes;
uint8_t ReportCount;
uint8_t ReportID;
} HID_StateTable_t;
#endif
/* Disable C linkage for C++ Compilers: */
#if defined(__cplusplus)
}
#endif
/** @} */

1
lib/tusb_gamepad Submodule

Submodule lib/tusb_gamepad added at 5e5ee00ee8

1
lib/tusb_xinput Submodule

Submodule lib/tusb_xinput added at b34398847a

View File

@@ -1,62 +0,0 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

@@ -1,197 +0,0 @@
#include <cstdint>
#include "Gamepad.h"
int16_t scale_uint8_to_int16(uint8_t value, bool invert)
{
const uint32_t scaling_factor = 65535;
const int32_t bias = -32768;
int32_t scaled_value = ((uint32_t)value * scaling_factor) >> 8;
scaled_value += bias;
if (scaled_value < -32768)
{
scaled_value = -32768;
}
else if (scaled_value > 32767)
{
scaled_value = 32767;
}
if (invert)
{
scaled_value = -scaled_value - 1;
}
return (int16_t)scaled_value;
}
void Gamepad::update_gamepad_state_from_dualshock4(const sony_ds4_report_t* ds4_data)
{
reset_state();
switch(ds4_data->dpad)
{
case PS4_DPAD_MASK_UP:
state.up = true;
break;
case PS4_DPAD_MASK_UP_RIGHT:
state.up = true;
state.right = true;
break;
case PS4_DPAD_MASK_RIGHT:
state.right = true;
break;
case PS4_DPAD_MASK_RIGHT_DOWN:
state.right = true;
state.down = true;
break;
case PS4_DPAD_MASK_DOWN:
state.down = true;
break;
case PS4_DPAD_MASK_DOWN_LEFT:
state.down = true;
state.left = true;
break;
case PS4_DPAD_MASK_LEFT:
state.left = true;
break;
case PS4_DPAD_MASK_LEFT_UP:
state.left = true;
state.up = true;
break;
}
if (ds4_data->square) state.x = true;
if (ds4_data->cross) state.a = true;
if (ds4_data->circle) state.b = true;
if (ds4_data->triangle) state.y = true;
if (ds4_data->share) state.back = true;
if (ds4_data->option) state.start = true;
if (ds4_data->ps) state.sys = true;
if (ds4_data->l1) state.lb = true;
if (ds4_data->r1) state.rb = true;
if (ds4_data->l3) state.l3 = true;
if (ds4_data->r3) state.r3 = true;
state.lt = ds4_data->l2_trigger;
state.rt = ds4_data->r2_trigger;
state.lx = scale_uint8_to_int16(ds4_data->lx, false);
state.ly = scale_uint8_to_int16(ds4_data->ly, true);
state.rx = scale_uint8_to_int16(ds4_data->rx, false);
state.ry = scale_uint8_to_int16(ds4_data->ry, true);
}
void Gamepad::update_gamepad_state_from_dualsense(const dualsense_input_report* ds_data)
{
reset_state();
switch(ds_data->button[0])
{
case PS5_MASK_DPAD_UP:
state.up = true;
break;
case PS5_MASK_DPAD_UP_RIGHT:
state.up = true;
state.right = true;
break;
case PS5_MASK_DPAD_RIGHT:
state.right = true;
break;
case PS5_MASK_DPAD_RIGHT_DOWN:
state.right = true;
state.down = true;
break;
case PS5_MASK_DPAD_DOWN:
state.down = true;
break;
case PS5_MASK_DPAD_DOWN_LEFT:
state.down = true;
state.left = true;
break;
case PS5_MASK_DPAD_LEFT:
state.left = true;
break;
case PS5_MASK_DPAD_LEFT_UP:
state.left = true;
state.up = true;
break;
}
if (ds_data->button[0] & PS5_MASK_SQUARE) state.x = true;
if (ds_data->button[0] & PS5_MASK_CROSS) state.a = true;
if (ds_data->button[0] & PS5_MASK_CIRCLE) state.b = true;
if (ds_data->button[0] & PS5_MASK_TRIANGLE) state.y = true;
if (ds_data->button[1] & PS5_MASK_L1) state.lb = true;
if (ds_data->button[1] & PS5_MASK_R1) state.rb = true;
if (ds_data->button[1] & PS5_MASK_SHARE) state.back = true;
if (ds_data->button[1] & PS5_MASK_OPTIONS) state.start = true;
if (ds_data->button[1] & PS5_MASK_L3) state.l3 = true;
if (ds_data->button[1] & PS5_MASK_R3) state.r3 = true;
if (ds_data->button[2] & PS5_MASK_PS) state.sys = true;
state.lt = ds_data->lt;
state.rt = ds_data->rt;
state.lx = scale_uint8_to_int16(ds_data->lx, false);
state.ly = scale_uint8_to_int16(ds_data->ly, true);
state.rx = scale_uint8_to_int16(ds_data->rx, false);
state.ry = scale_uint8_to_int16(ds_data->ry, true);
}
void Gamepad::update_gamepad_state_from_xinput(const xinput_gamepad_t* xinput_data)
{
reset_state();
state.up = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
state.down = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
state.left = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
state.right = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
state.a = (xinput_data->wButtons & XINPUT_GAMEPAD_A) != 0;
state.b = (xinput_data->wButtons & XINPUT_GAMEPAD_B) != 0;
state.x = (xinput_data->wButtons & XINPUT_GAMEPAD_X) != 0;
state.y = (xinput_data->wButtons & XINPUT_GAMEPAD_Y) != 0;
state.l3 = (xinput_data->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
state.r3 = (xinput_data->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
state.back = (xinput_data->wButtons & XINPUT_GAMEPAD_BACK) != 0;
state.start = (xinput_data->wButtons & XINPUT_GAMEPAD_START) != 0;
state.rb = (xinput_data->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
state.lb = (xinput_data->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
state.sys = (xinput_data->wButtons & XINPUT_GAMEPAD_GUIDE) != 0;
state.lt = xinput_data->bLeftTrigger;
state.rt = xinput_data->bRightTrigger;
state.lx = xinput_data->sThumbLX;
state.ly = xinput_data->sThumbLY;
state.rx = xinput_data->sThumbRX;
state.ry = xinput_data->sThumbRY;
}
void Gamepad::reset_state()
{
state.up = state.down = state.left = state.right = false;
state.a = state.b = state.x = state.y = false;
state.l3 = state.r3 = state.back = state.start = false;
state.rb = state.lb = state.sys = false;
state.lt = state.rt = 0;
state.lx = state.ly = state.rx = state.ry = 0;
}
void GamepadOut::update_gamepad_rumble(uint8_t left_rumble, uint8_t right_rumble)
{
out_state.lrumble = left_rumble;
out_state.rrumble = right_rumble;}

View File

@@ -1,69 +0,0 @@
#pragma once
#ifndef _GAMEPAD_H_
#define _GAMEPAD_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <cstdint>
#include "usbh/tusb_hid/ps4.h"
#include "usbh/tusb_hid/ps5.h"
#include "usbh/tusb_xinput/xinput_host.h"
struct GamepadState{
bool up {false};
bool down {false};
bool left {false};
bool right {false};
bool a {false};
bool b {false};
bool x {false};
bool y {false};
bool l3 {false};
bool r3 {false};
bool back {false};
bool start {false};
bool rb {false};
bool lb {false};
bool sys {false};
uint8_t lt {0};
uint8_t rt {0};
int16_t lx {0};
int16_t ly {0};
int16_t rx {0};
int16_t ry {0};
};
struct GamepadOutState{
uint8_t lrumble {0};
uint8_t rrumble {0};
};
class Gamepad {
public:
GamepadState state;
void update_gamepad_state_from_dualsense(const dualsense_input_report* ds_data);
void update_gamepad_state_from_dualshock4(const sony_ds4_report_t* ds4_data);
void update_gamepad_state_from_xinput(const xinput_gamepad_t* xinput_data);
void reset_state();
};
class GamepadOut {
public:
GamepadOutState out_state;
void update_gamepad_rumble(uint8_t left_rumble, uint8_t right_rumble);
};
extern Gamepad gamepad;
extern GamepadOut gamepadOut;
#ifdef __cplusplus
}
#endif
#endif // _GAMEPAD_H_

View File

@@ -1,16 +1,128 @@
#include <pico/stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/flash.h"
#include "tusb.h"
#include "input_mode.h"
enum InputMode load_input_mode()
#define AIRCR_REG (*((volatile uint32_t *)(0xE000ED0C))) // Address of the AIRCR register
#define AIRCR_SYSRESETREQ (1 << 2) // Position of SYSRESETREQ bit in AIRCR
#define AIRCR_VECTKEY (0x5FA << 16) // VECTKEY value
#define FLASH_TARGET_OFFSET (256 * 1024)
#define FLASH_SIZE_BYTES (2 * 1024 * 1024)
InputMode current_input_mode;
void system_reset()
{
#ifdef HOST_ORIGINAL_XBOX
return INPUT_MODE_XBOXORIGINAL;
#elif HOST_NINTENDO_SWITCH
return INPUT_MODE_SWITCH;
#else
return INPUT_MODE_XINPUT;
#endif
AIRCR_REG = AIRCR_VECTKEY | AIRCR_SYSRESETREQ;
while(1);
}
bool store_input_mode(enum InputMode new_mode)
{
int buf[FLASH_PAGE_SIZE/sizeof(int)];
memset(buf, 0xFF, FLASH_PAGE_SIZE);
int saved_mode = new_mode; // changed to uint8?
buf[0] = saved_mode;
uint32_t saved_interrupts = save_and_disable_interrupts();
flash_range_erase((FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE), FLASH_SECTOR_SIZE);
flash_range_program((FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE), (uint8_t *)buf, FLASH_PAGE_SIZE);
restore_interrupts(saved_interrupts);
const uint8_t *flash_target_contents = (const uint8_t *)(XIP_BASE + FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE);
if ((uint8_t)saved_mode != *flash_target_contents)
{
return false;
}
return true;
}
bool change_input_mode(GamepadButtons buttons)
{
if (!buttons.start)
{
return false;
}
InputMode new_mode = current_input_mode;
if (buttons.up)
{
new_mode = INPUT_MODE_XINPUT;
}
else if (buttons.left)
{
new_mode = INPUT_MODE_DINPUT;
}
else if (buttons.right)
{
new_mode = INPUT_MODE_XBOXORIGINAL;
}
else if (buttons.down)
{
new_mode = INPUT_MODE_SWITCH;
}
else if (buttons.a)
{
new_mode = INPUT_MODE_PSCLASSIC;
}
bool mode_stored = false;
if (new_mode)
{
tud_disconnect();
sleep_ms(300);
multicore_reset_core1(); // stop tusb host
if (store_input_mode(new_mode))
{
system_reset(); // reset rp2040
mode_stored = true;
}
}
return mode_stored;
}
enum InputMode get_input_mode()
{
#if (CDC_DEBUG > 0)
return INPUT_MODE_USBSERIAL;
#endif
const uint8_t *stored_value = (const uint8_t *)(XIP_BASE + FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE);
#if (MAX_GAMEPADS < 1)
if ((*stored_value == INPUT_MODE_DINPUT) || (*stored_value == INPUT_MODE_SWITCH))
{
current_input_mode = (enum InputMode)*stored_value;
}
else
{
current_input_mode = INPUT_MODE_DINPUT;
}
#else
if (*stored_value >= INPUT_MODE_XINPUT && *stored_value <= INPUT_MODE_XBOXORIGINAL)
{
current_input_mode = (enum InputMode)*stored_value;
}
else
{
current_input_mode = INPUT_MODE_XBOXORIGINAL;
}
#endif
return current_input_mode;
}

View File

@@ -5,26 +5,10 @@
extern "C" {
#endif
enum InputMode
{
INPUT_MODE_XINPUT,
INPUT_MODE_SWITCH,
// INPUT_MODE_HID,
// INPUT_MODE_KEYBOARD,
// INPUT_MODE_PS4,
// INPUT_MODE_XBONE,
// INPUT_MODE_MDMINI,
// INPUT_MODE_NEOGEO,
// INPUT_MODE_PCEMINI,
// INPUT_MODE_EGRET,
// INPUT_MODE_ASTRO,
// INPUT_MODE_PSCLASSIC,
INPUT_MODE_XBOXORIGINAL,
// INPUT_MODE_CONFIG,
};
#include "tusb_gamepad.h"
// void check_and_update_input_mode (uint8_t input_id);
enum InputMode load_input_mode();
enum InputMode get_input_mode();
bool change_input_mode(GamepadButtons buttons);
#ifdef __cplusplus
}

View File

@@ -1,31 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "tusb.h"
#include "bsp/board_api.h"
#include "tusb_gamepad.h"
#include "drivermanager.h"
#include "drivers/gpdriver.h"
#include "usbh/tusb_host.h"
#include "usbd/drivers/drivermanager.h"
#include "usbd/drivers/gpdriver.h"
#include "Gamepad.h"
#include "input_mode.h"
Gamepad gamepad;
GamepadOut gamepadOut;
int main(void)
{
set_sys_clock_khz(120000, true);
board_init();
InputMode mode = load_input_mode();
InputMode mode = get_input_mode();
DriverManager& driverManager = DriverManager::getInstance();
driverManager.setup(mode);
@@ -34,11 +28,39 @@ int main(void)
multicore_reset_core1();
multicore_launch_core1(usbh_main);
GamepadButtons prev_gamepad_buttons = gamepad(0)->buttons;
absolute_time_t last_time_gamepad_changed = get_absolute_time();
absolute_time_t last_time_gamepad_checked = get_absolute_time();
while (1)
{
for (int i = 0; i < MAX_GAMEPADS; i++)
{
uint8_t outBuffer[64];
GPDriver* driver = driverManager.getDriver();
driver->process(&gamepad, outBuffer);
driver->process(i, gamepad(i), outBuffer);
driver->update_rumble(i, gamepad(i));
}
if (absolute_time_diff_us(last_time_gamepad_checked, get_absolute_time()) >= 500000)
{
// check if digital buttons have changed
if (memcmp(&gamepad(0)->buttons, &prev_gamepad_buttons, sizeof(GamepadButtons)) != 0)
{
memcpy(&prev_gamepad_buttons, &gamepad(0)->buttons, sizeof(GamepadButtons));
last_time_gamepad_changed = get_absolute_time();
}
// haven't changed for 3 seconds
else if (absolute_time_diff_us(last_time_gamepad_changed, get_absolute_time()) >= 3000000)
{
if (!change_input_mode(prev_gamepad_buttons))
{
last_time_gamepad_changed = get_absolute_time();
}
}
last_time_gamepad_checked = get_absolute_time();
}
sleep_ms(1);
tud_task();

39
src/ogxm_config.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef _OGXM_CONFIG_H_
#define _OGXM_CONFIG_H_
#define ADAFRUIT_FEATHER_USBH 1
#define PI_PICO 2
#define RP2040_ZERO_INTERPOSER 3
// Options //
#define OGXM_BOARD ADAFRUIT_FEATHER_USBH
#define CDC_DEBUG 0
// ------- //
#if OGXM_BOARD == ADAFRUIT_FEATHER_USBH
#define PIO_USB_DP_PIN 16 // DM = 17
#define LED_INDICATOR_PIN 13
#define VCC_EN_PIN 18
// #define NEOPIXEL_PWR_PIN 20
// #define NEOPIXEL_CTRL_PIN 21
#elif OGXM_BOARD == PI_PICO
#define PIO_USB_DP_PIN 0 // DM = 1
#define LED_INDICATOR_PIN 25
#elif OGXM_BOARD == RP2040_ZERO_INTERPOSER
#define PIO_USB_DP_PIN 10 // DM = 11
#define LED_INDICATOR_PIN 13
#endif
#ifndef OGXM_BOARD
#error OGXM_BOARD must be defined in ogxm_config.h
#endif
#ifndef CDC_DEBUG
#define CDC_DEBUG 0
#endif
#endif // _OGXM_CONFIG_H_

View File

@@ -26,6 +26,10 @@
#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_
#include "board_config.h"
#define MAX_GAMEPADS 1
#ifdef __cplusplus
extern "C" {
#endif
@@ -87,7 +91,7 @@
#define CFG_TUD_ENABLED 1
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
// #define CFG_TUSB_DEBUG 0
#define CFG_TUSB_DEBUG 0
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
@@ -129,10 +133,13 @@
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
#define CFG_TUD_CDC_RX_BUFSIZE 256
#define CFG_TUD_CDC_TX_BUFSIZE 256
//------------- CLASS -------------//
#define CFG_TUD_CDC 0
#define CFG_TUD_CDC 1
#define CFG_TUD_ECM_RNDIS 0
#define CFG_TUD_HID 2
#define CFG_TUD_HID (MAX_GAMEPADS + 1)
//--------------------------------------------------------------------
// HOST CONFIGURATION
@@ -147,12 +154,12 @@
// Size of buffer to hold descriptors and other data used for enumeration
#define CFG_TUH_ENUMERATION_BUFSIZE 512
#define CFG_TUH_HUB 1
#define CFG_TUH_HUB MAX_GAMEPADS
#define CFG_TUH_CDC 0
#define CFG_TUH_HID 1 // typical keyboard + mouse device can have 3-4 HID interfaces
#define CFG_TUH_HID MAX_GAMEPADS // typical keyboard + mouse device can have 3-4 HID interfaces
#define CFG_TUH_MSC 0
#define CFG_TUH_VENDOR 0
#define CFG_TUH_XINPUT 1
#define CFG_TUH_XINPUT MAX_GAMEPADS
// max device support (excluding hub device)
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports

View File

@@ -1,73 +0,0 @@
#include "drivermanager.h"
// #include "net/NetDriver.h"
// #include "astro/AstroDriver.h"
// #include "egret/EgretDriver.h"
// #include "hid/HIDDriver.h"
// #include "keyboard/KeyboardDriver.h"
// #include "mdmini/MDMiniDriver.h"
// #include "neogeo/NeoGeoDriver.h"
// #include "pcengine/PCEngineDriver.h"
// #include "psclassic/PSClassicDriver.h"
// #include "ps4/PS4Driver.h"
#include "switch/SwitchDriver.h"
// #include "xbone/XBOneDriver.h"
#include "xboxog/XboxOriginalDriver.h"
#include "xinput/XInputDriver.h"
// ^ working on more of these
void DriverManager::setup(InputMode mode) {
switch (mode) {
// case INPUT_MODE_CONFIG:
// driver = new NetDriver();
// break;
// case INPUT_MODE_ASTRO:
// driver = new AstroDriver();
// break;
// case INPUT_MODE_EGRET:
// driver = new EgretDriver();
// break;
// case INPUT_MODE_HID:
// driver = new HIDDriver();
// break;
// case INPUT_MODE_KEYBOARD:
// driver = new KeyboardDriver();
// break;
// case INPUT_MODE_MDMINI:
// driver = new MDMiniDriver();
// break;
// case INPUT_MODE_NEOGEO:
// driver = new NeoGeoDriver();
// break;
// case INPUT_MODE_PSCLASSIC:
// driver = new PSClassicDriver();
// break;
// case INPUT_MODE_PCEMINI:
// driver = new PCEngineDriver();
// break;
// case INPUT_MODE_PS4:
// driver = new PS4Driver();
// break;
case INPUT_MODE_SWITCH:
driver = new SwitchDriver();
break;
// case INPUT_MODE_XBONE:
// driver = new XBOneDriver();
// break;
case INPUT_MODE_XBOXORIGINAL:
driver = new XboxOriginalDriver();
break;
case INPUT_MODE_XINPUT:
driver = new XInputDriver();
break;
default:
return;
}
// Initialize our chosen driver
driver->initialize();
// Start the TinyUSB Device functionality
tud_init(TUD_OPT_RHPORT);
}

View File

@@ -1,24 +0,0 @@
#ifndef _DRIVERMANAGER_H
#define _DRIVERMANAGER_H
#include "gpdriver.h"
#include "input_mode.h"
class GPDriver;
class DriverManager {
public:
DriverManager(DriverManager const&) = delete;
void operator=(DriverManager const&) = delete;
static DriverManager& getInstance() {// Thread-safe storage ensures cross-thread talk
static DriverManager instance; // Guaranteed to be destroyed. // Instantiated on first use.
return instance;
}
GPDriver * getDriver() { return driver; }
void setup(InputMode mode);
private:
DriverManager() {}
GPDriver * driver;
};
#endif

View File

@@ -1,40 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
*/
#ifndef _GPDRIVER_H_
#define _GPDRIVER_H_
#include "Gamepad.h"
#include "tusb_config.h"
#include "tusb.h"
#include "class/hid/hid.h"
#include "device/usbd_pvt.h"
// Forward declare gamepad
class Gamepad;
//
// GP2040-CE USB Device Class Driver
//
class GPDriver {
public:
virtual void initialize() = 0;
virtual void process(Gamepad * gamepad, uint8_t * outBuffer) = 0;
virtual uint16_t get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) = 0;
virtual void set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) = 0;
virtual bool vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) = 0;
virtual const uint16_t * get_descriptor_string_cb(uint8_t index, uint16_t langid) = 0;
virtual const uint8_t * get_descriptor_device_cb() = 0;
virtual const uint8_t * get_hid_descriptor_report_cb(uint8_t itf) = 0;
virtual const uint8_t * get_descriptor_configuration_cb(uint8_t index) = 0;
virtual const uint8_t * get_descriptor_device_qualifier_cb() = 0;
virtual uint16_t GetJoystickMidValue() = 0;
const usbd_class_driver_t * get_class_driver() { return &class_driver; }
protected:
usbd_class_driver_t class_driver;
};
#endif

View File

@@ -1,26 +0,0 @@
#ifndef _DRIVER_HELPER_H_
#define _DRIVER_HELPER_H_
static uint16_t * getStringDescriptor(const char * value, uint8_t index)
{
static uint16_t descriptorStringBuffer[32]; // Max 64 bytes, 31 unicode characters
size_t charCount;
if ( index == 0 ) // language always has a character count of 1
charCount = 1;
else {
charCount = strlen(value);
if (charCount > 31)
charCount = 31;
}
// Fill descriptionStringBuffer[1] .. [32]
for (uint8_t i = 0; i < charCount; i++)
descriptorStringBuffer[i + 1] = value[i];
// first byte (descriptionStringBuffer[0]) is length (including header), second byte is string type
descriptorStringBuffer[0] = (0x03 << 8) | (2 * (uint8_t)charCount + 2);
// Cast temp buffer to final result
return descriptorStringBuffer;
}
#endif // _DRIVER_HELPER_H_

View File

@@ -1,194 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2021 Jason Skuby (mytechtoybox.com)
*/
#pragma once
#include <stdint.h>
#define SWITCH_ENDPOINT_SIZE 64
// HAT report (4 bits)
#define SWITCH_HAT_UP 0x00
#define SWITCH_HAT_UPRIGHT 0x01
#define SWITCH_HAT_RIGHT 0x02
#define SWITCH_HAT_DOWNRIGHT 0x03
#define SWITCH_HAT_DOWN 0x04
#define SWITCH_HAT_DOWNLEFT 0x05
#define SWITCH_HAT_LEFT 0x06
#define SWITCH_HAT_UPLEFT 0x07
#define SWITCH_HAT_NOTHING 0x08
// Button report (16 bits)
#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)
// Switch analog sticks only report 8 bits
#define SWITCH_JOYSTICK_MIN 0x00
#define SWITCH_JOYSTICK_MID 0x80
#define SWITCH_JOYSTICK_MAX 0xFF
typedef struct __attribute((packed, aligned(1)))
{
uint16_t buttons;
uint8_t hat;
uint8_t lx;
uint8_t ly;
uint8_t rx;
uint8_t ry;
uint8_t vendor;
} SwitchReport;
typedef struct
{
uint16_t buttons;
uint8_t hat;
uint8_t lx;
uint8_t ly;
uint8_t rx;
uint8_t ry;
} SwitchOutReport;
static const uint8_t switch_string_language[] = { 0x09, 0x04 };
static const uint8_t switch_string_manufacturer[] = "HORI CO.,LTD.";
static const uint8_t switch_string_product[] = "POKKEN CONTROLLER";
static const uint8_t switch_string_version[] = "1.0";
static const uint8_t *switch_string_descriptors[] __attribute__((unused)) =
{
switch_string_language,
switch_string_manufacturer,
switch_string_product,
switch_string_version
};
static const uint8_t switch_device_descriptor[] =
{
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB 2.00
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 64
0x0D, 0x0F, // idVendor 0x0F0D
0x92, 0x00, // idProduct 0x92
0x00, 0x01, // bcdDevice 2.00
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x00, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
static const uint8_t switch_hid_descriptor[] =
{
0x09, // bLength
0x21, // bDescriptorType (HID)
0x11, 0x01, // bcdHID 1.11
0x00, // bCountryCode
0x01, // bNumDescriptors
0x22, // bDescriptorType[0] (HID)
0x56, 0x00, // wDescriptorLength[0] 86
};
static const uint8_t switch_configuration_descriptor[] =
{
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0x29, 0x00, // wTotalLength 41
0x01, // bNumInterfaces 1
0x01, // bConfigurationValue
0x00, // iConfiguration (String Index)
0x80, // bmAttributes
0xFA, // bMaxPower 500mA
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber 0
0x00, // bAlternateSetting
0x02, // bNumEndpoints 2
0x03, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface (String Index)
0x09, // bLength
0x21, // bDescriptorType (HID)
0x11, 0x01, // bcdHID 1.11
0x00, // bCountryCode
0x01, // bNumDescriptors
0x22, // bDescriptorType[0] (HID)
0x56, 0x00, // wDescriptorLength[0] 86
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x02, // bEndpointAddress (OUT/H2D)
0x03, // bmAttributes (Interrupt)
0x40, 0x00, // wMaxPacketSize 64
0x01, // bInterval 1 (unit depends on device speed)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt)
0x40, 0x00, // wMaxPacketSize 64
0x01, // bInterval 1 (unit depends on device speed)
};
static const uint8_t switch_report_descriptor[] =
{
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 (0x01)
0x29, 0x10, // Usage Maximum (0x10)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
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 (System: English Rotation, Length: Centimeter)
0x09, 0x39, // Usage (Hat switch)
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
0x65, 0x00, // Unit (None)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x46, 0xFF, 0x00, // Physical Maximum (255)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x35, // Usage (Rz)
0x75, 0x08, // Report Size (8)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x20, // Usage (0x20)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0A, 0x21, 0x26, // Usage (0x2621)
0x95, 0x08, // Report Count (8)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
};

View File

@@ -1,143 +0,0 @@
#include "drivers/switch/SwitchDriver.h"
#include "drivers/shared/driverhelper.h"
void SwitchDriver::initialize() {
switchReport = {
.buttons = 0,
.hat = SWITCH_HAT_NOTHING,
.lx = SWITCH_JOYSTICK_MID,
.ly = SWITCH_JOYSTICK_MID,
.rx = SWITCH_JOYSTICK_MID,
.ry = SWITCH_JOYSTICK_MID,
.vendor = 0,
};
class_driver = {
#if CFG_TUSB_DEBUG >= 2
.name = "SWITCH",
#endif
.init = hidd_init,
.reset = hidd_reset,
.open = hidd_open,
.control_xfer_cb = hidd_control_xfer_cb,
.xfer_cb = hidd_xfer_cb,
.sof = NULL
};
}
void SwitchDriver::process(Gamepad * gamepad, uint8_t * outBuffer) {
switchReport.hat = SWITCH_HAT_NOTHING;
if (gamepad->state.up)
{
if (gamepad->state.right)
{
switchReport.hat = SWITCH_HAT_UPRIGHT;
}
else if (gamepad->state.left)
{
switchReport.hat = SWITCH_HAT_UPLEFT;
}
else
{
switchReport.hat = SWITCH_HAT_UP;
}
}
else if (gamepad->state.down)
{
if (gamepad->state.right)
{
switchReport.hat = SWITCH_HAT_DOWNRIGHT;
}
else if (gamepad->state.left)
{
switchReport.hat = SWITCH_HAT_DOWNLEFT;
}
else
{
switchReport.hat = SWITCH_HAT_DOWN;
}
}
else if (gamepad->state.left)
{
switchReport.hat = SWITCH_HAT_LEFT;
}
else if (gamepad->state.right)
{
switchReport.hat = SWITCH_HAT_RIGHT;
}
switchReport.buttons = 0
| (gamepad->state.a ? SWITCH_MASK_B : 0)
| (gamepad->state.b ? SWITCH_MASK_A : 0)
| (gamepad->state.x ? SWITCH_MASK_Y : 0)
| (gamepad->state.y ? SWITCH_MASK_X : 0)
| (gamepad->state.lb ? SWITCH_MASK_L : 0)
| (gamepad->state.rb ? SWITCH_MASK_R : 0)
| (gamepad->state.lt ? SWITCH_MASK_ZL : 0)
| (gamepad->state.rt ? SWITCH_MASK_ZR : 0)
| (gamepad->state.back ? SWITCH_MASK_MINUS : 0)
| (gamepad->state.start ? SWITCH_MASK_PLUS : 0)
| (gamepad->state.l3 ? SWITCH_MASK_L3 : 0)
| (gamepad->state.r3 ? SWITCH_MASK_R3 : 0)
| (gamepad->state.sys ? SWITCH_MASK_HOME : 0)
// | (gamepad->pressedA2() ? SWITCH_MASK_CAPTURE : 0) // need to come up with something for this
;
switchReport.lx = static_cast<uint8_t>((gamepad->state.lx + 32768) >> 8);
switchReport.ly = static_cast<uint8_t>(((-gamepad->state.ly - 1) + 32768) >> 8);
switchReport.rx = static_cast<uint8_t>((gamepad->state.rx + 32768) >> 8);
switchReport.ry = static_cast<uint8_t>(((-gamepad->state.ry - 1) + 32768) >> 8);
// Wake up TinyUSB device
if (tud_suspended())
tud_remote_wakeup();
void * report = &switchReport;
uint16_t report_size = sizeof(switchReport);
if (memcmp(last_report, report, report_size) != 0) {
// HID ready + report sent, copy previous report
if (tud_hid_ready() && tud_hid_report(0, report, report_size) == true ) {
memcpy(last_report, report, report_size);
}
}
}
// tud_hid_get_report_cb
uint16_t SwitchDriver::get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
memcpy(buffer, &switchReport, sizeof(SwitchReport));
return sizeof(SwitchReport);
}
// Only PS4 does anything with set report
void SwitchDriver::set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {}
// Only XboxOG and Xbox One use vendor control xfer cb
bool SwitchDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
return false;
}
const uint16_t * SwitchDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) {
const char *value = (const char *)switch_string_descriptors[index];
return getStringDescriptor(value, index); // getStringDescriptor returns a static array
}
const uint8_t * SwitchDriver::get_descriptor_device_cb() {
return switch_device_descriptor;
}
const uint8_t * SwitchDriver::get_hid_descriptor_report_cb(uint8_t itf) {
return switch_report_descriptor;
}
const uint8_t * SwitchDriver::get_descriptor_configuration_cb(uint8_t index) {
return switch_configuration_descriptor;
}
const uint8_t * SwitchDriver::get_descriptor_device_qualifier_cb() {
return nullptr;
}
uint16_t SwitchDriver::GetJoystickMidValue() {
return SWITCH_JOYSTICK_MID << 8;
}

View File

@@ -1,30 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
*/
#ifndef _SWITCH_DRIVER_H_
#define _SWITCH_DRIVER_H_
#include "gpdriver.h"
#include "drivers/switch/SwitchDescriptors.h"
class SwitchDriver : public GPDriver {
public:
virtual void initialize();
virtual void process(Gamepad * gamepad, uint8_t * outBuffer);
virtual uint16_t get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen);
virtual void set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize);
virtual bool vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request);
virtual const uint16_t * get_descriptor_string_cb(uint8_t index, uint16_t langid);
virtual const uint8_t * get_descriptor_device_cb();
virtual const uint8_t * get_hid_descriptor_report_cb(uint8_t itf) ;
virtual const uint8_t * get_descriptor_configuration_cb(uint8_t index);
virtual const uint8_t * get_descriptor_device_qualifier_cb();
virtual uint16_t GetJoystickMidValue();
private:
uint8_t last_report[CFG_TUD_ENDPOINT0_SIZE] = { };
SwitchReport switchReport;
};
#endif // _SWITCH_DRIVER_H_

View File

@@ -1,113 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 Open Stick Community (gp2040-ce.info)
*/
#ifndef _USBDRIVER_CPP_
#define _USBDRIVER_CPP_
#include "tusb.h"
#include "drivermanager.h"
static bool usb_mounted;
static bool usb_suspended;
bool get_usb_mounted(void)
{
return usb_mounted;
}
bool get_usb_suspended(void)
{
return usb_suspended;
}
const usbd_class_driver_t *usbd_app_driver_get_cb(uint8_t *driver_count)
{
*driver_count = 1;
return DriverManager::getInstance().getDriver()->get_class_driver();
}
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
return DriverManager::getInstance().getDriver()->get_report(report_id, report_type, buffer, reqlen);
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
DriverManager::getInstance().getDriver()->set_report(report_id, report_type, buffer, bufsize);
tud_hid_report(report_id, buffer, bufsize); // echo back anything we received from host
}
// Invoked when device is mounted
void tud_mount_cb(void)
{
usb_mounted = true;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
usb_mounted = false;
}
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en)
{
(void)remote_wakeup_en;
usb_suspended = true;
}
// Invoked when usb bus is resumed
void tud_resume_cb(void)
{
usb_suspended = false;
}
// Vendor Controlled XFER occured
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
{
return DriverManager::getInstance().getDriver()->vendor_control_xfer_cb(rhport, stage, request);
}
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
return DriverManager::getInstance().getDriver()->get_descriptor_string_cb(index, langid);
}
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const *tud_descriptor_device_cb()
{
return DriverManager::getInstance().getDriver()->get_descriptor_device_cb();
}
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf)
{
return DriverManager::getInstance().getDriver()->get_hid_descriptor_report_cb(itf);
}
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
return DriverManager::getInstance().getDriver()->get_descriptor_configuration_cb(index);
}
uint8_t const* tud_descriptor_device_qualifier_cb()
{
return DriverManager::getInstance().getDriver()->get_descriptor_device_qualifier_cb();
}
#endif

View File

@@ -1,7 +0,0 @@
#ifndef _USB_DRIVER_H_
#define _USB_DRIVER_H_
bool get_usb_mounted(void);
bool get_usb_suspended(void);
#endif // #ifndef _USB_DRIVER_H_

View File

@@ -1,23 +0,0 @@
#pragma once
#include <stdint.h>
#include "drivers/xboxog/xid/xid_driver.h"
#define XboxOriginalReport USB_XboxGamepad_InReport_t
static const uint8_t xboxoriginal_string_language[] = { 0x09, 0x04 };
static const uint8_t xboxoriginal_string_manufacturer[] = "";
static const uint8_t xboxoriginal_string_product[] = "";
static const uint8_t xboxoriginal_string_version[] = "1.0";
static const uint8_t *xboxoriginal_string_descriptors[] __attribute__((unused)) =
{
xboxoriginal_string_language,
xboxoriginal_string_manufacturer,
xboxoriginal_string_product,
xboxoriginal_string_version
};
static const uint8_t *xboxoriginal_device_descriptor = (const uint8_t*)&XID_DESC_DEVICE;
static const uint8_t *xboxoriginal_configuration_descriptor = (const uint8_t*)&XID_DESC_CONFIGURATION;

View File

@@ -1,120 +0,0 @@
#include "drivers/xboxog/XboxOriginalDriver.h"
#include "drivers/xboxog/xid/xid.h"
#include "drivers/shared/driverhelper.h"
#include "Gamepad.h"
void xid_process_rumble_data(USB_XboxGamepad_OutReport_t xid_rumble_data)
{
gamepadOut.out_state.lrumble = xid_rumble_data.lValue >> 8;
gamepadOut.out_state.rrumble = xid_rumble_data.rValue >> 8;
}
void XboxOriginalDriver::initialize() {
xboxOriginalReport = {
.dButtons = 0,
.A = 0,
.B = 0,
.X = 0,
.Y = 0,
.BLACK = 0,
.WHITE = 0,
.L = 0,
.R = 0,
.leftStickX = 0,
.leftStickY = 0,
.rightStickX = 0,
.rightStickY = 0,
};
// Copy XID driver to local class driver
memcpy(&class_driver, xid_get_driver(), sizeof(usbd_class_driver_t));
}
void XboxOriginalDriver::process(Gamepad * gamepad, uint8_t * outBuffer) {
// digital buttons
xboxOriginalReport.dButtons = 0
| (gamepad->state.up ? XID_DUP : 0)
| (gamepad->state.down ? XID_DDOWN : 0)
| (gamepad->state.left ? XID_DLEFT : 0)
| (gamepad->state.right ? XID_DRIGHT : 0)
| (gamepad->state.start ? XID_START : 0)
| (gamepad->state.back ? XID_BACK : 0)
| (gamepad->state.l3 ? XID_LS : 0)
| (gamepad->state.r3 ? XID_RS : 0)
;
// analog buttons - convert to digital
xboxOriginalReport.A = (gamepad->state.a ? 0xFF : 0);
xboxOriginalReport.B = (gamepad->state.b ? 0xFF : 0);
xboxOriginalReport.X = (gamepad->state.x ? 0xFF : 0);
xboxOriginalReport.Y = (gamepad->state.y ? 0xFF : 0);
xboxOriginalReport.BLACK = (gamepad->state.rb ? 0xFF : 0);
xboxOriginalReport.WHITE = (gamepad->state.lb ? 0xFF : 0);
// analog triggers
xboxOriginalReport.L = gamepad->state.lt;
xboxOriginalReport.R = gamepad->state.rt;
// analog sticks
xboxOriginalReport.leftStickX = gamepad->state.lx;
xboxOriginalReport.leftStickY = gamepad->state.ly;
xboxOriginalReport.rightStickX = gamepad->state.rx;
xboxOriginalReport.rightStickY = gamepad->state.ry;
if (tud_suspended())
tud_remote_wakeup();
uint8_t xIndex = xid_get_index_by_type(0, XID_TYPE_GAMECONTROLLER);
if (memcmp(last_report, &xboxOriginalReport, sizeof(XboxOriginalReport)) != 0) {
if ( xid_send_report(xIndex, &xboxOriginalReport, sizeof(XboxOriginalReport)) == true ) {
memcpy(last_report, &xboxOriginalReport, sizeof(XboxOriginalReport));
}
}
USB_XboxGamepad_OutReport_t xpad_rumble_data;
if (xid_get_report(xIndex, &xpad_rumble_data, sizeof(xpad_rumble_data)))
{
xid_process_rumble_data(xpad_rumble_data);
}
}
// tud_hid_get_report_cb
uint16_t XboxOriginalDriver::get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
memcpy(buffer, &xboxOriginalReport, sizeof(XboxOriginalReport));
return sizeof(XboxOriginalReport);
}
// Only PS4 does anything with set report
void XboxOriginalDriver::set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {}
// Only XboxOG and Xbox One use vendor control xfer cb
bool XboxOriginalDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
return class_driver.control_xfer_cb(rhport, stage, request);
}
const uint16_t * XboxOriginalDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) {
const char *value = (const char *)xboxoriginal_string_descriptors[index];
return getStringDescriptor(value, index); // getStringDescriptor returns a static array
}
const uint8_t * XboxOriginalDriver::get_descriptor_device_cb() {
return xboxoriginal_device_descriptor;
}
const uint8_t * XboxOriginalDriver::get_hid_descriptor_report_cb(uint8_t itf) {
return nullptr;
}
const uint8_t * XboxOriginalDriver::get_descriptor_configuration_cb(uint8_t index) {
return xboxoriginal_configuration_descriptor;
}
const uint8_t * XboxOriginalDriver::get_descriptor_device_qualifier_cb() {
return nullptr;
}
uint16_t XboxOriginalDriver::GetJoystickMidValue() {
return 0;
}

View File

@@ -1,30 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
*/
#ifndef _XBOX_ORIGINAL_DRIVER_H_
#define _XBOX_ORIGINAL_DRIVER_H_
#include "gpdriver.h"
#include "drivers/xboxog/XboxOriginalDescriptors.h"
class XboxOriginalDriver : public GPDriver {
public:
virtual void initialize();
virtual void process(Gamepad * gamepad, uint8_t * outBuffer);
virtual uint16_t get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen);
virtual void set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize);
virtual bool vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request);
virtual const uint16_t * get_descriptor_string_cb(uint8_t index, uint16_t langid);
virtual const uint8_t * get_descriptor_device_cb();
virtual const uint8_t * get_hid_descriptor_report_cb(uint8_t itf) ;
virtual const uint8_t * get_descriptor_configuration_cb(uint8_t index);
virtual const uint8_t * get_descriptor_device_qualifier_cb();
virtual uint16_t GetJoystickMidValue();
private:
uint8_t last_report[CFG_TUD_ENDPOINT0_SIZE] = { };
XboxOriginalReport xboxOriginalReport;
};
#endif // _XBOX_ORIGINAL_DRIVER_H_

View File

@@ -1,257 +0,0 @@
#include "drivers/xboxog/xid/xid.h"
bool duke_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid);
bool steelbattalion_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid);
bool xremote_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid);
#define MAX_XIDS (XID_DUKE + XID_STEELBATTALION + XID_XREMOTE)
CFG_TUSB_MEM_SECTION static xid_interface_t _xid_itf[MAX_XIDS];
static inline int8_t get_index_by_itfnum(uint8_t itf_num)
{
for (uint8_t i = 0; i < MAX_XIDS; i++)
{
if (itf_num == _xid_itf[i].itf_num)
return i;
//Xremote has two interfaces. Handle is separately.
if (_xid_itf[i].type == XID_TYPE_XREMOTE)
{
if (itf_num == _xid_itf[i].itf_num + 1)
return i;
}
}
return -1;
}
static inline int8_t get_index_by_ep_addr(uint8_t ep_addr)
{
for (uint8_t i = 0; i < MAX_XIDS; i++)
{
if (ep_addr == _xid_itf[i].ep_in)
return i;
if (ep_addr == _xid_itf[i].ep_out)
return i;
}
return -1;
}
static inline xid_interface_t *find_available_interface()
{
for (uint8_t i = 0; i < MAX_XIDS; i++)
{
if (_xid_itf[i].ep_in == 0)
return &_xid_itf[i];
}
return NULL;
}
static void xid_init(void)
{
//#define MAX_XIDS (XID_DUKE + XID_STEELBATTALION + XID_XREMOTE)
tu_memclr(_xid_itf, sizeof(_xid_itf));
for (uint8_t i = 0; i < MAX_XIDS; i++)
{
_xid_itf[i].type = (i < (XID_DUKE)) ? XID_TYPE_GAMECONTROLLER :
(i < (XID_DUKE + XID_STEELBATTALION)) ? XID_TYPE_STEELBATTALION :
(i < (XID_DUKE + XID_STEELBATTALION + XID_XREMOTE)) ? XID_TYPE_XREMOTE : 0xFF;
}
}
static void xid_reset(uint8_t rhport)
{
xid_init();
}
static uint16_t xid_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
{
TU_VERIFY(itf_desc->bInterfaceClass == XID_INTERFACE_CLASS, 0);
TU_VERIFY(itf_desc->bInterfaceSubClass == XID_INTERFACE_SUBCLASS, 0);
xid_interface_t *p_xid = find_available_interface();
TU_ASSERT(p_xid != NULL, 0);
uint16_t const drv_len = (p_xid->type == XID_TYPE_GAMECONTROLLER) ? TUD_XID_DUKE_DESC_LEN :
(p_xid->type == XID_TYPE_STEELBATTALION) ? TUD_XID_SB_DESC_LEN :
(p_xid->type == XID_TYPE_XREMOTE) ? TUD_XID_XREMOTE_DESC_LEN : 0;
TU_ASSERT(max_len >= drv_len, 0);
p_xid->itf_num = itf_desc->bInterfaceNumber;
tusb_desc_endpoint_t *ep_desc;
ep_desc = (tusb_desc_endpoint_t *)tu_desc_next(itf_desc);
if (tu_desc_type(ep_desc) == TUSB_DESC_ENDPOINT)
{
usbd_edpt_open(rhport, ep_desc);
(ep_desc->bEndpointAddress & 0x80) ? (p_xid->ep_in = ep_desc->bEndpointAddress) :
(p_xid->ep_out = ep_desc->bEndpointAddress);
}
TU_VERIFY(itf_desc->bNumEndpoints >= 2, drv_len);
ep_desc = (tusb_desc_endpoint_t *)tu_desc_next(ep_desc);
if (tu_desc_type(ep_desc) == TUSB_DESC_ENDPOINT)
{
usbd_edpt_open(rhport, ep_desc);
(ep_desc->bEndpointAddress & 0x80) ? (p_xid->ep_in = ep_desc->bEndpointAddress) :
(p_xid->ep_out = ep_desc->bEndpointAddress);
}
return drv_len;
}
int8_t xid_get_index_by_type(uint8_t type_index, xid_type_t type)
{
uint8_t _type_index = 0;
for (uint8_t i = 0; i < MAX_XIDS; i++)
{
if (_xid_itf[i].type == type)
{
if (_type_index == type_index)
return i;
_type_index++;
}
}
return -1;
}
bool xid_get_report(uint8_t index, void *report, uint16_t len)
{
TU_VERIFY(index < MAX_XIDS, false);
TU_VERIFY(_xid_itf[index].ep_out != 0, false);
TU_VERIFY(len < XID_MAX_PACKET_SIZE, false);
memcpy(report, _xid_itf[index].out, len);
//Queue request on out endpoint
//Most games send to control pipe, but some send to out pipe. THPSX2 atleast
if (tud_ready() && !usbd_edpt_busy(TUD_OPT_RHPORT, _xid_itf[index].ep_out))
{
usbd_edpt_xfer(TUD_OPT_RHPORT, _xid_itf[index].ep_out, _xid_itf[index].ep_out_buff, len);
}
return true;
}
bool xid_send_report_ready(uint8_t index)
{
TU_VERIFY(index < MAX_XIDS, false);
TU_VERIFY(_xid_itf[index].ep_in != 0, false);
return (tud_ready() && !usbd_edpt_busy(TUD_OPT_RHPORT, _xid_itf[index].ep_in));
}
bool xid_send_report(uint8_t index, void *report, uint16_t len)
{
TU_VERIFY(len < XID_MAX_PACKET_SIZE, false);
TU_VERIFY(index < MAX_XIDS, false);
TU_VERIFY(_xid_itf[index].ep_in != 0, false);
TU_VERIFY(xid_send_report_ready(index), false);
if (tud_suspended())
tud_remote_wakeup();
//Maintain a local copy of the report
memcpy(_xid_itf[index].in, report, len);
//Send it to the host
return usbd_edpt_xfer(TUD_OPT_RHPORT, _xid_itf[index].ep_in, _xid_itf[index].in, len);
}
static bool xid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
(void)rhport;
uint8_t index = get_index_by_ep_addr(ep_addr);
TU_VERIFY(result == XFER_RESULT_SUCCESS, true);
TU_VERIFY(index != -1, true);
TU_VERIFY(xferred_bytes < XID_MAX_PACKET_SIZE, true);
if (ep_addr == _xid_itf[index].ep_out)
{
memcpy(_xid_itf[index].out, _xid_itf[index].ep_out_buff, MIN(xferred_bytes, sizeof( _xid_itf[index].ep_out_buff)));
}
return true;
}
bool xid_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
{
TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE);
uint8_t index = get_index_by_itfnum((uint8_t)request->wIndex);
TU_VERIFY(index != -1, false);
bool ret = false;
//Get HID Report
if (request->bmRequestType == 0xA1 && request->bRequest == 0x01 && request->wValue == 0x0100)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending HID report on control pipe for index %02x\n", request->wIndex);
tud_control_xfer(rhport, request, _xid_itf[index].in, MIN(request->wLength, sizeof(_xid_itf[index].in)));
}
return true;
}
//Set HID Report
if (request->bmRequestType == 0x21 && request->bRequest == 0x09 && request->wValue == 0x0200 && request->wLength == 0x06)
{
if (stage == CONTROL_STAGE_SETUP)
{
//Host is sending a rumble command to control pipe. Queue receipt.
tud_control_xfer(rhport, request, _xid_itf[index].ep_out_buff, MIN(request->wLength, sizeof(_xid_itf[index].ep_out_buff)));
}
else if (stage == CONTROL_STAGE_ACK)
{
//Receipt complete. Copy data to rumble struct
TU_LOG1("Got HID report from control pipe for index %02x\n", request->wIndex);
memcpy(_xid_itf[index].out, _xid_itf[index].ep_out_buff, MIN(request->wLength, sizeof(_xid_itf[index].out)));
}
return true;
}
switch (_xid_itf[index].type)
{
case XID_TYPE_GAMECONTROLLER:
ret = duke_control_xfer(rhport, stage, request, &_xid_itf[index]);
break;
case XID_TYPE_STEELBATTALION:
ret = steelbattalion_control_xfer(rhport, stage, request, &_xid_itf[index]);
break;
case XID_TYPE_XREMOTE:
ret = xremote_control_xfer(rhport, stage, request, &_xid_itf[index]);
break;
default:
break;
}
if (ret == false)
{
TU_LOG1("STALL: wIndex: %02x bmRequestType: %02x, bRequest: %02x, wValue: %04x\n",
request->wIndex,
request->bmRequestType,
request->bRequest,
request->wValue);
return false;
}
return true;
}
static const usbd_class_driver_t xid_driver =
{
#if CFG_TUSB_DEBUG >= 2
.name = "XID DRIVER (DUKE,SB OR XREMOTE)",
#endif
.init = xid_init,
.reset = xid_reset,
.open = xid_open,
.control_xfer_cb = xid_control_xfer_cb,
.xfer_cb = xid_xfer_cb,
.sof = NULL
};
const usbd_class_driver_t *xid_get_driver()
{
return &xid_driver;
}

View File

@@ -1,53 +0,0 @@
#ifndef XID_H_
#define XID_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <tusb.h>
#include <device/usbd_pvt.h>
#include "drivers/xboxog/xid/xid_gamepad.h"
#include "drivers/xboxog/xid/xid_remote.h"
#include "drivers/xboxog/xid/xid_steelbattalion.h"
#define XID_DUKE 1
#define XID_STEELBATTALION 0
#define XID_XREMOTE 0
#define MSC_XMU 0
#define XID_INTERFACE_CLASS 0x58
#define XID_INTERFACE_SUBCLASS 0x42
#define XID_MAX_PACKET_SIZE 32
typedef enum
{
XID_TYPE_GAMECONTROLLER,
XID_TYPE_STEELBATTALION,
XID_TYPE_XREMOTE,
} xid_type_t;
typedef struct
{
uint8_t itf_num;
xid_type_t type;
uint8_t ep_in;
uint8_t ep_out;
CFG_TUSB_MEM_ALIGN uint8_t ep_out_buff[XID_MAX_PACKET_SIZE];
CFG_TUSB_MEM_ALIGN uint8_t in[XID_MAX_PACKET_SIZE];
CFG_TUSB_MEM_ALIGN uint8_t out[XID_MAX_PACKET_SIZE];
} xid_interface_t;
int8_t xid_get_index_by_type(uint8_t type_index, xid_type_t type);
bool xid_get_report(uint8_t index, void *report, uint16_t len);
bool xid_send_report_ready(uint8_t index);
bool xid_send_report(uint8_t index, void *report, uint16_t len);
const usbd_class_driver_t *xid_get_driver();
#ifdef __cplusplus
}
#endif
#endif //XID_H_

View File

@@ -1,6 +0,0 @@
#include "drivers/xboxog/xid/xid.h"
uint8_t *xremote_get_rom()
{
return NULL;
}

View File

@@ -1,87 +0,0 @@
#ifndef XID_DRIVER_H_
#define XID_DRIVER_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <tusb.h>
#include <device/usbd_pvt.h>
#include "drivers/xboxog/xid/xid.h"
static const tusb_desc_device_t XID_DESC_DEVICE =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0110,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x045E,
.idProduct = 0x0289,
.bcdDevice = 0x0121,
.iManufacturer = 0x00,
.iProduct = 0x00,
.iSerialNumber = 0x00,
.bNumConfigurations = 0x01
};
enum
{
#if (XID_DUKE >= 1)
ITF_NUM_XID_DUKE,
#endif
#if (XID_STEELBATTALION >= 1)
ITF_NUM_XID_STEELBATTALION,
#endif
#if (XID_XREMOTE >= 1)
ITF_NUM_XID_XREMOTE,
ITF_NUM_XID_XREMOTE_ROM,
#endif
#if (MSC_XMU >= 1)
ITF_NUM_MSC,
#endif
XID_ITF_NUM_TOTAL
};
#define XID_CONFIG_TOTAL_LEN \
(TUD_CONFIG_DESC_LEN) + \
(TUD_XID_DUKE_DESC_LEN * XID_DUKE) + \
(TUD_XID_SB_DESC_LEN * XID_STEELBATTALION) + \
(TUD_XID_XREMOTE_DESC_LEN * XID_XREMOTE) + \
(TUD_MSC_DESC_LEN * MSC_XMU)
static uint8_t const XID_DESC_CONFIGURATION[] =
{
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, XID_ITF_NUM_TOTAL, 0, XID_CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500),
#if (XID_DUKE >= 1)
TUD_XID_DUKE_DESCRIPTOR(ITF_NUM_XID_DUKE, ITF_NUM_XID_DUKE + 1, 0x80 | (ITF_NUM_XID_DUKE + 1)),
#endif
#if (XID_STEELBATTALION >= 1)
TUD_XID_SB_DESCRIPTOR(ITF_NUM_XID_STEELBATTALION, ITF_NUM_XID_STEELBATTALION + 1, 0x80 | (ITF_NUM_XID_STEELBATTALION + 1)),
#endif
#if (XID_XREMOTE >= 1)
TUD_XID_XREMOTE_DESCRIPTOR(ITF_NUM_XID_XREMOTE, 0x80 | (ITF_NUM_XID_XREMOTE + 1)),
#endif
#if (CFG_TUD_MSC >= 1)
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, ITF_NUM_MSC + 1, 0x80 | (ITF_NUM_MSC + 1), 64),
#endif
};
#ifdef __cplusplus
}
#endif
#endif //XID_DRIVER_H_

View File

@@ -1,33 +0,0 @@
#include "drivers/xboxog/xid/xid_driver.h"
bool duke_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid)
{
if (request->bmRequestType == 0xC1 && request->bRequest == 0x06 && request->wValue == 0x4200)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending DUKE_DESC_XID\n");
tud_control_xfer(rhport, request, (void *)DUKE_DESC_XID, sizeof(DUKE_DESC_XID));
}
return true;
}
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x01 && request->wValue == 0x0100)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending DUKE_CAPABILITIES_IN\n");
tud_control_xfer(rhport, request, (void *)DUKE_CAPABILITIES_IN, sizeof(DUKE_CAPABILITIES_IN));
}
return true;
}
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x01 && request->wValue == 0x0200)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending DUKE_CAPABILITIES_OUT\n");
tud_control_xfer(rhport, request, (void *)DUKE_CAPABILITIES_OUT, sizeof(DUKE_CAPABILITIES_OUT));
}
return true;
}
return false;
}

View File

@@ -1,94 +0,0 @@
#ifndef XID_DUKE_H_
#define XID_DUKE_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <tusb.h>
/* Digital Button Masks */
#define XID_DUP (1 << 0)
#define XID_DDOWN (1 << 1)
#define XID_DLEFT (1 << 2)
#define XID_DRIGHT (1 << 3)
#define XID_START (1 << 4)
#define XID_BACK (1 << 5)
#define XID_LS (1 << 6)
#define XID_RS (1 << 7)
typedef struct __attribute__((packed))
{
uint8_t zero;
uint8_t bLength;
uint8_t dButtons;
uint8_t reserved;
uint8_t A;
uint8_t B;
uint8_t X;
uint8_t Y;
uint8_t BLACK;
uint8_t WHITE;
uint8_t L;
uint8_t R;
int16_t leftStickX;
int16_t leftStickY;
int16_t rightStickX;
int16_t rightStickY;
} USB_XboxGamepad_InReport_t;
typedef struct __attribute__((packed))
{
uint8_t zero;
uint8_t bLength;
uint16_t lValue;
uint16_t rValue;
} USB_XboxGamepad_OutReport_t;
#define TUD_XID_DUKE_DESC_LEN (9+7+7)
#define TUD_XID_DUKE_DESCRIPTOR(_itfnum, _epout, _epin) \
/* Interface */\
9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, XID_INTERFACE_CLASS, XID_INTERFACE_SUBCLASS, 0x00, 0x00,\
/* Endpoint In */\
7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(32), 4, \
/* Endpoint Out */\
7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(32), 4
static const uint8_t DUKE_DESC_XID[] = {
0x10,
0x42,
0x00, 0x01,
0x01,
0x02,
0x14,
0x06,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
static const uint8_t DUKE_CAPABILITIES_IN[] = {
0x00,
0x14,
0xFF,
0x00,
0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF
};
static const uint8_t DUKE_CAPABILITIES_OUT[] = {
0x00,
0x06,
0xFF, 0xFF, 0xFF, 0xFF
};
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,47 +0,0 @@
#include "drivers/xboxog/xid/xid_driver.h"
uint8_t *xremote_get_rom();
bool xremote_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid)
{
if (request->bmRequestType == 0xC1 && request->bRequest == 0x06 && request->wValue == 0x4200)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending REMOTE_DESC_XID\r\n");
tud_control_xfer(rhport, request, (void *)REMOTE_DESC_XID, sizeof(REMOTE_DESC_XID));
}
return true;
}
//INFO PACKET (Interface 1)
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x01 && request->wIndex == 1 && request->wValue == 0x0000)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending XREMOTE INFO\r\n");
uint8_t *rom = xremote_get_rom();
if (rom == NULL)
{
return false; //STALL
}
tud_control_xfer(rhport, request, &rom[0], request->wLength);
}
return true;
}
//ROM DATA (Interface 1)
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x02 && request->wIndex == 1)
{
if (stage == CONTROL_STAGE_SETUP)
{
uint8_t *rom = xremote_get_rom();
if (rom == NULL)
{
return false; //STALL
}
tud_control_xfer(rhport, request, &rom[request->wValue * 1024], request->wLength);
}
return true;
}
return false;
}

View File

@@ -1,77 +0,0 @@
#ifndef XID_REMOTE_H_
#define XID_REMOTE_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <tusb.h>
#define XID_XREMOTE_ROM_CLASS 0x59
typedef enum
{
XREMOTE_SELECT = 0x0A0B,
XREMOTE_UP = 0x0AA6,
XREMOTE_DOWN = 0x0AA7,
XREMOTE_RIGHT = 0x0AA8,
XREMOTE_LEFT = 0x0AA9,
XREMOTE_INFO = 0x0AC3,
XREMOTE_NINE = 0x0AC6,
XREMOTE_EIGHT = 0x0AC7,
XREMOTE_SEVEN = 0x0AC8,
XREMOTE_SIX = 0x0AC9,
XREMOTE_FIVE = 0x0ACA,
XREMOTE_FOUR = 0x0ACB,
XREMOTE_THREE = 0x0ACC,
XREMOTE_TWO = 0x0ACD,
XREMOTE_ONE = 0x0ACE,
XREMOTE_ZERO = 0x0ACF,
XREMOTE_DISPLAY = 0x0AD5,
XREMOTE_BACK = 0x0AD8,
XREMOTE_SKIP_MINUS = 0x0ADD,
XREMOTE_SKIP_PLUS = 0x0ADF,
XREMOTE_STOP = 0x0AE0,
XREMOTE_REVERSE = 0x0AE2,
XREMOTE_FORWARD = 0x0AE3,
XREMOTE_TITLE = 0x0AE5,
XREMOTE_PAUSE = 0x0AE6,
XREMOTE_PLAY = 0x0AEA,
XREMOTE_MENU = 0x0AF7,
} xremote_buttoncode_t;
typedef struct __attribute__((packed))
{
uint8_t zero;
uint8_t bLength;
xremote_buttoncode_t buttonCode;
uint16_t timeElapsed; //ms since last button press
} USB_XboxRemote_InReport_t;
#define TUD_XID_XREMOTE_DESC_LEN (9+7+9)
#define TUD_XID_XREMOTE_DESCRIPTOR(_itfnum, _epin) \
/* Interface 0 (HID DATA)*/\
9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, XID_INTERFACE_CLASS, XID_INTERFACE_SUBCLASS, 0x00, 0x00,\
/* Endpoint In */\
7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(8), 16, \
/* Interface 1 (ROM DATA)*/\
9, TUSB_DESC_INTERFACE, _itfnum + 1, 0, 0, XID_XREMOTE_ROM_CLASS, 0x00, 0x00, 0x00
static const uint8_t REMOTE_DESC_XID[] = {
0x08,
0x42,
0x00, 0x01,
0x03,
0x00,
0x06,
0x00
};
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,33 +0,0 @@
#include "drivers/xboxog/xid/xid_driver.h"
bool steelbattalion_control_xfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, xid_interface_t *p_xid)
{
if (request->bmRequestType == 0xC1 && request->bRequest == 0x06 && request->wValue == 0x4200)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending STEELBATTALION_DESC_XID\n");
tud_control_xfer(rhport, request, (void *)STEELBATTALION_DESC_XID, sizeof(STEELBATTALION_DESC_XID));
}
return true;
}
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x01 && request->wValue == 0x0100)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending STEELBATTALION_CAPABILITIES_IN\n");
tud_control_xfer(rhport, request, (void *)STEELBATTALION_CAPABILITIES_IN, sizeof(STEELBATTALION_CAPABILITIES_IN));
}
return true;
}
else if (request->bmRequestType == 0xC1 && request->bRequest == 0x01 && request->wValue == 0x0200)
{
if (stage == CONTROL_STAGE_SETUP)
{
TU_LOG1("Sending STEELBATTALION_CAPABILITIES_OUT\n");
tud_control_xfer(rhport, request, (void *)STEELBATTALION_CAPABILITIES_OUT, sizeof(STEELBATTALION_CAPABILITIES_OUT));
}
return true;
}
return false;
}

View File

@@ -1,155 +0,0 @@
#ifndef XID_STEELBATTALION_H_
#define XID_STEELBATTALION_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <tusb.h>
/* https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/blob/master/src/core/hle/XAPI/XapiCxbxr.h */
#define CXBX_SBC_GAMEPAD_W0_RIGHTJOYMAINWEAPON 0x0001
#define CXBX_SBC_GAMEPAD_W0_RIGHTJOYFIRE 0x0002
#define CXBX_SBC_GAMEPAD_W0_RIGHTJOYLOCKON 0x0004
#define CXBX_SBC_GAMEPAD_W0_EJECT 0x0008
#define CXBX_SBC_GAMEPAD_W0_COCKPITHATCH 0x0010
#define CXBX_SBC_GAMEPAD_W0_IGNITION 0x0020
#define CXBX_SBC_GAMEPAD_W0_START 0x0040
#define CXBX_SBC_GAMEPAD_W0_MULTIMONOPENCLOSE 0x0080
#define CXBX_SBC_GAMEPAD_W0_MULTIMONMAPZOOMINOUT 0x0100
#define CXBX_SBC_GAMEPAD_W0_MULTIMONMODESELECT 0x0200
#define CXBX_SBC_GAMEPAD_W0_MULTIMONSUBMONITOR 0x0400
#define CXBX_SBC_GAMEPAD_W0_MAINMONZOOMIN 0x0800
#define CXBX_SBC_GAMEPAD_W0_MAINMONZOOMOUT 0x1000
#define CXBX_SBC_GAMEPAD_W0_FUNCTIONFSS 0x2000
#define CXBX_SBC_GAMEPAD_W0_FUNCTIONMANIPULATOR 0x4000
#define CXBX_SBC_GAMEPAD_W0_FUNCTIONLINECOLORCHANGE 0x8000
#define CXBX_SBC_GAMEPAD_W1_WASHING 0x0001
#define CXBX_SBC_GAMEPAD_W1_EXTINGUISHER 0x0002
#define CXBX_SBC_GAMEPAD_W1_CHAFF 0x0004
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONTANKDETACH 0x0008
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONOVERRIDE 0x0010
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONNIGHTSCOPE 0x0020
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONF1 0x0040
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONF2 0x0080
#define CXBX_SBC_GAMEPAD_W1_FUNCTIONF3 0x0100
#define CXBX_SBC_GAMEPAD_W1_WEAPONCONMAIN 0x0200
#define CXBX_SBC_GAMEPAD_W1_WEAPONCONSUB 0x0400
#define CXBX_SBC_GAMEPAD_W1_WEAPONCONMAGAZINE 0x0800
#define CXBX_SBC_GAMEPAD_W1_COMM1 0x1000
#define CXBX_SBC_GAMEPAD_W1_COMM2 0x2000
#define CXBX_SBC_GAMEPAD_W1_COMM3 0x4000
#define CXBX_SBC_GAMEPAD_W1_COMM4 0x8000
#define CXBX_SBC_GAMEPAD_W2_COMM5 0x0001
#define CXBX_SBC_GAMEPAD_W2_LEFTJOYSIGHTCHANGE 0x0002
#define CXBX_SBC_GAMEPAD_W2_TOGGLEFILTERCONTROL 0x0004
#define CXBX_SBC_GAMEPAD_W2_TOGGLEOXYGENSUPPLY 0x0008
#define CXBX_SBC_GAMEPAD_W2_TOGGLEFUELFLOWRATE 0x0010
#define CXBX_SBC_GAMEPAD_W2_TOGGLEBUFFREMATERIAL 0x0020
#define CXBX_SBC_GAMEPAD_W2_TOGGLEVTLOCATION 0x0040
typedef struct __attribute__((packed))
{
uint8_t zero;
uint8_t bLength;
uint16_t dButtons[3];
uint16_t aimingX; //0 to 2^16 left to right
uint16_t aimingY; //0 to 2^16 top to bottom
int16_t rotationLever;
int16_t sightChangeX;
int16_t sightChangeY;
uint16_t leftPedal; //Sidestep, 0x0000 to 0xFF00
uint16_t middlePedal; //Brake, 0x0000 to 0xFF00
uint16_t rightPedal; //Acceleration, 0x0000 to oxFF00
int8_t tunerDial; //0-15 is from 9oclock, around clockwise
int8_t gearLever; //7-13 is gears R,1,2,3,4,5
} USB_SteelBattalion_InReport_t;
typedef struct
{
uint8_t zero;
uint8_t bLength;
uint8_t CockpitHatch_EmergencyEject;
uint8_t Start_Ignition;
uint8_t MapZoomInOut_OpenClose;
uint8_t SubMonitorModeSelect_ModeSelect;
uint8_t MainMonitorZoomOut_MainMonitorZoomIn;
uint8_t Manipulator_ForecastShootingSystem;
uint8_t Washing_LineColorChange;
uint8_t Chaff_Extinguisher;
uint8_t Override_TankDetach;
uint8_t F1_NightScope;
uint8_t F3_F2;
uint8_t SubWeaponControl_MainWeaponControl;
uint8_t Comm1_MagazineChange;
uint8_t Comm3_Comm2;
uint8_t Comm5_Comm4;
uint8_t GearR_;
uint8_t Gear1_GearN;
uint8_t Gear3_Gear2;
uint8_t Gear5_Gear4;
uint8_t dummy;
} USB_SteelBattalion_OutReport_t;
#define TUD_XID_SB_DESC_LEN (9+7+7)
#define TUD_XID_SB_DESCRIPTOR(_itfnum, _epout, _epin) \
/* Interface */\
9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, XID_INTERFACE_CLASS, XID_INTERFACE_SUBCLASS, 0x00, 0x00,\
/* Endpoint In */\
7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(32), 4, \
/* Endpoint Out */\
7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(32), 4
static const uint8_t STEELBATTALION_DESC_XID[] = {
0x10,
0x42,
0x00, 0x01,
0x80,
0x01,
0x1A,
0x16,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
static const uint8_t STEELBATTALION_CAPABILITIES_IN[] = {
0x00,
0x1A,
0xFF,
0xFF,
0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF
};
static const uint8_t STEELBATTALION_CAPABILITIES_OUT[] = {
0x00,
0x16,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF
};
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,140 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2021 Jason Skuby (mytechtoybox.com)
*/
#pragma once
#include <stdint.h>
#define XINPUT_ENDPOINT_SIZE 20
// Buttons 1 (8 bits)
// TODO: Consider using an enum class here.
#define XBOX_MASK_UP (1U << 0)
#define XBOX_MASK_DOWN (1U << 1)
#define XBOX_MASK_LEFT (1U << 2)
#define XBOX_MASK_RIGHT (1U << 3)
#define XBOX_MASK_START (1U << 4)
#define XBOX_MASK_BACK (1U << 5)
#define XBOX_MASK_LS (1U << 6)
#define XBOX_MASK_RS (1U << 7)
// Buttons 2 (8 bits)
// TODO: Consider using an enum class here.
#define XBOX_MASK_LB (1U << 0)
#define XBOX_MASK_RB (1U << 1)
#define XBOX_MASK_HOME (1U << 2)
//#define UNUSED (1U << 3)
#define XBOX_MASK_A (1U << 4)
#define XBOX_MASK_B (1U << 5)
#define XBOX_MASK_X (1U << 6)
#define XBOX_MASK_Y (1U << 7)
#define XBOX_REPORT_TYPE_RUMBLE 0x00
#define XBOX_REPORT_TYPE_LED 0x01
typedef struct __attribute((packed, aligned(1)))
{
uint8_t report_id;
uint8_t report_size;
uint8_t buttons1;
uint8_t buttons2;
uint8_t lt;
uint8_t rt;
int16_t lx;
int16_t ly;
int16_t rx;
int16_t ry;
uint8_t _reserved[6];
} XInputReport;
typedef struct __attribute((packed, aligned(1)))
{
uint8_t report_type;
uint8_t report_size;
uint8_t led;
uint8_t lrumble;
uint8_t rrumble;
uint8_t reserved[3];
} XInputOutReport;
static const uint8_t xinput_string_language[] = { 0x09, 0x04 };
static const uint8_t xinput_string_manfacturer[] = "Microsoft";
static const uint8_t xinput_string_product[] = "XInput STANDARD GAMEPAD";
static const uint8_t xinput_string_version[] = "1.0";
static const uint8_t *xinput_string_descriptors[] __attribute__((unused)) =
{
xinput_string_language,
xinput_string_manfacturer,
xinput_string_product,
xinput_string_version
};
static const uint8_t xinput_device_descriptor[] =
{
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB 2.00
0xFF, // bDeviceClass
0xFF, // bDeviceSubClass
0xFF, // bDeviceProtocol
0x40, // bMaxPacketSize0 64
0x5E, 0x04, // idVendor 0x045E
0x8E, 0x02, // idProduct 0x028E
0x14, 0x01, // bcdDevice 2.14
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x03, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
static const uint8_t xinput_configuration_descriptor[] =
{
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0x30, 0x00, // wTotalLength 48
0x01, // bNumInterfaces 1
0x01, // bConfigurationValue
0x00, // iConfiguration (String Index)
0x80, // bmAttributes
0xFA, // bMaxPower 500mA
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber 0
0x00, // bAlternateSetting
0x02, // bNumEndpoints 2
0xFF, // bInterfaceClass
0x5D, // bInterfaceSubClass
0x01, // bInterfaceProtocol
0x00, // iInterface (String Index)
0x10, // bLength
0x21, // bDescriptorType (HID)
0x10, 0x01, // bcdHID 1.10
0x01, // bCountryCode
0x24, // bNumDescriptors
0x81, // bDescriptorType[0] (Unknown 0x81)
0x14, 0x03, // wDescriptorLength[0] 788
0x00, // bDescriptorType[1] (Unknown 0x00)
0x03, 0x13, // wDescriptorLength[1] 4867
0x01, // bDescriptorType[2] (Unknown 0x02)
0x00, 0x03, // wDescriptorLength[2] 768
0x00, // bDescriptorType[3] (Unknown 0x00)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt)
0x20, 0x00, // wMaxPacketSize 32
0x01, // bInterval 1 (unit depends on device speed)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x01, // bEndpointAddress (OUT/H2D)
0x03, // bmAttributes (Interrupt)
0x20, 0x00, // wMaxPacketSize 32
0x08, // bInterval 8 (unit depends on device speed)
};

View File

@@ -1,217 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
*/
#include "drivers/xinput/XInputDriver.h"
#include "drivers/shared/driverhelper.h"
#include "Gamepad.h"
#define XINPUT_OUT_SIZE 32
uint8_t endpoint_in = 0;
uint8_t endpoint_out = 0;
uint8_t xinput_out_buffer[XINPUT_OUT_SIZE] = {};
void xinput_process_rumble_data(const uint8_t* outBuffer)
{
XInputOutReport out_report;
memcpy(&out_report, outBuffer, sizeof(XInputOutReport));
if (out_report.report_type == XBOX_REPORT_TYPE_RUMBLE)
{
gamepadOut.out_state.lrumble = out_report.lrumble;
gamepadOut.out_state.rrumble = out_report.rrumble;
}
}
static void xinput_init(void)
{
}
static void xinput_reset(uint8_t rhport)
{
(void)rhport;
}
static uint16_t xinput_open(uint8_t rhport, tusb_desc_interface_t const *itf_descriptor, uint16_t max_length)
{
uint16_t driver_length = sizeof(tusb_desc_interface_t) + (itf_descriptor->bNumEndpoints * sizeof(tusb_desc_endpoint_t)) + 16;
TU_VERIFY(max_length >= driver_length, 0);
uint8_t const *current_descriptor = tu_desc_next(itf_descriptor);
uint8_t found_endpoints = 0;
while ((found_endpoints < itf_descriptor->bNumEndpoints) && (driver_length <= max_length))
{
tusb_desc_endpoint_t const *endpoint_descriptor = (tusb_desc_endpoint_t const *)current_descriptor;
if (TUSB_DESC_ENDPOINT == tu_desc_type(endpoint_descriptor))
{
TU_ASSERT(usbd_edpt_open(rhport, endpoint_descriptor));
if (tu_edpt_dir(endpoint_descriptor->bEndpointAddress) == TUSB_DIR_IN)
endpoint_in = endpoint_descriptor->bEndpointAddress;
else
endpoint_out = endpoint_descriptor->bEndpointAddress;
++found_endpoints;
}
current_descriptor = tu_desc_next(current_descriptor);
}
return driver_length;
}
static bool xinput_device_control_request(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
{
(void)rhport;
(void)stage;
(void)request;
return true;
}
static bool xinput_control_complete(uint8_t rhport, tusb_control_request_t const *request)
{
(void)rhport;
(void)request;
return true;
}
static bool xinput_xfer_callback(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
(void)rhport;
(void)result;
(void)xferred_bytes;
if (ep_addr == endpoint_out) usbd_edpt_xfer(0, endpoint_out, xinput_out_buffer, XINPUT_OUT_SIZE);
xinput_process_rumble_data(xinput_out_buffer);
return true;
}
void XInputDriver::initialize() {
xinputReport = {
.report_id = 0,
.report_size = XINPUT_ENDPOINT_SIZE,
.buttons1 = 0,
.buttons2 = 0,
.lt = 0,
.rt = 0,
.lx = 0,
.ly = 0,
.rx = 0,
.ry = 0,
._reserved = { },
};
class_driver = {
#if CFG_TUSB_DEBUG >= 2
.name = "XINPUT",
#endif
.init = xinput_init,
.reset = xinput_reset,
.open = xinput_open,
.control_xfer_cb = xinput_device_control_request,
.xfer_cb = xinput_xfer_callback,
.sof = NULL
};
}
void XInputDriver::process(Gamepad * gamepad, uint8_t * outBuffer) {
xinputReport.buttons1 = 0
| (gamepad->state.up ? XBOX_MASK_UP : 0)
| (gamepad->state.down ? XBOX_MASK_DOWN : 0)
| (gamepad->state.left ? XBOX_MASK_LEFT : 0)
| (gamepad->state.right ? XBOX_MASK_RIGHT : 0)
| (gamepad->state.start ? XBOX_MASK_START : 0)
| (gamepad->state.back ? XBOX_MASK_BACK : 0)
| (gamepad->state.l3 ? XBOX_MASK_LS : 0)
| (gamepad->state.r3 ? XBOX_MASK_RS : 0)
;
xinputReport.buttons2 = 0
| (gamepad->state.rb ? XBOX_MASK_RB : 0)
| (gamepad->state.lb ? XBOX_MASK_LB : 0)
| (gamepad->state.sys ? XBOX_MASK_HOME : 0)
| (gamepad->state.a ? XBOX_MASK_A : 0)
| (gamepad->state.b ? XBOX_MASK_B : 0)
| (gamepad->state.x ? XBOX_MASK_X : 0)
| (gamepad->state.y ? XBOX_MASK_Y : 0)
;
xinputReport.lt = gamepad->state.lt;
xinputReport.rt = gamepad->state.rt;
xinputReport.lx = gamepad->state.lx;
xinputReport.ly = gamepad->state.ly;
xinputReport.rx = gamepad->state.rx;
xinputReport.ry = gamepad->state.ry;
// printf("processing rumble\n");
// compare against previous report and send new
if ( memcmp(last_report, &xinputReport, sizeof(XInputReport)) != 0) {
if ( tud_ready() && // Is the device ready?
(endpoint_in != 0) && (!usbd_edpt_busy(0, endpoint_in)) ) // Is the IN endpoint available?
{
usbd_edpt_claim(0, endpoint_in); // Take control of IN endpoint
usbd_edpt_xfer(0, endpoint_in, (uint8_t *)&xinputReport, sizeof(XInputReport)); // Send report buffer
usbd_edpt_release(0, endpoint_in); // Release control of IN endpoint
memcpy(last_report, &xinputReport, sizeof(XInputReport)); // save if we sent it
}
}
// check for player LEDs
if (tud_ready() &&
(endpoint_out != 0) && (!usbd_edpt_busy(0, endpoint_out)))
{
usbd_edpt_claim(0, endpoint_out); // Take control of OUT endpoint
usbd_edpt_xfer(0, endpoint_out, outBuffer, XINPUT_OUT_SIZE); // Retrieve report buffer
usbd_edpt_release(0, endpoint_out); // Release control of OUT endpoint
}
}
// tud_hid_get_report_cb
uint16_t XInputDriver::get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
memcpy(buffer, &xinputReport, sizeof(XInputReport));
return sizeof(XInputReport);
}
// Only PS4 does anything with set report
void XInputDriver::set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {}
// Only XboxOG and Xbox One use vendor control xfer cb
bool XInputDriver::vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
return false;
}
const uint16_t * XInputDriver::get_descriptor_string_cb(uint8_t index, uint16_t langid) {
const char *value = (const char *)xinput_string_descriptors[index];
return getStringDescriptor(value, index); // getStringDescriptor returns a static array
}
const uint8_t * XInputDriver::get_descriptor_device_cb() {
return xinput_device_descriptor;
}
const uint8_t * XInputDriver::get_hid_descriptor_report_cb(uint8_t itf) {
return nullptr;
}
const uint8_t * XInputDriver::get_descriptor_configuration_cb(uint8_t index) {
return xinput_configuration_descriptor;
}
const uint8_t * XInputDriver::get_descriptor_device_qualifier_cb() {
return nullptr;
}
uint16_t XInputDriver::GetJoystickMidValue() {
return 0;
}

View File

@@ -1,30 +0,0 @@
/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Copyright (c) 2024 OpenStickCommunity (gp2040-ce.info)
*/
#ifndef _XINPUT_DRIVER_H_
#define _XINPUT_DRIVER_H_
#include "gpdriver.h"
#include "drivers/xinput/XInputDescriptors.h"
class XInputDriver : public GPDriver {
public:
virtual void initialize();
virtual void process(Gamepad * gamepad, uint8_t * outBuffer);
virtual uint16_t get_report(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen);
virtual void set_report(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize);
virtual bool vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request);
virtual const uint16_t * get_descriptor_string_cb(uint8_t index, uint16_t langid);
virtual const uint8_t * get_descriptor_device_cb();
virtual const uint8_t * get_hid_descriptor_report_cb(uint8_t itf) ;
virtual const uint8_t * get_descriptor_configuration_cb(uint8_t index);
virtual const uint8_t * get_descriptor_device_qualifier_cb();
virtual uint16_t GetJoystickMidValue();
private:
uint8_t last_report[CFG_TUD_ENDPOINT0_SIZE] = { };
XInputReport xinputReport;
};
#endif

23
src/usbh/GPHostDriver.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef _GPHOSTDRIVER_H_
#define _GPHOSTDRIVER_H_
#include <stdint.h>
#include "host/usbh.h" // needed so xinput_host will build
#include "xinput_host.h"
#include "tusb_gamepad.h"
#include "usbh/shared/shared.h"
class GPHostDriver
{
public:
virtual ~GPHostDriver() = default;
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance) = 0;
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) = 0;
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len) = 0;
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len) = 0;
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance) = 0;
};
#endif // _GPHOSTDRIVER_H_

113
src/usbh/n64usb/N64USB.cpp Normal file
View File

@@ -0,0 +1,113 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "usbh/shared/scaling.h"
#include "usbh/n64usb/N64USB.h"
void N64USB::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
n64usb.player_id = player_id;
tuh_hid_receive_report(dev_addr, instance);
}
void N64USB::update_gamepad(Gamepad* gamepad, const N64USBReport* n64_data)
{
gamepad->reset_pad(gamepad);
uint8_t n64_dpad = n64_data->buttons & N64_DPAD_MASK;
switch(n64_dpad)
{
case N64_DPAD_MASK_UP:
gamepad->buttons.up = true;
break;
case N64_DPAD_MASK_UP_RIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case N64_DPAD_MASK_RIGHT:
gamepad->buttons.right = true;
break;
case N64_DPAD_MASK_RIGHT_DOWN:
gamepad->buttons.right = true;
gamepad->buttons.down = true;
break;
case N64_DPAD_MASK_DOWN:
gamepad->buttons.down = true;
break;
case N64_DPAD_MASK_DOWN_LEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case N64_DPAD_MASK_LEFT:
gamepad->buttons.left = true;
break;
case N64_DPAD_MASK_LEFT_UP:
gamepad->buttons.left = true;
gamepad->buttons.up = true;
break;
}
if (n64_data->buttons & N64_C_UP_MASK) gamepad->joysticks.ry = INT16_MAX;
if (n64_data->buttons & N64_C_DOWN_MASK) gamepad->joysticks.ry = INT16_MIN;
if (n64_data->buttons & N64_C_LEFT_MASK) gamepad->joysticks.rx = INT16_MIN;
if (n64_data->buttons & N64_C_RIGHT_MASK) gamepad->joysticks.rx = INT16_MAX;
if (n64_data->buttons & N64_A_MASK) gamepad->buttons.a = true;
if (n64_data->buttons & N64_B_MASK) gamepad->buttons.b = true;
if (n64_data->buttons & N64_START_MASK) gamepad->buttons.start = true;
if (n64_data->buttons & N64_L_MASK) gamepad->buttons.lb = true;
if (n64_data->buttons & N64_R_MASK) gamepad->buttons.rb = true;
if (n64_data->buttons & N64_Z_MASK) gamepad->triggers.r = 0xFF;
gamepad->joysticks.ly = scale_uint8_to_int16(n64_data->y, true);
gamepad->joysticks.lx = scale_uint8_to_int16(n64_data->x, false);
}
void N64USB::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static N64USBReport prev_report = {};
N64USBReport n64_report;
memcpy(&n64_report, report, sizeof(n64_report));
if (memcmp(&n64_report, &prev_report, sizeof(n64_report)) != 0)
{
update_gamepad(gamepad, &n64_report);
prev_report = n64_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void N64USB::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void N64USB::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool N64USB::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
return true;
}

67
src/usbh/n64usb/N64USB.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef _N64USB_H_
#define _N64USB_H_
#include <stdint.h>
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t n64_devices[] =
{
{0x0079, 0x0006} // Retrolink N64 USB gamepad
};
enum n64usb_dpad_mask
{
N64_DPAD_MASK_UP = 0x00,
N64_DPAD_MASK_UP_RIGHT = 0x01,
N64_DPAD_MASK_RIGHT = 0x02,
N64_DPAD_MASK_RIGHT_DOWN = 0x03,
N64_DPAD_MASK_DOWN = 0x04,
N64_DPAD_MASK_DOWN_LEFT = 0x05,
N64_DPAD_MASK_LEFT = 0x06,
N64_DPAD_MASK_LEFT_UP = 0x07,
N64_DPAD_MASK_NONE = 0x08,
};
#define N64_DPAD_MASK 0x0F
#define N64_C_UP_MASK (1 << 4)
#define N64_C_RIGHT_MASK (1 << 5)
#define N64_C_DOWN_MASK (1 << 6)
#define N64_C_LEFT_MASK (1 << 7)
#define N64_L_MASK (1 << 8)
#define N64_R_MASK (1 << 9)
#define N64_A_MASK (1 << 10)
#define N64_Z_MASK (1 << 11)
#define N64_B_MASK (1 << 12)
#define N64_START_MASK (1 << 13)
typedef struct __attribute__((packed))
{
uint8_t x;
uint8_t y;
uint8_t padding[3];
uint16_t buttons;
} N64USBReport;
struct N64USBState
{
uint8_t player_id = {0};
};
class N64USB: public GPHostDriver
{
public:
~N64USB() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
N64USBState n64usb;
void update_gamepad(Gamepad* gp, const N64USBReport* n64_data);
};
#endif // _N64USB_H_

146
src/usbh/ps3/DInput.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "class/hid/hid_host.h"
#include "usbh/ps3/DInput.h"
#include "usbh/shared/scaling.h"
void DInput::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
dinput.player_id = player_id;
tuh_hid_receive_report(dev_addr, instance);
}
void DInput::update_gamepad(Gamepad* gamepad, const DInputReport* dinput_report)
{
gamepad->reset_pad(gamepad);
switch (dinput_report->direction)
{
case DINPUT_HAT_UP:
gamepad->buttons.up = true;
break;
case DINPUT_HAT_UPRIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case DINPUT_HAT_RIGHT:
gamepad->buttons.right = true;
break;
case DINPUT_HAT_DOWNRIGHT:
gamepad->buttons.right = true;
gamepad->buttons.down = true;
break;
case DINPUT_HAT_DOWN:
gamepad->buttons.down = true;
break;
case DINPUT_HAT_DOWNLEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case DINPUT_HAT_LEFT:
gamepad->buttons.left = true;
break;
case DINPUT_HAT_UPLEFT:
gamepad->buttons.up = true;
gamepad->buttons.left = true;
break;
}
if (dinput_report->square_btn) gamepad->buttons.x = true;
if (dinput_report->triangle_btn) gamepad->buttons.y = true;
if (dinput_report->cross_btn) gamepad->buttons.a = true;
if (dinput_report->circle_btn) gamepad->buttons.b = true;
if (dinput_report->select_btn) gamepad->buttons.back = true;
if (dinput_report->start_btn) gamepad->buttons.start = true;
if (dinput_report->ps_btn) gamepad->buttons.sys = true;
if (dinput_report->l3_btn) gamepad->buttons.l3 = true;
if (dinput_report->r3_btn) gamepad->buttons.r3 = true;
if (dinput_report->l1_btn) gamepad->buttons.lb = true;
if (dinput_report->r1_btn) gamepad->buttons.rb = true;
if (dinput_report->l2_axis > 0)
{
gamepad->triggers.l = dinput_report->l2_axis;
}
else if (dinput_report->l2_btn)
{
gamepad->triggers.l = 0xFF;
}
if (dinput_report->r2_axis > 0)
{
gamepad->triggers.r = dinput_report->r2_axis;
}
else if (dinput_report->r2_btn)
{
gamepad->triggers.r = 0xFF;
}
gamepad->analog_buttons.up = dinput_report->up_axis;
gamepad->analog_buttons.down = dinput_report->down_axis;
gamepad->analog_buttons.left = dinput_report->left_axis;
gamepad->analog_buttons.right = dinput_report->right_axis;
gamepad->analog_buttons.x = dinput_report->square_axis;
gamepad->analog_buttons.y = dinput_report->triangle_axis;
gamepad->analog_buttons.a = dinput_report->cross_axis;
gamepad->analog_buttons.b = dinput_report->circle_axis;
gamepad->analog_buttons.lb = dinput_report->l1_axis;
gamepad->analog_buttons.rb = dinput_report->r1_axis;
gamepad->joysticks.lx = scale_uint8_to_int16(dinput_report->l_x_axis, false);
gamepad->joysticks.ly = scale_uint8_to_int16(dinput_report->l_y_axis, true);
gamepad->joysticks.rx = scale_uint8_to_int16(dinput_report->r_x_axis, false);
gamepad->joysticks.ry = scale_uint8_to_int16(dinput_report->r_y_axis, true);
}
void DInput::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static DInputReport prev_report = {};
DInputReport dinput_report;
memcpy(&dinput_report, report, sizeof(dinput_report));
if (memcmp(&dinput_report, &prev_report, sizeof(dinput_report)) != 0)
{
update_gamepad(gamepad, &dinput_report);
prev_report = dinput_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void DInput::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void DInput::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool DInput::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
return true;
}

38
src/usbh/ps3/DInput.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef _DINPUT_H_
#define _DINPUT_H_
#include <stdint.h>
#include "descriptors/DInputDescriptors.h"
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t dinput_devices[] =
{
{0x044F, 0xB324}, // ThrustMaster Dual Trigger (PS3 mode)
{0x0738, 0x8818}, // MadCatz Street Fighter IV Arcade FightStick
{0x0810, 0x0003}, // Personal Communication Systems, Inc. Generic
{0x146B, 0x0902}, // BigBen Interactive Wired Mini PS3 Game Controller
{0x2563, 0x0575} // SHANWAN 2In1 USB Joystick
};
struct DInputState
{
uint8_t player_id = {0};
};
class DInput : public GPHostDriver
{
public:
~DInput() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
DInputState dinput;
void update_gamepad(Gamepad* gp, const DInputReport* dinput_report);
};
#endif // _DINPUT_H_

340
src/usbh/ps3/Dualshock3.cpp Normal file
View File

@@ -0,0 +1,340 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "class/hid/hid_host.h"
#include "usbh/ps3/Dualshock3.h"
#include "usbh/shared/scaling.h"
void Dualshock3::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
dualshock3.player_id = player_id;
dualshock3.response_count = 0;
dualshock3.reports_enabled = false;
// tuh_hid_get_report not working correctly?
tusb_control_request_t setup_packet =
{
.bmRequestType = 0xA1,
.bRequest = 0x01, // GET_REPORT
.wValue = (HID_REPORT_TYPE_FEATURE << 8) | 0xF2,
.wIndex = 0x0000,
.wLength = 17
};
tuh_xfer_s transfer =
{
.daddr = dev_addr,
.ep_addr = 0x00,
.setup = &setup_packet,
.buffer = (uint8_t*)&dualshock3.en_buffer,
.complete_cb = NULL,
.user_data = 0
};
if (tuh_control_xfer(&transfer))
{
get_report_complete_cb(dev_addr, instance);
}
// tuh_hid_get_report(dev_addr, instance, 0xF2, HID_REPORT_TYPE_FEATURE, &dualshock3.en_buffer, 17);
}
void Dualshock3::get_report_complete_cb(uint8_t dev_addr, uint8_t instance)
{
if (dualshock3.response_count == 0)
{
tusb_control_request_t setup_packet =
{
.bmRequestType = 0xA1,
.bRequest = 0x01, // GET_REPORT
.wValue = (HID_REPORT_TYPE_FEATURE << 8) | 0xF2,
.wIndex = 0x0000,
.wLength = 17
};
tuh_xfer_s transfer =
{
.daddr = dev_addr,
.ep_addr = 0x00,
.setup = &setup_packet,
.buffer = (uint8_t*)&dualshock3.en_buffer,
.complete_cb = NULL,
.user_data = 0
};
if (tuh_control_xfer(&transfer))
{
dualshock3.response_count++;
get_report_complete_cb(dev_addr, instance);
return;
}
}
else if (dualshock3.response_count == 1)
{
tusb_control_request_t setup_packet =
{
.bmRequestType = 0xA1,
.bRequest = 0x01, // GET_REPORT
.wValue = (HID_REPORT_TYPE_FEATURE << 8) | 0xF2,
.wIndex = 0x0000,
.wLength = 8
};
tuh_xfer_s transfer =
{
.daddr = dev_addr,
.ep_addr = 0x00,
.setup = &setup_packet,
.buffer = (uint8_t*)&dualshock3.en_buffer,
.complete_cb = NULL,
.user_data = 0
};
if (tuh_control_xfer(&transfer))
{
dualshock3.response_count++;
get_report_complete_cb(dev_addr, instance);
return;
}
}
else if (dualshock3.response_count == 2)
{
dualshock3.response_count++;
uint8_t default_report[] =
{
0x01, 0xff, 0x00, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00
};
memcpy(&dualshock3.out_report, &default_report, sizeof(Dualshock3OutReport));
dualshock3.out_report.leds_bitmap = 0x1 << (instance + 1);
dualshock3.out_report.led[instance].time_enabled = UINT8_MAX;
tusb_control_request_t setup_packet =
{
.bmRequestType = 0x21,
.bRequest = 0x09, // SET_REPORT
.wValue = 0x0201,
.wIndex = 0x0000,
.wLength = sizeof(Dualshock3OutReport)
};
tuh_xfer_s transfer =
{
.daddr = dev_addr,
.ep_addr = 0x00,
.setup = &setup_packet,
.buffer = (uint8_t*)&dualshock3.out_report,
.complete_cb = NULL,
.user_data = 0
};
if (tuh_control_xfer(&transfer))
{
dualshock3.reports_enabled = true;
tuh_hid_receive_report(dev_addr, instance);
}
}
}
void Dualshock3::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
// if (dualshock3.response_count == 0)
// {
// if (tuh_hid_get_report(dev_addr, instance, 0xF2, HID_REPORT_TYPE_FEATURE, &dualshock3.en_buffer, 17))
// {
// dualshock3.response_count++;
// }
// }
// else if (dualshock3.response_count == 1)
// {
// if (tuh_hid_get_report(dev_addr, instance, 0xF5, HID_REPORT_TYPE_FEATURE, &dualshock3.en_buffer, 8))
// {
// dualshock3.response_count++;
// }
// }
// else if (dualshock3.response_count == 2)
// {
// dualshock3.response_count++;
// uint8_t default_report[] =
// {
// 0x01, 0xff, 0x00, 0xff, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xff, 0x27, 0x10, 0x00, 0x32,
// 0xff, 0x27, 0x10, 0x00, 0x32,
// 0xff, 0x27, 0x10, 0x00, 0x32,
// 0xff, 0x27, 0x10, 0x00, 0x32,
// 0x00, 0x00, 0x00, 0x00, 0x00
// };
// memcpy(&dualshock3.out_report, &default_report, sizeof(Dualshock3OutReport));
// dualshock3.out_report.leds_bitmap = 0x1 << (instance + 1);
// dualshock3.out_report.led[instance].time_enabled = UINT8_MAX;
// tusb_control_request_t setup_packet =
// {
// .bmRequestType = 0x21,
// .bRequest = 0x09, // SET_REPORT
// .wValue = 0x0201,
// .wIndex = 0x0000,
// .wLength = sizeof(Dualshock3OutReport)
// };
// tuh_xfer_s transfer =
// {
// .daddr = dev_addr,
// .ep_addr = 0x00,
// .setup = &setup_packet,
// .buffer = (uint8_t*)&dualshock3.out_report,
// .complete_cb = NULL,
// .user_data = 0
// };
// if (tuh_control_xfer(&transfer))
// {
// dualshock3.reports_enabled = true;
// tuh_hid_receive_report(dev_addr, instance);
// }
// }
}
void Dualshock3::update_gamepad(Gamepad* gamepad, const Dualshock3Report* ds3_data)
{
gamepad->reset_pad(gamepad);
if (ds3_data->up) gamepad->buttons.up =true;
if (ds3_data->down) gamepad->buttons.down =true;
if (ds3_data->left) gamepad->buttons.left =true;
if (ds3_data->right) gamepad->buttons.right =true;
if (ds3_data->square) gamepad->buttons.x = true;
if (ds3_data->triangle) gamepad->buttons.y = true;
if (ds3_data->cross) gamepad->buttons.a = true;
if (ds3_data->circle) gamepad->buttons.b = true;
if (ds3_data->select) gamepad->buttons.back = true;
if (ds3_data->start) gamepad->buttons.start = true;
if (ds3_data->ps) gamepad->buttons.sys = true;
if (ds3_data->l3) gamepad->buttons.l3 = true;
if (ds3_data->r3) gamepad->buttons.r3 = true;
if (ds3_data->l1) gamepad->buttons.lb = true;
if (ds3_data->r1) gamepad->buttons.rb = true;
gamepad->analog_buttons.up = ds3_data->up_axis;
gamepad->analog_buttons.down = ds3_data->down_axis;
gamepad->analog_buttons.left = ds3_data->left_axis;
gamepad->analog_buttons.right = ds3_data->right_axis;
gamepad->analog_buttons.x = ds3_data->square_axis;
gamepad->analog_buttons.y = ds3_data->triangle_axis;
gamepad->analog_buttons.a = ds3_data->cross_axis;
gamepad->analog_buttons.b = ds3_data->circle_axis;
gamepad->analog_buttons.lb = ds3_data->l1_axis;
gamepad->analog_buttons.rb = ds3_data->r1_axis;
gamepad->triggers.l = ds3_data->l2_axis;
gamepad->triggers.r = ds3_data->r2_axis;
gamepad->joysticks.lx = scale_uint8_to_int16(ds3_data->left_x, false);
gamepad->joysticks.ly = scale_uint8_to_int16(ds3_data->left_y, true);
gamepad->joysticks.rx = scale_uint8_to_int16(ds3_data->right_x, false);
gamepad->joysticks.ry = scale_uint8_to_int16(ds3_data->right_y, true);
}
void Dualshock3::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static Dualshock3Report prev_report = {};
Dualshock3Report ds3_report;
memcpy(&ds3_report, report, sizeof(ds3_report));
if (memcmp(&ds3_report, &prev_report, sizeof(ds3_report)) != 0)
{
update_gamepad(gamepad, &ds3_report);
prev_report = ds3_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void Dualshock3::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
bool Dualshock3::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
(void)instance;
static absolute_time_t next_allowed_time = {0};
absolute_time_t current_time = get_absolute_time();
if (!dualshock3.reports_enabled)
{
return false;
}
dualshock3.out_report.rumble.right_duration = (gamepad->rumble.r > 0) ? 20: 0;
dualshock3.out_report.rumble.right_motor_on = (gamepad->rumble.r > 0) ? 1 : 0;
dualshock3.out_report.rumble.left_duration = (gamepad->rumble.l > 0) ? 20 : 0;
dualshock3.out_report.rumble.left_motor_force = gamepad->rumble.l;
if (gamepad->rumble.l > 0 || gamepad->rumble.r > 0 ||
absolute_time_diff_us(current_time, next_allowed_time) < 0)
{
tusb_control_request_t setup_packet =
{
.bmRequestType = 0x21,
.bRequest = 0x09,
.wValue = 0x0201,
.wIndex = 0x0000,
.wLength = sizeof(Dualshock3OutReport)
};
tuh_xfer_s transfer =
{
.daddr = dev_addr,
.ep_addr = 0x00,
.setup = &setup_packet,
.buffer = (uint8_t*)&dualshock3.out_report,
.complete_cb = NULL,
.user_data = 0
};
if (tuh_control_xfer(&transfer))
{
if (gamepad->rumble.l == 0 && gamepad->rumble.r == 0)
{
next_allowed_time = delayed_by_us(get_absolute_time(), 500000);
}
return true;
}
}
return false;
}

39
src/usbh/ps3/Dualshock3.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef _DUALSHOCK3_H_
#define _DUALSHOCK3_H_
#include <stdint.h>
#include "descriptors/PS3Descriptors.h"
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t ps3_devices[] =
{
{0x054C, 0x0268}, // Sony Batoh (Dualshock 3)
};
struct Dualshock3State
{
uint8_t player_id {0};
bool reports_enabled {false};
uint8_t en_buffer[17];
Dualshock3OutReport out_report;
int response_count {0};
};
class Dualshock3 : public GPHostDriver
{
public:
~Dualshock3() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
Dualshock3State dualshock3;
void update_gamepad(Gamepad* gp, const Dualshock3Report* ds3_data);
void get_report_complete_cb(uint8_t dev_addr, uint8_t instance);
};
#endif

152
src/usbh/ps4/Dualshock4.cpp Normal file
View File

@@ -0,0 +1,152 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "CRC32.h"
#include "usbh/ps4/Dualshock4.h"
#include "usbh/shared/scaling.h"
#define REPORT_ID_GAMEPAD_STATE 0x11
void Dualshock4::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
dualshock4.player_id = player_id;
// set_leds();
tuh_hid_receive_report(dev_addr, instance);
}
/* this DCs the controller, come back to it */
bool Dualshock4::set_leds(uint8_t dev_addr, uint8_t instance)
{
// see: https://github.com/Hydr8gon/VitaControl
static uint8_t buffer[79] = {};
buffer[0] = 0xA2;
buffer[1] = 0x11;
buffer[2] = 0xC0;
buffer[3] = 0x20;
buffer[4] = 0xF3;
buffer[5] = 0x04;
buffer[9] = led_colors[instance][0];
buffer[10] = led_colors[instance][1];
buffer[11] = led_colors[instance][2];
// Calculate the CRC of the data (including the 0xA2 byte) and append it to the end
uint32_t crc = CRC32::calculate(buffer, 75);
buffer[75] = crc >> 0;
buffer[76] = crc >> 8;
buffer[77] = crc >> 16;
buffer[78] = crc >> 24;
// Send the write request, omitting the 0xA2 byte
dualshock4.leds_set = tuh_hid_send_report(dev_addr, instance, 0, &buffer + 1, sizeof(buffer) - 1);
return dualshock4.leds_set;
}
void Dualshock4::update_gamepad(Gamepad* gamepad, const Dualshock4Report* ds4_data)
{
gamepad->reset_pad(gamepad);
switch(ds4_data->dpad)
{
case PS4_DPAD_MASK_UP:
gamepad->buttons.up = true;
break;
case PS4_DPAD_MASK_UP_RIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case PS4_DPAD_MASK_RIGHT:
gamepad->buttons.right = true;
break;
case PS4_DPAD_MASK_RIGHT_DOWN:
gamepad->buttons.right = true;
gamepad->buttons.down = true;
break;
case PS4_DPAD_MASK_DOWN:
gamepad->buttons.down = true;
break;
case PS4_DPAD_MASK_DOWN_LEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case PS4_DPAD_MASK_LEFT:
gamepad->buttons.left = true;
break;
case PS4_DPAD_MASK_LEFT_UP:
gamepad->buttons.left = true;
gamepad->buttons.up = true;
break;
}
if (ds4_data->square) gamepad->buttons.x = true;
if (ds4_data->cross) gamepad->buttons.a = true;
if (ds4_data->circle) gamepad->buttons.b = true;
if (ds4_data->triangle) gamepad->buttons.y = true;
if (ds4_data->share) gamepad->buttons.back = true;
if (ds4_data->option) gamepad->buttons.start = true;
if (ds4_data->ps) gamepad->buttons.sys = true;
if (ds4_data->tpad) gamepad->buttons.misc = true;
if (ds4_data->l1) gamepad->buttons.lb = true;
if (ds4_data->r1) gamepad->buttons.rb = true;
if (ds4_data->l3) gamepad->buttons.l3 = true;
if (ds4_data->r3) gamepad->buttons.r3 = true;
gamepad->triggers.l = ds4_data->l2_trigger;
gamepad->triggers.r = ds4_data->r2_trigger;
gamepad->joysticks.lx = scale_uint8_to_int16(ds4_data->lx, false);
gamepad->joysticks.ly = scale_uint8_to_int16(ds4_data->ly, true);
gamepad->joysticks.rx = scale_uint8_to_int16(ds4_data->rx, false);
gamepad->joysticks.ry = scale_uint8_to_int16(ds4_data->ry, true);
}
void Dualshock4::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static Dualshock4Report prev_report = {};
Dualshock4Report ds4_report;
memcpy(&ds4_report, report, sizeof(ds4_report));
if (memcmp(&ds4_report, &prev_report, sizeof(ds4_report)) != 0)
{
update_gamepad(gamepad, &ds4_report);
prev_report = ds4_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void Dualshock4::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void Dualshock4::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool Dualshock4::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
Dualshock4OutReport out_report = {0};
out_report.set_rumble = 1;
out_report.motor_left = gamepad->rumble.l;
out_report.motor_right = gamepad->rumble.r;
return tuh_hid_send_report(dev_addr, instance, 5, &out_report, sizeof(out_report));
}

View File

@@ -1,22 +1,30 @@
#pragma once
#ifndef _PS4_H_
#define _PS4_H_
#ifndef _DUALSHOCK4_H_
#define _DUALSHOCK4_H_
#include <stdint.h>
#include "tusb_hid/hid_vid_pid.h"
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t ps4_devices[] =
{
{0x054C, 0x05C4}, // DS4
{0x054C, 0x09CC}, // DS4
{0x054C, 0x0BA0}, // DS4 wireless adapter
{0x2563, 0x0357}, // MPOW Wired Gamepad (ShenZhen ShanWan)
{0x0F0D, 0x005E}, // Hori FC4
{0x0F0D, 0x00EE}, // Hori PS4 Mini (PS4-099U)
{0x1F4F, 0x1002} // ASW GG Xrd controller
};
enum dualshock4_dpad_mask
static const uint8_t led_colors[][3] =
{
{ 0x00, 0x00, 0x40 }, // Blue
{ 0x40, 0x00, 0x00 }, // Red
{ 0x00, 0x40, 0x00 }, // Green
{ 0x20, 0x00, 0x20 }, // Pink
};
enum Dualshock4DpadMask
{
PS4_DPAD_MASK_UP = 0x00,
PS4_DPAD_MASK_UP_RIGHT = 0x01,
@@ -31,6 +39,8 @@ enum dualshock4_dpad_mask
typedef struct __attribute__((packed))
{
uint8_t report_id;
uint8_t lx, ly, rx, ry; // joystick
struct {
@@ -69,11 +79,10 @@ typedef struct __attribute__((packed))
// there is still more info
} sony_ds4_report_t;
} Dualshock4Report;
typedef struct __attribute__((packed))
{
// First 16 bits set what data is pertinent in this structure (1 = set; 0 = not set)
uint8_t set_rumble : 1;
uint8_t set_led : 1;
uint8_t set_led_blink : 1;
@@ -103,17 +112,28 @@ typedef struct __attribute__((packed))
uint8_t volume_speaker;
uint8_t other[9];
} sony_ds4_output_report_t;
} Dualshock4OutReport;
// #ifdef __cplusplus
// extern "C" {
// #endif
struct Dualshock4State
{
uint8_t player_id = {0};
bool leds_set = {false};
};
bool send_fb_data_to_dualshock4(uint8_t dev_addr, uint8_t instance);
void process_dualshock4(uint8_t const* report, uint16_t len);
class Dualshock4 : public GPHostDriver
{
public:
~Dualshock4() override {}
// #ifdef __cplusplus
// }
// #endif
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
Dualshock4State dualshock4;
void update_gamepad(Gamepad* gp, const Dualshock4Report* ds4_data);
bool set_leds(uint8_t dev_addr, uint8_t instance);
};
#endif // _PS4_H_
#endif

115
src/usbh/ps5/Dualsense.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "usbh/ps5/Dualsense.h"
#include "usbh/shared/scaling.h"
void Dualsense::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
dualsense.player_id = player_id;
tuh_hid_receive_report(dev_addr, instance);
}
void Dualsense::update_gamepad(Gamepad* gamepad, const DualsenseReport* ds_report)
{
gamepad->reset_pad(gamepad);
switch(ds_report->dpad)
{
case PS5_MASK_DPAD_UP:
gamepad->buttons.up = true;
break;
case PS5_MASK_DPAD_UP_RIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case PS5_MASK_DPAD_RIGHT:
gamepad->buttons.right = true;
break;
case PS5_MASK_DPAD_RIGHT_DOWN:
gamepad->buttons.right = true;
gamepad->buttons.down = true;
break;
case PS5_MASK_DPAD_DOWN:
gamepad->buttons.down = true;
break;
case PS5_MASK_DPAD_DOWN_LEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case PS5_MASK_DPAD_LEFT:
gamepad->buttons.left = true;
break;
case PS5_MASK_DPAD_LEFT_UP:
gamepad->buttons.left = true;
gamepad->buttons.up = true;
break;
}
if (ds_report->square) gamepad->buttons.x = true;
if (ds_report->cross) gamepad->buttons.a = true;
if (ds_report->circle) gamepad->buttons.b = true;
if (ds_report->triangle) gamepad->buttons.y = true;
if (ds_report->buttons[0] & PS5_MASK_L1) gamepad->buttons.lb = true;
if (ds_report->buttons[0] & PS5_MASK_R1) gamepad->buttons.rb = true;
if (ds_report->buttons[0] & PS5_MASK_SHARE) gamepad->buttons.back = true;
if (ds_report->buttons[0] & PS5_MASK_OPTIONS) gamepad->buttons.start = true;
if (ds_report->buttons[0] & PS5_MASK_L3) gamepad->buttons.l3 = true;
if (ds_report->buttons[0] & PS5_MASK_R3) gamepad->buttons.r3 = true;
if (ds_report->buttons[1] & PS5_MASK_PS) gamepad->buttons.sys = true;
if (ds_report->buttons[1] & PS5_MASK_MIC) gamepad->buttons.misc = true;
gamepad->triggers.l = ds_report->lt;
gamepad->triggers.r = ds_report->rt;
gamepad->joysticks.lx = scale_uint8_to_int16(ds_report->lx, false);
gamepad->joysticks.ly = scale_uint8_to_int16(ds_report->ly, true);
gamepad->joysticks.rx = scale_uint8_to_int16(ds_report->rx, false);
gamepad->joysticks.ry = scale_uint8_to_int16(ds_report->ry, true);
}
void Dualsense::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
DualsenseReport ds_report;
memcpy(&ds_report, report, sizeof(ds_report));
update_gamepad(gamepad, &ds_report);
tuh_hid_receive_report(dev_addr, instance);
}
void Dualsense::process_xinput_report(Gamepad* gp, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gp;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void Dualsense::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool Dualsense::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
// need to figure out if the flags are necessary and how the LEDs work
DualsenseOutReport out_report = {0};
out_report.valid_flag0 = 0x02; // idk what this means
out_report.valid_flag1 = 0x02; // this one either
out_report.valid_flag2 = 0x04; // uhhhhh
out_report.motor_left = gamepad->rumble.l;
out_report.motor_right = gamepad->rumble.r;
return tuh_hid_send_report(dev_addr, instance, 5, &out_report, sizeof(out_report));
}

View File

@@ -1,18 +1,17 @@
#pragma once
#ifndef _PS5_H_
#define _PS5_H_
#ifndef _DUALSENSE_H_
#define _DUALSENSE_H_
#include <stdint.h>
#include "hid_vid_pid.h"
// TODO: find more of these
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t ps5_devices[] =
{
{0x054C, 0x0CE6} // dualsense
{0x054C, 0x0CE6}, // dualsense
{0x054C, 0x0DF2} // dualsense edge
};
enum dualsense_button0_mask
enum dualsense_dpad_mask
{
PS5_MASK_DPAD_UP = 0x00,
PS5_MASK_DPAD_UP_RIGHT = 0x01,
@@ -23,13 +22,17 @@ enum dualsense_button0_mask
PS5_MASK_DPAD_LEFT = 0x06,
PS5_MASK_DPAD_LEFT_UP = 0x07,
PS5_MASK_DPAD_NONE = 0x08,
PS5_MASK_SQUARE = 0x10,
PS5_MASK_CROSS = 0x20,
PS5_MASK_CIRCLE = 0x40,
PS5_MASK_TRIANGLE = 0x80,
};
enum dualsense_button1_mask
// enum dualsense_action_mask
// {
// PS5_MASK_SQUARE = 0x10,
// PS5_MASK_CROSS = 0x20,
// PS5_MASK_CIRCLE = 0x40,
// PS5_MASK_TRIANGLE = 0x80,
// };
enum dualsense_buttons0_mask
{
PS5_MASK_L1 = 0x01,
PS5_MASK_R1 = 0x02,
@@ -41,19 +44,28 @@ enum dualsense_button1_mask
PS5_MASK_R3 = 0x80,
};
enum dualsense_button2_mask
enum dualsense_buttons1_mask
{
PS5_MASK_PS = 0x01,
PS5_MASK_TOUCH_PAD = 0x02,
PS5_MASK_MIC = 0x04,
};
struct dualsense_input_report {
struct DualsenseReport {
uint8_t report_id;
uint8_t lx, ly;
uint8_t rx, ry;
uint8_t lt, rt;
uint8_t seq_number; // Sequence number for the input report
uint8_t button[4]; // Array of button masks
uint8_t seq_number;
uint8_t dpad : 4;
uint8_t square : 1;
uint8_t cross : 1;
uint8_t circle : 1;
uint8_t triangle : 1;
uint8_t buttons[3];
// Motion sensors
uint16_t gyro[3]; // Gyroscope data for x, y, z axes
@@ -76,7 +88,7 @@ struct dualsense_input_report {
uint8_t reserved4[10];
} __attribute__((packed));
struct dualsense_output_report_t {
struct DualsenseOutReport {
uint8_t valid_flag0;
uint8_t valid_flag1;
@@ -117,15 +129,22 @@ struct dualsense_output_report_t {
uint8_t lightbar_blue;
} __attribute__((packed));
#ifdef __cplusplus
extern "C" {
#endif
struct DualsenseState
{
uint8_t player_id = {0};
};
bool send_fb_data_to_dualsense(uint8_t dev_addr, uint8_t instance);
void process_dualsense(uint8_t const* report, uint16_t len);
class Dualsense : public GPHostDriver
{
public:
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
DualsenseState dualsense;
void update_gamepad(Gamepad* gp, const DualsenseReport* ds_report);
};
#ifdef __cplusplus
}
#endif
#endif // _PS5_H_
#endif // _DUALSENSE_H_

View File

@@ -0,0 +1,107 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "usbh/psclassic/PSClassic.h"
void PSClassic::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
psclassic.player_id = player_id;
tuh_hid_receive_report(dev_addr, instance);
}
void PSClassic::update_gamepad(Gamepad* gamepad, const PSClassicReport* psc_data)
{
gamepad->reset_pad(gamepad);
switch (psc_data->buttons & 0x3C00) {
case PSCLASSIC_MASK_UP_LEFT:
gamepad->buttons.up = true;
gamepad->buttons.left = true;
break;
case PSCLASSIC_MASK_UP:
gamepad->buttons.up = true;
break;
case PSCLASSIC_MASK_UP_RIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case PSCLASSIC_MASK_LEFT:
gamepad->buttons.left = true;
break;
case PSCLASSIC_MASK_RIGHT:
gamepad->buttons.right = true;
break;
case PSCLASSIC_MASK_DOWN_LEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case PSCLASSIC_MASK_DOWN:
gamepad->buttons.down = true;
break;
case PSCLASSIC_MASK_DOWN_RIGHT:
gamepad->buttons.down = true;
gamepad->buttons.right = true;
break;
}
if (psc_data->buttons & PSCLASSIC_MASK_TRIANGLE) gamepad->buttons.y = true;
if (psc_data->buttons & PSCLASSIC_MASK_CIRCLE) gamepad->buttons.b = true;
if (psc_data->buttons & PSCLASSIC_MASK_CROSS) gamepad->buttons.a = true;
if (psc_data->buttons & PSCLASSIC_MASK_SQUARE) gamepad->buttons.x = true;
if (psc_data->buttons & PSCLASSIC_MASK_L2) gamepad->triggers.l = 0xFF;
if (psc_data->buttons & PSCLASSIC_MASK_R2) gamepad->triggers.r = 0xFF;
if (psc_data->buttons & PSCLASSIC_MASK_L1) gamepad->buttons.lb = true;
if (psc_data->buttons & PSCLASSIC_MASK_R1) gamepad->buttons.rb = true;
if (psc_data->buttons & PSCLASSIC_MASK_SELECT) gamepad->buttons.back = true;
if (psc_data->buttons & PSCLASSIC_MASK_START) gamepad->buttons.start = true;
}
void PSClassic::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static PSClassicReport prev_report = {};
PSClassicReport psc_report;
memcpy(&psc_report, report, sizeof(psc_report));
if (memcmp(&psc_report, &prev_report, sizeof(psc_report)) != 0)
{
update_gamepad(gamepad, &psc_report);
prev_report = psc_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void PSClassic::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void PSClassic::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool PSClassic::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
return true;
}

View File

@@ -0,0 +1,34 @@
#ifndef _PSCLASSIC_H_
#define _PSCLASSIC_H_
#include <stdint.h>
#include "descriptors/PSClassicDescriptors.h"
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t psc_devices[] =
{
{0x054C, 0x0CDA} // psclassic
};
struct PSClassicState
{
uint8_t player_id = {0};
};
class PSClassic : public GPHostDriver
{
public:
~PSClassic() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
PSClassicState psclassic;
void update_gamepad(Gamepad* gp, const PSClassicReport* psc_data);
};
#endif // _PSCLASSIC_H_

View File

@@ -0,0 +1,13 @@
#include "host/usbh.h"
#include "host/usbh_pvt.h"
#include "class/hid/hid_host.h"
#include "usbh/shared/hid_class_driver.h"
usbh_class_driver_t const usbh_hid_driver =
{
.init = hidh_init,
.open = hidh_open,
.set_config = hidh_set_config,
.xfer_cb = hidh_xfer_cb,
.close = hidh_close
};

View File

@@ -0,0 +1,8 @@
#pragma once
#ifndef _HID_CLASS_DRIVER_H_
#define _HID_CLASS_DRIVER_H_
extern usbh_class_driver_t const usbh_hid_driver;
#endif

View File

@@ -0,0 +1,26 @@
#include "usbh/shared/scaling.h"
int16_t scale_uint8_to_int16(uint8_t value, bool invert)
{
const uint32_t scaling_factor = UINT16_MAX;
const int32_t bias = INT16_MIN;
int32_t scaled_value = ((uint32_t)value * scaling_factor) >> 8;
scaled_value += bias;
if (invert)
{
scaled_value = -scaled_value - 1;
}
if (scaled_value < INT16_MIN)
{
scaled_value = INT16_MIN;
}
else if (scaled_value > INT16_MAX)
{
scaled_value = INT16_MAX;
}
return (int16_t)scaled_value;
}

View File

@@ -0,0 +1,8 @@
#ifndef _SCALING_H_
#define _SCALING_H_
#include <stdint.h>
int16_t scale_uint8_to_int16(uint8_t value, bool invert);
#endif // _SCALING_H_

12
src/usbh/shared/shared.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#ifndef _SHARED_H_
#define _SHARED_H_
typedef struct
{
uint16_t vid;
uint16_t pid;
} usb_vid_pid_t;
#endif // _SHARED_H_

View File

@@ -0,0 +1,260 @@
#include <stdint.h>
#include <cmath>
#include "pico/stdlib.h"
#include "tusb.h"
#include "usbh/switch/SwitchPro.h"
void SwitchPro::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
(void)dev_addr;
(void)instance;
switch_pro.player_id = player_id;
}
void SwitchPro::send_handshake(uint8_t dev_addr, uint8_t instance)
{
if (tuh_hid_send_ready(dev_addr, instance))
{
uint8_t handshake_command[2] = {CMD_HID, SUBCMD_HANDSHAKE};
switch_pro.handshake_sent = tuh_hid_send_report(dev_addr, instance, 0, handshake_command, sizeof(handshake_command));
}
tuh_hid_receive_report(dev_addr, instance);
}
uint8_t SwitchPro::get_output_sequence_counter()
{
// increments each report, resets to 0 after 15
uint8_t counter = switch_pro.output_sequence_counter;
switch_pro.output_sequence_counter = (switch_pro.output_sequence_counter + 1) & 0x0F;
return counter;
}
void SwitchPro::disable_timeout(uint8_t dev_addr, uint8_t instance)
{
if (tuh_hid_send_ready(dev_addr, instance))
{
uint8_t disable_timeout_cmd[2] = {CMD_HID, SUBCMD_DISABLE_TIMEOUT};
switch_pro.timeout_disabled = tuh_hid_send_report(dev_addr, instance, 0, disable_timeout_cmd, sizeof(disable_timeout_cmd));
}
}
void SwitchPro::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
if (!switch_pro.handshake_sent)
{
return;
}
else if (!switch_pro.timeout_disabled)
{
disable_timeout(dev_addr, instance); // response to handshake
tuh_hid_receive_report(dev_addr, instance);
return;
}
static SwitchProReport prev_report = {};
SwitchProReport switch_report;
memcpy(&switch_report, report, sizeof(switch_report));
if (memcmp(&switch_report, &prev_report, sizeof(switch_report)) != 0)
{
update_gamepad(gamepad, &switch_report);
prev_report = switch_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
int16_t SwitchPro::normalize_axes(uint16_t value)
{
/* 12bit value from the controller doesnt cover the full 12bit range seemingly
doesn't seem completely centered at 2047 so I may be missing something here
tried to get as close as possible with the multiplier */
int32_t normalized_value = (value - 2047) * 22;
if (normalized_value < INT16_MIN)
{
normalized_value = INT16_MIN;
}
else if (normalized_value > INT16_MAX)
{
normalized_value = INT16_MAX;
}
return (int16_t)normalized_value;
}
void SwitchPro::update_gamepad(Gamepad* gamepad, const SwitchProReport* switch_report)
{
gamepad->reset_pad(gamepad);
if (switch_report->up) gamepad->buttons.up =true;
if (switch_report->down) gamepad->buttons.down =true;
if (switch_report->left) gamepad->buttons.left =true;
if (switch_report->right) gamepad->buttons.right =true;
if (switch_report->y) gamepad->buttons.x = true;
if (switch_report->x) gamepad->buttons.y = true;
if (switch_report->b) gamepad->buttons.a = true;
if (switch_report->a) gamepad->buttons.b = true;
if (switch_report->minus) gamepad->buttons.back = true;
if (switch_report->plus) gamepad->buttons.start = true;
if (switch_report->home) gamepad->buttons.sys = true;
if (switch_report->capture) gamepad->buttons.misc = true;
if (switch_report->stickL) gamepad->buttons.l3 = true;
if (switch_report->stickR) gamepad->buttons.r3 = true;
if (switch_report->l) gamepad->buttons.lb = true;
if (switch_report->r) gamepad->buttons.rb = true;
if (switch_report->zl) gamepad->triggers.l = 0xFF;
if (switch_report->zr) gamepad->triggers.r = 0xFF;
gamepad->joysticks.lx = normalize_axes(switch_report->leftX );
gamepad->joysticks.ly = normalize_axes(switch_report->leftY );
gamepad->joysticks.rx = normalize_axes(switch_report->rightX);
gamepad->joysticks.ry = normalize_axes(switch_report->rightY);
}
void SwitchPro::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void SwitchPro::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool SwitchPro::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
if (!switch_pro.handshake_sent)
{
send_handshake(dev_addr, instance);
}
else if (!switch_pro.timeout_disabled)
{
return false;
}
// See: https://github.com/Dan611/hid-procon
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
// https://github.com/HisashiKato/USB_Host_Shield_Library_2.0
SwitchProOutReport report = {};
uint8_t report_size = 10;
report.command = CMD_RUMBLE_ONLY;
report.sequence_counter = get_output_sequence_counter();
if (gamepad->rumble.l > 0)
{
uint8_t amplitude_l = static_cast<uint8_t>(((gamepad->rumble.l / 255.0f) * 0.8f + 0.5f) * (0xC0 - 0x40) + 0x40);
report.rumble_l[0] = amplitude_l;
report.rumble_l[1] = 0x88;
report.rumble_l[2] = amplitude_l / 2;
report.rumble_l[3] = 0x61;
}
else
{
report.rumble_l[0] = 0x00;
report.rumble_l[1] = 0x01;
report.rumble_l[2] = 0x40;
report.rumble_l[3] = 0x40;
}
if (gamepad->rumble.r > 0)
{
uint8_t amplitude_r = static_cast<uint8_t>(((gamepad->rumble.r / 255.0f) * 0.8f + 0.5f) * (0xC0 - 0x40) + 0x40);
report.rumble_r[0] = amplitude_r;
report.rumble_r[1] = 0x88;
report.rumble_r[2] = amplitude_r / 2;
report.rumble_r[3] = 0x61;
}
else
{
report.rumble_r[0] = 0x00;
report.rumble_r[1] = 0x01;
report.rumble_r[2] = 0x40;
report.rumble_r[3] = 0x40;
}
if (!switch_pro.commands_sent)
{
if (!switch_pro.led_set)
{
report_size = 12;
report.command = CMD_AND_RUMBLE;
report.sub_command = CMD_LED;
report.sub_command_args[0] = switch_pro.player_id;
switch_pro.led_set = tuh_hid_send_report(dev_addr, instance, 0, &report, report_size);
return switch_pro.led_set;
}
else if (!switch_pro.led_home_set)
{
report_size = 14;
report.command = CMD_AND_RUMBLE;
report.sub_command = CMD_LED_HOME;
report.sub_command_args[0] = (0 /* Number of cycles */ << 4) | (true ? 0xF : 0);
report.sub_command_args[1] = (0xF /* LED start intensity */ << 4) | 0x0 /* Number of full cycles */;
report.sub_command_args[2] = (0xF /* Mini Cycle 1 LED intensity */ << 4) | 0x0 /* Mini Cycle 2 LED intensity */;
switch_pro.led_home_set = tuh_hid_send_report(dev_addr, instance, 0, &report, report_size);
return switch_pro.led_home_set;
}
else if (!switch_pro.full_report_enabled)
{
report_size = 12;
report.command = CMD_AND_RUMBLE;
report.sub_command = CMD_MODE;
report.sub_command_args[0] = SUBCMD_FULL_REPORT_MODE;
switch_pro.full_report_enabled = tuh_hid_send_report(dev_addr, instance, 0, &report, report_size);
return switch_pro.full_report_enabled;
}
else if (!switch_pro.imu_enabled)
{
report_size = 12;
report.command = CMD_AND_RUMBLE;
report.sub_command = CMD_GYRO;
report.sub_command_args[0] = 1 ? 1 : 0;
switch_pro.imu_enabled = tuh_hid_send_report(dev_addr, instance, 0, &report, report_size);
return switch_pro.imu_enabled;
}
else
{
switch_pro.commands_sent = true;
}
}
return tuh_hid_send_report(dev_addr, instance, 0, &report, report_size);
}

View File

@@ -0,0 +1,46 @@
#ifndef _SWITCHPRO_H_
#define _SWITCHPRO_H_
#include <stdint.h>
#include "descriptors/SwitchProDescriptors.h"
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t switch_pro_devices[] =
{
{0x057E, 0x2009} // Switch Pro
};
struct SwitchProState
{
uint8_t player_id = {0};
bool handshake_sent {false};
bool timeout_disabled {false};
bool full_report_enabled {false};
bool led_set {false};
bool led_home_set {false};
bool imu_enabled {false};
bool commands_sent {false};
uint8_t output_sequence_counter {0};
};
class SwitchPro : public GPHostDriver
{
public:
~SwitchPro() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
SwitchProState switch_pro;
void send_handshake(uint8_t dev_addr, uint8_t instance);
void disable_timeout(uint8_t dev_addr, uint8_t instance);
uint8_t get_output_sequence_counter();
int16_t normalize_axes(uint16_t value);
void update_gamepad(Gamepad* gp, const SwitchProReport* switch_pro_data);
};
#endif // _SWITCHPRO_H_

View File

@@ -0,0 +1,121 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "pico/time.h"
#include "tusb.h"
#include "descriptors/SwitchDescriptors.h"
#include "usbh/switch/SwitchWired.h"
#include "usbh/shared/scaling.h"
void SwitchWired::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
switch_wired.player_id = player_id;
tuh_hid_receive_report(dev_addr, instance);
}
void SwitchWired::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)len;
static SwitchWiredReport prev_report = {};
SwitchWiredReport switch_report;
memcpy(&switch_report, report, sizeof(switch_report));
if (memcmp(&switch_report, &prev_report, sizeof(switch_report)) != 0)
{
update_gamepad(gamepad, &switch_report);
prev_report = switch_report;
}
tuh_hid_receive_report(dev_addr, instance);
}
void SwitchWired::update_gamepad(Gamepad* gamepad, const SwitchWiredReport* switch_report)
{
gamepad->reset_pad(gamepad);
switch (switch_report->dpad)
{
case SWITCH_HAT_UP:
gamepad->buttons.up = true;
break;
case SWITCH_HAT_UPRIGHT:
gamepad->buttons.up = true;
gamepad->buttons.right = true;
break;
case SWITCH_HAT_RIGHT:
gamepad->buttons.right = true;
break;
case SWITCH_HAT_DOWNRIGHT:
gamepad->buttons.right = true;
gamepad->buttons.down = true;
break;
case SWITCH_HAT_DOWN:
gamepad->buttons.down = true;
break;
case SWITCH_HAT_DOWNLEFT:
gamepad->buttons.down = true;
gamepad->buttons.left = true;
break;
case SWITCH_HAT_LEFT:
gamepad->buttons.left = true;
break;
case SWITCH_HAT_UPLEFT:
gamepad->buttons.up = true;
gamepad->buttons.left = true;
break;
}
if (switch_report->b) gamepad->buttons.a = true;
if (switch_report->a) gamepad->buttons.b = true;
if (switch_report->y) gamepad->buttons.x = true;
if (switch_report->x) gamepad->buttons.y = true;
if (switch_report->minus) gamepad->buttons.back = true;
if (switch_report->plus) gamepad->buttons.start = true;
if (switch_report->l3) gamepad->buttons.l3 = true;
if (switch_report->r3) gamepad->buttons.r3 = true;
if(switch_report->home) gamepad->buttons.sys = true;
if(switch_report->capture) gamepad->buttons.misc = true;
if(switch_report->l) gamepad->buttons.lb = true;
if(switch_report->r) gamepad->buttons.rb = true;
if(switch_report->lz) gamepad->triggers.l = 0xFF;
if(switch_report->rz) gamepad->triggers.r = 0xFF;
gamepad->joysticks.lx = scale_uint8_to_int16(switch_report->lx, false);
gamepad->joysticks.ly = scale_uint8_to_int16(switch_report->ly, true);
gamepad->joysticks.rx = scale_uint8_to_int16(switch_report->rx, false);
gamepad->joysticks.ry = scale_uint8_to_int16(switch_report->ry, true);
}
void SwitchWired::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
}
void SwitchWired::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool SwitchWired::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
(void)gamepad;
(void)dev_addr;
(void)instance;
return true; // not aware of a wired switch gamepad with rumble
}

View File

@@ -0,0 +1,67 @@
#ifndef _SWITCHWIRED_H_
#define _SWITCHWIRED_H_
#include <stdint.h>
#include "usbh/GPHostDriver.h"
const usb_vid_pid_t switch_wired_devices[] =
{
{0x20D6, 0xA719}, // PowerA wired
{0x20D6, 0xA713}, // PowerA Enhanced wired
{0x0F0D, 0x0092} // Hori Pokken wired, I don't have this one so not 100% on if it'll work
};
struct SwitchWiredReport
{
uint8_t y : 1;
uint8_t b : 1;
uint8_t a : 1;
uint8_t x : 1;
uint8_t l : 1;
uint8_t r : 1;
uint8_t lz : 1;
uint8_t rz : 1;
uint8_t minus : 1;
uint8_t plus : 1;
uint8_t l3 : 1;
uint8_t r3 : 1;
uint8_t home : 1;
uint8_t capture : 1;
uint8_t : 2;
uint8_t dpad : 4;
uint8_t : 4;
uint8_t lx;
uint8_t ly;
uint8_t rx;
uint8_t ry;
}
__attribute__((packed));
struct SwitchWiredState
{
uint8_t player_id = {0};
};
class SwitchWired : public GPHostDriver
{
public:
~SwitchWired() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
SwitchWiredState switch_wired;
void update_gamepad(Gamepad* gp, const SwitchWiredReport* switch_pro_data);
};
#endif // _SWITCHWIRED_H_

View File

@@ -1,104 +0,0 @@
#include <stdint.h>
#include "pico/stdlib.h"
// #include "bsp/board_api.h"
#include "tusb.h"
#include "host/usbh.h"
#include "host/usbh_pvt.h"
#include "class/hid/hid_host.h"
#include "tusb_hid/hid_host_app.h"
#include "tusb_hid/ps4.h"
#include "tusb_hid/ps5.h"
#include "tusb_host_manager.h" // global enum host_mode
static bool gamepad_mounted = false;
static uint8_t gamepad_dev_addr = 0;
static uint8_t gamepad_instance = 0;
usbh_class_driver_t const usbh_hid_driver =
{
.init = hidh_init,
.open = hidh_open,
.set_config = hidh_set_config,
.xfer_cb = hidh_xfer_cb,
.close = hidh_close
};
bool hid_gamepad_mounted()
{
return gamepad_mounted;
}
// Invoked when device with hid interface is mounted
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
{
(void)desc_report;
(void)desc_len;
if (!gamepad_mounted)
{
gamepad_dev_addr = dev_addr;
gamepad_instance = instance;
gamepad_mounted = true;
}
// request to receive report
// tuh_hid_report_received_cb() will be invoked when report is available
if (!tuh_hid_receive_report(dev_addr, instance))
{
printf("Error: cannot request to receive report\r\n");
}
}
// Invoked when device with hid interface is un-mounted
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
{
if (gamepad_mounted && gamepad_dev_addr == dev_addr && gamepad_instance == instance)
{
gamepad_mounted = false;
}
}
// Invoked when received report from device via interrupt endpoint
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
switch(host_mode)
{
case HOST_MODE_HID_PS4:
process_dualshock4(report, len);
break;
case HOST_MODE_HID_PS5:
process_dualsense(report, len);
break;
}
if ( !tuh_hid_receive_report(dev_addr, instance) )
{
printf("Error: cannot request to receive report\r\n");
}
}
// send rumble data
bool send_fb_data_to_hid_gamepad()
{
bool rumble_sent = false;
if (gamepad_mounted)
{
switch(host_mode)
{
case HOST_MODE_HID_PS4:
rumble_sent = send_fb_data_to_dualshock4(gamepad_dev_addr, gamepad_instance);
break;
case HOST_MODE_HID_PS5:
rumble_sent = send_fb_data_to_dualsense(gamepad_dev_addr, gamepad_instance);
break;
}
}
return rumble_sent;
}

View File

@@ -1,9 +0,0 @@
#ifndef _HID_HOST_APP_H_
#define _HID_HOST_APP_H_
extern usbh_class_driver_t const usbh_hid_driver;
bool hid_gamepad_mounted();
bool send_fb_data_to_hid_gamepad();
#endif // _HID_HOST_APP_H_

View File

@@ -1,15 +0,0 @@
#pragma once
#ifndef _HID_VID_PID_H_
#define _HID_VID_PID_H_
#include <stdint.h>
// put this somewhere else?
typedef struct
{
uint16_t vid;
uint16_t pid;
} usb_vid_pid_t;
#endif // _HID_VID_PID_H_

View File

@@ -1,42 +0,0 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "tusb_hid/ps4.h"
#include "Gamepad.h"
void process_dualshock4(uint8_t const* report, uint16_t len)
{
// previous report, compared for changes
static sony_ds4_report_t prev_report = { 0 };
// Increment pointer and decrement length to skip report ID
report++;
len--;
sony_ds4_report_t ds4_report;
memcpy(&ds4_report, report, sizeof(ds4_report));
// Check if the new report is different from the previous one
if (memcmp(&ds4_report, &prev_report, sizeof(ds4_report)) != 0)
{
gamepad.update_gamepad_state_from_dualshock4(&ds4_report);
// Update the previous report
prev_report = ds4_report;
}
}
bool send_fb_data_to_dualshock4(uint8_t dev_addr, uint8_t instance)
{
sony_ds4_output_report_t output_report = {0};
output_report.set_rumble = 1;
output_report.motor_left = gamepadOut.out_state.lrumble;
output_report.motor_right = gamepadOut.out_state.rrumble;
bool rumble_sent = tuh_hid_send_report(dev_addr, instance, 5, &output_report, sizeof(output_report));
return rumble_sent;
}

View File

@@ -1,44 +0,0 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "tusb.h"
#include "tusb_hid/ps5.h"
#include "Gamepad.h"
void process_dualsense(uint8_t const* report, uint16_t len)
{
// previous report, compared for changes
static dualsense_input_report prev_report = { 0 };
// Increment pointer and decrement length to skip report ID
report++;
len--;
dualsense_input_report ds5_report;
memcpy(&ds5_report, report, sizeof(ds5_report));
// Check if the new report is different from the previous one
if (memcmp(&ds5_report, &prev_report, sizeof(ds5_report)) != 0)
{
gamepad.update_gamepad_state_from_dualsense(&ds5_report);
prev_report = ds5_report;
}
}
bool send_fb_data_to_dualsense(uint8_t dev_addr, uint8_t instance)
{
// need to figure out if the flags are necessary and how the LEDs work
dualsense_output_report_t output_report = {0};
output_report.valid_flag0 = 0x02; // idk what this means
output_report.valid_flag1 = 0x02; // this one either
output_report.valid_flag2 = 0x04; // uhhhhh
output_report.motor_left = gamepadOut.out_state.lrumble;
output_report.motor_right = gamepadOut.out_state.rrumble;
bool rumble_sent = tuh_hid_send_report(dev_addr, instance, 5, &output_report, sizeof(output_report));
return rumble_sent;
}

View File

@@ -1,44 +1,29 @@
#include <stdlib.h>
#include <stdarg.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "pio_usb.h"
#include "tusb.h"
#include "tusb_host.h"
#include "tusb_host_manager.h"
#ifdef FEATHER_RP2040
#define PIO_USB_DP_PIN 16 // DM = 17
#define PIN_5V_EN 18
// #define RED_LED_PIN 13
// #define NEOPIXEL_PWR_PIN 20
// #define NEOPIXEL_CTRL_PIN 21
void board_setup()
{
gpio_init(PIN_5V_EN);
gpio_set_dir(PIN_5V_EN, GPIO_OUT);
gpio_put(PIN_5V_EN, 1);
}
#else
#define PIO_USB_DP_PIN 0 // DM = 1
// #define PICO_LED_PIN 25
void board_setup()
{
// gpio_init(PICO_LED_PIN);
// gpio_set_dir(PICO_LED_PIN, GPIO_OUT);
// gpio_put(PICO_LED_PIN, 1);
}
#endif
#include "usbh/tusb_host.h"
#include "usbh/tusb_host_manager.h"
#include "ogxm_config.h"
#define PIO_USB_CONFIG { PIO_USB_DP_PIN, PIO_USB_TX_DEFAULT, PIO_SM_USB_TX_DEFAULT, PIO_USB_DMA_TX_DEFAULT, PIO_USB_RX_DEFAULT, PIO_SM_USB_RX_DEFAULT, PIO_SM_USB_EOP_DEFAULT, NULL, PIO_USB_DEBUG_PIN_NONE, PIO_USB_DEBUG_PIN_NONE, false, PIO_USB_PINOUT_DPDM }
void board_setup()
{
#if OGXM_BOARD == ADAFRUIT_FEATHER_USBH
gpio_init(VCC_EN_PIN);
gpio_set_dir(VCC_EN_PIN, GPIO_OUT);
gpio_put(VCC_EN_PIN, 1);
#endif
gpio_init(LED_INDICATOR_PIN);
gpio_set_dir(LED_INDICATOR_PIN, GPIO_OUT);
gpio_put(LED_INDICATOR_PIN, 0);
}
void usbh_main()
{
pio_usb_configuration_t pio_cfg = PIO_USB_CONFIG;

View File

@@ -1,105 +1,345 @@
#include "hardware/gpio.h"
#include "tusb.h"
#include "host/usbh.h"
#include "class/hid/hid_host.h"
#include "tusb_gamepad.h"
#include "tusb_xinput/xinput_host_app.h"
#include "tusb_xinput/xinput_host.h"
#include "usbh/xinput/XInput.h"
#include "usbh/n64usb/N64USB.h"
#include "usbh/psclassic/PSClassic.h"
#include "usbh/ps3/DInput.h"
#include "usbh/ps3/Dualshock3.h"
#include "usbh/ps4/Dualshock4.h"
#include "usbh/ps5/Dualsense.h"
#include "usbh/switch/SwitchPro.h"
#include "usbh/switch/SwitchWired.h"
#include "tusb_hid/hid_host_app.h"
#include "tusb_hid/ps4.h"
#include "tusb_hid/ps5.h"
#include "usbh/shared/hid_class_driver.h"
#include "usbh/tusb_host_manager.h"
#include "tusb_host_manager.h"
#include "ogxm_config.h"
#include "Gamepad.h"
HostMode host_mode;
bool device_mounted = false;
uint8_t device_daddr = 0;
// HostMode get_host_mode()
// {
// return host_mode;
// }
// not sure why this and tuh_mounted are always true
// unused atm, come back to it
// bool tuh_device_mounted()
// {
// return device_mounted;
// }
void tuh_mount_cb(uint8_t daddr)
struct HostManager
{
if (!device_mounted)
bool hid = {false};
bool mounted = {false};
uint8_t address;
uint8_t instance;
const usbh_class_driver_t* class_driver = {&usbh_xinput_driver};
GPHostDriver* gp_host_driver = {nullptr};
};
HostManager host_manager[MAX_GAMEPADS] = {};
typedef struct
{
const usb_vid_pid_t* devices;
size_t num_devices;
HostMode check_mode;
} DeviceType;
const DeviceType device_types[] =
{
{ n64_devices, sizeof(n64_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_N64USB },
{ psc_devices, sizeof(psc_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_PSCLASSIC },
{ dinput_devices, sizeof(dinput_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_DINPUT},
{ ps3_devices, sizeof(ps3_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_PS3 },
{ ps4_devices, sizeof(ps4_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_PS4 },
{ ps5_devices, sizeof(ps5_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_PS5 },
{ switch_wired_devices, sizeof(switch_wired_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_SWITCH_WIRED },
{ switch_pro_devices, sizeof(switch_pro_devices) / sizeof(usb_vid_pid_t), HOST_MODE_HID_SWITCH_PRO }
};
void led_mounted_indicator(bool mounted)
{
gpio_put(LED_INDICATOR_PIN, mounted ? 1 : 0);
}
bool check_vid_pid(const usb_vid_pid_t* devices, size_t num_devices, uint16_t vid, uint16_t pid)
{
for (size_t i = 0; i < num_devices; i++)
{
device_mounted = true;
device_daddr = daddr;
if (vid == devices[i].vid && pid == devices[i].pid)
{
return true;
}
}
uint16_t vid, pid;
tuh_vid_pid_get(daddr, &vid, &pid);
return false;
}
// lists of device VIP/PIDs
const size_t num_ps4_devices = sizeof(ps4_devices) / sizeof(usb_vid_pid_t);
const size_t num_ps5_devices = sizeof(ps5_devices) / sizeof(usb_vid_pid_t);
host_mode = HOST_MODE_XINPUT;
// check if VID/PID match any in playstation devices
for (size_t i = 0; i < num_ps4_devices || i < num_ps5_devices; i++)
void init_gp_host_driver(HostMode host_mode, int idx)
{
switch(host_mode)
{
if (i < num_ps4_devices && vid == ps4_devices[i].vid && pid == ps4_devices[i].pid)
{
host_mode = HOST_MODE_HID_PS4;
case HOST_MODE_XINPUT:
host_manager[idx].gp_host_driver = new XInputHost();
break;
case HOST_MODE_HID_PSCLASSIC:
host_manager[idx].gp_host_driver = new PSClassic();
break;
case HOST_MODE_HID_DINPUT:
host_manager[idx].gp_host_driver = new DInput();
break;
case HOST_MODE_HID_PS3:
host_manager[idx].gp_host_driver = new Dualshock3();
break;
case HOST_MODE_HID_PS4:
host_manager[idx].gp_host_driver = new Dualshock4();
break;
case HOST_MODE_HID_PS5:
host_manager[idx].gp_host_driver = new Dualsense();
break;
case HOST_MODE_HID_SWITCH_PRO:
host_manager[idx].gp_host_driver = new SwitchPro();
break;
case HOST_MODE_HID_SWITCH_WIRED:
host_manager[idx].gp_host_driver = new SwitchWired();
break;
case HOST_MODE_HID_N64USB:
host_manager[idx].gp_host_driver = new N64USB();
break;
default:
break;
}
if (i < num_ps5_devices && vid == ps5_devices[i].vid && pid == ps5_devices[i].pid)
if (host_manager[idx].gp_host_driver)
{
host_mode = HOST_MODE_HID_PS5;
break;
}
host_manager[idx].gp_host_driver->init((uint8_t)idx + 1, host_manager[idx].address, host_manager[idx].instance);
}
}
// void tuh_umount_cb(uint8_t daddr)
// {
// if (device_mounted && device_daddr == daddr)
// {
// device_mounted = false;
// }
// }
int find_free_slot()
{
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (!host_manager[i].mounted)
{
host_manager[i].mounted = true;
return i;
}
}
return -1;
}
void unmount_gamepad(uint8_t dev_addr, uint8_t instance)
{
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (host_manager[i].mounted &&
host_manager[i].address == dev_addr &&
host_manager[i].instance == instance)
{
host_manager[i].mounted = false;
gamepad(i)->reset_pad(gamepad(i));
gamepad(i)->enable_analog_buttons = false;
if (host_manager[i].gp_host_driver)
{
delete host_manager[i].gp_host_driver;
host_manager[i].gp_host_driver = nullptr;
}
}
}
bool all_free = true;
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (host_manager[i].gp_host_driver != nullptr)
{
all_free = false;
break;
}
}
if (all_free)
{
led_mounted_indicator(false);
}
}
/* ----------- TUSB ----------- */
void tuh_mount_cb(uint8_t daddr)
{
(void)daddr;
led_mounted_indicator(true);
}
void tuh_umount_cb(uint8_t daddr)
{
(void)daddr;
}
usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count)
{
*driver_count = 1;
switch (host_mode)
return host_manager[0].class_driver;
}
/* ----------- HID ----------- */
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
{
(void)desc_report;
(void)desc_len;
int slot = find_free_slot();
if (slot < 0) return; // no available slots, shouldn't happen
host_manager[slot].address = dev_addr;
host_manager[slot].instance = instance;
uint16_t vid, pid;
tuh_vid_pid_get(dev_addr, &vid, &pid);
const size_t num_device_types = sizeof(device_types) / sizeof(DeviceType); // set host mode depending on VID/PID match
for (size_t i = 0; i < num_device_types; i++)
{
case HOST_MODE_XINPUT:
return &usbh_xinput_driver;
default:
return &usbh_hid_driver;
if (check_vid_pid(device_types[i].devices, device_types[i].num_devices, vid, pid))
{
host_manager[slot].hid = true;
init_gp_host_driver(device_types[i].check_mode, slot);
if (device_types[i].check_mode == HOST_MODE_HID_DINPUT ||
device_types[i].check_mode == HOST_MODE_HID_PS3)
{
gamepad(slot)->enable_analog_buttons = true;
}
break;
}
}
// tuh_hid_receive_report(dev_addr, instance);
}
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
{
unmount_gamepad(dev_addr, instance);
}
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (host_manager[i].mounted &&
host_manager[i].address == dev_addr &&
host_manager[i].instance == instance)
{
if (host_manager[i].gp_host_driver)
{
host_manager[i].gp_host_driver->process_hid_report(gamepad(i), dev_addr, instance, report, len);
}
}
}
}
// Current version of TinyUSB may be bugged so omitting this for now
// void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
// {
// for (int i = 0; i < MAX_GAMEPADS; i++)
// {
// if (host_manager[i].mounted &&
// host_manager[i].address == dev_addr &&
// host_manager[i].instance == instance)
// {
// host_manager[i].gp_host_driver->hid_get_report_complete_cb(dev_addr, instance, report_id, report_type, len);
// }
// }
// }
/* ----------- XINPUT ----------- */
void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf)
{
(void)xinput_itf;
int slot = find_free_slot();
if (slot < 0) return; // no available slots
host_manager[slot].address = dev_addr;
host_manager[slot].instance = instance;
init_gp_host_driver(HOST_MODE_XINPUT, slot);
if (xinput_itf->type == XBOXOG)
{
gamepad(slot)->enable_analog_buttons = true;
}
tuh_xinput_receive_report(dev_addr, instance);
}
void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance)
{
unmount_gamepad(dev_addr, instance);
}
void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (host_manager[i].mounted && host_manager[i].address == dev_addr && host_manager[i].instance == instance)
{
if (host_manager[i].gp_host_driver)
{
host_manager[i].gp_host_driver->process_xinput_report(gamepad(i), dev_addr, instance, report, len);
}
}
}
tuh_xinput_receive_report(dev_addr, instance);
}
/* ----------- SEND FEEDBACK ----------- */
void reset_hid_rumble(Gamepad* gp)
{
if (gp->rumble.l != UINT8_MAX)
{
gp->rumble.l = 0;
}
if (gp->rumble.r != UINT8_MAX)
{
gp->rumble.r = 0;
}
}
void send_fb_data_to_gamepad()
{
bool rumble_sent = false;
static const uint8_t fb_interval_ms = 100;
static unsigned long fb_sent_time[MAX_GAMEPADS] = {to_ms_since_boot(get_absolute_time())};
if (host_mode == HOST_MODE_XINPUT)
for (int i = 0; i < MAX_GAMEPADS; i++)
{
rumble_sent = send_fb_data_to_xinput_gamepad();
if (!host_manager[i].mounted || !host_manager[i].gp_host_driver) break;
unsigned long current_time = to_ms_since_boot(get_absolute_time());
if (current_time - fb_sent_time[i] >= fb_interval_ms)
{
if (host_manager[i].gp_host_driver->send_fb_data(gamepad(i), host_manager[i].address, host_manager[i].instance))
{
fb_sent_time[i] = current_time;
if (host_manager[i].hid)
{
reset_hid_rumble(gamepad(i));
}
}
else
{
rumble_sent = send_fb_data_to_hid_gamepad();
// needed so rumble doesn't get stuck on
// breaks xinput rumble
if (rumble_sent && (gamepadOut.out_state.lrumble != UINT8_MAX || gamepadOut.out_state.rrumble != UINT8_MAX))
{
gamepadOut.out_state.lrumble = 0;
gamepadOut.out_state.rrumble = 0;
}
}
}

View File

@@ -3,15 +3,18 @@
typedef enum
{
HOST_MODE_NONE,
HOST_MODE_XINPUT,
HOST_MODE_HID_SWITCH_PRO,
HOST_MODE_HID_SWITCH_WIRED,
HOST_MODE_HID_PSCLASSIC,
HOST_MODE_HID_DINPUT,
HOST_MODE_HID_PS3,
HOST_MODE_HID_PS4,
HOST_MODE_HID_PS5
HOST_MODE_HID_PS5,
HOST_MODE_HID_N64USB
} HostMode;
extern HostMode host_mode;
// bool tuh_device_mounted();
void send_fb_data_to_gamepad();
#endif // _TUSB_HOST_MANAGER_H_

View File

@@ -1,617 +0,0 @@
// Copyright 2020, Ryan Wendland, usb64
// SPDX-License-Identifier: MIT
#include "tusb_option.h"
#if (TUSB_OPT_HOST_ENABLED && CFG_TUH_XINPUT)
// #include "host/usbh.h"
// #include "host/usbh_pvt.h"
#include "xinput_host.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-const-variable"
//Wired 360 commands
static const uint8_t xbox360_wired_rumble[] = {0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t xbox360_wired_led[] = {0x01, 0x03, 0x00};
//Xbone one
#define GIP_CMD_ACK 0x01
#define GIP_CMD_ANNOUNCE 0x02
#define GIP_CMD_IDENTIFY 0x04
#define GIP_CMD_POWER 0x05
#define GIP_CMD_AUTHENTICATE 0x06
#define GIP_CMD_VIRTUAL_KEY 0x07
#define GIP_CMD_RUMBLE 0x09
#define GIP_CMD_LED 0x0a
#define GIP_CMD_FIRMWARE 0x0c
#define GIP_CMD_INPUT 0x20
#define GIP_SEQ0 0x00
#define GIP_OPT_ACK 0x10
#define GIP_OPT_INTERNAL 0x20
#define GIP_PL_LEN(N) (N)
#define GIP_PWR_ON 0x00
#define GIP_PWR_SLEEP 0x01
#define GIP_PWR_OFF 0x04
#define GIP_PWR_RESET 0x07
#define GIP_LED_ON 0x01
#define BIT(n) (1UL << (n))
#define GIP_MOTOR_R BIT(0)
#define GIP_MOTOR_L BIT(1)
#define GIP_MOTOR_RT BIT(2)
#define GIP_MOTOR_LT BIT(3)
#define GIP_MOTOR_ALL (GIP_MOTOR_R | GIP_MOTOR_L | GIP_MOTOR_RT | GIP_MOTOR_LT)
static const uint8_t xboxone_power_on[] = {GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(1), GIP_PWR_ON};
static const uint8_t xboxone_s_init[] = {GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(15), 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t xboxone_s_led_init[] = {GIP_CMD_LED, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(3), 0x00, 0x01, 0x14};
static const uint8_t extra_input_packet_init[] = {0x4d, 0x10, GIP_SEQ0, 0x02, 0x07, 0x00};
static const uint8_t xboxone_pdp_led_on[] = {GIP_CMD_LED, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(3), 0x00, GIP_LED_ON, 0x14};
static const uint8_t xboxone_pdp_auth[] = {GIP_CMD_AUTHENTICATE, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(2), 0x01, 0x00};
static const uint8_t xboxone_rumble[] = {GIP_CMD_RUMBLE, 0x00, 0x00, GIP_PL_LEN(9), 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF};
//Wireless 360 commands
static const uint8_t xbox360w_led[] = {0x00, 0x00, 0x08, 0x40};
//Sending 0x00, 0x00, 0x08, 0x00 will permanently disable rumble until you do this:
static const uint8_t xbox360w_rumble_enable[] = {0x00, 0x00, 0x08, 0x01};
static const uint8_t xbox360w_rumble[] = {0x00, 0x01, 0x0F, 0xC0, 0x00, 0x00};
static const uint8_t xbox360w_inquire_present[] = {0x08, 0x00, 0x0F, 0xC0};
static const uint8_t xbox360w_controller_info[] = {0x00, 0x00, 0x00, 0x40};
static const uint8_t xbox360w_unknown[] = {0x00, 0x00, 0x02, 0x80};
static const uint8_t xbox360w_power_off[] = {0x00, 0x00, 0x08, 0xC0};
static const uint8_t xbox360w_chatpad_init[] = {0x00, 0x00, 0x0C, 0x1B};
static const uint8_t xbox360w_chatpad_keepalive1[] = {0x00, 0x00, 0x0C, 0x1F};
static const uint8_t xbox360w_chatpad_keepalive2[] = {0x00, 0x00, 0x0C, 0x1E};
//Original Xbox
static const uint8_t xboxog_rumble[] = {0x00, 0x06, 0x00, 0x00, 0x00, 0x00};
#pragma GCC diagnostic pop
typedef struct
{
uint8_t inst_count;
xinputh_interface_t instances[CFG_TUH_XINPUT];
} xinputh_device_t;
static xinputh_device_t _xinputh_dev[CFG_TUH_DEVICE_MAX];
TU_ATTR_ALWAYS_INLINE static inline xinputh_device_t *get_dev(uint8_t dev_addr)
{
return &_xinputh_dev[dev_addr - 1];
}
TU_ATTR_ALWAYS_INLINE static inline xinputh_interface_t *get_instance(uint8_t dev_addr, uint8_t instance)
{
return &_xinputh_dev[dev_addr - 1].instances[instance];
}
static uint8_t get_instance_id_by_epaddr(uint8_t dev_addr, uint8_t ep_addr)
{
for (uint8_t inst = 0; inst < CFG_TUH_XINPUT; inst++)
{
xinputh_interface_t *hid = get_instance(dev_addr, inst);
if ((ep_addr == hid->ep_in) || (ep_addr == hid->ep_out))
return inst;
}
return 0xff;
}
static uint8_t get_instance_id_by_itfnum(uint8_t dev_addr, uint8_t itf)
{
for (uint8_t inst = 0; inst < CFG_TUH_XINPUT; inst++)
{
xinputh_interface_t *hid = get_instance(dev_addr, inst);
if ((hid->itf_num == itf) && (hid->ep_in || hid->ep_out))
return inst;
}
return 0xff;
}
static void wait_for_tx_complete(uint8_t dev_addr, uint8_t ep_out)
{
while (usbh_edpt_busy(dev_addr, ep_out))
tuh_task();
}
static void xboxone_init( xinputh_interface_t *xid_itf, uint8_t dev_addr, uint8_t instance)
{
uint16_t PID, VID;
tuh_vid_pid_get(dev_addr, &VID, &PID);
tuh_xinput_send_report(dev_addr, instance, xboxone_power_on, sizeof(xboxone_power_on));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
tuh_xinput_send_report(dev_addr, instance, xboxone_s_init, sizeof(xboxone_s_init));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
if (VID == 0x045e && (PID == 0x0b00))
{
tuh_xinput_send_report(dev_addr, instance, extra_input_packet_init, sizeof(extra_input_packet_init));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
}
//Required for PDP aftermarket controllers
if (VID == 0x0e6f)
{
tuh_xinput_send_report(dev_addr, instance, xboxone_pdp_led_on, sizeof(xboxone_pdp_led_on));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
tuh_xinput_send_report(dev_addr, instance, xboxone_pdp_auth, sizeof(xboxone_pdp_auth));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
}
}
bool tuh_xinput_receive_report(uint8_t dev_addr, uint8_t instance)
{
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
TU_VERIFY(usbh_edpt_claim(dev_addr, xid_itf->ep_in));
if ( !usbh_edpt_xfer(dev_addr, xid_itf->ep_in, xid_itf->epin_buf, xid_itf->epin_size) )
{
usbh_edpt_release(dev_addr, xid_itf->ep_in);
return false;
}
return true;
}
bool tuh_xinput_send_report(uint8_t dev_addr, uint8_t instance, const uint8_t *txbuf, uint16_t len)
{
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
TU_ASSERT(len <= xid_itf->epout_size);
TU_VERIFY(usbh_edpt_claim(dev_addr, xid_itf->ep_out));
memcpy(xid_itf->epout_buf, txbuf, len);
if ( !usbh_edpt_xfer(dev_addr, xid_itf->ep_out, xid_itf->epout_buf, len))
{
usbh_edpt_release(dev_addr, xid_itf->ep_out);
return false;
}
return true;
}
bool tuh_xinput_set_led(uint8_t dev_addr, uint8_t instance, uint8_t quadrant, bool block)
{
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
uint8_t txbuf[32];
uint16_t len;
switch (xid_itf->type)
{
case XBOX360_WIRELESS:
memcpy(txbuf, xbox360w_led, sizeof(xbox360w_led));
txbuf[3] = (quadrant == 0) ? 0x40 : (0x40 | (quadrant + 5));
len = sizeof(xbox360w_led);
break;
case XBOX360_WIRED:
memcpy(txbuf, xbox360_wired_led, sizeof(xbox360_wired_led));
txbuf[2] = (quadrant == 0) ? 0 : (quadrant + 5);
len = sizeof(xbox360_wired_led);
break;
default:
return true;
}
bool ret = tuh_xinput_send_report(dev_addr, instance, txbuf, len);
if (block && ret)
{
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
}
return ret;
}
bool tuh_xinput_set_rumble(uint8_t dev_addr, uint8_t instance, uint8_t lValue, uint8_t rValue, bool block)
{
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
uint8_t txbuf[32];
uint16_t len;
switch (xid_itf->type)
{
case XBOX360_WIRELESS:
memcpy(txbuf, xbox360w_rumble, sizeof(xbox360w_rumble));
txbuf[5] = lValue;
txbuf[6] = rValue;
len = sizeof(xbox360w_rumble);
break;
case XBOX360_WIRED:
memcpy(txbuf, xbox360_wired_rumble, sizeof(xbox360_wired_rumble));
txbuf[3] = lValue;
txbuf[4] = rValue;
len = sizeof(xbox360_wired_rumble);
break;
case XBOXONE:
memcpy(txbuf, xboxone_rumble, sizeof(xboxone_rumble));
txbuf[8] = lValue / 2; // 0 - 128
txbuf[9] = rValue / 2; // 0 - 128
len = sizeof(xboxone_rumble);
break;
case XBOXOG:
memcpy(txbuf, xboxog_rumble, sizeof(xboxog_rumble));
txbuf[2] = lValue;
txbuf[3] = lValue;
txbuf[4] = rValue;
txbuf[5] = rValue;
len = sizeof(xboxog_rumble);
break;
default:
return true;
}
bool ret = tuh_xinput_send_report(dev_addr, instance, txbuf, len);
if (block && ret)
{
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
}
return true;
}
//--------------------------------------------------------------------+
// USBH API
//--------------------------------------------------------------------+
void xinputh_init(void)
{
tu_memclr(_xinputh_dev, sizeof(_xinputh_dev));
}
bool xinputh_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len)
{
TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX);
xinput_type_t type = XINPUT_UNKNOWN;
if (desc_itf->bNumEndpoints < 2)
type = XINPUT_UNKNOWN;
else if (desc_itf->bInterfaceSubClass == 0x5D && //Xbox360 wireless bInterfaceSubClass
desc_itf->bInterfaceProtocol == 0x81) //Xbox360 wireless bInterfaceProtocol
type = XBOX360_WIRELESS;
else if (desc_itf->bInterfaceSubClass == 0x5D && //Xbox360 wired bInterfaceSubClass
desc_itf->bInterfaceProtocol == 0x01) //Xbox360 wired bInterfaceProtocol
type = XBOX360_WIRED;
else if (desc_itf->bInterfaceSubClass == 0x47 && //Xbone and SX bInterfaceSubClass
desc_itf->bInterfaceProtocol == 0xD0) //Xbone and SX bInterfaceProtocol
type = XBOXONE;
else if (desc_itf->bInterfaceClass == 0x58 && //XboxOG bInterfaceClass
desc_itf->bInterfaceSubClass == 0x42) //XboxOG bInterfaceSubClass
type = XBOXOG;
if (type == XINPUT_UNKNOWN)
{
TU_LOG2("XINPUT: Not a valid interface\n");
return false;
}
TU_LOG2("XINPUT opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
xinputh_device_t *xinput_dev = get_dev(dev_addr);
TU_ASSERT(xinput_dev->inst_count < CFG_TUH_XINPUT, 0);
xinputh_interface_t *xid_itf = get_instance(dev_addr, xinput_dev->inst_count);
xid_itf->itf_num = desc_itf->bInterfaceNumber;
xid_itf->type = type;
//Parse descriptor for all endpoints and open them
uint8_t const *p_desc = (uint8_t const *)desc_itf;
int endpoint = 0;
int pos = 0;
while (endpoint < desc_itf->bNumEndpoints && pos < max_len)
{
if (tu_desc_type(p_desc) != TUSB_DESC_ENDPOINT)
{
pos += tu_desc_len(p_desc);
p_desc = tu_desc_next(p_desc);
continue;
}
tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *)p_desc;
TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType);
TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep));
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT)
{
xid_itf->ep_out = desc_ep->bEndpointAddress;
xid_itf->epout_size = tu_edpt_packet_size(desc_ep);
}
else
{
xid_itf->ep_in = desc_ep->bEndpointAddress;
xid_itf->epin_size = tu_edpt_packet_size(desc_ep);
}
endpoint++;
pos += tu_desc_len(p_desc);
p_desc = tu_desc_next(p_desc);
}
xinput_dev->inst_count++;
return true;
}
bool xinputh_set_config(uint8_t dev_addr, uint8_t itf_num)
{
uint8_t instance = get_instance_id_by_itfnum(dev_addr, itf_num);
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
xid_itf->connected = true;
if (xid_itf->type == XBOX360_WIRELESS)
{
//Wireless controllers may not be connected yet.
xid_itf->connected = false;
tuh_xinput_send_report(dev_addr, instance, xbox360w_inquire_present, sizeof(xbox360w_inquire_present));
wait_for_tx_complete(dev_addr, xid_itf->ep_out);
}
else if (xid_itf->type == XBOX360_WIRED)
{
}
else if (xid_itf->type == XBOXONE)
{
xboxone_init(xid_itf, dev_addr, instance);
}
if (tuh_xinput_mount_cb)
{
tuh_xinput_mount_cb(dev_addr, instance, xid_itf);
}
usbh_driver_set_config_complete(dev_addr, xid_itf->itf_num);
return true;
}
bool xinputh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
uint8_t const dir = tu_edpt_dir(ep_addr);
uint8_t const instance = get_instance_id_by_epaddr(dev_addr, ep_addr);
xinputh_interface_t *xid_itf = get_instance(dev_addr, instance);
xinput_gamepad_t *pad = &xid_itf->pad;
uint8_t *rdata = xid_itf->epin_buf;
xid_itf->last_xfer_result = result;
xid_itf->last_xferred_bytes = xferred_bytes;
// On transfer error, bail early but notify the application
if (result != XFER_RESULT_SUCCESS)
{
if (dir == TUSB_DIR_IN)
{
tuh_xinput_report_received_cb(dev_addr, instance, xid_itf, sizeof(xinputh_interface_t));
}
else if (tuh_xinput_report_sent_cb)
{
tuh_xinput_report_sent_cb(dev_addr, instance, xid_itf->epout_buf, xferred_bytes);
}
return false;
}
if (dir == TUSB_DIR_IN)
{
TU_LOG2("Get Report callback (%u, %u, %u bytes)\r\n", dev_addr, instance, xferred_bytes);
if (xid_itf->type == XBOX360_WIRED)
{
#define GET_USHORT(a) (uint16_t)((a)[1] << 8 | (a)[0])
#define GET_SHORT(a) ((int16_t)GET_USHORT(a))
if (rdata[1] == 0x14)
{
tu_memclr(pad, sizeof(xinput_gamepad_t));
uint16_t wButtons = rdata[3] << 8 | rdata[2];
//Map digital buttons
if (wButtons & (1 << 0)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_UP;
if (wButtons & (1 << 1)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
if (wButtons & (1 << 2)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
if (wButtons & (1 << 3)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
if (wButtons & (1 << 4)) pad->wButtons |= XINPUT_GAMEPAD_START;
if (wButtons & (1 << 5)) pad->wButtons |= XINPUT_GAMEPAD_BACK;
if (wButtons & (1 << 6)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (wButtons & (1 << 7)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
if (wButtons & (1 << 8)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER;
if (wButtons & (1 << 9)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER;
if (wButtons & (1 << 10)) pad->wButtons |= XINPUT_GAMEPAD_GUIDE;
if (wButtons & (1 << 12)) pad->wButtons |= XINPUT_GAMEPAD_A;
if (wButtons & (1 << 13)) pad->wButtons |= XINPUT_GAMEPAD_B;
if (wButtons & (1 << 14)) pad->wButtons |= XINPUT_GAMEPAD_X;
if (wButtons & (1 << 15)) pad->wButtons |= XINPUT_GAMEPAD_Y;
//Map the left and right triggers
pad->bLeftTrigger = rdata[4];
pad->bRightTrigger = rdata[5];
//Map analog sticks
pad->sThumbLX = rdata[7] << 8 | rdata[6];
pad->sThumbLY = rdata[9] << 8 | rdata[8];
pad->sThumbRX = rdata[11] << 8 | rdata[10];
pad->sThumbRY = rdata[13] << 8 | rdata[12];
xid_itf->new_pad_data = true;
}
}
else if (xid_itf->type == XBOX360_WIRELESS)
{
//Connect/Disconnect packet
if (rdata[0] & 0x08)
{
if (rdata[1] != 0x00 && xid_itf->connected == false)
{
TU_LOG2("XINPUT: WIRELESS CONTROLLER CONNECTED\n");
xid_itf->connected = true;
}
else if (rdata[1] == 0x00 && xid_itf->connected == true)
{
TU_LOG2("XINPUT: WIRELESS CONTROLLER DISCONNECTED\n");
xid_itf->connected = false;
}
}
//Button status packet
if ((rdata[1] & 1) && rdata[5] == 0x13)
{
tu_memclr(pad, sizeof(xinput_gamepad_t));
uint16_t wButtons = rdata[7] << 8 | rdata[6];
//Map digital buttons
if (wButtons & (1 << 0)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_UP;
if (wButtons & (1 << 1)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
if (wButtons & (1 << 2)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
if (wButtons & (1 << 3)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
if (wButtons & (1 << 4)) pad->wButtons |= XINPUT_GAMEPAD_START;
if (wButtons & (1 << 5)) pad->wButtons |= XINPUT_GAMEPAD_BACK;
if (wButtons & (1 << 6)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (wButtons & (1 << 7)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
if (wButtons & (1 << 8)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER;
if (wButtons & (1 << 9)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER;
if (wButtons & (1 << 12)) pad->wButtons |= XINPUT_GAMEPAD_A;
if (wButtons & (1 << 13)) pad->wButtons |= XINPUT_GAMEPAD_B;
if (wButtons & (1 << 14)) pad->wButtons |= XINPUT_GAMEPAD_X;
if (wButtons & (1 << 15)) pad->wButtons |= XINPUT_GAMEPAD_Y;
//Map the left and right triggers
pad->bLeftTrigger = rdata[8];
pad->bRightTrigger = rdata[9];
//Map analog sticks
pad->sThumbLX = rdata[11] << 8 | rdata[10];
pad->sThumbLY = rdata[13] << 8 | rdata[12];
pad->sThumbRX = rdata[15] << 8 | rdata[14];
pad->sThumbRY = rdata[17] << 8 | rdata[16];
xid_itf->new_pad_data = true;
}
}
else if (xid_itf->type == XBOXONE)
{
if (rdata[0] == GIP_CMD_INPUT)
{
tu_memclr(pad, sizeof(xinput_gamepad_t));
uint16_t wButtons = rdata[5] << 8 | rdata[4];
//Map digital buttons
if (wButtons & (1 << 8)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_UP;
if (wButtons & (1 << 9)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
if (wButtons & (1 << 10)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
if (wButtons & (1 << 11)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
if (wButtons & (1 << 2)) pad->wButtons |= XINPUT_GAMEPAD_START;
if (wButtons & (1 << 3)) pad->wButtons |= XINPUT_GAMEPAD_BACK;
if (wButtons & (1 << 14)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (wButtons & (1 << 15)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
if (wButtons & (1 << 12)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER;
if (wButtons & (1 << 13)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER;
if (wButtons & (1 << 4)) pad->wButtons |= XINPUT_GAMEPAD_A;
if (wButtons & (1 << 5)) pad->wButtons |= XINPUT_GAMEPAD_B;
if (wButtons & (1 << 6)) pad->wButtons |= XINPUT_GAMEPAD_X;
if (wButtons & (1 << 7)) pad->wButtons |= XINPUT_GAMEPAD_Y;
if (rdata[22] && 0x01) pad->wButtons |= XINPUT_GAMEPAD_SHARE;
//Map the left and right triggers
pad->bLeftTrigger = (rdata[7] << 8 | rdata[6]) >> 2;
pad->bRightTrigger = (rdata[9] << 8 | rdata[8]) >> 2;
//Map analog sticks
pad->sThumbLX = rdata[11] << 8 | rdata[10];
pad->sThumbLY = rdata[13] << 8 | rdata[12];
pad->sThumbRX = rdata[15] << 8 | rdata[14];
pad->sThumbRY = rdata[17] << 8 | rdata[16];
xid_itf->new_pad_data = true;
}
else if (rdata[0] == GIP_CMD_VIRTUAL_KEY)
{
if (rdata[4] == 0x01 && !(pad->wButtons & XINPUT_GAMEPAD_GUIDE)) {
xid_itf->new_pad_data = true;
pad->wButtons |= XINPUT_GAMEPAD_GUIDE;
}
else if (rdata[4] == 0x00 && (pad->wButtons & XINPUT_GAMEPAD_GUIDE)) {
xid_itf->new_pad_data = true;
pad->wButtons &= ~XINPUT_GAMEPAD_GUIDE;
}
}
else if (rdata[0] == GIP_CMD_ANNOUNCE)
{
xboxone_init(xid_itf, dev_addr, instance);
}
}
else if (xid_itf->type == XBOXOG)
{
if (rdata[1] == 0x14)
{
tu_memclr(pad, sizeof(xinput_gamepad_t));
uint16_t wButtons = rdata[3] << 8 | rdata[2];
//Map digital buttons
if (wButtons & (1 << 0)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_UP;
if (wButtons & (1 << 1)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
if (wButtons & (1 << 2)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
if (wButtons & (1 << 3)) pad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
if (wButtons & (1 << 4)) pad->wButtons |= XINPUT_GAMEPAD_START;
if (wButtons & (1 << 5)) pad->wButtons |= XINPUT_GAMEPAD_BACK;
if (wButtons & (1 << 6)) pad->wButtons |= XINPUT_GAMEPAD_LEFT_THUMB;
if (wButtons & (1 << 7)) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB;
if (rdata[4] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_A;
if (rdata[5] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_B;
if (rdata[6] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_X;
if (rdata[7] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_Y;
if (rdata[8] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER;
if (rdata[9] > 0x20) pad->wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER;
//Map the left and right triggers
pad->bLeftTrigger = rdata[10];
pad->bRightTrigger = rdata[11];
//Map analog sticks
pad->sThumbLX = rdata[13] << 8 | rdata[12];
pad->sThumbLY = rdata[15] << 8 | rdata[14];
pad->sThumbRX = rdata[17] << 8 | rdata[16];
pad->sThumbRY = rdata[19] << 8 | rdata[18];
xid_itf->new_pad_data = true;
}
}
if (xid_itf->new_pad_data)
{
tuh_xinput_report_received_cb(dev_addr, instance, xid_itf, sizeof(xinputh_interface_t));
xid_itf->new_pad_data = false;
} else {
tuh_xinput_receive_report(dev_addr, instance);
}
}
else
{
if (tuh_xinput_report_sent_cb)
{
tuh_xinput_report_sent_cb(dev_addr, instance, xid_itf->epout_buf, xferred_bytes);
}
}
return true;
}
void xinputh_close(uint8_t dev_addr)
{
TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX, );
xinputh_device_t *xinput_dev = get_dev(dev_addr);
for (uint8_t inst = 0; inst < xinput_dev->inst_count; inst++)
{
if (tuh_xinput_umount_cb)
{
tuh_xinput_umount_cb(dev_addr, inst);
}
}
tu_memclr(xinput_dev, sizeof(xinputh_device_t));
}
#ifndef DRIVER_NAME
#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL
#define DRIVER_NAME(_name) .name = _name,
#else
#define DRIVER_NAME(_name)
#endif
#endif
usbh_class_driver_t const usbh_xinput_driver =
{
DRIVER_NAME("XINPUT")
.init = xinputh_init,
.open = xinputh_open,
.set_config = xinputh_set_config,
.xfer_cb = xinputh_xfer_cb,
.close = xinputh_close
};
#endif

View File

@@ -1,192 +0,0 @@
// Copyright 2020, Ryan Wendland, usb64
// SPDX-License-Identifier: MIT
#ifndef _TUSB_XINPUT_HOST_H_
#define _TUSB_XINPUT_HOST_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "host/usbh.h"
#include "host/usbh_pvt.h"
//--------------------------------------------------------------------+
// Class Driver Configuration
//--------------------------------------------------------------------+
#ifndef CFG_TUH_XINPUT_EPIN_BUFSIZE
#define CFG_TUH_XINPUT_EPIN_BUFSIZE 64
#endif
#ifndef CFG_TUH_XINPUT_EPOUT_BUFSIZE
#define CFG_TUH_XINPUT_EPOUT_BUFSIZE 64
#endif
//XINPUT defines and struct format from
//https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
#define XINPUT_GAMEPAD_START 0x0010
#define XINPUT_GAMEPAD_BACK 0x0020
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
#define XINPUT_GAMEPAD_GUIDE 0x0400
#define XINPUT_GAMEPAD_SHARE 0x0800
#define XINPUT_GAMEPAD_A 0x1000
#define XINPUT_GAMEPAD_B 0x2000
#define XINPUT_GAMEPAD_X 0x4000
#define XINPUT_GAMEPAD_Y 0x8000
#define MAX_PACKET_SIZE 32
typedef struct xinput_gamepad
{
uint16_t wButtons;
uint8_t bLeftTrigger;
uint8_t bRightTrigger;
int16_t sThumbLX;
int16_t sThumbLY;
int16_t sThumbRX;
int16_t sThumbRY;
} xinput_gamepad_t;
typedef enum
{
XINPUT_UNKNOWN = 0,
XBOXONE,
XBOX360_WIRELESS,
XBOX360_WIRED,
XBOXOG
} xinput_type_t;
typedef struct
{
xinput_type_t type;
xinput_gamepad_t pad;
uint8_t connected;
uint8_t new_pad_data;
uint8_t itf_num;
uint8_t ep_in;
uint8_t ep_out;
uint16_t epin_size;
uint16_t epout_size;
uint8_t epin_buf[CFG_TUH_XINPUT_EPIN_BUFSIZE];
uint8_t epout_buf[CFG_TUH_XINPUT_EPOUT_BUFSIZE];
xfer_result_t last_xfer_result;
uint32_t last_xferred_bytes;
} xinputh_interface_t;
extern usbh_class_driver_t const usbh_xinput_driver;
/**
* @brief Callback function called when a report is received from an XInput device.
*
* This function is called when a report is received from the specified XInput device.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @param xid_itf Pointer to the xif_itf structure containing the received report data and transfer result
* @param len Length of the received report data.
*/
void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* xid_itf, uint16_t len);
/**
* @brief Weak callback function called when a report is sent to an XInput device.
*
* This function is a weakly bound callback called when a report is sent to the specified XInput device.
* Implementing this function is optional, and the weak attribute allows it to be overridden if needed.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @param report Pointer to the sent report data.
* @param len Length of the sent report data.
*/
TU_ATTR_WEAK void tuh_xinput_report_sent_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
/**
* @brief Weak callback function called when an XInput device is unmounted.
*
* This function is a weakly bound callback called when an XInput device is unmounted.
* Implementing this function is optional, and the weak attribute allows it to be overridden if needed.
*
* @param dev_addr Device address of the unmounted XInput device.
* @param instance Instance of the unmounted XInput device.
*/
TU_ATTR_WEAK void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance);
/**
* @brief Weak callback function called when an XInput device is mounted.
*
* This function is a weakly bound callback called when an XInput device is mounted.
* Implementing this function is optional, and the weak attribute allows it to be overridden if needed.
*
* @param dev_addr Device address of the mounted XInput device.
* @param instance Instance of the mounted XInput device.
* @param xinput_itf Pointer to the XInput interface information.
*/
TU_ATTR_WEAK void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf);
/**
* @brief Receive a report from an XInput device.
*
* This function attempts to receive a report from the specified XInput device.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @return True if the report is received successfully, false otherwise.
*/
bool tuh_xinput_receive_report(uint8_t dev_addr, uint8_t instance);
/**
* @brief Send a report to an XInput device.
*
* This function attempts to send a report to the specified XInput device. This is a non blocking function.
* tuh_xinput_report_sent_cb() is called when the transfer is completed.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @param txbuf Pointer to the data to be sent.
* @param len Length of the data to be sent.
* @return True if the report is sent successfully, false otherwise.
*/
bool tuh_xinput_send_report(uint8_t dev_addr, uint8_t instance, const uint8_t *txbuf, uint16_t len);
/**
* @brief Set LED status on an XInput device. (Applicated to Xbox 360 controllers only)
*
* This function sets the LED status on the specified XInput device for the specified quadrant.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @param quadrant Quadrant of the LED to set.
* @param block Indicates whether the operation should be blocking.
* @return True if LED status is set successfully, false otherwise.
*/
bool tuh_xinput_set_led(uint8_t dev_addr, uint8_t instance, uint8_t quadrant, bool block);
/**
* @brief Set rumble values on an XInput device.
*
* This function sets the rumble values on the specified XInput device for left and right motors.
*
* @param dev_addr Device address of the XInput device.
* @param instance Instance of the XInput device.
* @param lValue Intensity of the left motor rumble (0 to 255)
* @param rValue Intensity of the right motor rumble. (0 to 255)
* @param block Indicates whether the operation should be blocking.
* @return True if rumble values are set successfully, false otherwise.
*/
bool tuh_xinput_set_rumble(uint8_t dev_addr, uint8_t instance, uint8_t lValue, uint8_t rValue, bool block);
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_XINPUT_HOST_H_ */

View File

@@ -1,66 +0,0 @@
#include <stdlib.h>
#include <stdarg.h>
#include "tusb.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "tusb_xinput/xinput_host.h"
#include "Gamepad.h"
static bool gamepad_mounted = false;
uint8_t connected_dev_addr = 0;
int8_t connected_instance = -1;
bool xinput_gamepad_mounted()
{
return gamepad_mounted;
}
void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf)
{
gamepad_mounted = true;
connected_dev_addr = dev_addr;
connected_instance = instance;
// If this is a Xbox 360 Wireless controller we need to wait for a connection packet
// on the in pipe before setting LEDs etc. So just start getting data until a controller is connected.
if (xinput_itf->type == XBOX360_WIRELESS && xinput_itf->connected == false)
{
tuh_xinput_receive_report(dev_addr, instance);
return;
}
tuh_xinput_set_led(dev_addr, instance, 0, true);
tuh_xinput_set_led(dev_addr, instance, 1, true);
tuh_xinput_set_rumble(dev_addr, instance, 0, 0, true);
tuh_xinput_receive_report(dev_addr, instance);
}
void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance)
{
if (gamepad_mounted && connected_dev_addr == dev_addr && connected_instance == instance)
{
gamepad_mounted = false;
}
}
void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
xinputh_interface_t *xid_itf = (xinputh_interface_t *)report;
xinput_gamepad_t *p = &xid_itf->pad;
if (xid_itf->connected && xid_itf->new_pad_data)
{
gamepad.update_gamepad_state_from_xinput(p);
}
tuh_xinput_receive_report(dev_addr, instance);
}
bool send_fb_data_to_xinput_gamepad()
{
bool rumble_sent = tuh_xinput_set_rumble(connected_dev_addr, connected_instance, gamepadOut.out_state.lrumble, gamepadOut.out_state.rrumble, true);
return rumble_sent;
}

View File

@@ -1,7 +0,0 @@
#ifndef _XINPUT_HOST_APP_H_
#define _XINPUT_HOST_APP_H_
bool xinput_gamepad_mounted();
bool send_fb_data_to_xinput_gamepad();
#endif // _XINPUT_HOST_APP_

114
src/usbh/xinput/XInput.cpp Normal file
View File

@@ -0,0 +1,114 @@
#include <stdint.h>
#include "pico/stdlib.h"
#include "usbh/xinput/XInput.h"
void XInputHost::init(uint8_t player_id, uint8_t dev_addr, uint8_t instance)
{
xinput.player_id = player_id;
tuh_xinput_receive_report(dev_addr, instance);
}
void XInputHost::set_leds(uint8_t dev_addr, uint8_t instance)
{
tuh_xinput_set_led(dev_addr, instance, 0, true);
xinput.leds_set = tuh_xinput_set_led(dev_addr, instance, xinput.player_id, true);
}
void XInputHost::process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len)
{
(void)len;
xinputh_interface_t *xid_itf = (xinputh_interface_t *)report;
xinput_gamepad_t *xinput_data = &xid_itf->pad;
if (xid_itf->connected && xid_itf->new_pad_data)
{
gamepad->reset_pad(gamepad);
gamepad->buttons.up = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
gamepad->buttons.down = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
gamepad->buttons.left = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
gamepad->buttons.right = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
gamepad->buttons.a = (xinput_data->wButtons & XINPUT_GAMEPAD_A) != 0;
gamepad->buttons.b = (xinput_data->wButtons & XINPUT_GAMEPAD_B) != 0;
gamepad->buttons.x = (xinput_data->wButtons & XINPUT_GAMEPAD_X) != 0;
gamepad->buttons.y = (xinput_data->wButtons & XINPUT_GAMEPAD_Y) != 0;
gamepad->buttons.l3 = (xinput_data->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
gamepad->buttons.r3 = (xinput_data->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
gamepad->buttons.back = (xinput_data->wButtons & XINPUT_GAMEPAD_BACK) != 0;
gamepad->buttons.start = (xinput_data->wButtons & XINPUT_GAMEPAD_START) != 0;
gamepad->buttons.rb = (xinput_data->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
gamepad->buttons.lb = (xinput_data->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
gamepad->buttons.sys = (xinput_data->wButtons & XINPUT_GAMEPAD_GUIDE) != 0;
gamepad->buttons.misc = (xinput_data->wButtons & XINPUT_GAMEPAD_SHARE) != 0;
if (gamepad->enable_analog_buttons)
{
gamepad->analog_buttons.up = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_UP) ? UINT8_MAX : 0;
gamepad->analog_buttons.down = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? UINT8_MAX : 0;
gamepad->analog_buttons.left = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? UINT8_MAX : 0;
gamepad->analog_buttons.right = (xinput_data->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? UINT8_MAX : 0;
gamepad->analog_buttons.a = xinput_data->analogButtons.a;
gamepad->analog_buttons.b = xinput_data->analogButtons.b;
gamepad->analog_buttons.x = xinput_data->analogButtons.x;
gamepad->analog_buttons.y = xinput_data->analogButtons.y;
gamepad->analog_buttons.lb = xinput_data->analogButtons.white;
gamepad->analog_buttons.rb = xinput_data->analogButtons.black;
}
gamepad->triggers.l = xinput_data->bLeftTrigger;
gamepad->triggers.r = xinput_data->bRightTrigger;
gamepad->joysticks.lx = xinput_data->sThumbLX;
gamepad->joysticks.ly = xinput_data->sThumbLY;
gamepad->joysticks.rx = xinput_data->sThumbRX;
gamepad->joysticks.ry = xinput_data->sThumbRY;
}
tuh_xinput_receive_report(dev_addr, instance);
}
void XInputHost::process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report;
(void)len;
(void)gamepad;
}
void XInputHost::hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len)
{
(void)dev_addr;
(void)instance;
(void)report_id;
(void)report_type;
(void)len;
}
bool XInputHost::send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance)
{
static int report_num = 0;
if (!xinput.leds_set || report_num == 0)
{
report_num++;
set_leds(dev_addr, instance);
return false;
}
report_num++;
if (report_num >= 50) // send led cmd every so often incase wireless 360 controller disconnects/reconnects
{
report_num = 0;
}
return tuh_xinput_set_rumble(dev_addr, instance, gamepad->rumble.l, gamepad->rumble.r, true);
}

32
src/usbh/xinput/XInput.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#ifndef _XINPUT_H_
#define _XINPUT_H_
#include <stdint.h>
#include "usbh/GPHostDriver.h"
struct XInputState
{
uint8_t player_id = {0};
bool report_received = {false};
bool leds_set = {false};
};
class XInputHost : public GPHostDriver
{
public:
~XInputHost() override {}
virtual void init(uint8_t player_id, uint8_t dev_addr, uint8_t instance);
virtual void process_hid_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
virtual void process_xinput_report(Gamepad* gamepad, uint8_t dev_addr, uint8_t instance, xinputh_interface_t const* report, uint16_t len);
virtual void hid_get_report_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, uint16_t len);
virtual bool send_fb_data(const Gamepad* gamepad, uint8_t dev_addr, uint8_t instance);
private:
XInputState xinput;
void set_leds(uint8_t dev_addr, uint8_t instance);
};
#endif

View File

@@ -1,3 +0,0 @@
#pragma once
#define FW_VERSION "0.1.0"