diff --git a/examples/PS5USB/PS5USB.ino b/examples/PS5USB/PS5USB.ino new file mode 100644 index 0000000..bab045c --- /dev/null +++ b/examples/PS5USB/PS5USB.ino @@ -0,0 +1,64 @@ +#include "Arduino.h" +#include +#include + +#define SerialDebug Serial1 + +USBHost usb; +PS5USB PS5(&usb); + +bool printAngle, printTouch; +uint8_t oldL2Value, oldR2Value; + + +void setup() +{ + delay(1000); + SerialDebug.begin(115200); + SerialDebug.println("Starting PS5 USB Test"); + + SerialDebug.println("Initializing USB"); + if (usb.Init() == -1) + SerialDebug.println("USBhost did not start."); + SerialDebug.println("USB Started"); + + delay( 20 ); +} + +uint32_t lastUSBstate; +uint16_t lastMessageCounter = -1; + +void loop() +{ + usb.Task(); + + if (PS5.connected() && lastMessageCounter!=PS5.getMessageCounter()) { + SerialDebug.print(PS5.getMessageCounter()); + SerialDebug.print(F("\tLeftHatX: ")); + SerialDebug.print(PS5.getAnalogHat(LeftHatX)); + SerialDebug.print(F("\tLeftHatY: ")); + SerialDebug.print(PS5.getAnalogHat(LeftHatY)); + SerialDebug.print(F("\tRightHatX: ")); + SerialDebug.print(PS5.getAnalogHat(RightHatX)); + SerialDebug.print(F("\tRightHatY: ")); + SerialDebug.print(PS5.getAnalogHat(RightHatY)); + SerialDebug.print(F("\tL2: ")); + SerialDebug.print(PS5.getAnalogButton(L2)); + SerialDebug.print(F("\tR2: ")); + SerialDebug.print(PS5.getAnalogButton(R2)); + SerialDebug.println(); + + // set the left trigger to resist at the right trigger's level + PS5.leftTrigger.setTriggerForce(0, PS5.getAnalogButton(R2), 255); + + if (PS5.getButtonClick(CROSS)) { + PS5.setLed(255,0,0); + } + if (PS5.getButtonClick(SQUARE)) { + PS5.setLed(0,255,0); + } + if (PS5.getButtonClick(CIRCLE)) { + PS5.setLed(0,0,255); + } + } +} \ No newline at end of file diff --git a/src/PS5Parser.cpp b/src/PS5Parser.cpp new file mode 100644 index 0000000..ab00c3d --- /dev/null +++ b/src/PS5Parser.cpp @@ -0,0 +1,158 @@ +/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. 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 Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + + PS4 to PS5 port by Joseph Duchesne, based on reading 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 (buf[i], 0x80); + Notify(PSTR(" "), 0x80); + } +#endif + + if (buf[0] == 0x01) // Check report ID + memcpy(&ps5Data, buf + 1, min((uint8_t)(len - 1), 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 (len, 0x80); +#endif + return; + } + memcpy(&ps5Data, buf + 3, min((uint8_t)(len - 3), sizeof(ps5Data))); + } else { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nUnknown report id: "), 0x80); + D_PrintHex (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)/sizeof(ps5Data.xy[0]); i++) { + for (uint8_t j = 0; j < sizeof(ps5Data.xy[0].finger)/sizeof(ps5Data.xy[0].finger[0]); j++) + ps5Data.xy[i].finger[j].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.r = ps5Output.g = ps5Output.b = 0; + ps5Output.reportChanged = false; +}; diff --git a/src/PS5Parser.h b/src/PS5Parser.h new file mode 100644 index 0000000..ca14198 --- /dev/null +++ b/src/PS5Parser.h @@ -0,0 +1,392 @@ +/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. 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 Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + + PS5 support by Joseph Duchesne + */ + +#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 +}; + +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 left_stick : 1; + uint8_t right_stick : 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 touchpadXY { + 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)); + +struct PS5Status { + // first byte + uint8_t headphone : 1; + uint8_t dummy : 2; + uint8_t usb : 1; // charging + uint8_t dummy2: 4; + + // second byte + uint8_t battery : 4; + uint8_t dummy3 : 1; + uint8_t battery_full : 1; + uint8_t dummy4 : 2; +} __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 + touchpadXY 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: + * ::UP, ::RIGHT, ::DOWN, ::LEFT, ::L1, ::L2, ::R1, ::R2, + * ::TRIANGLE, ::CIRCLE, ::CROSS, ::SQUARE, and ::T. + * @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. + * @param xyId The controller sends out three packets with the same structure. + * The third one will contain the last measure, but if you read from the controller then there is only be data in the first one. + * For that reason it will be set to 0 if the argument is omitted. + * @return Returns the x-coordinate of the finger. + */ + uint16_t getX(uint8_t finger = 0, uint8_t xyId = 0) { + return ps5Data.xy[xyId].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. + * @param xyId The controller sends out three packets with the same structure. + * The third one will contain the last measure, but if you read from the controller then there is only be data in the first one. + * For that reason it will be set to 0 if the argument is omitted. + * @return Returns the y-coordinate of the finger. + */ + uint16_t getY(uint8_t finger = 0, uint8_t xyId = 0) { + return ps5Data.xy[xyId].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. + * @param xyId The controller sends out three packets with the same structure. + * The third one will contain the last measure, but if you read from the controller then there is only be data in the first one. + * For that reason it will be set to 0 if the argument is omitted. + * @return Returns true if the specific finger is touching the touchpad. + */ + bool isTouching(uint8_t finger = 0, uint8_t xyId = 0) { + return !(ps5Data.xy[xyId].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. + * @param xyId The controller sends out three packets with the same structure. + * The third one will contain the last measure, but if you read from the controller then there is only be data in the first one. + * For that reason it will be set to 0 if the argument is omitted. + * @return Return the value of the counter, note that it is only a 7-bit value. + */ + uint8_t getTouchCounter(uint8_t finger = 0, uint8_t xyId = 0) { + return ps5Data.xy[xyId].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 false; // TODO: fix this. 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)); + }; + + 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 diff --git a/src/PS5Trigger.cpp b/src/PS5Trigger.cpp new file mode 100644 index 0000000..f8198f2 --- /dev/null +++ b/src/PS5Trigger.cpp @@ -0,0 +1,97 @@ +/** + * @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. + * + */ + + +#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.Continuous.startPosition; + buffer[0x02] = data.Continuous.force; + + 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; +} \ No newline at end of file diff --git a/src/PS5Trigger.h b/src/PS5Trigger.h new file mode 100644 index 0000000..a8e2246 --- /dev/null +++ b/src/PS5Trigger.h @@ -0,0 +1,173 @@ +/** + * @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. + * + */ + +#ifndef _ps5trigger_h_ +#define _ps5trigger_h_ + +#include + +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 + } 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 + } 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 + } EffectEx; + }; + } 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 trigger 0=left, 1=right + * @param start 0-255 trigger pull to start resisting + * @param force The force amount + */ + void setTriggerForce(uint8_t trigger, 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 trigger 0=left, 1=right + * @param start trigger pull to start resisting + * @param end trigger pull to stop resisting + */ + void setTriggerForceSection(uint8_t trigger, uint8_t start, uint8_t end) { + data.effectType = EffectType::SectionResitance; + data.Continuous.startPosition = start; + data.Continuous.force = end; + + reportChanged = true; + }; + + /** + * Set effect force feedback on trigger + * @param trigger 0=left, 1=right + * @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 trigger, 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 = keep; + + reportChanged = true; + }; + +}; + +#endif \ No newline at end of file diff --git a/src/PS5USB.h b/src/PS5USB.h new file mode 100644 index 0000000..d12f8a8 --- /dev/null +++ b/src/PS5USB.h @@ -0,0 +1,157 @@ +/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. 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 Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + + PS4->PS5 port by Joseph Duchesne with 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(USBHost *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(HID *hid, bool is_rpt_id, 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 uint32_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) { + // 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