From e9bd896ca2a8bc40bd3e6e628446d53a65e96837 Mon Sep 17 00:00:00 2001 From: Kristian Lauszus Date: Fri, 10 Jan 2014 17:44:51 +0100 Subject: [PATCH] Added support for the PS4 controller via Bluetooth --- BTD.cpp | 43 ++++--- BTD.h | 3 + BTHID.cpp | 4 +- PS4BT.cpp | 117 +++++++++++++++++++ PS4BT.h | 176 +++++++++++++++++++++++++++++ controllerEnums.h | 6 + examples/Bluetooth/PS4BT/PS4BT.ino | 98 ++++++++++++++++ keywords.txt | 7 +- 8 files changed, 438 insertions(+), 16 deletions(-) create mode 100644 PS4BT.cpp create mode 100644 PS4BT.h create mode 100644 examples/Bluetooth/PS4BT/PS4BT.ino diff --git a/BTD.cpp b/BTD.cpp index 7fc278da..b646b160 100755 --- a/BTD.cpp +++ b/BTD.cpp @@ -302,6 +302,7 @@ void BTD::Initialize() { incomingWii = false; connectToHIDDevice = false; incomingHIDDevice = false; + incomingPS4 = false; bAddress = 0; // Clear device address bNumEP = 1; // Must have to be reset to 1 qNextPollTime = 0; // Reset next poll time @@ -434,7 +435,6 @@ void BTD::HCI_event_task() { #endif for(uint8_t i = 0; i < hcibuf[2]; i++) { uint8_t offset = 8 * hcibuf[2] + 3 * i; - uint8_t classOfDevice[3]; for(uint8_t j = 0; j < 3; j++) classOfDevice[j] = hcibuf[j + 4 + offset]; @@ -450,12 +450,14 @@ void BTD::HCI_event_task() { hci_set_flag(HCI_FLAG_DEVICE_FOUND); break; - } else if(pairWithHIDDevice && (classOfDevice[1] & 0x05) && (classOfDevice[0] & 0xC0)) { // Check if it is a mouse or keyboard - see: http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html + } else if(pairWithHIDDevice && (classOfDevice[1] & 0x05) && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad - see: http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html #ifdef DEBUG_USB_HOST if(classOfDevice[0] & 0x80) Notify(PSTR("\r\nMouse found"), 0x80); if(classOfDevice[0] & 0x40) Notify(PSTR("\r\nKeyboard found"), 0x80); + if(classOfDevice[0] & 0x08) + Notify(PSTR("\r\nGamepad found"), 0x80); #endif for(uint8_t j = 0; j < 6; j++) @@ -516,23 +518,28 @@ void BTD::HCI_event_task() { for(uint8_t i = 0; i < 6; i++) disc_bdaddr[i] = hcibuf[i + 2]; - if((hcibuf[9] & 0x05) && (hcibuf[8] & 0xC0)) { // Check if it is a mouse or keyboard + for(uint8_t i = 0; i < 3; i++) + classOfDevice[i] = hcibuf[i + 8]; + + if((classOfDevice[1] & 0x05) && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad #ifdef DEBUG_USB_HOST - if(hcibuf[8] & 0x80) + if(classOfDevice[0] & 0x80) Notify(PSTR("\r\nMouse is connecting"), 0x80); - if(hcibuf[8] & 0x40) + if(classOfDevice[0] & 0x40) Notify(PSTR("\r\nKeyboard is connecting"), 0x80); + if(classOfDevice[0] & 0x08) + Notify(PSTR("\r\nGamepad is connecting"), 0x80); #endif incomingHIDDevice = true; } #ifdef EXTRADEBUG Notify(PSTR("\r\nClass of device: "), 0x80); - D_PrintHex (hcibuf[10], 0x80); + D_PrintHex (classOfDevice[2], 0x80); Notify(PSTR(" "), 0x80); - D_PrintHex (hcibuf[9], 0x80); + D_PrintHex (classOfDevice[1], 0x80); Notify(PSTR(" "), 0x80); - D_PrintHex (hcibuf[8], 0x80); + D_PrintHex (classOfDevice[0], 0x80); #endif hci_set_flag(HCI_FLAG_INCOMING_REQUEST); break; @@ -816,6 +823,12 @@ void BTD::HCI_task() { wiiUProController = false; } } + 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); +#endif + incomingPS4 = true; + } if(pairWithWii && motionPlusInside) hci_state = HCI_CONNECT_DEVICE_STATE; else { @@ -835,6 +848,9 @@ void BTD::HCI_task() { } D_PrintHex (disc_bdaddr[0], 0x80); #endif + if(incomingPS4) + connectToHIDDevice = true; // We should always connect to the PS4 controller + // Clear these flags for a new connection l2capConnectionClaimed = false; sdpConnectionClaimed = false; @@ -866,6 +882,7 @@ void BTD::HCI_task() { connectToWii = incomingWii = pairWithWii = false; connectToHIDDevice = incomingHIDDevice = pairWithHIDDevice = false; + incomingPS4 = false; hci_state = HCI_SCANNING_STATE; } @@ -967,7 +984,7 @@ void BTD::hci_accept_connection() { hcibuf[6] = disc_bdaddr[3]; hcibuf[7] = disc_bdaddr[4]; hcibuf[8] = disc_bdaddr[5]; - hcibuf[9] = 0x00; //switch role to master + hcibuf[9] = 0x00; // Switch role to master HCI_Command(hcibuf, 10); } @@ -983,10 +1000,10 @@ void BTD::hci_remote_name() { hcibuf[6] = disc_bdaddr[3]; hcibuf[7] = disc_bdaddr[4]; hcibuf[8] = disc_bdaddr[5]; - hcibuf[9] = 0x01; //Page Scan Repetition Mode - hcibuf[10] = 0x00; //Reserved - hcibuf[11] = 0x00; //Clock offset - low byte - hcibuf[12] = 0x00; //Clock offset - high byte + hcibuf[9] = 0x01; // Page Scan Repetition Mode + hcibuf[10] = 0x00; // Reserved + hcibuf[11] = 0x00; // Clock offset - low byte + hcibuf[12] = 0x00; // Clock offset - high byte HCI_Command(hcibuf, 13); } diff --git a/BTD.h b/BTD.h index bacd38c3..0fa6de70 100755 --- a/BTD.h +++ b/BTD.h @@ -571,6 +571,9 @@ private: uint8_t pollInterval; bool bPollEnable; + bool incomingPS4; // True if a PS4 controller is connecting + uint8_t classOfDevice[3]; // Class of device of last device + /* Variables used by high level HCI task */ uint8_t hci_state; //current state of bluetooth hci connection uint16_t hci_counter; // counter used for bluetooth hci reset loops diff --git a/BTHID.cpp b/BTHID.cpp index 3a84ed2d..d6accb58 100644 --- a/BTHID.cpp +++ b/BTHID.cpp @@ -192,14 +192,14 @@ void BTHID::ACLData(uint8_t* l2capinbuf) { case 0x01: // Keyboard events if(pRptParser[KEYBOARD_PARSER_ID]) { uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]); - pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast(this), 0, (uint8_t)length, &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance + pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance } break; case 0x02: // Mouse events if(pRptParser[MOUSE_PARSER_ID]) { uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]); - pRptParser[MOUSE_PARSER_ID]->Parse(reinterpret_cast(this), 0, (uint8_t)length, &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance + pRptParser[MOUSE_PARSER_ID]->Parse(reinterpret_cast(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance } break; #ifdef DEBUG_USB_HOST diff --git a/PS4BT.cpp b/PS4BT.cpp new file mode 100644 index 00000000..abab9cfc --- /dev/null +++ b/PS4BT.cpp @@ -0,0 +1,117 @@ +/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. All rights reserved. + + This software may be distributed and modified under the terms of the GNU + General Public License version 2 (GPL2) as published by the Free Software + Foundation and appearing in the file GPL2.TXT included in the packaging of + this file. Please note that GPL2 Section 2[b] requires that all works based + on this software must also be made publicly available under the terms of + the GPL2 ("Copyleft"). + + Contact information + ------------------- + + Kristian Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + */ + +#include "PS4BT.h" + +// To enable serial debugging see "settings.h" +//#define PRINTREPORT // Uncomment to print the report send by the PS4 Controller + +/** Buttons on the controller */ +const uint8_t PS4_BUTTONS[] PROGMEM = { + DPAD_UP, // UP + DPAD_RIGHT, // RIGHT + DPAD_DOWN, // DOWN + DPAD_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, // KEYPAD +}; + +/** Analog buttons on the controller */ +const uint8_t PS4_ANALOG_BUTTONS[] PROGMEM = { + 0, 0, 0, 0, 0, 0, 0, 0, // Skip UP_ANALOG, RIGHT_ANALOG, DOWN_ANALOG, LEFT_ANALOG, SELECT, L3, R3 and START + 0, // L2_ANALOG + 1, // R2_ANALOG +}; + +bool PS4BT::checkDpad(PS4Buttons ps4Buttons, DPADEnum b) { + return ps4Buttons.dpad == b; +} + +bool PS4BT::getButtonPress(ButtonEnum b) { + uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]); + if (b < 4) // Dpad + return checkDpad(ps4Data.btn, (DPADEnum)button); + else { + uint8_t index = button < 8 ? 0 : button < 16 ? 1 : 2; + uint8_t mask = (1 << (button - 8 * index)); + return ps4Data.btn.val[index] & mask; + } +} + +bool PS4BT::getButtonClick(ButtonEnum b) { + uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]); + if (b < 4) { // Dpad + if (checkDpad(buttonClickState, (DPADEnum)button)) { + buttonClickState.dpad = DPAD_OFF; + return true; + } + return false; + } else { + uint8_t index = button < 8 ? 0 : button < 16 ? 1 : 2; + uint8_t mask = (1 << (button - 8 * index)); + + bool click = buttonClickState.val[index] & mask; + buttonClickState.val[index] &= ~mask; // Clear "click" event + return click; + } +} + +uint8_t PS4BT::getAnalogButton(ButtonEnum a) { + return (uint8_t)(ps4Data.trigger[pgm_read_byte(&PS4_ANALOG_BUTTONS[(uint8_t)a])]); +} + +uint8_t PS4BT::getAnalogHat(AnalogHatEnum a) { + return ps4Data.hatValue[(uint8_t)a]; +} + +void PS4BT::Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) { + if (len == sizeof(PS4Data) && buf) { + memcpy(&ps4Data, buf, len); + + for (uint8_t i = 0; i < sizeof(ps4Data.btn); i++) { + if (ps4Data.btn.val[i] != oldButtonState.val[i]) { + buttonClickState.val[i] = ps4Data.btn.val[i] & ~oldButtonState.val[i]; // Update click state variable + oldButtonState.val[i] = ps4Data.btn.val[i]; + if (i == 0) + buttonClickState.dpad = ps4Data.btn.dpad; // The DPAD buttons does not set the different bits, but set a value corresponding to the buttons pressed + } + } +#ifdef PRINTREPORT + for (uint8_t i = 0; i < len; i++) { + D_PrintHex (buf[i], 0x80); + Notify(PSTR(" "), 0x80); + } + Notify(PSTR("\r\n"), 0x80); +#endif + } +} \ No newline at end of file diff --git a/PS4BT.h b/PS4BT.h new file mode 100644 index 00000000..207cec4f --- /dev/null +++ b/PS4BT.h @@ -0,0 +1,176 @@ +/* Copyright (C) 2014 Kristian Lauszus, TKJ Electronics. All rights reserved. + + This software may be distributed and modified under the terms of the GNU + General Public License version 2 (GPL2) as published by the Free Software + Foundation and appearing in the file GPL2.TXT included in the packaging of + this file. Please note that GPL2 Section 2[b] requires that all works based + on this software must also be made publicly available under the terms of + the GPL2 ("Copyleft"). + + Contact information + ------------------- + + Kristian Lauszus, TKJ Electronics + Web : http://www.tkjelectronics.com + e-mail : kristianl@tkjelectronics.com + */ + +#ifndef _ps4bt_h_ +#define _ps4bt_h_ + +#include "BTHID.h" +#include "PS3Enums.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, +}; + +union PS4Buttons { + 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 options : 1; + uint8_t l3 : 1; + uint8_t r3 : 1; + + uint8_t ps : 1; + uint8_t keypad : 1; + uint8_t dummy : 6; + }; + uint8_t val[3]; +}; + +struct PS4Data { + uint8_t hatValue[4]; + PS4Buttons btn; + uint8_t trigger[2]; +}; + +/** This BluetoothService class implements support for the PS4 controller via Bluetooth. */ +class PS4BT : public HIDReportParser { +public: + /** + * Constructor for the PS4BT class. + * @param p Pointer to the BTD class instance. + */ + PS4BT(BTHID *p) : + pBthid(p) { + pBthid->SetReportParser(KEYBOARD_PARSER_ID, this); + Reset(); + }; + + virtual void Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); + + /** @name PS4 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 PS4 Controller functions */ + /** + * Used to get the analog value from button presses. + * @param a The ::ButtonEnum to read. + * The supported buttons are: + * ::UP, ::RIGHT, ::DOWN, ::LEFT, ::L1, ::L2, ::R1, ::R2, + * ::TRIANGLE, ::CIRCLE, ::CROSS, ::SQUARE, and ::T. + * @return Analog value in the range of 0-255. + */ + uint8_t getAnalogButton(ButtonEnum a); + + /** + * 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); + /**@}*/ + + /** True if a device is connected */ + bool connected() { + if (pBthid) + return pBthid->connected; + return false; + }; + + /** Used this to disconnect the devices. */ + void disconnect() { + if (pBthid) + pBthid->disconnect(); + }; + + /** Call this to start the paring sequence with a device */ + void pair(void) { + if (pBthid) + pBthid->pair(); + }; + + void Reset() { + uint8_t i; + for (0; i < sizeof(ps4Data.hatValue); i++) + ps4Data.hatValue[i] = 127; + for (0; i < sizeof(PS4Buttons); i++) { + ps4Data.btn.val[i] = 0; + oldButtonState.val[i] = 0; + } + for (0; i < sizeof(ps4Data.trigger); i++) + ps4Data.trigger[i] = 0; + + ps4Data.btn.dpad = DPAD_OFF; + oldButtonState.dpad = DPAD_OFF; + buttonClickState.dpad = DPAD_OFF; + }; + + /** + * Used to call your own function when the device is successfully initialized. + * @param funcOnInit Function to call. + */ + void attachOnInit(void (*funcOnInit)(void)) { + pFuncOnInit = funcOnInit; + }; + +private: + /** + * 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. + */ + void onInit() { + Reset(); + if(pFuncOnInit) + pFuncOnInit(); // Call the user function + }; + void (*pFuncOnInit)(void); // Pointer to function called in onInit() + + bool checkDpad(PS4Buttons ps4Buttons, DPADEnum b); // Used to check PS4 DPAD buttons + + BTHID *pBthid; // Pointer to BTHID instance + PS4Data ps4Data; + PS4Buttons oldButtonState, buttonClickState; +}; +#endif \ No newline at end of file diff --git a/controllerEnums.h b/controllerEnums.h index b5129725..700e50a2 100644 --- a/controllerEnums.h +++ b/controllerEnums.h @@ -94,6 +94,12 @@ enum ButtonEnum { T = 18, // Covers 12 bits - we only need to read the top 8 /**@}*/ + /** PS4 controllers buttons - SHARE and OPTIONS are present instead of SELECT and START */ + SHARE = 4, + OPTIONS = 5, + KEYPAD = 17, + /**@}*/ + /**@{*/ /** Xbox buttons */ BACK = 4, diff --git a/examples/Bluetooth/PS4BT/PS4BT.ino b/examples/Bluetooth/PS4BT/PS4BT.ino new file mode 100644 index 00000000..220014e2 --- /dev/null +++ b/examples/Bluetooth/PS4BT/PS4BT.ino @@ -0,0 +1,98 @@ +/* + Example sketch for the PS4 Bluetooth library - developed by Kristian Lauszus + For more information visit my blog: http://blog.tkjelectronics.dk/ or + send me an e-mail: kristianl@tkjelectronics.com + */ + +#include +#include + +// Satisfy IDE, which only needs to see the include statment in the ino. +#ifdef dobogusinclude +#include +#endif + +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 BTHID class in two ways */ +// This will start an inquiry and then pair with the PS4 controller - you only have to do this once +BTHID bthid(&Btd, PAIR); + +// After that you can simply create the instance like so and then press the PS button on the device +//BTHID bthid(&Btd); + +PS4BT PS4(&bthid); // You should not modify this instance + +void setup() { + Serial.begin(115200); + while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection + if (Usb.Init() == -1) { + Serial.print(F("\r\nOSC did not start")); + while (1); // Halt + } + Serial.print(F("\r\nPS4 Bluetooth Library Started")); +} +void loop() { + Usb.Task(); + + if (PS4.connected()) { + if (PS4.getAnalogHat(LeftHatX) > 137 || PS4.getAnalogHat(LeftHatX) < 117 || PS4.getAnalogHat(LeftHatY) > 137 || PS4.getAnalogHat(LeftHatY) < 117 || PS4.getAnalogHat(RightHatX) > 137 || PS4.getAnalogHat(RightHatX) < 117 || PS4.getAnalogHat(RightHatY) > 137 || PS4.getAnalogHat(RightHatY) < 117) { + Serial.print(F("\r\nLeftHatX: ")); + Serial.print(PS4.getAnalogHat(LeftHatX)); + Serial.print(F("\tLeftHatY: ")); + Serial.print(PS4.getAnalogHat(LeftHatY)); + Serial.print(F("\tRightHatX: ")); + Serial.print(PS4.getAnalogHat(RightHatX)); + Serial.print(F("\tRightHatY: ")); + Serial.print(PS4.getAnalogHat(RightHatY)); + } + + if (PS4.getAnalogButton(L2) || PS4.getAnalogButton(R2)) { // These are the only analog buttons on the PS4 controller + Serial.print(F("\r\nL2: ")); + Serial.print(PS4.getAnalogButton(L2)); + Serial.print(F("\tR2: ")); + Serial.print(PS4.getAnalogButton(R2)); + } + if (PS4.getButtonClick(PS)) { + Serial.print(F("\r\nPS")); + PS4.disconnect(); + } + else { + if (PS4.getButtonClick(TRIANGLE)) + Serial.print(F("\r\nTraingle")); + if (PS4.getButtonClick(CIRCLE)) + Serial.print(F("\r\nCircle")); + if (PS4.getButtonClick(CROSS)) + Serial.print(F("\r\nCross")); + if (PS4.getButtonClick(SQUARE)) + Serial.print(F("\r\nSquare")); + + if (PS4.getButtonClick(UP)) + Serial.print(F("\r\nUp")); + if (PS4.getButtonClick(RIGHT)) + Serial.print(F("\r\nRight")); + if (PS4.getButtonClick(DOWN)) + Serial.print(F("\r\nDown")); + if (PS4.getButtonClick(LEFT)) + Serial.print(F("\r\nLeft")); + + if (PS4.getButtonClick(L1)) + Serial.print(F("\r\nL1")); + if (PS4.getButtonClick(L3)) + Serial.print(F("\r\nL3")); + if (PS4.getButtonClick(R1)) + Serial.print(F("\r\nR1")); + if (PS4.getButtonClick(R3)) + Serial.print(F("\r\nR3")); + + if (PS4.getButtonClick(SHARE)) + Serial.print(F("\r\nShare")); + if (PS4.getButtonClick(OPTIONS)) + Serial.print(F("\r\nOptions")); + if (PS4.getButtonClick(KEYPAD)) + Serial.print(F("\r\nKeypad")); + } + } +} diff --git a/keywords.txt b/keywords.txt index 1c987a4e..4f3cd1a9 100644 --- a/keywords.txt +++ b/keywords.txt @@ -25,13 +25,14 @@ BTD KEYWORD1 Task KEYWORD2 #################################################### -# Syntax Coloring Map For PS3 Bluetooth/USB Library +# Syntax Coloring Map For PS3/PS4 Bluetooth/USB Library #################################################### #################################################### # Datatypes (KEYWORD1) #################################################### +PS4BT KEYWORD1 PS3BT KEYWORD1 PS3USB KEYWORD1 @@ -118,6 +119,10 @@ PS LITERAL1 MOVE LITERAL1 T LITERAL1 +SHARE LITERAL1 +OPTIONS LITERAL1 +KEYPAD LITERAL1 + LeftHatX LITERAL1 LeftHatY LITERAL1 RightHatX LITERAL1