Merge pull request #585 from felis/ps5

Added support for the PS5 controller
This commit is contained in:
Kristian Sloth Lauszus 2021-01-19 22:36:05 +01:00 committed by GitHub
commit 5af56accc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1607 additions and 17 deletions

View file

@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
# find examples -type f -name "*.ino" | rev | cut -d/ -f2- | rev | sort | sed -z 's/\n/, /g'
example: [examples/acm/acm_terminal, examples/adk/adk_barcode, examples/adk/ArduinoBlinkLED, examples/adk/demokit_20, examples/adk/term_test, examples/adk/term_time, examples/Bluetooth/BTHID, examples/Bluetooth/PS3BT, examples/Bluetooth/PS3Multi, examples/Bluetooth/PS3SPP, examples/Bluetooth/PS4BT, examples/Bluetooth/SPP, examples/Bluetooth/SPPMulti, examples/Bluetooth/Wii, examples/Bluetooth/WiiBalanceBoard, examples/Bluetooth/WiiIRCamera, examples/Bluetooth/WiiMulti, examples/Bluetooth/WiiUProController, examples/board_qc, examples/cdc_XR21B1411/XR_terminal, examples/ftdi/USBFTDILoopback, examples/GPIO/Blink, examples/GPIO/Blink_LowLevel, examples/GPIO/Input, examples/HID/le3dp, examples/HID/scale, examples/HID/SRWS1, examples/HID/t16km, examples/HID/USBHIDBootKbd, examples/HID/USBHIDBootKbdAndMouse, examples/HID/USBHIDBootMouse, examples/HID/USBHID_desc, examples/HID/USBHIDJoystick, examples/HID/USBHIDMultimediaKbd, examples/hub_demo, examples/max_LCD, examples/pl2303/pl2303_gprs_terminal, examples/pl2303/pl2303_gps, examples/pl2303/pl2303_tinygps, examples/pl2303/pl2303_xbee_terminal, examples/PS3USB, examples/PS4USB, examples/PSBuzz, examples/USB_desc, examples/USBH_MIDI/bidirectional_converter, examples/USBH_MIDI/eVY1_sample, examples/USBH_MIDI/USBH_MIDI_dump, examples/USBH_MIDI/USB_MIDI_converter, examples/USBH_MIDI/USB_MIDI_converter_multi, examples/Xbox/XBOXOLD, examples/Xbox/XBOXONE, examples/Xbox/XBOXONESBT, examples/Xbox/XBOXRECV, examples/Xbox/XBOXUSB]
example: [examples/acm/acm_terminal, examples/adk/adk_barcode, examples/adk/ArduinoBlinkLED, examples/adk/demokit_20, examples/adk/term_test, examples/adk/term_time, examples/Bluetooth/BTHID, examples/Bluetooth/PS3BT, examples/Bluetooth/PS3Multi, examples/Bluetooth/PS3SPP, examples/Bluetooth/PS4BT, examples/Bluetooth/PS5BT, examples/Bluetooth/SPP, examples/Bluetooth/SPPMulti, examples/Bluetooth/Wii, examples/Bluetooth/WiiBalanceBoard, examples/Bluetooth/WiiIRCamera, examples/Bluetooth/WiiMulti, examples/Bluetooth/WiiUProController, examples/board_qc, examples/cdc_XR21B1411/XR_terminal, examples/ftdi/USBFTDILoopback, examples/GPIO/Blink, examples/GPIO/Blink_LowLevel, examples/GPIO/Input, examples/HID/le3dp, examples/HID/scale, examples/HID/SRWS1, examples/HID/t16km, examples/HID/USBHIDBootKbd, examples/HID/USBHIDBootKbdAndMouse, examples/HID/USBHIDBootMouse, examples/HID/USBHID_desc, examples/HID/USBHIDJoystick, examples/HID/USBHIDMultimediaKbd, examples/hub_demo, examples/max_LCD, examples/pl2303/pl2303_gprs_terminal, examples/pl2303/pl2303_gps, examples/pl2303/pl2303_tinygps, examples/pl2303/pl2303_xbee_terminal, examples/PS3USB, examples/PS4USB, examples/PS5USB, examples/PSBuzz, examples/USB_desc, examples/USBH_MIDI/bidirectional_converter, examples/USBH_MIDI/eVY1_sample, examples/USBH_MIDI/USBH_MIDI_dump, examples/USBH_MIDI/USB_MIDI_converter, examples/USBH_MIDI/USB_MIDI_converter_multi, examples/Xbox/XBOXOLD, examples/Xbox/XBOXONE, examples/Xbox/XBOXONESBT, examples/Xbox/XBOXRECV, examples/Xbox/XBOXUSB]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2

12
BTD.cpp
View file

@ -317,7 +317,7 @@ void BTD::Initialize() {
incomingWii = false;
connectToHIDDevice = false;
incomingHIDDevice = false;
incomingPS4 = false;
incomingPSController = false;
bAddress = 0; // Clear device address
bNumEP = 1; // Must have to be reset to 1
qNextPollTime = 0; // Reset next poll time
@ -1011,9 +1011,9 @@ void BTD::HCI_task() {
}
if(classOfDevice[2] == 0 && classOfDevice[1] == 0x25 && classOfDevice[0] == 0x08 && strncmp((const char*)remote_name, "Wireless Controller", 19) == 0) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nPS4 controller is connecting"), 0x80);
Notify(PSTR("\r\nPS4/PS5 controller is connecting"), 0x80);
#endif
incomingPS4 = true;
incomingPSController = true;
}
if((pairWithWii || pairWithHIDDevice) && checkRemoteName)
hci_state = HCI_CONNECT_DEVICE_STATE;
@ -1034,8 +1034,8 @@ void BTD::HCI_task() {
}
D_PrintHex<uint8_t > (disc_bdaddr[0], 0x80);
#endif
if(incomingPS4)
connectToHIDDevice = true; // We should always connect to the PS4 controller
if(incomingPSController)
connectToHIDDevice = true; // We should always connect to the PS4/PS5 controller
// Clear these flags for a new connection
l2capConnectionClaimed = false;
@ -1068,7 +1068,7 @@ void BTD::HCI_task() {
connectToWii = incomingWii = pairWithWii = false;
connectToHIDDevice = incomingHIDDevice = pairWithHIDDevice = checkRemoteName = false;
incomingPS4 = false;
incomingPSController = false;
hci_state = HCI_SCANNING_STATE;
}

2
BTD.h
View file

@ -575,7 +575,7 @@ private:
bool pairWiiUsingSync; // True if pairing was done using the Wii SYNC button.
bool checkRemoteName; // Used to check remote device's name before connecting.
bool incomingPS4; // True if a PS4 controller is connecting
bool incomingPSController; // True if a PS4/PS5 controller is connecting
uint8_t classOfDevice[3]; // Class of device of last device
/* Variables used by high level HCI task */

View file

@ -336,11 +336,11 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR(" "), 0x80);
}
#endif
if(l2capinbuf[8] == 0xA1) { // HID_THDR_DATA_INPUT
if(l2capinbuf[8] == 0xA1) { // HID BT DATA (0xA0) | Report Type (Input 0x01)
uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]);
ParseBTHIDData((uint8_t)(length - 1), &l2capinbuf[9]);
ParseBTHIDData((uint8_t)(length - 1), &l2capinbuf[9]); // First byte will be the report ID
switch(l2capinbuf[9]) {
switch(l2capinbuf[9]) { // Report ID
case 0x01: // Keyboard or Joystick events
if(pRptParser[KEYBOARD_PARSER_ID])
pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast<USBHID *>(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance
@ -357,6 +357,11 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
break;
#endif
}
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nUnhandled L2CAP interrupt report: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
#endif
}
} else if(l2capinbuf[6] == control_dcid[0] && l2capinbuf[7] == control_dcid[1]) { // l2cap_control
#ifdef PRINTREPORT
@ -366,9 +371,14 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR(" "), 0x80);
}
#endif
if(l2capinbuf[8] == 0xA3) {
if(l2capinbuf[8] == 0xA3) { // HID BT DATA (0xA0) | Report Type (Feature 0x03)
uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]);
ParseBTHIDControlData((uint8_t)(length - 1), &l2capinbuf[9]);
ParseBTHIDControlData((uint8_t)(length - 1), &l2capinbuf[9]); // First byte will be the report ID
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nUnhandled L2CAP control report: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
#endif
}
}
#ifdef EXTRADEBUG

233
PS5BT.h Normal file
View file

@ -0,0 +1,233 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : https://lauszus.com
e-mail : lauszus@gmail.com
*/
#ifndef _ps5bt_h_
#define _ps5bt_h_
#include "BTHID.h"
#include "PS5Parser.h"
/**
* Generated from the standard Ethernet CRC-32 polynomial:
* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0
* Source: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c
*/
const uint32_t crc32_table[] PROGMEM = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
/*
* There are multiple 16-bit CRC polynomials in common use, but this is
* *the* standard CRC-32 polynomial, first popularized by Ethernet.
* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0
*/
#define CRC32_POLY_LE 0xedb88320
static inline uint32_t crc32_le_generic(uint32_t crc, uint8_t const *p, size_t len, uint32_t polynomial) {
// Source: https://github.com/torvalds/linux/blob/c4cf498dc0241fa2d758dba177634268446afb06/lib/crc32.c
int i;
while (len--) {
crc ^= *p++;
for (i = 0; i < 8; i++)
crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0);
}
return crc;
}
static inline uint32_t crc32(uint32_t crc, const void *buf, size_t size) {
#if 1 // Use a table, as it's faster, but takes up more space
// Inspired by: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c
const uint8_t *p = (const uint8_t*)buf;
while (size--)
crc = pgm_read_dword(&crc32_table[*p++ ^ (crc & 0xFF)]) ^ (crc >> 8);
return crc;
#else // Can be used to save flash, but is slower
return crc32_le_generic(crc, (uint8_t const*)buf, size, CRC32_POLY_LE);
#endif
};
/**
* This class implements support for the PS5 controller via Bluetooth.
* It uses the BTHID class for all the Bluetooth communication.
*/
class PS5BT : public BTHID, public PS5Parser {
public:
/**
* Constructor for the PS5BT class.
* @param p Pointer to the BTD class instance.
* @param pair Set this to true in order to pair with the device. If the argument is omitted then it will not pair with it. One can use ::PAIR to set it to true.
* @param pin Write the pin to BTD#btdPin. If argument is omitted, then "0000" will be used.
*/
PS5BT(BTD *p, bool pair = false, const char *pin = "0000") :
BTHID(p, pair, pin), output_sequence_counter(0) {
PS5Parser::Reset();
};
/**
* Used to check if a PS5 controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return BTHID::connected;
};
protected:
/** @name BTHID implementation */
/**
* Used to parse Bluetooth HID data.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseBTHIDData(uint8_t len, uint8_t *buf) {
PS5Parser::Parse(len, buf);
};
/**
* Called when a device is successfully initialized.
* Use attachOnInit(void (*funcOnInit)(void)) to call your own function.
* This is useful for instance if you want to set the LEDs in a specific way.
*/
virtual void OnInitBTHID() {
PS5Parser::Reset();
enable_sixaxis(); // Make the controller send out the entire output report
// Only call this is a user function has not been set
if (!pFuncOnInit)
setLed(Red); // Set the LED to red, as the PS5 controller turns Bluetooth when searching for a device
};
/** Used to reset the different buffers to there default values */
virtual void ResetBTHID() {
PS5Parser::Reset();
};
/**@}*/
/** @name PS5Parser implementation */
virtual void sendOutputReport(PS5Output *output) {
// See the series of patches here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[1 /* BT DATA Output Report */ + 1 /* report id */ + 1 /* seq_tag */ + 1 /* tag */ + 47 /* common */ + 24 /* reserved */ + 4 /* crc32 */];
memset(buf, 0, sizeof(buf));
// Send as a Bluetooth HID DATA output report on the interrupt channel
buf[0] = 0xA2; // HID BT DATA (0xA0) | Report Type (Output 0x02)
buf[0x01] = 0x31; // Report ID
buf[0x02] = (output_sequence_counter << 4) | 0x0; // Highest 4-bit is a sequence number, which needs to be increased every report. Lowest 4-bit is tag and can be zero for now.
if(++output_sequence_counter == 15)
output_sequence_counter = 0;
buf[0x03] = 0x10; // Magic number must be set to 0x10
buf[0x01 + 3] = 0xFF; // feature flags 1
buf[0x02 + 3]= 0xF7; // feature flags 2
buf[0x03 + 3] = output->smallRumble; // Small Rumble
buf[0x04 + 3] = output->bigRumble; // Big rumble
// 5-7 headphone, speaker, mic volume, audio flags
buf[0x09 + 3] = (uint8_t)output->microphoneLed;
// 0x0A mute flags
// Adaptive Triggers: 0x0B-0x14 right, 0x15 unknown, 0x16-0x1F left
rightTrigger.processTrigger(&buf[0x0B + 3]); // right
leftTrigger.processTrigger(&buf[0x16 + 3]); // left
// 0x20-0x24 unknown
// 0x25 trigger motor effect strengths
// 0x26 speaker volume
// player LEDs
buf[0x27 + 3] = 0x03; // led brightness, pulse
buf[0x2A + 3] = output->disableLeds ? 0x01 : 0x2; // led pulse option
// buf[0x2B] LED brightness, 0 = full, 1= medium, 2 = low
buf[0x2C + 3] = output->playerLeds; // 5 white player LEDs
// lightbar
buf[0x2D + 3] = output->r; // Red
buf[0x2E + 3] = output->g; // Green
buf[0x2F + 3] = output->b; // Blue
uint32_t crc = ~crc32(0xFFFFFFFF, buf, sizeof(buf) - 4 /* Do not include the crc32 */); // Note how the report type is also included in the output report
buf[75] = crc & 0xFF;
buf[76] = (crc >> 8) & 0xFF;
buf[77] = (crc >> 16);
buf[78] = (crc >> 24);
output->reportChanged = false;
// Send the Bluetooth DATA output report on the interrupt channel
pBtd->L2CAP_Command(hci_handle, buf, sizeof(buf), interrupt_scid[0], interrupt_scid[1]);
};
/**@}*/
private:
void enable_sixaxis() { // Command used to make the PS5 controller send out the entire output report
// Request the paring info. This makes the controller send out the full report - see: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[2];
buf[0] = 0x43; // HID BT Get_report (0x40) | Report Type (Feature 0x03)
buf[1] = 0x09; // Report ID for paring info
// Send the Bluetooth Get_report Feature report on the control channel
pBtd->L2CAP_Command(hci_handle, buf, 2, control_scid[0], control_scid[1]);
};
uint8_t output_sequence_counter;
};
#endif

165
PS5Parser.cpp Normal file
View file

@ -0,0 +1,165 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : https://lauszus.com
e-mail : lauszus@gmail.com
Thanks to Joseph Duchesne for the initial code.
Based on Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows PS5 port
and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
*/
#include "PS5Parser.h"
enum DPADEnum {
DPAD_UP = 0x0,
DPAD_UP_RIGHT = 0x1,
DPAD_RIGHT = 0x2,
DPAD_RIGHT_DOWN = 0x3,
DPAD_DOWN = 0x4,
DPAD_DOWN_LEFT = 0x5,
DPAD_LEFT = 0x6,
DPAD_LEFT_UP = 0x7,
DPAD_OFF = 0x8,
};
// To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the PS5 Controller
bool PS5Parser::checkDpad(ButtonEnum b) {
switch (b) {
case UP:
return ps5Data.btn.dpad == DPAD_LEFT_UP || ps5Data.btn.dpad == DPAD_UP || ps5Data.btn.dpad == DPAD_UP_RIGHT;
case RIGHT:
return ps5Data.btn.dpad == DPAD_UP_RIGHT || ps5Data.btn.dpad == DPAD_RIGHT || ps5Data.btn.dpad == DPAD_RIGHT_DOWN;
case DOWN:
return ps5Data.btn.dpad == DPAD_RIGHT_DOWN || ps5Data.btn.dpad == DPAD_DOWN || ps5Data.btn.dpad == DPAD_DOWN_LEFT;
case LEFT:
return ps5Data.btn.dpad == DPAD_DOWN_LEFT || ps5Data.btn.dpad == DPAD_LEFT || ps5Data.btn.dpad == DPAD_LEFT_UP;
default:
return false;
}
}
bool PS5Parser::getButtonPress(ButtonEnum b) {
if (b <= LEFT) // Dpad
return checkDpad(b);
else
return ps5Data.btn.val & (1UL << pgm_read_byte(&PS5_BUTTONS[(uint8_t)b]));
}
bool PS5Parser::getButtonClick(ButtonEnum b) {
uint32_t mask = 1UL << pgm_read_byte(&PS5_BUTTONS[(uint8_t)b]);
bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event
return click;
}
uint8_t PS5Parser::getAnalogButton(ButtonEnum b) {
if (b == L2) // These are the only analog buttons on the controller
return ps5Data.trigger[0];
else if (b == R2)
return ps5Data.trigger[1];
return 0;
}
uint8_t PS5Parser::getAnalogHat(AnalogHatEnum a) {
return ps5Data.hatValue[(uint8_t)a];
}
void PS5Parser::Parse(uint8_t len, uint8_t *buf) {
if (len > 1 && buf) {
#ifdef PRINTREPORT
Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80);
Notify(PSTR(", data: "), 0x80);
for (uint8_t i = 0; i < len; i++) {
D_PrintHex<uint8_t > (buf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
if (buf[0] == 0x01) // Check report ID
memcpy(&ps5Data, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(ps5Data)));
else if (buf[0] == 0x31) { // This report is send via Bluetooth, it has an offset of 1 compared to the USB data
if (len < 3) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReport is too short: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
memcpy(&ps5Data, buf + 2, min((uint8_t)(len - 2), MFK_CASTUINT8T sizeof(ps5Data)));
} else {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUnknown report id: "), 0x80);
D_PrintHex<uint8_t > (buf[0], 0x80);
Notify(PSTR(", len: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
if (ps5Data.btn.val != oldButtonState.val) { // Check if anything has changed
buttonClickState.val = ps5Data.btn.val & ~oldButtonState.val; // Update click state variable
oldButtonState.val = ps5Data.btn.val;
// The DPAD buttons does not set the different bits, but set a value corresponding to the buttons pressed, we will simply set the bits ourself
uint8_t newDpad = 0;
if (checkDpad(UP))
newDpad |= 1 << UP;
if (checkDpad(RIGHT))
newDpad |= 1 << RIGHT;
if (checkDpad(DOWN))
newDpad |= 1 << DOWN;
if (checkDpad(LEFT))
newDpad |= 1 << LEFT;
if (newDpad != oldDpad) {
buttonClickState.dpad = newDpad & ~oldDpad; // Override values
oldDpad = newDpad;
}
}
message_counter++;
}
if (ps5Output.reportChanged || leftTrigger.reportChanged || rightTrigger.reportChanged)
sendOutputReport(&ps5Output); // Send output report
}
void PS5Parser::Reset() {
uint8_t i;
for (i = 0; i < sizeof(ps5Data.hatValue); i++)
ps5Data.hatValue[i] = 127; // Center value
ps5Data.btn.val = 0;
oldButtonState.val = 0;
for (i = 0; i < sizeof(ps5Data.trigger); i++)
ps5Data.trigger[i] = 0;
for (i = 0; i < sizeof(ps5Data.xy.finger)/sizeof(ps5Data.xy.finger[0]); i++)
ps5Data.xy.finger[i].touching = 1; // The bit is cleared if the finger is touching the touchpad
ps5Data.btn.dpad = DPAD_OFF;
oldButtonState.dpad = DPAD_OFF;
buttonClickState.dpad = 0;
oldDpad = 0;
leftTrigger.Reset();
rightTrigger.Reset();
ps5Output.bigRumble = ps5Output.smallRumble = 0;
ps5Output.microphoneLed = 0;
ps5Output.disableLeds = 0;
ps5Output.playerLeds = 0;
ps5Output.r = ps5Output.g = ps5Output.b = 0;
ps5Output.reportChanged = false;
};

414
PS5Parser.h Normal file
View file

@ -0,0 +1,414 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : https://lauszus.com
e-mail : lauszus@gmail.com
Thanks to Joseph Duchesne for the initial code.
Based on Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows PS5 port
and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
*/
#ifndef _ps5parser_h_
#define _ps5parser_h_
#include "Usb.h"
#include "controllerEnums.h"
#include "PS5Trigger.h"
/** Buttons on the controller */
const uint8_t PS5_BUTTONS[] PROGMEM = {
UP, // UP
RIGHT, // RIGHT
DOWN, // DOWN
LEFT, // LEFT
0x0C, // CREATE
0x0D, // OPTIONS
0x0E, // L3
0x0F, // R3
0x0A, // L2
0x0B, // R2
0x08, // L1
0x09, // R1
0x07, // TRIANGLE
0x06, // CIRCLE
0x05, // CROSS
0x04, // SQUARE
0x10, // PS
0x11, // TOUCHPAD
0x12, // MICROPHONE
};
union PS5Buttons {
struct {
uint8_t dpad : 4;
uint8_t square : 1;
uint8_t cross : 1;
uint8_t circle : 1;
uint8_t triangle : 1;
uint8_t l1 : 1;
uint8_t r1 : 1;
uint8_t l2 : 1;
uint8_t r2 : 1;
uint8_t create : 1;
uint8_t menu : 1;
uint8_t l3 : 1;
uint8_t r3 : 1;
uint8_t ps : 1;
uint8_t touchpad : 1;
uint8_t mic : 1;
uint8_t dummy : 5;
} __attribute__((packed));
uint32_t val : 24;
} __attribute__((packed));
struct ps5TouchpadXY {
struct {
uint8_t counter : 7; // Increments every time a finger is touching the touchpad
uint8_t touching : 1; // The top bit is cleared if the finger is touching the touchpad
uint16_t x : 12;
uint16_t y : 12;
} __attribute__((packed)) finger[2]; // 0 = first finger, 1 = second finger
} __attribute__((packed));
union PS5Status {
struct {
// first byte
uint8_t headphone : 1;
uint8_t dummy : 2; // Seems to change when a jack is plugged in. First bit stays on when a mic is plugged in.
uint8_t usb : 1; // charging
uint8_t dummy2: 4;
// second byte
uint8_t mic : 1;
uint8_t dummy3 : 3;
} __attribute__((packed));
uint16_t val;
} __attribute__((packed));
struct PS5Data {
/* Button and joystick values */
uint8_t hatValue[4]; // 0-3 bytes
uint8_t trigger[2]; // 4-5
uint8_t sequence_number; // 6
PS5Buttons btn; // 7-9
uint8_t reserved[5]; // 0xA-0xD
/* Gyro and accelerometer values */
int16_t gyroX, gyroZ, gyroY; // 0x0F - 0x14
int16_t accX, accZ, accY; // 0x15-0x1A
int32_t sensor_timestamp;
uint8_t reserved2;
// 0x20 - 0x23 touchpad point 1
// 0x24 - 0x27 touchpad point 2
ps5TouchpadXY xy;
#if 0 // The status byte depends on if it's sent via USB or Bluetooth, so is not parsed for now
uint8_t reserved3; // 0x28
uint8_t rightTriggerFeedback; // 0x29
uint8_t leftTriggerFeedback; // 0x2A
uint8_t reserved4[10]; // 0x2B - 0x34
// status bytes 0x35-0x36
PS5Status status;
#endif
} __attribute__((packed));
struct PS5Output {
uint8_t bigRumble, smallRumble; // Rumble
uint8_t microphoneLed;
uint8_t disableLeds;
uint8_t playerLeds;
uint8_t r, g, b; // RGB for lightbar
bool reportChanged; // The data is send when data is received from the controller
} __attribute__((packed));
/** This class parses all the data sent by the PS5 controller */
class PS5Parser {
public:
/** Constructor for the PS5Parser class. */
PS5Parser() : leftTrigger(), rightTrigger() {
Reset();
};
/** Used these to manipulate the haptic triggers */
PS5Trigger leftTrigger, rightTrigger;
/** @name PS5 Controller functions */
/**
* getButtonPress(ButtonEnum b) will return true as long as the button is held down.
*
* While getButtonClick(ButtonEnum b) will only return it once.
*
* So you instance if you need to increase a variable once you would use getButtonClick(ButtonEnum b),
* but if you need to drive a robot forward you would use getButtonPress(ButtonEnum b).
* @param b ::ButtonEnum to read.
* @return getButtonPress(ButtonEnum b) will return a true as long as a button is held down, while getButtonClick(ButtonEnum b) will return true once for each button press.
*/
bool getButtonPress(ButtonEnum b);
bool getButtonClick(ButtonEnum b);
/**@}*/
/** @name PS5 Controller functions */
/**
* Used to get the analog value from button presses.
* @param b The ::ButtonEnum to read.
* The supported buttons are:
* ::L2 and ::R2.
* @return Analog value in the range of 0-255.
*/
uint8_t getAnalogButton(ButtonEnum b);
/**
* Used to read the analog joystick.
* @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY.
* @return Return the analog value in the range of 0-255.
*/
uint8_t getAnalogHat(AnalogHatEnum a);
/**
* Get the x-coordinate of the touchpad. Position 0 is in the top left.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
*/
uint16_t getX(uint8_t finger = 0) {
return ps5Data.xy.finger[finger].x;
};
/**
* Get the y-coordinate of the touchpad. Position 0 is in the top left.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Returns the y-coordinate of the finger.
*/
uint16_t getY(uint8_t finger = 0) {
return ps5Data.xy.finger[finger].y;
};
/**
* Returns whenever the user is toucing the touchpad.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Returns true if the specific finger is touching the touchpad.
*/
bool isTouching(uint8_t finger = 0) {
return !(ps5Data.xy.finger[finger].touching); // The bit is cleared when a finger is touching the touchpad
};
/**
* This counter increments every time a finger touches the touchpad.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Return the value of the counter, note that it is only a 7-bit value.
*/
uint8_t getTouchCounter(uint8_t finger = 00) {
return ps5Data.xy.finger[finger].counter;
};
/**
* Get the angle of the controller calculated using the accelerometer.
* @param a Either ::Pitch or ::Roll.
* @return Return the angle in the range of 0-360.
*/
float getAngle(AngleEnum a) {
if (a == Pitch)
return (atan2f(-ps5Data.accY, -ps5Data.accZ) + PI) * RAD_TO_DEG;
else
return (atan2f(ps5Data.accX, -ps5Data.accZ) + PI) * RAD_TO_DEG;
};
/**
* Used to get the raw values from the 3-axis gyroscope and 3-axis accelerometer inside the PS5 controller.
* @param s The sensor to read.
* @return Returns the raw sensor reading.
*/
int16_t getSensor(SensorEnum s) {
switch(s) {
case gX:
return ps5Data.gyroX;
case gY:
return ps5Data.gyroY;
case gZ:
return ps5Data.gyroZ;
case aX:
return ps5Data.accX;
case aY:
return ps5Data.accY;
case aZ:
return ps5Data.accZ;
default:
return 0;
}
};
#if 0 // Seems to only be available via Bluetooth, so have been disabled for now
/**
* Return the battery level of the PS5 controller.
* @return The battery level in the range 0-15.
*/
uint8_t getBatteryLevel() {
return ps5Data.status.battery;
};
#endif
#if 0 // These are only valid via USB, so have been commented out for now
/**
* Use this to check if an USB cable is connected to the PS5 controller.
* @return Returns true if an USB cable is connected.
*/
bool getUsbStatus() {
return ps5Data.status.usb;
};
/**
* Use this to check if an audio jack cable is connected to the PS5 controller.
* @return Returns true if an audio jack cable is connected.
*/
bool getAudioStatus() {
return ps5Data.status.headphone;
};
/**
* Use this to check if a microphone is connected to the PS5 controller.
* @return Returns true if a microphone is connected.
*/
bool getMicStatus() {
return ps5Data.status.mic;
};
#endif
/** Turn both rumble and the LEDs off. */
void setAllOff() {
setRumbleOff();
setLedOff();
};
/** Set rumble off. */
void setRumbleOff() {
setRumbleOn(0, 0);
};
/**
* Turn on rumble.
* @param mode Either ::RumbleHigh or ::RumbleLow.
*/
void setRumbleOn(RumbleEnum mode) {
if (mode == RumbleLow)
setRumbleOn(0x00, 0xFF);
else
setRumbleOn(0xFF, 0x00);
};
/**
* Turn on rumble.
* @param bigRumble Value for big motor.
* @param smallRumble Value for small motor.
*/
void setRumbleOn(uint8_t bigRumble, uint8_t smallRumble) {
ps5Output.bigRumble = bigRumble;
ps5Output.smallRumble = smallRumble;
ps5Output.reportChanged = true;
};
/** Turn all LEDs off. */
void setLedOff() {
setLed(0, 0, 0);
};
/**
* Use this to set the color using RGB values.
* @param r,g,b RGB value.
*/
void setLed(uint8_t r, uint8_t g, uint8_t b) {
ps5Output.r = r;
ps5Output.g = g;
ps5Output.b = b;
ps5Output.reportChanged = true;
};
/**
* Use this to set the color using the predefined colors in ::ColorsEnum.
* @param color The desired color.
*/
void setLed(ColorsEnum color) {
setLed((uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
};
/** Turn all player LEDs off. */
void setPlayerLedOff() {
setPlayerLed(0);
}
/**
* Use this to set five player LEDs.
* @param mask Bit mask to set the five player LEDs. The first 5 bits represent a LED each.
*/
void setPlayerLed(uint8_t mask) {
ps5Output.playerLeds = mask;
ps5Output.reportChanged = true;
}
/** Use to turn the microphone LED off. */
void setMicLedOff() {
setMicLed(0);
}
/**
* Use this to turn the microphone LED on/off.
* @param on Turn the microphone LED on/off.
*/
void setMicLed(bool on) {
ps5Output.microphoneLed = on ? 1 : 0;
ps5Output.reportChanged = true;
}
/** Get the incoming message count. */
uint16_t getMessageCounter(){
return message_counter;
}
protected:
/**
* Used to parse data sent from the PS5 controller.
* @param len Length of the data.
* @param buf Pointer to the data buffer.
*/
void Parse(uint8_t len, uint8_t *buf);
/** Used to reset the different buffers to their default values */
void Reset();
/**
* Send the output to the PS5 controller. This is implemented in PS5BT.h and PS5USB.h.
* @param output Pointer to PS5Output buffer;
*/
virtual void sendOutputReport(PS5Output *output) = 0;
private:
bool checkDpad(ButtonEnum b); // Used to check PS5 DPAD buttons
PS5Data ps5Data;
PS5Buttons oldButtonState, buttonClickState;
PS5Output ps5Output;
uint8_t oldDpad;
uint16_t message_counter = 0;
};
#endif

94
PS5Trigger.cpp Normal file
View file

@ -0,0 +1,94 @@
/**
* @file PS5Trigger.cpp
* @author Ludwig Füchsl, adapted for USB_Host_Library SAMD by Joseph Duchesne
* @brief Based on Ludwig Füchsl's DualSense Windows driver https://github.com/Ohjurot/DualSense-Windows
* @date 2020-11-25
*
* @copyright Copyright (c) 2020 Ludwig Füchsl
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Minor updates by Kristian Sloth Lauszus.
*/
#include "PS5Trigger.h"
void PS5Trigger::processTrigger(uint8_t* buffer) {
// Switch on effect
switch (data.effectType) {
// Continious
case EffectType::ContinuousResitance:
// Mode
buffer[0x00] = 0x01;
// Parameters
buffer[0x01] = data.Continuous.startPosition;
buffer[0x02] = data.Continuous.force;
break;
// Section
case EffectType::SectionResitance:
// Mode
buffer[0x00] = 0x02;
// Parameters
buffer[0x01] = data.Section.startPosition;
buffer[0x02] = data.Section.endPosition;
break;
// EffectEx
case EffectType::EffectEx:
// Mode
buffer[0x00] = 0x02 | 0x20 | 0x04;
// Parameters
buffer[0x01] = 0xFF - data.EffectEx.startPosition;
// Keep flag
if (data.EffectEx.keepEffect)
buffer[0x02] = 0x02;
// Forces
buffer[0x04] = data.EffectEx.beginForce;
buffer[0x05] = data.EffectEx.middleForce;
buffer[0x06] = data.EffectEx.endForce;
// Frequency
buffer[0x09] = data.EffectEx.frequency / 2;
if(buffer[0x09] < 1) buffer[0x09] = 1; // minimum frequency
break;
// Calibrate
case EffectType::Calibrate:
// Mode
buffer[0x00] = 0xFC;
break;
// No resistance / default
case EffectType::NoResitance:
default:
// All zero
buffer[0x00] = 0x00;
buffer[0x01] = 0x00;
buffer[0x02] = 0x00;
break;
}
reportChanged = false;
}

168
PS5Trigger.h Normal file
View file

@ -0,0 +1,168 @@
/**
* @file PS5Trigger.h
* @author Ludwig Füchsl, adapted for USB_Host_Library SAMD by Joseph Duchesne
* @brief Based on Ludwig Füchsl's DualSense Windows driver https://github.com/Ohjurot/DualSense-Windows
* @version 0.1
* @date 2020-11-25
*
* @copyright Copyright (c) 2020 Ludwig Füchsl
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Minor updates by Kristian Sloth Lauszus.
*/
#ifndef _ps5trigger_h_
#define _ps5trigger_h_
#include <inttypes.h>
class PS5Trigger {
private:
// Type of trigger effect
typedef enum _EffectType : uint8_t {
NoResitance = 0x00, // No resistance is applied
ContinuousResitance = 0x01, // Continuous Resitance is applied
SectionResitance = 0x02, // Seciton resistance is appleyed
EffectEx = 0x26, // Extended trigger effect
Calibrate = 0xFC, // Calibrate triggers
} EffectType;
// Trigger effect
typedef struct _EffectData {
// Trigger effect type
EffectType effectType;
// Union for effect parameters
union {
// Union one raw data
uint8_t _u1_raw[6];
// For type == ContinuousResitance
struct {
uint8_t startPosition; // Start position of resistance
uint8_t force; // Force of resistance
uint8_t _pad[4]; // PAD / UNUSED
} __attribute__((packed)) Continuous;
// For type == SectionResitance
struct {
uint8_t startPosition; // Start position of resistance
uint8_t endPosition; // End position of resistance (>= start)
uint8_t _pad[4]; // PAD / UNUSED
} __attribute__((packed)) Section;
// For type == EffectEx
struct {
uint8_t startPosition; // Position at witch the effect starts
bool keepEffect; // Wher the effect should keep playing when trigger goes beyond 255
uint8_t beginForce; // Force applied when trigger >= (255 / 2)
uint8_t middleForce; // Force applied when trigger <= (255 / 2)
uint8_t endForce; // Force applied when trigger is beyond 255
uint8_t frequency; // Vibration frequency of the trigger
} __attribute__((packed)) EffectEx;
} __attribute__((packed));
} __attribute__((packed)) EffectData;
EffectData data;
public:
bool reportChanged = false;
/**
* @brief Apply the trigger data to a PS5 update buffer
*
* @param buffer The buffer at the start offset for this trigger data
*/
void processTrigger(uint8_t* buffer);
/**
* Clear force feedback on trigger without report changed
*/
void Reset() {
data.effectType = EffectType::NoResitance;
reportChanged = false;
};
/**
* Clear force feedback on trigger
*/
void clearTriggerForce() {
data.effectType = EffectType::NoResitance;
reportChanged = true;
};
/**
* Set continuous force feedback on trigger
* @param start 0-255 trigger pull to start resisting
* @param force The force amount
*/
void setTriggerForce(uint8_t start, uint8_t force) {
if (force == 0)
data.effectType = EffectType::NoResitance;
else {
data.effectType = EffectType::ContinuousResitance;
data.Continuous.startPosition = start;
data.Continuous.force = force;
}
reportChanged = true;
};
/**
* Set section force feedback on trigger
* @param start trigger pull to start resisting
* @param end trigger pull to stop resisting
*/
void setTriggerForceSection(uint8_t start, uint8_t end) {
data.effectType = EffectType::SectionResitance;
data.Section.startPosition = start;
data.Section.endPosition = end;
reportChanged = true;
};
/**
* Set effect force feedback on trigger
* @param start trigger pull to start resisting
* @param keep Keep effect active after max trigger pull
* @param begin_force 0-255 force at start position
* @param mid_force 0-255 force half way between start and max pull
* @param end_force 0-255 force at max pull
* @param frequency Vibration frequency of the trigger
*/
void setTriggerForceEffect(uint8_t start, bool keep, uint8_t begin_force, uint8_t mid_force, uint8_t end_force, uint8_t frequency) {
data.effectType = EffectType::SectionResitance;
data.EffectEx.startPosition = start;
data.EffectEx.keepEffect = keep;
data.EffectEx.beginForce = begin_force;
data.EffectEx.middleForce = mid_force;
data.EffectEx.endForce = end_force;
data.EffectEx.frequency = frequency;
reportChanged = true;
};
};
#endif

157
PS5USB.h Normal file
View file

@ -0,0 +1,157 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : https://lauszus.com
e-mail : lauszus@gmail.com
Thanks to Joseph Duchesne for the initial port. Data structure mapping partially based
on values from Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows
*/
#ifndef _ps5usb_h_
#define _ps5usb_h_
#include "hiduniversal.h"
#include "PS5Parser.h"
#define PS5_VID 0x054C // Sony Corporation
#define PS5_PID 0x0CE6 // PS5 Controller
/**
* This class implements support for the PS5 controller via USB.
* It uses the HIDUniversal class for all the USB communication.
*/
class PS5USB : public HIDUniversal, public PS5Parser {
public:
/**
* Constructor for the PS5USB class.
* @param p Pointer to the USB class instance.
*/
PS5USB(USB *p) :
HIDUniversal(p) {
PS5Parser::Reset();
};
/**
* Used to check if a PS5 controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return HIDUniversal::isReady() && HIDUniversal::VID == PS5_VID && HIDUniversal::PID == PS5_PID;
};
/**
* Used to call your own function when the device is successfully initialized.
* @param funcOnInit Function to call.
*/
void attachOnInit(void (*funcOnInit)(void)) {
pFuncOnInit = funcOnInit;
};
protected:
/** @name HIDUniversal implementation */
/**
* Used to parse USB HID data.
* @param hid Pointer to the HID class.
* @param is_rpt_id Only used for Hubs.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseHIDData(USBHID *hid __attribute__((unused)), bool is_rpt_id __attribute__((unused)), uint8_t len, uint8_t *buf) {
if (HIDUniversal::VID == PS5_VID && HIDUniversal::PID == PS5_PID)
PS5Parser::Parse(len, buf);
};
/**
* Called when a device is successfully initialized.
* Use attachOnInit(void (*funcOnInit)(void)) to call your own function.
* This is useful for instance if you want to set the LEDs in a specific way.
*/
virtual uint8_t OnInitSuccessful() {
if (HIDUniversal::VID == PS5_VID && HIDUniversal::PID == PS5_PID) {
PS5Parser::Reset();
if (pFuncOnInit)
pFuncOnInit(); // Call the user function
else
setLed(Red); // Set the LED to red, so it is consistent with the PS5BT driver
};
return 0;
};
/**@}*/
/** @name PS5Parser implementation */
virtual void sendOutputReport(PS5Output *output) { // Source: https://github.com/chrippa/ds4drv
// PS4 Source: https://github.com/chrippa/ds4drv
// PS5 values from https://www.reddit.com/r/gamedev/comments/jumvi5/dualsense_haptics_leds_and_more_hid_output_report/,
// Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows and
// the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[1 /* report id */ + 47 /* common */];
memset(buf, 0, sizeof(buf));
buf[0x00] = 0x02; // Report ID
buf[0x01] = 0xFF; // feature flags 1
buf[0x02]= 0xF7; // feature flags 2
buf[0x03] = output->smallRumble; // Small Rumble
buf[0x04] = output->bigRumble; // Big rumble
// 5-7 headphone, speaker, mic volume, audio flags
buf[0x09] = (uint8_t)output->microphoneLed;
// 0x0A mute flags
// Adaptive Triggers: 0x0B-0x14 right, 0x15 unknown, 0x16-0x1F left
rightTrigger.processTrigger(&buf[0x0B]); // right
leftTrigger.processTrigger(&buf[0x16]); // left
// 0x20-0x24 unknown
// 0x25 trigger motor effect strengths
// 0x26 speaker volume
// player LEDs
buf[0x27] = 0x03; // led brightness, pulse
buf[0x2A] = output->disableLeds ? 0x01 : 0x2; // led pulse option
// buf[0x2B] LED brightness, 0 = full, 1= medium, 2 = low
buf[0x2C] = output->playerLeds; // 5 white player LEDs
// lightbar
buf[0x2D] = output->r; // Red
buf[0x2E] = output->g; // Green
buf[0x2F] = output->b; // Blue
output->reportChanged = false;
// There is no need to calculate a crc32 when the controller is connected via USB
pUsb->outTransfer(bAddress, epInfo[ hidInterfaces[0].epIndex[epInterruptOutIndex] ].epAddr, sizeof(buf), buf);
};
/**@}*/
/** @name USBDeviceConfig implementation */
/**
* Used by the USB core to check what this driver support.
* @param vid The device's VID.
* @param pid The device's PID.
* @return Returns true if the device's VID and PID matches this driver.
*/
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
return (vid == PS5_VID && pid == PS5_PID);
};
/**@}*/
private:
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
};
#endif

View file

@ -23,7 +23,7 @@ For more information about the hardware see the [Hardware Manual](https://chome.
* __Alexei Glushchenko__ - <alex-gl@mail.ru>
* Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries
* __Kristian Sloth Lauszus__ - <lauszus@gmail.com>
* Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-library), [PS4](#ps4-library), [PS3](#ps3-library), [Wii](#wii-library), [Xbox](#xbox-library), and [PSBuzz](#ps-buzz-library) libraries
* Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-library), [PS5](#ps5-library), [PS4](#ps4-library), [PS3](#ps3-library), [Wii](#wii-library), [Xbox](#xbox-library), and [PSBuzz](#ps-buzz-library) libraries
* __Andrew Kroll__ - <xxxajk@gmail.com>
* Major contributor to mass storage code
* __guruthree__
@ -44,6 +44,7 @@ For more information about the hardware see the [Hardware Manual](https://chome.
* [Bluetooth libraries](#bluetooth-libraries)
* [BTHID library](#bthid-library)
* [SPP library](#spp-library)
* [PS5 Library](#ps5-library)
* [PS4 Library](#ps4-library)
* [PS3 Library](#ps3-library)
* [Xbox Libraries](#xbox-libraries)
@ -171,9 +172,25 @@ More information can be found at these blog posts:
To implement the SPP protocol I used a Bluetooth sniffing tool called [PacketLogger](http://www.tkjelectronics.com/uploads/PacketLogger.zip) developed by Apple.
It enables me to see the Bluetooth communication between my Mac and any device.
### PS5 Library
The PS5 library is split up into the [PS5BT](PS5BT.h) and the [PS5USB](PS5USB.h) library. These allow you to use the Sony PS5 controller via Bluetooth and USB.
The [PS5BT.ino](examples/Bluetooth/PS5BT/PS5BT.ino) and [PS5USB.ino](examples/PS5USB/PS5USB.ino) examples shows how to easily read the buttons, joysticks, touchpad and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble, lightbar, microphone LED and player LEDs on the controller. Furthermore the new haptic trigger effects are also supported.
To pair with the PS5 controller via Bluetooth you need create the PS5BT instance like so: ```PS5BT PS5(&Btd, PAIR);``` and then hold down the Create button and then hold down the PS without releasing the Create button. The PS5 controller will then start to blink blue indicating that it is in pairing mode.
It should then automatically pair the dongle with your controller. This only have to be done once.
Thanks to Joseph Duchesne for the initial USB code.
The driver is based on the official Sony driver for Linux: <https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/>.
Also thanks to Ludwig Füchsl's <https://github.com/Ohjurot/DualSense-Windows> for his work on the haptic triggers.
### PS4 Library
The PS4BT library is split up into the [PS4BT](PS4BT.h) and the [PS4USB](PS4USB.h) library. These allow you to use the Sony PS4 controller via Bluetooth and USB.
The PS4 library is split up into the [PS4BT](PS4BT.h) and the [PS4USB](PS4USB.h) library. These allow you to use the Sony PS4 controller via Bluetooth and USB.
The [PS4BT.ino](examples/Bluetooth/PS4BT/PS4BT.ino) and [PS4USB.ino](examples/PS4USB/PS4USB.ino) examples shows how to easily read the buttons, joysticks, touchpad and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble and light on the controller and get the battery level.

View file

@ -150,6 +150,7 @@ enum ButtonEnum {
MENU = 5,
/**@}*/
/**@{*/
/** PS Buzz controllers */
RED = 0,
YELLOW = 1,
@ -157,6 +158,12 @@ enum ButtonEnum {
ORANGE = 3,
BLUE = 4,
/**@}*/
/**@{*/
/** PS5 buttons */
CREATE = 4,
MICROPHONE = 18,
/**@}*/
};
/** Joysticks on the PS3 and Xbox controllers. */

View file

@ -0,0 +1,170 @@
/*
Example sketch for the PS5 Bluetooth library - developed by Kristian Sloth Lauszus
For more information visit the Github repository: github.com/felis/USB_Host_Shield_2.0 or
send me an e-mail: lauszus@gmail.com
*/
#include <PS5BT.h>
#include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
//USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so
/* You can create the instance of the PS5BT class in two ways */
// This will start an inquiry and then pair with the PS5 controller - you only have to do this once
// You will need to hold down the PS and Share button at the same time, the PS5 controller will then start to blink rapidly indicating that it is in pairing mode
PS5BT PS5(&Btd, PAIR);
// After that you can simply create the instance like so and then press the PS button on the device
//PS5BT PS5(&Btd);
bool printAngle = false, printTouch = false;
uint16_t lastMessageCounter = -1;
uint8_t player_led_mask = 0;
bool microphone_led = false;
uint32_t ps_timer;
void setup() {
Serial.begin(115200);
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
if (Usb.Init() == -1) {
Serial.print(F("\r\nOSC did not start"));
while (1); // Halt
}
Serial.print(F("\r\nPS5 Bluetooth Library Started"));
}
void loop() {
Usb.Task();
if (PS5.connected() && lastMessageCounter != PS5.getMessageCounter()) {
lastMessageCounter = PS5.getMessageCounter();
if (PS5.getAnalogHat(LeftHatX) > 137 || PS5.getAnalogHat(LeftHatX) < 117 || PS5.getAnalogHat(LeftHatY) > 137 || PS5.getAnalogHat(LeftHatY) < 117 || PS5.getAnalogHat(RightHatX) > 137 || PS5.getAnalogHat(RightHatX) < 117 || PS5.getAnalogHat(RightHatY) > 137 || PS5.getAnalogHat(RightHatY) < 117) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(PS5.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
Serial.print(PS5.getAnalogHat(LeftHatY));
Serial.print(F("\tRightHatX: "));
Serial.print(PS5.getAnalogHat(RightHatX));
Serial.print(F("\tRightHatY: "));
Serial.print(PS5.getAnalogHat(RightHatY));
}
if (PS5.getAnalogButton(L2) || PS5.getAnalogButton(R2)) { // These are the only analog buttons on the PS5 controller
Serial.print(F("\r\nL2: "));
Serial.print(PS5.getAnalogButton(L2));
Serial.print(F("\tR2: "));
Serial.print(PS5.getAnalogButton(R2));
}
// Set the left trigger to resist at the right trigger's level
static uint8_t oldR2Value = 0xFF;
if (PS5.getAnalogButton(R2) != oldR2Value) {
oldR2Value = PS5.getAnalogButton(R2);
PS5.leftTrigger.setTriggerForce(oldR2Value, 255);
}
// Hold the PS button for 1 second to disconnect the controller
// This prevents the controller from disconnecting when it is reconnected,
// as the PS button is sent when it reconnects
if (PS5.getButtonPress(PS)) {
if (millis() - ps_timer > 1000)
PS5.disconnect();
} else
ps_timer = millis();
if (PS5.getButtonClick(PS))
Serial.print(F("\r\nPS"));
if (PS5.getButtonClick(TRIANGLE)) {
Serial.print(F("\r\nTriangle"));
PS5.setRumbleOn(RumbleLow);
}
if (PS5.getButtonClick(CIRCLE)) {
Serial.print(F("\r\nCircle"));
PS5.setRumbleOn(RumbleHigh);
}
if (PS5.getButtonClick(CROSS)) {
Serial.print(F("\r\nCross"));
// Set the player LEDs
player_led_mask = (player_led_mask << 1) | 1;
if (player_led_mask > 0x1F)
player_led_mask = 0;
PS5.setPlayerLed(player_led_mask); // The bottom 5 bits set player LEDs
}
if (PS5.getButtonClick(SQUARE)) {
Serial.print(F("\r\nSquare"));
PS5.setRumbleOff();
}
if (PS5.getButtonClick(UP)) {
Serial.print(F("\r\nUp"));
PS5.setLed(Red);
} if (PS5.getButtonClick(RIGHT)) {
Serial.print(F("\r\nRight"));
PS5.setLed(Blue);
} if (PS5.getButtonClick(DOWN)) {
Serial.print(F("\r\nDown"));
PS5.setLed(Yellow);
} if (PS5.getButtonClick(LEFT)) {
Serial.print(F("\r\nLeft"));
PS5.setLed(Green);
}
if (PS5.getButtonClick(L1))
Serial.print(F("\r\nL1"));
if (PS5.getButtonClick(L3))
Serial.print(F("\r\nL3"));
if (PS5.getButtonClick(R1))
Serial.print(F("\r\nR1"));
if (PS5.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (PS5.getButtonClick(CREATE))
Serial.print(F("\r\nCreate"));
if (PS5.getButtonClick(OPTIONS)) {
Serial.print(F("\r\nOptions"));
printAngle = !printAngle;
}
if (PS5.getButtonClick(TOUCHPAD)) {
Serial.print(F("\r\nTouchpad"));
printTouch = !printTouch;
}
if (PS5.getButtonClick(MICROPHONE)) {
Serial.print(F("\r\nMicrophone"));
microphone_led = !microphone_led;
PS5.setMicLed(microphone_led);
}
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(PS5.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(PS5.getAngle(Roll));
}
if (printTouch) { // Print the x, y coordinates of the touchpad
if (PS5.isTouching(0) || PS5.isTouching(1)) // Print newline and carriage return if any of the fingers are touching the touchpad
Serial.print(F("\r\n"));
for (uint8_t i = 0; i < 2; i++) { // The touchpad track two fingers
if (PS5.isTouching(i)) { // Print the position of the finger if it is touching the touchpad
Serial.print(F("X")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getX(i));
Serial.print(F("\tY")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getY(i));
Serial.print(F("\t"));
}
}
}
}
}

150
examples/PS5USB/PS5USB.ino Normal file
View file

@ -0,0 +1,150 @@
/*
Example sketch for the PS5 USB library - developed by Kristian Sloth Lauszus
For more information visit the Github repository: github.com/felis/USB_Host_Shield_2.0 or
send me an e-mail: lauszus@gmail.com
*/
#include <PS5USB.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
PS5USB PS5(&Usb);
bool printAngle = false, printTouch = false;
uint16_t lastMessageCounter = -1;
uint8_t player_led_mask = 0;
bool microphone_led = false;
void setup() {
Serial.begin(115200);
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
if (Usb.Init() == -1) {
Serial.print(F("\r\nOSC did not start"));
while (1); // Halt
}
Serial.print(F("\r\nPS5 USB Library Started"));
}
void loop() {
Usb.Task();
if (PS5.connected() && lastMessageCounter != PS5.getMessageCounter()) {
lastMessageCounter = PS5.getMessageCounter();
if (PS5.getAnalogHat(LeftHatX) > 137 || PS5.getAnalogHat(LeftHatX) < 117 || PS5.getAnalogHat(LeftHatY) > 137 || PS5.getAnalogHat(LeftHatY) < 117 || PS5.getAnalogHat(RightHatX) > 137 || PS5.getAnalogHat(RightHatX) < 117 || PS5.getAnalogHat(RightHatY) > 137 || PS5.getAnalogHat(RightHatY) < 117) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(PS5.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
Serial.print(PS5.getAnalogHat(LeftHatY));
Serial.print(F("\tRightHatX: "));
Serial.print(PS5.getAnalogHat(RightHatX));
Serial.print(F("\tRightHatY: "));
Serial.print(PS5.getAnalogHat(RightHatY));
}
if (PS5.getAnalogButton(L2) || PS5.getAnalogButton(R2)) { // These are the only analog buttons on the PS5 controller
Serial.print(F("\r\nL2: "));
Serial.print(PS5.getAnalogButton(L2));
Serial.print(F("\tR2: "));
Serial.print(PS5.getAnalogButton(R2));
}
// Set the left trigger to resist at the right trigger's level
static uint8_t oldR2Value = 0xFF;
if (PS5.getAnalogButton(R2) != oldR2Value) {
oldR2Value = PS5.getAnalogButton(R2);
PS5.leftTrigger.setTriggerForce(oldR2Value, 255);
}
if (PS5.getButtonClick(PS))
Serial.print(F("\r\nPS"));
if (PS5.getButtonClick(TRIANGLE)) {
Serial.print(F("\r\nTriangle"));
PS5.setRumbleOn(RumbleLow);
}
if (PS5.getButtonClick(CIRCLE)) {
Serial.print(F("\r\nCircle"));
PS5.setRumbleOn(RumbleHigh);
}
if (PS5.getButtonClick(CROSS)) {
Serial.print(F("\r\nCross"));
// Set the player LEDs
player_led_mask = (player_led_mask << 1) | 1;
if (player_led_mask > 0x1F)
player_led_mask = 0;
PS5.setPlayerLed(player_led_mask); // The bottom 5 bits set player LEDs
}
if (PS5.getButtonClick(SQUARE)) {
Serial.print(F("\r\nSquare"));
PS5.setRumbleOff();
}
if (PS5.getButtonClick(UP)) {
Serial.print(F("\r\nUp"));
PS5.setLed(Red);
} if (PS5.getButtonClick(RIGHT)) {
Serial.print(F("\r\nRight"));
PS5.setLed(Blue);
} if (PS5.getButtonClick(DOWN)) {
Serial.print(F("\r\nDown"));
PS5.setLed(Yellow);
} if (PS5.getButtonClick(LEFT)) {
Serial.print(F("\r\nLeft"));
PS5.setLed(Green);
}
if (PS5.getButtonClick(L1))
Serial.print(F("\r\nL1"));
if (PS5.getButtonClick(L3))
Serial.print(F("\r\nL3"));
if (PS5.getButtonClick(R1))
Serial.print(F("\r\nR1"));
if (PS5.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (PS5.getButtonClick(CREATE))
Serial.print(F("\r\nCreate"));
if (PS5.getButtonClick(OPTIONS)) {
Serial.print(F("\r\nOptions"));
printAngle = !printAngle;
}
if (PS5.getButtonClick(TOUCHPAD)) {
Serial.print(F("\r\nTouchpad"));
printTouch = !printTouch;
}
if (PS5.getButtonClick(MICROPHONE)) {
Serial.print(F("\r\nMicrophone"));
microphone_led = !microphone_led;
PS5.setMicLed(microphone_led);
}
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(PS5.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(PS5.getAngle(Roll));
}
if (printTouch) { // Print the x, y coordinates of the touchpad
if (PS5.isTouching(0) || PS5.isTouching(1)) // Print newline and carriage return if any of the fingers are touching the touchpad
Serial.print(F("\r\n"));
for (uint8_t i = 0; i < 2; i++) { // The touchpad track two fingers
if (PS5.isTouching(i)) { // Print the position of the finger if it is touching the touchpad
Serial.print(F("X")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getX(i));
Serial.print(F("\tY")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getY(i));
Serial.print(F("\t"));
}
}
}
}
}

View file

@ -36,6 +36,8 @@ PS3BT KEYWORD1
PS3USB KEYWORD1
PS4BT KEYWORD1
PS4USB KEYWORD1
PS5BT KEYWORD1
PS5USB KEYWORD1
####################################################
# Methods and Functions (KEYWORD2)
@ -135,6 +137,9 @@ SHARE LITERAL1
OPTIONS LITERAL1
TOUCHPAD LITERAL1
CREATE LITERAL1
MICROPHONE LITERAL1
LeftHatX LITERAL1
LeftHatY LITERAL1
RightHatX LITERAL1

View file

@ -1,6 +1,6 @@
{
"name": "USB-Host-Shield-20",
"keywords": "usb, host, ftdi, adk, acm, pl2303, hid, bluetooth, spp, ps3, ps4, buzz, xbox, wii, mass storage",
"keywords": "usb, host, ftdi, adk, acm, pl2303, hid, bluetooth, spp, ps3, ps4, ps5, buzz, xbox, wii, mass storage",
"description": "Revision 2.0 of MAX3421E-based USB Host Shield Library",
"authors":
[

View file

@ -3,7 +3,7 @@ version=1.4.0
author=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Sloth Lauszus <lauszus@gmail.com>, Andrew Kroll <xxxajk@gmail.com>, Alexei Glushchenko (Circuits@Home) <alex-gl@mail.ru>
maintainer=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Sloth Lauszus <lauszus@gmail.com>, Andrew Kroll <xxxajk@gmail.com>
sentence=Revision 2.0 of MAX3421E-based USB Host Shield Library.
paragraph=Supports HID devices, FTDI, ADK, ACM, PL2303, Bluetooth HID devices, SPP communication and mass storage devices. Furthermore it supports PS3, PS4, PS Buzz, Wii and Xbox controllers.
paragraph=Supports HID devices, FTDI, ADK, ACM, PL2303, Bluetooth HID devices, SPP communication and mass storage devices. Furthermore it supports PS3, PS4, PS5, PS Buzz, Wii and Xbox controllers.
category=Other
url=https://github.com/felis/USB_Host_Shield_2.0
architectures=*