Added support for the PS4 controller via USB

Also improved the PS4BT library
This commit is contained in:
Kristian Lauszus 2014-01-18 22:36:01 +01:00
parent 0c05413447
commit da2ee95445
14 changed files with 573 additions and 249 deletions

View file

@ -46,11 +46,7 @@ void BTHID::Reset() {
activeConnection = false; activeConnection = false;
l2cap_event_flag = 0; // Reset flags l2cap_event_flag = 0; // Reset flags
l2cap_state = L2CAP_WAIT; l2cap_state = L2CAP_WAIT;
ResetBTHID();
for(uint8_t i = 0; i < BTHID_NUM_SERVICES; i++) {
if(bthidService[i])
bthidService[i]->Reset();
}
} }
void BTHID::disconnect() { // Use this void to disconnect the device void BTHID::disconnect() { // Use this void to disconnect the device
@ -193,19 +189,18 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
} }
#endif #endif
if(l2capinbuf[8] == 0xA1) { // HID_THDR_DATA_INPUT if(l2capinbuf[8] == 0xA1) { // HID_THDR_DATA_INPUT
uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]);
ParseBTHID(this, (uint8_t)(length - 1), &l2capinbuf[9]);
switch(l2capinbuf[9]) { switch(l2capinbuf[9]) {
case 0x01: // Keyboard events case 0x01: // Keyboard or Joystick events
if(pRptParser[KEYBOARD_PARSER_ID]) { 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 - 2), &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; break;
case 0x02: // Mouse events case 0x02: // Mouse events
if(pRptParser[MOUSE_PARSER_ID]) { 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 - 2), &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; break;
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
default: default:

44
BTHID.h
View file

@ -25,14 +25,6 @@
#define MOUSE_PARSER_ID 1 #define MOUSE_PARSER_ID 1
#define NUM_PARSERS 2 #define NUM_PARSERS 2
#define BTHID_NUM_SERVICES 4 // Max number of Bluetooth HID services - if you need more than 4 simply increase this number
class BTHIDService {
public:
virtual void onInit();
virtual void Reset();
};
/** This BluetoothService class implements support for the HID keyboard and mice. */ /** This BluetoothService class implements support for the HID keyboard and mice. */
class BTHID : public BluetoothService { class BTHID : public BluetoothService {
public: public:
@ -56,7 +48,6 @@ public:
virtual void Reset(); virtual void Reset();
/** Used this to disconnect the devices. */ /** Used this to disconnect the devices. */
virtual void disconnect(); virtual void disconnect();
/**@}*/ /**@}*/
/** /**
@ -114,25 +105,30 @@ public:
pFuncOnInit = funcOnInit; pFuncOnInit = funcOnInit;
}; };
protected:
/** @name Overridable functions */
/** /**
* Register Bluetooth HID services. * Used to parse Bluetooth HID data to any class that inherits this class.
* @param pService Pointer to BTHIDService class instance. * @param bthid Pointer to this class.
* @return The service ID on success or -1 on fail. * @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/ */
int8_t registerServiceClass(BTHIDService *pService) { virtual void ParseBTHID(BTHID *bthid, uint8_t len, uint8_t *buf) {
for(uint8_t i = 0; i < BTHID_NUM_SERVICES; i++) { return;
if(!bthidService[i]) {
bthidService[i] = pService;
return i; // Return ID
}
}
return -1; // ErrorregisterServiceClass
}; };
/** Called when a device is connected */
virtual void OnInitBTHID() {
return;
};
/** Used to reset any buffers in the class that inherits this */
virtual void ResetBTHID() {
return;
}
/**@}*/
private: private:
BTD *pBtd; // Pointer to BTD instance BTD *pBtd; // Pointer to BTD instance
HIDReportParser *pRptParser[NUM_PARSERS]; // Pointer to HIDReportParsers. HIDReportParser *pRptParser[NUM_PARSERS]; // Pointer to HIDReportParsers.
BTHIDService *bthidService[BTHID_NUM_SERVICES];
/** Set report protocol. */ /** Set report protocol. */
void setProtocol(); void setProtocol();
@ -146,11 +142,7 @@ private:
void onInit() { void onInit() {
if(pFuncOnInit) if(pFuncOnInit)
pFuncOnInit(); // Call the user function pFuncOnInit(); // Call the user function
OnInitBTHID();
for(uint8_t i = 0; i < BTHID_NUM_SERVICES; i++) {
if(bthidService[i])
bthidService[i]->onInit();
}
}; };
void (*pFuncOnInit)(void); // Pointer to function called in onInit() void (*pFuncOnInit)(void); // Pointer to function called in onInit()

View file

@ -174,12 +174,6 @@ enum SensorEnum {
mYmove = 50, mYmove = 50,
}; };
/** Used to get the angle calculated using the accelerometer. */
enum AngleEnum {
Pitch = 0x01,
Roll = 0x02,
};
enum StatusEnum { enum StatusEnum {
// Note that the location is shifted 9 when it's connected via USB // Note that the location is shifted 9 when it's connected via USB
// Byte location | bit location // Byte location | bit location

155
PS4BT.h
View file

@ -19,119 +19,53 @@
#define _ps4bt_h_ #define _ps4bt_h_
#include "BTHID.h" #include "BTHID.h"
#include "controllerEnums.h" #include "PS4Parser.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 touchpad : 1;
uint8_t dummy : 6;
};
uint8_t val[3];
};
struct PS4Data {
uint8_t hatValue[4];
PS4Buttons btn;
uint8_t trigger[2];
};
/** /**
* This class implements support for the PS4 controller via Bluetooth. * This class implements support for the PS4 controller via Bluetooth.
* It uses the BTHID class for all the Bluetooth communication. * It uses the BTHID class for all the Bluetooth communication.
*/ */
class PS4BT : public HIDReportParser, public BTHIDService { class PS4BT : public BTHID, public PS4Parser {
public: public:
/** /**
* Constructor for the PS4BT class. * Constructor for the PS4BT class.
* @param p Pointer to the BTHID class instance. * @param p Pointer to the BTHID class instance.
*/ */
PS4BT(BTHID *p) : PS4BT(BTD *p, bool pair = false, const char *pin = "0000") :
pBthid(p) { BTHID(p, pair, pin) {
pBthid->SetReportParser(KEYBOARD_PARSER_ID, this); PS4Parser::Reset();
pBthid->registerServiceClass(this); // Register it as a Bluetooth HID service
Reset();
}; };
virtual void Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); /** @name BTHID implementation */
/** @name PS4 Controller functions */
/** /**
* getButtonPress(ButtonEnum b) will return true as long as the button is held down. * Used to parse Bluetooth HID data.
* * @param bthid Pointer to the BTHID class.
* While getButtonClick(ButtonEnum b) will only return it once. * @param len The length of the incoming data.
* * @param buf Pointer to the data buffer.
* 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); virtual void ParseBTHID(BTHID *bthid, uint8_t len, uint8_t *buf) {
bool getButtonClick(ButtonEnum b); PS4Parser::Parse(len, buf);
/**@}*/ };
/** @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. * Called when a device is successfully initialized.
* @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY. * Use attachOnInit(void (*funcOnInit)(void)) to call your own function.
* @return Return the analog value in the range of 0-255. * This is useful for instance if you want to set the LEDs in a specific way.
*/ */
uint8_t getAnalogHat(AnalogHatEnum a); virtual void OnInitBTHID() {
if (pFuncOnInit)
pFuncOnInit(); // Call the user function
};
/** Used to reset the different buffers to there default values */
virtual void ResetBTHID() {
PS4Parser::Reset();
};
/**@}*/ /**@}*/
/** True if a device is connected */ /** True if a device is connected */
bool connected() { bool connected() {
if (pBthid) BTHID::connected;
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();
}; };
/** /**
@ -142,44 +76,7 @@ public:
pFuncOnInit = funcOnInit; pFuncOnInit = funcOnInit;
}; };
/** @name BTHIDService implementation */
/** Used to reset the different buffers to there default values */
virtual 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 = 0;
oldDpad = 0;
};
/**
* 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 onInit() {
if (pFuncOnInit)
pFuncOnInit(); // Call the user function
};
/**@}*/
private: private:
void (*pFuncOnInit)(void); // Pointer to function called in onInit() void (*pFuncOnInit)(void); // Pointer to function called in onInit()
bool checkDpad(DPADEnum b); // Used to check PS4 DPAD buttons
BTHID *pBthid; // Pointer to BTHID instance
PS4Data ps4Data;
PS4Buttons oldButtonState, buttonClickState;
uint8_t oldDpad;
}; };
#endif #endif

View file

@ -15,78 +15,45 @@
e-mail : kristianl@tkjelectronics.com e-mail : kristianl@tkjelectronics.com
*/ */
#include "PS4BT.h" #include "PS4Parser.h"
// To enable serial debugging see "settings.h" // To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the PS4 Controller //#define PRINTREPORT // Uncomment to print the report send by the PS4 Controller
/** Buttons on the controller */ bool PS4Parser::checkDpad(ButtonEnum b) {
const uint8_t PS4_BUTTONS[] PROGMEM = { switch (b) {
DPAD_UP, // UP case UP:
DPAD_RIGHT, // RIGHT return ps4Data.btn.dpad == DPAD_LEFT_UP || ps4Data.btn.dpad == DPAD_UP || ps4Data.btn.dpad == DPAD_UP_RIGHT;
DPAD_DOWN, // DOWN case RIGHT:
DPAD_LEFT, // LEFT return ps4Data.btn.dpad == DPAD_UP_RIGHT || ps4Data.btn.dpad == DPAD_RIGHT || ps4Data.btn.dpad == DPAD_RIGHT_DOWN;
case DOWN:
0x0C, // SHARE return ps4Data.btn.dpad == DPAD_RIGHT_DOWN || ps4Data.btn.dpad == DPAD_DOWN || ps4Data.btn.dpad == DPAD_DOWN_LEFT;
0x0D, // OPTIONS case LEFT:
0x0E, // L3 return ps4Data.btn.dpad == DPAD_DOWN_LEFT || ps4Data.btn.dpad == DPAD_LEFT || ps4Data.btn.dpad == DPAD_LEFT_UP;
0x0F, // R3 default:
return false;
0x0A, // L2 }
0x0B, // R2
0x08, // L1
0x09, // R1
0x07, // TRIANGLE
0x06, // CIRCLE
0x05, // CROSS
0x04, // SQUARE
0x10, // PS
0x11, // TOUCHPAD
};
/** Analog buttons on the controller */
const uint8_t PS4_ANALOG_BUTTONS[] PROGMEM = {
0, 0, 0, 0, 0, 0, 0, 0, // Skip UP, RIGHT, DOWN, LEFT, SHARE, OPTIONS, L3, and R3
0, // L2
1, // R2
};
bool PS4BT::checkDpad(DPADEnum b) {
switch (b) {
case DPAD_UP:
return ps4Data.btn.dpad == DPAD_LEFT_UP || ps4Data.btn.dpad == DPAD_UP || ps4Data.btn.dpad == DPAD_UP_RIGHT;
case DPAD_RIGHT:
return ps4Data.btn.dpad == DPAD_UP_RIGHT || ps4Data.btn.dpad == DPAD_RIGHT || ps4Data.btn.dpad == DPAD_RIGHT_DOWN;
case DPAD_DOWN:
return ps4Data.btn.dpad == DPAD_RIGHT_DOWN || ps4Data.btn.dpad == DPAD_DOWN || ps4Data.btn.dpad == DPAD_DOWN_LEFT;
case DPAD_LEFT:
return ps4Data.btn.dpad == DPAD_DOWN_LEFT || ps4Data.btn.dpad == DPAD_LEFT || ps4Data.btn.dpad == DPAD_LEFT_UP;
default:
return false;
}
} }
bool PS4BT::getButtonPress(ButtonEnum b) { bool PS4Parser::getButtonPress(ButtonEnum b) {
uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]);
if (b <= LEFT) // Dpad if (b <= LEFT) // Dpad
return checkDpad((DPADEnum)button); return checkDpad(b);
else { else {
uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]);
uint8_t index = button < 8 ? 0 : button < 16 ? 1 : 2; uint8_t index = button < 8 ? 0 : button < 16 ? 1 : 2;
uint8_t mask = (1 << (button - 8 * index)); uint8_t mask = 1 << (button - 8 * index);
return ps4Data.btn.val[index] & mask; return ps4Data.btn.val[index] & mask;
} }
} }
bool PS4BT::getButtonClick(ButtonEnum b) { bool PS4Parser::getButtonClick(ButtonEnum b) {
uint8_t mask, index = 0; uint8_t mask, index = 0;
if (b <= LEFT) // Dpad if (b <= LEFT) // Dpad
mask = 1 << b; mask = 1 << b;
else { else {
uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]); uint8_t button = pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]);
index = button < 8 ? 0 : button < 16 ? 1 : 2; index = button < 8 ? 0 : button < 16 ? 1 : 2;
mask = (1 << (button - 8 * index)); mask = 1 << (button - 8 * index);
} }
bool click = buttonClickState.val[index] & mask; bool click = buttonClickState.val[index] & mask;
@ -94,17 +61,21 @@ bool PS4BT::getButtonClick(ButtonEnum b) {
return click; return click;
} }
uint8_t PS4BT::getAnalogButton(ButtonEnum a) { uint8_t PS4Parser::getAnalogButton(ButtonEnum a) {
return ps4Data.trigger[pgm_read_byte(&PS4_ANALOG_BUTTONS[(uint8_t)a])]; if (a == L2) // These are the only analog buttons on the controller
return ps4Data.trigger[0];
else if (a == R2)
return ps4Data.trigger[1];
return 0;
} }
uint8_t PS4BT::getAnalogHat(AnalogHatEnum a) { uint8_t PS4Parser::getAnalogHat(AnalogHatEnum a) {
return ps4Data.hatValue[(uint8_t)a]; return ps4Data.hatValue[(uint8_t)a];
} }
void PS4BT::Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) { void PS4Parser::Parse(uint8_t len, uint8_t *buf) {
if (len == sizeof(PS4Data) && buf) { if (len > 0 && buf) {
memcpy(&ps4Data, buf, len); memcpy(&ps4Data, buf, min(len, sizeof(ps4Data)));
for (uint8_t i = 0; i < sizeof(ps4Data.btn); i++) { for (uint8_t i = 0; i < sizeof(ps4Data.btn); i++) {
if (ps4Data.btn.val[i] != oldButtonState.val[i]) { // Check if anything has changed if (ps4Data.btn.val[i] != oldButtonState.val[i]) { // Check if anything has changed
@ -112,13 +83,13 @@ void PS4BT::Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
oldButtonState.val[i] = ps4Data.btn.val[i]; oldButtonState.val[i] = ps4Data.btn.val[i];
if (i == 0) { // 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 if (i == 0) { // 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; uint8_t newDpad = 0;
if (checkDpad(DPAD_UP)) if (checkDpad(UP))
newDpad |= 1 << UP; newDpad |= 1 << UP;
if (checkDpad(DPAD_RIGHT)) if (checkDpad(RIGHT))
newDpad |= 1 << RIGHT; newDpad |= 1 << RIGHT;
if (checkDpad(DPAD_DOWN)) if (checkDpad(DOWN))
newDpad |= 1 << DOWN; newDpad |= 1 << DOWN;
if (checkDpad(DPAD_LEFT)) if (checkDpad(LEFT))
newDpad |= 1 << LEFT; newDpad |= 1 << LEFT;
if (newDpad != oldDpad) { if (newDpad != oldDpad) {
buttonClickState.dpad = newDpad & ~oldDpad; // Override values buttonClickState.dpad = newDpad & ~oldDpad; // Override values

246
PS4Parser.h Normal file
View file

@ -0,0 +1,246 @@
/* 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 _ps4parser_h_
#define _ps4parser_h_
#include "hid.h"
#include "controllerEnums.h"
/** Buttons on the controller */
const uint8_t PS4_BUTTONS[] PROGMEM = {
UP, // UP
RIGHT, // RIGHT
DOWN, // DOWN
LEFT, // LEFT
0x0C, // SHARE
0x0D, // OPTIONS
0x0E, // L3
0x0F, // R3
0x0A, // L2
0x0B, // R2
0x08, // L1
0x09, // R1
0x07, // TRIANGLE
0x06, // CIRCLE
0x05, // CROSS
0x04, // SQUARE
0x10, // PS
0x11, // TOUCHPAD
};
union 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 touchpad : 1;
uint8_t timestamp : 6; // Only available via USB
};
uint8_t val[3];
};
struct touchpadXY {
uint8_t dummy; // I can not figure out what this data is for, it seems to change randomly, maybe a timestamp?
struct {
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
};
struct {
uint16_t x : 12;
uint16_t y : 12;
};
} finger[2]; // 0 = first finger, 1 = second finger
};
struct PS4Data {
/* Button and joystick values */
uint8_t report_id; // Always 0x01
uint8_t hatValue[4];
PS4Buttons btn;
uint8_t trigger[2];
// I still need to figure out how to make the PS4 controller send out the rest of the data via Bluetooth
/* Gyro and accelerometer values */
uint8_t dummy[3]; // First two looks random, while the third one might be some kind of status
int16_t gyroY, gyroZ, gyroX;
int16_t accX, accZ, accY;
/* The rest is data for the touchpad */
uint8_t dummy2[9]; // Byte 5 looks like some kind of status (maybe battery status), bit 1 of byte 9 is set every time a finger is moving around the touchpad
touchpadXY xy[3]; // It looks like it sends out three coordinates each time, this is possible because the microcontroller inside the PS4 controller is much faster than the Bluetooth connection.
// The last data is read from the last position in the array while the oldest measurement is from the first position.
// The first position will also keep it's value after the finger is released, while the other two will set them to zero.
// Note that if you read fast enough from the device, then only the first one will contain any data.
// The last three bytes are always: 0x00, 0x80, 0x00
};
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,
};
enum PS4SensorEnum {
gX, gY, gZ, /** Gyro values */
aX, aY, aZ, /** Accelerometer values */
};
/** This class parses all the data sent by the PS4 controller */
class PS4Parser {
public:
/** Constructor for the PS4Parser class. */
PS4Parser() {
Reset();
};
/** @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);
/**@}*/
/** @name Only available via USB at the moment */
uint16_t getX(uint8_t finger = 0, uint8_t xyId = 0) {
return ps4Data.xy[xyId].finger[finger].x;
};
uint16_t getY(uint8_t finger = 0, uint8_t xyId = 0) {
return ps4Data.xy[xyId].finger[finger].y;
};
uint8_t isTouching(uint8_t finger = 0, uint8_t xyId = 0) {
return !(ps4Data.xy[xyId].finger[finger].touching); // The bit is cleared every time when a finger is touching the touchpad
};
uint8_t getTouchCounter(uint8_t finger = 0, uint8_t xyId = 0) {
return ps4Data.xy[xyId].finger[finger].counter;
};
double getAngle(AngleEnum a) {
if(a == Pitch)
return (atan2(ps4Data.accY, ps4Data.accZ) + PI) * RAD_TO_DEG;
else
return (atan2(ps4Data.accX, ps4Data.accZ) + PI) * RAD_TO_DEG;
};
int16_t getSensor(PS4SensorEnum a) {
switch(a) {
case gX:
return ps4Data.gyroX;
case gY:
return ps4Data.gyroY;
case gZ:
return ps4Data.gyroZ;
case aX:
return ps4Data.accX;
case aY:
return ps4Data.accY;
case aZ:
return ps4Data.accZ;
default:
return 0;
}
};
/**@}*/
/** Used to reset the different buffers to their default values */
void Reset() {
uint8_t i;
for (i = 0; i < sizeof(ps4Data.hatValue); i++)
ps4Data.hatValue[i] = 127;
for (i = 0; i < sizeof(PS4Buttons); i++) {
ps4Data.btn.val[i] = 0;
oldButtonState.val[i] = 0;
}
for (i = 0; i < sizeof(ps4Data.trigger); i++)
ps4Data.trigger[i] = 0;
for (i = 0; i < sizeof(ps4Data.xy)/sizeof(ps4Data.xy[0]); i++) {
for (uint8_t j = 0; j < sizeof(ps4Data.xy[0].finger)/sizeof(ps4Data.xy[0].finger[0]); j++)
ps4Data.xy[i].finger[j].touching = 1; // The bit is cleared if the finger is touching the touchpad
}
ps4Data.btn.dpad = DPAD_OFF;
oldButtonState.dpad = DPAD_OFF;
buttonClickState.dpad = 0;
oldDpad = 0;
};
protected:
void Parse(uint8_t len, uint8_t *buf);
private:
bool checkDpad(ButtonEnum b); // Used to check PS4 DPAD buttons
PS4Data ps4Data;
PS4Buttons oldButtonState, buttonClickState;
uint8_t oldDpad;
};
#endif

87
PS4USB.h Normal file
View file

@ -0,0 +1,87 @@
/* 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 _ps4usb_h_
#define _ps4usb_h_
#include "hiduniversal.h"
#include "PS4Parser.h"
#define PS4_VID 0x054C // Sony Corporation
#define PS4_PID 0x05C4 // PS4 Controller
/**
* This class implements support for the PS4 controller via USB.
* It uses the HIDUniversal class for all the USB communication.
*/
class PS4USB : public HIDUniversal, public PS4Parser {
public:
/**
* Constructor for the PS4USB class.
* @param p Pointer to the HIDUniversal class instance.
*/
PS4USB(USB *p) :
HIDUniversal(p) {
PS4Parser::Reset();
};
/** @name HIDUniversal implementation */
/**
* Used to parse USB HID data.
* @param hid Pointer to the HID class.
* @param is_rpt_id Only used for Hubs.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseHIDData(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
if (HIDUniversal::VID == PS4_VID && HIDUniversal::PID == PS4_PID)
PS4Parser::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() {
PS4Parser::Reset();
if (HIDUniversal::VID == PS4_VID && HIDUniversal::PID == PS4_PID && pFuncOnInit)
pFuncOnInit(); // Call the user function
return 0;
};
/**@}*/
/**
* Used to check if a PS4 controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return HIDUniversal::isReady() && HIDUniversal::VID == PS4_VID && HIDUniversal::PID == PS4_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;
};
private:
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
};
#endif

View file

@ -116,15 +116,17 @@ It enables me to see the Bluetooth communication between my Mac and any device.
### PS4 Library ### PS4 Library
This is the [PS4BT](PS4BT.cpp) library. It works with the official Sony PS4 controller via Bluetooth. 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 [PS4BT.ino](examples/Bluetooth/PS4BT/PS4BT.ino) example shows how to easily read the buttons and joysticks on the controller. The [PS4BT.ino](examples/Bluetooth/PS4BT/PS4BT.ino) and [PS4USB.ino](examples/PS4USB/PS4USB.ino) examples shows how to easily read the buttons and joysticks on the controller via Bluetooth and USB respectively.
I still have not figured out how to read the touchpad, turn rumble on and off and set the color of the light, but hopefully I will figure that out soon. I still have not figured out how to turn rumble on and off and set the color of the light, but hopefully I will figure that out soon.
Before you can use the PS4 controller you will need to pair with it. Also the gyro, accelerometer and touchpad values are still only available via USB at the moment.
Simply create the BTHID instance like so: ```BTHID bthid(&Btd, PAIR);``` and then hold down the PS and Share button at the same time, the PS4 controller will then start to blink rapidly indicating that it is in paring mode. Before you can use the PS4 controller via Bluetooth you will need to pair with it.
Simply create the PS4BT instance like so: ```PS4BT PS4(&Btd, PAIR);``` and then hold down the PS and Share button at the same time, the PS4 controller will then start to blink rapidly indicating that it is in paring mode.
It should then automatically pair the dongle with your controller. This only have to be done once. It should then automatically pair the dongle with your controller. This only have to be done once.

View file

@ -124,4 +124,10 @@ enum AnalogHatEnum {
RightHatY = 3, RightHatY = 3,
}; };
/** Used to get the angle calculated using the PS3 controller and PS4 controller. */
enum AngleEnum {
Pitch = 0x01,
Roll = 0x02,
};
#endif #endif

View file

@ -16,15 +16,13 @@ USB Usb;
//USBHub Hub1(&Usb); // Some dongles have a hub inside //USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so 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 */ /* You can create the instance of the PS4BT class in two ways */
// This will start an inquiry and then pair with the PS4 controller - you only have to do this once // This will start an inquiry and then pair with the PS4 controller - you only have to do this once
// You will need to hold down the PS and Share button at the same time, the PS4 controller will then start to blink rapidly indicating that it is in paring mode // You will need to hold down the PS and Share button at the same time, the PS4 controller will then start to blink rapidly indicating that it is in paring mode
BTHID bthid(&Btd, PAIR); PS4BT PS4(&Btd, PAIR);
// After that you can simply create the instance like so and then press the PS button on the device // After that you can simply create the instance like so and then press the PS button on the device
//BTHID bthid(&Btd); //PS4BT PS4(&Btd);
PS4BT PS4(&bthid); // You should not modify this instance
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);

107
examples/PS4USB/PS4USB.ino Normal file
View file

@ -0,0 +1,107 @@
/*
Example sketch for the PS4 USB 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 <PS4USB.h>
USB Usb;
PS4USB PS4(&Usb);
boolean printAngle, printTouch;
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 USB 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"));
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"));
printAngle = !printAngle;
}
if (PS4.getButtonClick(TOUCHPAD)) {
Serial.print(F("\r\nTouchpad"));
printTouch = !printTouch;
}
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(PS4.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(PS4.getAngle(Roll));
}
if (printTouch) { // Print the x, y coordinates of the touchpad
if (PS4.isTouching(0) || PS4.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 (PS4.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(PS4.getX(i));
Serial.print(F("\tY")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS4.getY(i));
Serial.print(F("\t"));
}
}
}
}
}

View file

@ -3,6 +3,7 @@
HIDUniversal::HIDUniversal(USB *p) : HIDUniversal::HIDUniversal(USB *p) :
HID(p), HID(p),
qNextPollTime(0), qNextPollTime(0),
pollInterval(0),
bPollEnable(false), bPollEnable(false),
bHasReportId(false) { bHasReportId(false) {
Initialize(); Initialize();
@ -47,6 +48,7 @@ void HIDUniversal::Initialize() {
bNumEP = 1; bNumEP = 1;
bNumIface = 0; bNumIface = 0;
bConfNum = 0; bConfNum = 0;
pollInterval = 0;
ZeroMemory(constBuffLen, prevBuf); ZeroMemory(constBuffLen, prevBuf);
} }
@ -167,6 +169,9 @@ uint8_t HIDUniversal::Init(uint8_t parent, uint8_t port, bool lowspeed) {
if(rcode) if(rcode)
goto FailGetDevDescr; goto FailGetDevDescr;
VID = udd->idVendor; // Can be used by classes that inherits this class to check the VID and PID of the connected device
PID = udd->idProduct;
num_of_conf = udd->bNumConfigurations; num_of_conf = udd->bNumConfigurations;
// Assign epInfo to epinfo pointer // Assign epInfo to epinfo pointer
@ -198,7 +203,7 @@ uint8_t HIDUniversal::Init(uint8_t parent, uint8_t port, bool lowspeed) {
// Assign epInfo to epinfo pointer // Assign epInfo to epinfo pointer
rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo); rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo);
USBTRACE2("\r\nCnf:", bConfNum); USBTRACE2("Cnf:", bConfNum);
// Set Configuration Value // Set Configuration Value
rcode = pUsb->setConf(bAddress, 0, bConfNum); rcode = pUsb->setConf(bAddress, 0, bConfNum);
@ -307,6 +312,9 @@ void HIDUniversal::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint
// Fill in the endpoint index list // Fill in the endpoint index list
piface->epIndex[index] = bNumEP; //(pep->bEndpointAddress & 0x0F); piface->epIndex[index] = bNumEP; //(pep->bEndpointAddress & 0x0F);
if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
pollInterval = pep->bInterval;
bNumEP++; bNumEP++;
} }
//PrintEndpointDescriptor(pep); //PrintEndpointDescriptor(pep);
@ -346,7 +354,7 @@ uint8_t HIDUniversal::Poll() {
return 0; return 0;
if(qNextPollTime <= millis()) { if(qNextPollTime <= millis()) {
qNextPollTime = millis() + 50; qNextPollTime = millis() + pollInterval;
uint8_t buf[constBuffLen]; uint8_t buf[constBuffLen];
@ -381,6 +389,8 @@ uint8_t HIDUniversal::Poll() {
Notify(PSTR("\r\n"), 0x80); Notify(PSTR("\r\n"), 0x80);
ParseHIDData(this, bHasReportId, (uint8_t)read, buf);
HIDReportParser *prs = GetReportParser(((bHasReportId) ? *buf : 0)); HIDReportParser *prs = GetReportParser(((bHasReportId) ? *buf : 0));
if(prs) if(prs)

View file

@ -35,6 +35,7 @@ class HIDUniversal : public HID {
uint8_t bNumIface; // number of interfaces in the configuration uint8_t bNumIface; // number of interfaces in the configuration
uint8_t bNumEP; // total number of EP in the configuration uint8_t bNumEP; // total number of EP in the configuration
uint32_t qNextPollTime; // next poll time uint32_t qNextPollTime; // next poll time
uint8_t pollInterval;
bool bPollEnable; // poll enable flag bool bPollEnable; // poll enable flag
static const uint16_t constBuffLen = 64; // event buffer length static const uint16_t constBuffLen = 64; // event buffer length
@ -50,6 +51,8 @@ class HIDUniversal : public HID {
protected: protected:
bool bHasReportId; bool bHasReportId;
uint16_t PID, VID; // PID and VID of connected device
// HID implementation // HID implementation
virtual HIDReportParser* GetReportParser(uint8_t id); virtual HIDReportParser* GetReportParser(uint8_t id);
@ -57,6 +60,10 @@ protected:
return 0; return 0;
}; };
virtual void ParseHIDData(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
return;
};
public: public:
HIDUniversal(USB *p); HIDUniversal(USB *p);
@ -72,6 +79,10 @@ public:
return bAddress; return bAddress;
}; };
virtual bool isReady() {
return bPollEnable;
};
// UsbConfigXtracter implementation // UsbConfigXtracter implementation
virtual void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep); virtual void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep);
}; };

View file

@ -32,9 +32,10 @@ Task KEYWORD2
# Datatypes (KEYWORD1) # Datatypes (KEYWORD1)
#################################################### ####################################################
PS4BT KEYWORD1
PS3BT KEYWORD1 PS3BT KEYWORD1
PS3USB KEYWORD1 PS3USB KEYWORD1
PS4BT KEYWORD1
PS4USB KEYWORD1
#################################################### ####################################################
# Methods and Functions (KEYWORD2) # Methods and Functions (KEYWORD2)
@ -75,6 +76,11 @@ PS3NavigationConnected KEYWORD2
isReady KEYWORD2 isReady KEYWORD2
watingForConnection KEYWORD2 watingForConnection KEYWORD2
isTouching KEYWORD2
getX KEYWORD2
getY KEYWORD2
getTouchCounter KEYWORD2
#################################################### ####################################################
# Constants and enums (LITERAL1) # Constants and enums (LITERAL1)
#################################################### ####################################################
@ -131,6 +137,8 @@ RightHatY LITERAL1
aX LITERAL1 aX LITERAL1
aY LITERAL1 aY LITERAL1
aZ LITERAL1 aZ LITERAL1
gX LITERAL1
gY LITERAL1
gZ LITERAL1 gZ LITERAL1
aXmove LITERAL1 aXmove LITERAL1
aYmove LITERAL1 aYmove LITERAL1