From ee7bf6e5a0575d52a4bd283f0c94c212adea66ca Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Sun, 17 Jan 2021 20:17:32 +0100 Subject: [PATCH 1/5] PS5 controller is now working via USB --- PS5Parser.cpp | 160 +++++++++++++++ PS5Parser.h | 405 +++++++++++++++++++++++++++++++++++++ PS5Trigger.cpp | 94 +++++++++ PS5Trigger.h | 168 +++++++++++++++ PS5USB.h | 154 ++++++++++++++ controllerEnums.h | 6 + examples/PS5USB/PS5USB.ino | 146 +++++++++++++ keywords.txt | 2 + 8 files changed, 1135 insertions(+) create mode 100644 PS5Parser.cpp create mode 100644 PS5Parser.h create mode 100644 PS5Trigger.cpp create mode 100644 PS5Trigger.h create mode 100644 PS5USB.h create mode 100644 examples/PS5USB/PS5USB.ino diff --git a/PS5Parser.cpp b/PS5Parser.cpp new file mode 100644 index 00000000..b59d32c6 --- /dev/null +++ b/PS5Parser.cpp @@ -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 (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 (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 (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; +}; diff --git a/PS5Parser.h b/PS5Parser.h new file mode 100644 index 00000000..c299b700 --- /dev/null +++ b/PS5Parser.h @@ -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 diff --git a/PS5Trigger.cpp b/PS5Trigger.cpp new file mode 100644 index 00000000..534158ab --- /dev/null +++ b/PS5Trigger.cpp @@ -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; +} diff --git a/PS5Trigger.h b/PS5Trigger.h new file mode 100644 index 00000000..6c1fc024 --- /dev/null +++ b/PS5Trigger.h @@ -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 + +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 diff --git a/PS5USB.h b/PS5USB.h new file mode 100644 index 00000000..a7ce9dfc --- /dev/null +++ b/PS5USB.h @@ -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 diff --git a/controllerEnums.h b/controllerEnums.h index c1a5a497..66963ee4 100644 --- a/controllerEnums.h +++ b/controllerEnums.h @@ -150,6 +150,7 @@ enum ButtonEnum { MENU = 5, /**@}*/ + /**@{*/ /** PS Buzz controllers */ RED = 0, YELLOW = 1, @@ -157,6 +158,11 @@ enum ButtonEnum { ORANGE = 3, BLUE = 4, /**@}*/ + + /**@{*/ + /** PS5 buttons */ + MICROPHONE = 18, + /**@}*/ }; /** Joysticks on the PS3 and Xbox controllers. */ diff --git a/examples/PS5USB/PS5USB.ino b/examples/PS5USB/PS5USB.ino new file mode 100644 index 00000000..3f008fc8 --- /dev/null +++ b/examples/PS5USB/PS5USB.ino @@ -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 + +// Satisfy the IDE, which needs to see the include statment in the ino too. +#ifdef dobogusinclude +#include +#endif +#include + +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")); + } + } + } + } +} diff --git a/keywords.txt b/keywords.txt index 2513e98e..0f3ca523 100644 --- a/keywords.txt +++ b/keywords.txt @@ -135,6 +135,8 @@ SHARE LITERAL1 OPTIONS LITERAL1 TOUCHPAD LITERAL1 +MICROPHONE LITERAL1 + LeftHatX LITERAL1 LeftHatY LITERAL1 RightHatX LITERAL1 From 28a75dea6b615b517205932e243c583fed85b5eb Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Sun, 17 Jan 2021 23:34:31 +0100 Subject: [PATCH 2/5] PS5 is now also working via Bluetooth However the output report is still not working --- .github/workflows/main.yml | 2 +- BTD.cpp | 12 +- BTD.h | 2 +- PS5BT.h | 282 +++++++++++++++++++++++++++++ PS5Parser.cpp | 11 +- PS5Parser.h | 23 +-- PS5USB.h | 9 +- controllerEnums.h | 1 + examples/Bluetooth/PS5BT/PS5BT.ino | 159 ++++++++++++++++ examples/PS5USB/PS5USB.ino | 4 +- keywords.txt | 3 + 11 files changed, 480 insertions(+), 28 deletions(-) create mode 100644 PS5BT.h create mode 100644 examples/Bluetooth/PS5BT/PS5BT.ino diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ecc50c75..5cc29556 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/BTD.cpp b/BTD.cpp index 32efeb86..6ada433a 100644 --- a/BTD.cpp +++ b/BTD.cpp @@ -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 (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; } diff --git a/BTD.h b/BTD.h index 5337dea9..a070cb58 100644 --- a/BTD.h +++ b/BTD.h @@ -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 */ diff --git a/PS5BT.h b/PS5BT.h new file mode 100644 index 00000000..e00f0351 --- /dev/null +++ b/PS5BT.h @@ -0,0 +1,282 @@ +/* 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" + +/*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 +}; + +uint32_t crc32(uint8_t *buffer, size_t length) { // Inspired by: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c and http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=28214 + uint32_t crc = ~0L; // Initial value + for (size_t i = 0; i < length; i++) + crc = (crc >> 8) ^ pgm_read_dword(&crc32_table[*buffer++ ^ (crc & 0xFF)]); + return ~crc; +};*/ + +/* + * 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 + */ +#if 0 +#define CRC32_POLY_LE 0xedb88320 +#define CRC32_POLY_BE 0x04c11db7 + +#define CRC_LE_BITS 1 + +typedef uint32_t u32; + +static inline u32 crc32_le_generic(u32 crc, unsigned char const *p, size_t len, const u32 (*tab)[256], u32 polynomial) +{ +#if CRC_LE_BITS == 1 + int i; + while (len--) { + crc ^= *p++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0); + } +# elif CRC_LE_BITS == 2 + while (len--) { + crc ^= *p++; + crc = (crc >> 2) ^ tab[0][crc & 3]; + crc = (crc >> 2) ^ tab[0][crc & 3]; + crc = (crc >> 2) ^ tab[0][crc & 3]; + crc = (crc >> 2) ^ tab[0][crc & 3]; + } +# elif CRC_LE_BITS == 4 + while (len--) { + crc ^= *p++; + crc = (crc >> 4) ^ tab[0][crc & 15]; + crc = (crc >> 4) ^ tab[0][crc & 15]; + } +# elif CRC_LE_BITS == 8 + /* aka Sarwate algorithm */ + while (len--) { + crc ^= *p++; + crc = (crc >> 8) ^ tab[0][crc & 255]; + } +# else + crc = (__force u32) __cpu_to_le32(crc); + crc = crc32_body(crc, p, len, tab); + crc = __le32_to_cpu((__force __le32)crc); +#endif + return crc; +} + +#if CRC_LE_BITS == 1 +static u32 crc32_le(u32 crc, unsigned char const *p, size_t len) +{ + return crc32_le_generic(crc, p, len, NULL, CRC32_POLY_LE); +} +#else +static u32 crc32_le(u32 crc, unsigned char const *p, size_t len) +{ + return crc32_le_generic(crc, p, len, (const u32 (*)[256])crc32table_le, CRC32_POLY_LE); +} +#endif + +#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) { + 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 + if (pFuncOnInit) + pFuncOnInit(); // Call the user function + else + setLed(Blue); + }; + + /** Used to reset the different buffers to there default values */ + virtual void ResetBTHID() { + PS5Parser::Reset(); + }; + /**@}*/ + + /** @name PS5Parser implementation */ + virtual void sendOutputReport(PS5Output *output) { +#if 1 + return; // TODO: Fix this +#else + // See the series of patches here: https://patchwork.kernel.org/project/linux-input/patch/20201219062336.72568-14-roderick@gaikai.com/ + + uint8_t buf[1 /* BT Set Output Report */ + 1 /* report id */ + 1 /* seq_tag */ + 1 /* tag */ + 47 /* common */ + 24 /* reserved */ + 4 /* crc32 */]; + memset(buf, 0, sizeof(buf)); + + buf[0] = 0x52; // HID BT Set_report (0x50) | Report Type (Output 0x02) + + buf[0x01] = 0x31; // Report ID + buf[0x02] = (output_sequence << 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 == 15) + output_sequence = 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(&buf[1], 79 - 1 /* do not include the BT Set Output Report */ - 4 /* crc */); + + uint8_t seed = 0xA2; + uint32_t crc = crc32_le(0xFFFFFFFF, &seed, 1); + crc = ~crc32_le(crc, &buf[1], 79 - 1 /* do not include the BT Set Output Report */ - 4 /* crc */); + + buf[75] = crc; + buf[76] = crc >> 8; + buf[77] = crc >> 16; + buf[78] = crc >> 24; + + 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 + + HID_Command(buf, sizeof(buf)); +#endif + }; + /**@}*/ + +private: + uint8_t output_sequence = 0; + + 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/patch/20201219062336.72568-14-roderick@gaikai.com/ + uint8_t buf[2]; + buf[0] = 0x43; // HID BT Get_report (0x40) | Report Type (Feature 0x03) + buf[1] = 9; // Report ID for paring info + + HID_Command(buf, 2); + }; + + void HID_Command(uint8_t *data, uint8_t nbytes) { + pBtd->L2CAP_Command(hci_handle, data, nbytes, control_scid[0], control_scid[1]); + }; +}; +#endif diff --git a/PS5Parser.cpp b/PS5Parser.cpp index b59d32c6..1a28e82e 100644 --- a/PS5Parser.cpp +++ b/PS5Parser.cpp @@ -15,7 +15,8 @@ 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. + 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/patch/20201219062336.72568-14-roderick@gaikai.com/ */ #include "PS5Parser.h" @@ -88,19 +89,21 @@ void PS5Parser::Parse(uint8_t len, uint8_t *buf) { 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) { + else if (buf[0] == 0x31) { // This report is send via Bluetooth, it has an offset of 2 compared to the USB data + if (len < 3) { #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), MFK_CASTUINT8T sizeof(ps5Data))); + 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 (buf[0], 0x80); + Notify(PSTR(", len: "), 0x80); + D_PrintHex (len, 0x80); #endif return; } diff --git a/PS5Parser.h b/PS5Parser.h index c299b700..3286d6df 100644 --- a/PS5Parser.h +++ b/PS5Parser.h @@ -15,7 +15,8 @@ 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. + 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/patch/20201219062336.72568-14-roderick@gaikai.com/ */ #ifndef _ps5parser_h_ @@ -32,7 +33,7 @@ const uint8_t PS5_BUTTONS[] PROGMEM = { DOWN, // DOWN LEFT, // LEFT - 0x0C, // SHARE + 0x0C, // CREATE 0x0D, // OPTIONS 0x0E, // L3 0x0F, // R3 @@ -64,7 +65,7 @@ union PS5Buttons { uint8_t r1 : 1; uint8_t l2 : 1; uint8_t r2 : 1; - uint8_t share : 1; + uint8_t create : 1; uint8_t menu : 1; uint8_t l3 : 1; uint8_t r3 : 1; @@ -96,7 +97,7 @@ union PS5Status { // second byte uint8_t mic : 1; - uint8_t dummy4 : 3; + uint8_t dummy3 : 3; } __attribute__((packed)); uint16_t val; } __attribute__((packed)); @@ -106,28 +107,28 @@ struct PS5Data { uint8_t hatValue[4]; // 0-3 bytes uint8_t trigger[2]; // 4-5 - uint8_t dummy; // 6 unknown + uint8_t sequence_number; // 6 PS5Buttons btn; // 7-9 - uint8_t dummy2[5]; // 0xA-0xD unknown + 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 dummy3[5]; // 0x1B - 0x1F unknown + uint8_t reserved2; // 0x20 - 0x23 touchpad point 1 // 0x24 - 0x27 touchpad point 2 ps5TouchpadXY xy; - uint8_t dummy4; //0x28 unknown + uint8_t reserved3; // 0x28 uint8_t rightTriggerFeedback; // 0x29 uint8_t leftTriggerFeedback; // 0x2A - - uint8_t dummy5[10]; // 0x2B - 0x34 unknown + uint8_t reserved4[10]; // 0x2B - 0x34 // status bytes 0x35-0x36 PS5Status status; @@ -260,7 +261,7 @@ public: * @return The battery level in the range 0-15. */ /*uint8_t getBatteryLevel() { - return ps5Data.status.battery; + return ps5Data.status.battery; // TODO: Where to read the battery level? };*/ /** diff --git a/PS5USB.h b/PS5USB.h index a7ce9dfc..b656f161 100644 --- a/PS5USB.h +++ b/PS5USB.h @@ -93,13 +93,16 @@ protected: 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]; + // , Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows + // and the series of patches found here: https://patchwork.kernel.org/project/linux-input/patch/20201219062336.72568-14-roderick@gaikai.com/ + uint8_t buf[1 /* report id */ + 47 /* common */]; memset(buf, 0, sizeof(buf)); - buf[0x00] = 0x02; // report type + 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 diff --git a/controllerEnums.h b/controllerEnums.h index 66963ee4..3b45d5ef 100644 --- a/controllerEnums.h +++ b/controllerEnums.h @@ -161,6 +161,7 @@ enum ButtonEnum { /**@{*/ /** PS5 buttons */ + CREATE = 4, MICROPHONE = 18, /**@}*/ }; diff --git a/examples/Bluetooth/PS5BT/PS5BT.ino b/examples/Bluetooth/PS5BT/PS5BT.ino new file mode 100644 index 00000000..39bf361a --- /dev/null +++ b/examples/Bluetooth/PS5BT/PS5BT.ino @@ -0,0 +1,159 @@ +/* + 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 +#include + +// Satisfy the IDE, which needs to see the include statment in the ino too. +#ifdef dobogusinclude +#include +#endif +#include + +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, 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 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 + PS5.leftTrigger.setTriggerForce(PS5.getAnalogButton(R2), 255); + + if (PS5.getButtonClick(PS)) { + Serial.print(F("\r\nPS")); + PS5.disconnect(); + } else { + 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")); + } + } + } + } + } +} diff --git a/examples/PS5USB/PS5USB.ino b/examples/PS5USB/PS5USB.ino index 3f008fc8..a28ab3fd 100644 --- a/examples/PS5USB/PS5USB.ino +++ b/examples/PS5USB/PS5USB.ino @@ -106,8 +106,8 @@ void loop() { if (PS5.getButtonClick(R3)) Serial.print(F("\r\nR3")); - if (PS5.getButtonClick(SHARE)) - Serial.print(F("\r\nShare")); + if (PS5.getButtonClick(CREATE)) + Serial.print(F("\r\nCreate")); if (PS5.getButtonClick(OPTIONS)) { Serial.print(F("\r\nOptions")); printAngle = !printAngle; diff --git a/keywords.txt b/keywords.txt index 0f3ca523..ac952691 100644 --- a/keywords.txt +++ b/keywords.txt @@ -36,6 +36,8 @@ PS3BT KEYWORD1 PS3USB KEYWORD1 PS4BT KEYWORD1 PS4USB KEYWORD1 +PS5BT KEYWORD1 +PS5USB KEYWORD1 #################################################### # Methods and Functions (KEYWORD2) @@ -135,6 +137,7 @@ SHARE LITERAL1 OPTIONS LITERAL1 TOUCHPAD LITERAL1 +CREATE LITERAL1 MICROPHONE LITERAL1 LeftHatX LITERAL1 From 9a0a4940b3155748603ee2b27716a7fec5304784 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Tue, 19 Jan 2021 10:57:14 +0100 Subject: [PATCH 3/5] Setting the LED, lightbar, rumble via Bluetooth is now working --- BTHID.cpp | 20 +++- PS5BT.h | 133 +++++++---------------- PS5Parser.cpp | 10 +- PS5Parser.h | 14 ++- PS5USB.h | 10 +- examples/Bluetooth/PS5BT/PS5BT.ino | 169 +++++++++++++++-------------- examples/PS5USB/PS5USB.ino | 10 +- 7 files changed, 173 insertions(+), 193 deletions(-) diff --git a/BTHID.cpp b/BTHID.cpp index 00b09085..d0d93c2e 100644 --- a/BTHID.cpp +++ b/BTHID.cpp @@ -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(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 (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 (l2capinbuf[8], 0x80); +#endif } } #ifdef EXTRADEBUG diff --git a/PS5BT.h b/PS5BT.h index e00f0351..dbb3c8a6 100644 --- a/PS5BT.h +++ b/PS5BT.h @@ -21,7 +21,12 @@ #include "BTHID.h" #include "PS5Parser.h" -/*const uint32_t crc32_table[] PROGMEM = { +/** + * 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, @@ -67,76 +72,35 @@ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; -uint32_t crc32(uint8_t *buffer, size_t length) { // Inspired by: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c and http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=28214 - uint32_t crc = ~0L; // Initial value - for (size_t i = 0; i < length; i++) - crc = (crc >> 8) ^ pgm_read_dword(&crc32_table[*buffer++ ^ (crc & 0xFF)]); - return ~crc; -};*/ - /* * 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 */ -#if 0 #define CRC32_POLY_LE 0xedb88320 -#define CRC32_POLY_BE 0x04c11db7 -#define CRC_LE_BITS 1 - -typedef uint32_t u32; - -static inline u32 crc32_le_generic(u32 crc, unsigned char const *p, size_t len, const u32 (*tab)[256], u32 polynomial) -{ -#if CRC_LE_BITS == 1 +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); } -# elif CRC_LE_BITS == 2 - while (len--) { - crc ^= *p++; - crc = (crc >> 2) ^ tab[0][crc & 3]; - crc = (crc >> 2) ^ tab[0][crc & 3]; - crc = (crc >> 2) ^ tab[0][crc & 3]; - crc = (crc >> 2) ^ tab[0][crc & 3]; - } -# elif CRC_LE_BITS == 4 - while (len--) { - crc ^= *p++; - crc = (crc >> 4) ^ tab[0][crc & 15]; - crc = (crc >> 4) ^ tab[0][crc & 15]; - } -# elif CRC_LE_BITS == 8 - /* aka Sarwate algorithm */ - while (len--) { - crc ^= *p++; - crc = (crc >> 8) ^ tab[0][crc & 255]; - } -# else - crc = (__force u32) __cpu_to_le32(crc); - crc = crc32_body(crc, p, len, tab); - crc = __le32_to_cpu((__force __le32)crc); -#endif return crc; } -#if CRC_LE_BITS == 1 -static u32 crc32_le(u32 crc, unsigned char const *p, size_t len) -{ - return crc32_le_generic(crc, p, len, NULL, CRC32_POLY_LE); -} -#else -static u32 crc32_le(u32 crc, unsigned char const *p, size_t len) -{ - return crc32_le_generic(crc, p, len, (const u32 (*)[256])crc32table_le, CRC32_POLY_LE); -} -#endif - +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. @@ -151,7 +115,7 @@ public: * @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) { + BTHID(p, pair, pin), output_sequence_counter(0) { PS5Parser::Reset(); }; @@ -182,10 +146,10 @@ protected: virtual void OnInitBTHID() { PS5Parser::Reset(); enable_sixaxis(); // Make the controller send out the entire output report - if (pFuncOnInit) - pFuncOnInit(); // Call the user function - else - setLed(Blue); + + // 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 */ @@ -196,20 +160,17 @@ protected: /** @name PS5Parser implementation */ virtual void sendOutputReport(PS5Output *output) { -#if 1 - return; // TODO: Fix this -#else - // See the series of patches here: https://patchwork.kernel.org/project/linux-input/patch/20201219062336.72568-14-roderick@gaikai.com/ - - uint8_t buf[1 /* BT Set Output Report */ + 1 /* report id */ + 1 /* seq_tag */ + 1 /* tag */ + 47 /* common */ + 24 /* reserved */ + 4 /* crc32 */]; + // 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)); - buf[0] = 0x52; // HID BT Set_report (0x50) | Report Type (Output 0x02) + // 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 << 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 == 15) - output_sequence = 0; + 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 @@ -243,40 +204,30 @@ protected: buf[0x2E + 3] = output->g; // Green buf[0x2F + 3] = output->b; // Blue - //uint32_t crc = crc32(&buf[1], 79 - 1 /* do not include the BT Set Output Report */ - 4 /* crc */); - - uint8_t seed = 0xA2; - uint32_t crc = crc32_le(0xFFFFFFFF, &seed, 1); - crc = ~crc32_le(crc, &buf[1], 79 - 1 /* do not include the BT Set Output Report */ - 4 /* crc */); - - buf[75] = crc; - buf[76] = crc >> 8; - buf[77] = crc >> 16; - buf[78] = crc >> 24; + 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; - // The PS5 console actually set the four last bytes to a CRC32 checksum, but it seems like it is actually not needed - - HID_Command(buf, sizeof(buf)); -#endif + // 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: - uint8_t output_sequence = 0; - 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/patch/20201219062336.72568-14-roderick@gaikai.com/ + // 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] = 9; // Report ID for paring info + buf[1] = 0x09; // Report ID for paring info - HID_Command(buf, 2); + // Send the Bluetooth Get_report Feature report on the control channel + pBtd->L2CAP_Command(hci_handle, buf, 2, control_scid[0], control_scid[1]); }; - void HID_Command(uint8_t *data, uint8_t nbytes) { - pBtd->L2CAP_Command(hci_handle, data, nbytes, control_scid[0], control_scid[1]); - }; + uint8_t output_sequence_counter; }; #endif diff --git a/PS5Parser.cpp b/PS5Parser.cpp index 1a28e82e..fa3576c2 100644 --- a/PS5Parser.cpp +++ b/PS5Parser.cpp @@ -16,7 +16,7 @@ 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/patch/20201219062336.72568-14-roderick@gaikai.com/ + and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/ */ #include "PS5Parser.h" @@ -80,7 +80,8 @@ uint8_t PS5Parser::getAnalogHat(AnalogHatEnum a) { void PS5Parser::Parse(uint8_t len, uint8_t *buf) { if (len > 1 && buf) { #ifdef PRINTREPORT - Notify(PSTR("\r\n"), 0x80); + Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80); + Notify(PSTR(", data: "), 0x80); for (uint8_t i = 0; i < len; i++) { D_PrintHex (buf[i], 0x80); Notify(PSTR(" "), 0x80); @@ -89,7 +90,7 @@ void PS5Parser::Parse(uint8_t len, uint8_t *buf) { 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 2 compared to the USB data + 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); @@ -127,8 +128,9 @@ void PS5Parser::Parse(uint8_t len, uint8_t *buf) { oldDpad = newDpad; } } + + message_counter++; } - message_counter++; if (ps5Output.reportChanged || leftTrigger.reportChanged || rightTrigger.reportChanged) sendOutputReport(&ps5Output); // Send output report diff --git a/PS5Parser.h b/PS5Parser.h index 3286d6df..df7eb3b2 100644 --- a/PS5Parser.h +++ b/PS5Parser.h @@ -16,7 +16,7 @@ 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/patch/20201219062336.72568-14-roderick@gaikai.com/ + and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/ */ #ifndef _ps5parser_h_ @@ -146,14 +146,14 @@ struct PS5Output { /** 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(); }; + /** 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. @@ -227,9 +227,9 @@ public: */ float getAngle(AngleEnum a) { if (a == Pitch) - return (atan2f(ps5Data.accY, ps5Data.accZ) + PI) * RAD_TO_DEG; + return (atan2f(-ps5Data.accY, -ps5Data.accZ) + PI) * RAD_TO_DEG; else - return (atan2f(ps5Data.accX, ps5Data.accZ) + PI) * RAD_TO_DEG; + return (atan2f(ps5Data.accX, -ps5Data.accZ) + PI) * RAD_TO_DEG; }; /** @@ -356,6 +356,7 @@ public: */ void setPlayerLed(uint8_t mask) { ps5Output.playerLeds = mask; + ps5Output.reportChanged = true; } /** Use to turn the microphone LED off. */ @@ -369,6 +370,7 @@ public: */ void setMicLed(bool on) { ps5Output.microphoneLed = on ? 1 : 0; + ps5Output.reportChanged = true; } /** Get the incoming message count. */ diff --git a/PS5USB.h b/PS5USB.h index b656f161..7c7ee793 100644 --- a/PS5USB.h +++ b/PS5USB.h @@ -83,7 +83,7 @@ protected: if (pFuncOnInit) pFuncOnInit(); // Call the user function else - setLed(Blue); + setLed(Red); // Set the LED to red, so it is consistent with the PS5BT driver }; return 0; }; @@ -92,9 +92,9 @@ protected: /** @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/patch/20201219062336.72568-14-roderick@gaikai.com/ + // 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)); @@ -133,7 +133,7 @@ protected: 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 + // 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); }; diff --git a/examples/Bluetooth/PS5BT/PS5BT.ino b/examples/Bluetooth/PS5BT/PS5BT.ino index 39bf361a..6d6fc0d4 100644 --- a/examples/Bluetooth/PS5BT/PS5BT.ino +++ b/examples/Bluetooth/PS5BT/PS5BT.ino @@ -1,7 +1,7 @@ /* 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 + send me an e-mail: lauszus@gmail.com */ #include @@ -25,10 +25,11 @@ 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, printTouch; +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); @@ -67,91 +68,101 @@ void loop() { } // Set the left trigger to resist at the right trigger's level - PS5.leftTrigger.setTriggerForce(PS5.getAnalogButton(R2), 255); + static uint8_t oldR2Value = 0xFF; + if (PS5.getAnalogButton(R2) != oldR2Value) { + oldR2Value = PS5.getAnalogButton(R2); + PS5.leftTrigger.setTriggerForce(oldR2Value, 255); + } - if (PS5.getButtonClick(PS)) { + // 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")); - PS5.disconnect(); - } else { - 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")); + 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(); - } + // 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(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(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 (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 (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")); - } + 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")); } } } diff --git a/examples/PS5USB/PS5USB.ino b/examples/PS5USB/PS5USB.ino index a28ab3fd..36582dad 100644 --- a/examples/PS5USB/PS5USB.ino +++ b/examples/PS5USB/PS5USB.ino @@ -1,7 +1,7 @@ /* 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 + send me an e-mail: lauszus@gmail.com */ #include @@ -15,7 +15,7 @@ USB Usb; PS5USB PS5(&Usb); -bool printAngle, printTouch; +bool printAngle = false, printTouch = false; uint16_t lastMessageCounter = -1; uint8_t player_led_mask = 0; bool microphone_led = false; @@ -57,7 +57,11 @@ void loop() { } // Set the left trigger to resist at the right trigger's level - PS5.leftTrigger.setTriggerForce(PS5.getAnalogButton(R2), 255); + 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")); From fdb0ee31e79debd2f807996a7554177212404aa0 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Tue, 19 Jan 2021 20:29:04 +0100 Subject: [PATCH 4/5] Do not try to parse the PS5 status byte for now --- PS5Parser.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PS5Parser.h b/PS5Parser.h index df7eb3b2..0b311f42 100644 --- a/PS5Parser.h +++ b/PS5Parser.h @@ -124,6 +124,7 @@ struct PS5Data { // 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 @@ -132,6 +133,7 @@ struct PS5Data { // status bytes 0x35-0x36 PS5Status status; +#endif } __attribute__((packed)); struct PS5Output { @@ -256,14 +258,17 @@ public: } }; +#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; // TODO: Where to read the battery level? - };*/ + 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. @@ -287,6 +292,7 @@ public: bool getMicStatus() { return ps5Data.status.mic; }; +#endif /** Turn both rumble and the LEDs off. */ void setAllOff() { From 534fdb30cecc8cd1db4cbbcbd1d96afc6967f7df Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Tue, 19 Jan 2021 20:40:06 +0100 Subject: [PATCH 5/5] Updated documentation, as the PS5 controller is now working via USB and Bluetooth --- README.md | 21 +++++++++++++++++++-- library.json | 2 +- library.properties | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61b921a8..968377d7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ For more information about the hardware see the [Hardware Manual](https://chome. * __Alexei Glushchenko__ - * Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries * __Kristian Sloth Lauszus__ - - * 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__ - * 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: . + +Also thanks to Ludwig Füchsl's 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. diff --git a/library.json b/library.json index 0c240db4..559b7682 100644 --- a/library.json +++ b/library.json @@ -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": [ diff --git a/library.properties b/library.properties index edd159ff..05a06ab9 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=1.4.0 author=Oleg Mazurov (Circuits@Home) , Kristian Sloth Lauszus , Andrew Kroll , Alexei Glushchenko (Circuits@Home) maintainer=Oleg Mazurov (Circuits@Home) , Kristian Sloth Lauszus , Andrew Kroll 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=*