PS5 controller is now working via USB

This commit is contained in:
Kristian Sloth Lauszus 2021-01-17 20:17:32 +01:00
parent fba77f9119
commit ee7bf6e5a0
8 changed files with 1135 additions and 0 deletions

160
PS5Parser.cpp Normal file
View file

@ -0,0 +1,160 @@
/* 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.
*/
#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\n"), 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] == 0x11) { // This report is send via Bluetooth, it has an offset of 2 compared to the USB data
if (len < 4) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReport is too short: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
memcpy(&ps5Data, buf + 3, min((uint8_t)(len - 3), MFK_CASTUINT8T sizeof(ps5Data)));
} else {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUnknown report id: "), 0x80);
D_PrintHex<uint8_t > (buf[0], 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;
};

405
PS5Parser.h Normal file
View file

@ -0,0 +1,405 @@
/* 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.
*/
#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, // SHARE
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 share : 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 dummy4 : 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 dummy; // 6 unknown
PS5Buttons btn; // 7-9
uint8_t dummy2[5]; // 0xA-0xD unknown
/* Gyro and accelerometer values */
int16_t gyroX, gyroZ, gyroY; // 0x0F - 0x14
int16_t accX, accZ, accY; // 0x15-0x1A
uint8_t dummy3[5]; // 0x1B - 0x1F unknown
// 0x20 - 0x23 touchpad point 1
// 0x24 - 0x27 touchpad point 2
ps5TouchpadXY xy;
uint8_t dummy4; //0x28 unknown
uint8_t rightTriggerFeedback; // 0x29
uint8_t leftTriggerFeedback; // 0x2A
uint8_t dummy5[10]; // 0x2B - 0x34 unknown
// status bytes 0x35-0x36
PS5Status status;
} __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:
PS5Trigger leftTrigger;
PS5Trigger rightTrigger;
/** Constructor for the PS5Parser class. */
PS5Parser() : leftTrigger(), rightTrigger() {
Reset();
};
/** @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;
}
};
/**
* Return the battery level of the PS5 controller.
* @return The battery level in the range 0-15.
*/
/*uint8_t getBatteryLevel() {
return ps5Data.status.battery;
};*/
/**
* 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;
};
/** 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;
}
/** 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;
}
/** 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

154
PS5USB.h Normal file
View file

@ -0,0 +1,154 @@
/* 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(Blue);
};
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/
// and Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows
uint8_t buf[48];
memset(buf, 0, sizeof(buf));
buf[0x00] = 0x02; // report type
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;
// The PS5 console actually set the four last bytes to a CRC32 checksum, but it seems like it is actually not needed
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

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

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

@ -0,0 +1,146 @@
/*
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, printTouch;
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
PS5.leftTrigger.setTriggerForce(PS5.getAnalogButton(R2), 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(SHARE))
Serial.print(F("\r\nShare"));
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

@ -135,6 +135,8 @@ SHARE LITERAL1
OPTIONS LITERAL1 OPTIONS LITERAL1
TOUCHPAD LITERAL1 TOUCHPAD LITERAL1
MICROPHONE LITERAL1
LeftHatX LITERAL1 LeftHatX LITERAL1
LeftHatY LITERAL1 LeftHatY LITERAL1
RightHatX LITERAL1 RightHatX LITERAL1