Added support for the PS4 controller via Bluetooth

This commit is contained in:
Kristian Lauszus 2014-01-10 17:44:51 +01:00
parent 1f33f2bd08
commit e9bd896ca2
8 changed files with 438 additions and 16 deletions

35
BTD.cpp
View file

@ -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<uint8_t > (hcibuf[10], 0x80);
D_PrintHex<uint8_t > (classOfDevice[2], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (hcibuf[9], 0x80);
D_PrintHex<uint8_t > (classOfDevice[1], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (hcibuf[8], 0x80);
D_PrintHex<uint8_t > (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<uint8_t > (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);
}

3
BTD.h
View file

@ -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

View file

@ -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<HID *>(this), 0, (uint8_t)length, &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance
pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast<HID *>(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<HID *>(this), 0, (uint8_t)length, &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance
pRptParser[MOUSE_PARSER_ID]->Parse(reinterpret_cast<HID *>(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance
}
break;
#ifdef DEBUG_USB_HOST

117
PS4BT.cpp Normal file
View file

@ -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<uint8_t > (buf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
Notify(PSTR("\r\n"), 0x80);
#endif
}
}

176
PS4BT.h Normal file
View file

@ -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

View file

@ -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,

View file

@ -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 <PS4BT.h>
#include <usbhub.h>
// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#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"));
}
}
}

View file

@ -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