1237 lines
38 KiB
C++
1237 lines
38 KiB
C++
/*******************************************************************************
|
|
* This file is part of PsxNewLib. *
|
|
* *
|
|
* Copyright (C) 2019-2020 by SukkoPera <software@sukkology.net> *
|
|
* *
|
|
* PsxNewLib is free software: you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* PsxNewLib is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with PsxNewLib. If not, see http://www.gnu.org/licenses. *
|
|
******************************************************************************/
|
|
/**
|
|
* \file PsxNewLib.h
|
|
* \author SukkoPera <software@sukkology.net>
|
|
* \date 27 Jan 2020
|
|
* \brief Playstation controller interface library for Arduino
|
|
*
|
|
* Please refer to the GitHub page and wiki for any information:
|
|
* https://github.com/SukkoPera/PsxNewLib
|
|
*/
|
|
|
|
#ifndef PSXNEWLIB_H_
|
|
#define PSXNEWLIB_H_
|
|
|
|
// Uncomment this to have all byte exchanges logged to serial
|
|
//~ #define DUMP_COMMS
|
|
|
|
/** \brief Command Inter-Byte Delay (us)
|
|
*
|
|
* Commands are several bytes long. This is the time to wait between two
|
|
* consecutive bytes.
|
|
*
|
|
* This should actually be done by watching the \a Acknowledge line, but we are
|
|
* ignoring it at the moment.
|
|
*/
|
|
const byte INTER_CMD_BYTE_DELAY = 50;
|
|
|
|
/** \brief Command timeout (ms)
|
|
*
|
|
* Commands are sent to the controller repeatedly, until they succeed or time
|
|
* out. This is the length of that timeout.
|
|
*
|
|
* \sa COMMAND_RETRY_INTERVAL
|
|
*/
|
|
const unsigned long COMMAND_TIMEOUT = 250;
|
|
|
|
/** \brief Command Retry Interval (ms)
|
|
*
|
|
* When sending a command to the controller, if it does not succeed, it is
|
|
* retried after this amount of time.
|
|
*/
|
|
const unsigned long COMMAND_RETRY_INTERVAL = 10;
|
|
|
|
/** \brief Mode switch delay (ms)
|
|
*
|
|
* After a command has been issued successfully to the controller, this amount
|
|
* of time is waited to allow it to complete any internal procedures required to
|
|
* execute the command.
|
|
*
|
|
* \todo This is probably unnecessary.
|
|
*/
|
|
const unsigned long MODE_SWITCH_DELAY = 500;
|
|
|
|
|
|
/** \brief Type that is used to represent a single button in most places
|
|
*/
|
|
enum PsxButton {
|
|
PSB_NONE = 0x0000,
|
|
PSB_SELECT = 0x0001,
|
|
PSB_L3 = 0x0002,
|
|
PSB_R3 = 0x0004,
|
|
PSB_START = 0x0008,
|
|
PSB_PAD_UP = 0x0010,
|
|
PSB_PAD_RIGHT = 0x0020,
|
|
PSB_PAD_DOWN = 0x0040,
|
|
PSB_PAD_LEFT = 0x0080,
|
|
PSB_L2 = 0x0100,
|
|
PSB_R2 = 0x0200,
|
|
PSB_L1 = 0x0400,
|
|
PSB_R1 = 0x0800,
|
|
PSB_TRIANGLE = 0x1000,
|
|
PSB_CIRCLE = 0x2000,
|
|
PSB_CROSS = 0x4000,
|
|
PSB_SQUARE = 0x8000
|
|
};
|
|
|
|
/** \brief Type that is used to represent a single button when retrieving
|
|
* analog pressure data
|
|
*
|
|
* \sa getAnalogButton()
|
|
*/
|
|
enum PsxAnalogButton {
|
|
PSAB_PAD_RIGHT = 0,
|
|
PSAB_PAD_LEFT = 1,
|
|
PSAB_PAD_UP = 2,
|
|
PSAB_PAD_DOWN = 3,
|
|
PSAB_TRIANGLE = 4,
|
|
PSAB_CIRCLE = 5,
|
|
PSAB_CROSS = 6,
|
|
PSAB_SQUARE = 7,
|
|
PSAB_L1 = 8,
|
|
PSAB_R1 = 9,
|
|
PSAB_L2 = 10,
|
|
PSAB_R2 = 11
|
|
};
|
|
|
|
/** \brief Number of digital buttons
|
|
*
|
|
* Includes \a everything, i.e.: 4 directions, Square, Cross, Circle, Triangle,
|
|
* L1/2/3, R1/2/3, Select and Start.
|
|
*
|
|
* This is the number of entries in #PsxButton.
|
|
*/
|
|
const byte PSX_BUTTONS_NO = 16;
|
|
|
|
/** \brief Type that is used to report button presses
|
|
*/
|
|
typedef uint16_t PsxButtons;
|
|
|
|
/** \brief Size of buffer holding analog button data
|
|
*
|
|
* This is the size of the array returned by getAnalogButtonData().
|
|
*/
|
|
const byte PSX_ANALOG_BTN_DATA_SIZE = 12;
|
|
|
|
//! \name Controller Commands
|
|
//! @{
|
|
/** \brief Enter Configuration Mode
|
|
*
|
|
* Command used to enter the controller configuration (also known as \a escape)
|
|
* mode
|
|
*/
|
|
static const byte enter_config[] = {0x01, 0x43, 0x00, 0x01, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A};
|
|
static const byte exit_config[] = {0x01, 0x43, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A};
|
|
/* These shorter versions of enter_ and exit_config are accepted by all
|
|
* controllers I've tested, even in analog mode, EXCEPT SCPH-1200, so let's use
|
|
* the longer ones
|
|
*/
|
|
//~ static byte enter_config[] = {0x01, 0x43, 0x00, 0x01, 0x00};
|
|
//~ static byte exit_config[] = {0x01, 0x43, 0x00, 0x00, 0x00};
|
|
|
|
/** \brief Read Controller Type
|
|
*
|
|
* Command used to read the controller type.
|
|
*
|
|
* This does not seem to be 100% reliable, or at least we don't know how to tell
|
|
* all the various controllers apart.
|
|
*/
|
|
static const byte type_read[] = {0x01, 0x45, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A};
|
|
static const byte set_mode[] = {0x01, 0x44, 0x00, /* enabled */ 0x01, /* locked */ 0x03, 0x00, 0x00, 0x00, 0x00};
|
|
static const byte enable_rumble[] = {0x01, 0x4D, 0x00, /* motor 1 on */ 0x00, /* motor 2 on*/ 0x01, 0xff, 0xff, 0xff, 0xff};
|
|
static const byte set_pressures[] = {0x01, 0x4F, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00};
|
|
|
|
/** \brief Poll all buttons
|
|
*
|
|
* Command used to read the status of all buttons.
|
|
*/
|
|
static const byte poll[] = {0x01, 0x42, 0x00, 0xFF, 0xFF};
|
|
//! @}
|
|
|
|
/** \brief Controller Type
|
|
*
|
|
* This is somehow derived from the reply to the #type_read command. It is NOT
|
|
* much trustworthy, so it might be removed in the future.
|
|
*
|
|
* \sa getControllerType
|
|
*/
|
|
enum PsxControllerType {
|
|
PSCTRL_UNKNOWN = 0, //!< No idea
|
|
PSCTRL_DUALSHOCK, //!< DualShock or compatible
|
|
PSCTRL_DSWIRELESS, //!< Sony DualShock Wireless
|
|
PSCTRL_GUITHERO, //!< Guitar Hero controller
|
|
};
|
|
|
|
/** \brief Number of different controller types recognized
|
|
*
|
|
* This is the number of entries in #PsxControllerType.
|
|
*/
|
|
const byte PSCTRL_MAX = static_cast<byte> (PSCTRL_GUITHERO) + 1;
|
|
|
|
|
|
/** \brief Controller Protocol
|
|
*
|
|
* Identifies the protocol the controller uses to report axes positions and
|
|
* button presses. It's quite more reliable than #PsxControllerType, so use this
|
|
* if you must.
|
|
*
|
|
* \sa getProtocol
|
|
*/
|
|
enum PsxControllerProtocol {
|
|
PSPROTO_UNKNOWN = 0, //!< No idea
|
|
PSPROTO_DIGITAL, //!< Original controller (SCPH-1010) protocol (8 digital buttons + START + SELECT)
|
|
PSPROTO_DUALSHOCK, //!< DualShock (has analog axes)
|
|
PSPROTO_DUALSHOCK2, //!< DualShock 2 (has analog axes and buttons)
|
|
PSPROTO_FLIGHTSTICK, //!< Green-mode (like DualShock but missing SELECT, L3 and R3)
|
|
PSPROTO_NEGCON, //!< Namco neGcon (has 1 analog X axis and analog Square, Circle and L1 buttons)
|
|
PSPROTO_JOGCON, //!< Namco Jogcon (Wheel is mapped to analog X axis, half a rotation in each direction)
|
|
PSPROTO_GUNCON
|
|
};
|
|
|
|
/** \brief Number of different protocols supported
|
|
*
|
|
* This is the number of entries in #PsxControllerProtocol.
|
|
*/
|
|
const byte PSPROTO_MAX = static_cast<byte> (PSPROTO_JOGCON) + 1;
|
|
|
|
/** \brief Analog sticks minimum value
|
|
*
|
|
* Minimum value reported by analog sticks. This usually means that the stick is
|
|
* fully either at the top or left position. Note that some sticks might not get
|
|
* fully down to this value.
|
|
*
|
|
* \sa ANALOG_MAX_VALUE
|
|
* \sa ANALOG_IDLE_VALUE
|
|
*/
|
|
const byte ANALOG_MIN_VALUE = 0U;
|
|
|
|
/** \brief Analog sticks maximum value
|
|
*
|
|
* Maximum value reported by analog sticks. This usually means that the stick is
|
|
* fully either at the bottom or right position. Note that some sticks might not
|
|
* get fully up to this value.
|
|
*
|
|
* \sa ANALOGI_MAX_VALUE
|
|
* \sa ANALOG_IDLE_VALUE
|
|
*/
|
|
const byte ANALOG_MAX_VALUE = 255U;
|
|
|
|
/** \brief Analog sticks idle value
|
|
*
|
|
* Value reported when an analog stick is in the (ideal) center position. Note
|
|
* that old and worn-out sticks might not self-center perfectly when released,
|
|
* so you should never rely on this precise value to be reported.
|
|
*
|
|
* Also note that the up/down and left/right ranges are off by one, since values
|
|
* 0-127 represent up/left and 129-255 mean down/right. The former interval
|
|
* contains 128 different values, while the latter only 127. Sometimes you will
|
|
* need to take this in consideration.
|
|
*/
|
|
const byte ANALOG_IDLE_VALUE = 128U;
|
|
|
|
/** \brief neGcon I/II-button press threshold
|
|
*
|
|
* The neGcon does not report digital button press data for its analog buttons,
|
|
* so we have to make it up. The Square, Cross digital buttons will be
|
|
* reported as pressed when the analog value of the II and I buttons
|
|
* (respectively), goes over this threshold.
|
|
*
|
|
* \sa NEGCON_L_BUTTON_THRESHOLD
|
|
*/
|
|
const byte NEGCON_I_II_BUTTON_THRESHOLD = 128U;
|
|
|
|
/** \brief neGcon L-button press threshold
|
|
*
|
|
* The neGcon does not report digital button press data for its analog buttons,
|
|
* so we have to make it up. The L1 digital button will be reported as pressed
|
|
* when the analog value of the L buttons goes over this threshold.
|
|
*
|
|
* This value has been tuned so that the L button gets digitally triggered at
|
|
* about the same point as the non-analog R button. This is done "empirically"
|
|
* and might need tuning on a different controller than the one I actually have.
|
|
*
|
|
* \sa NEGCON_I_II_BUTTON_THRESHOLD
|
|
*/
|
|
const byte NEGCON_L_BUTTON_THRESHOLD = 240U;
|
|
|
|
//! \brief Guncon read status
|
|
enum GunconStatus {
|
|
//! Guncon data is valid
|
|
GUNCON_OK,
|
|
|
|
/** "Unexpected light": sensed light during VSYNC (e.g. from a Bulb or
|
|
* Sunlight)
|
|
*/
|
|
GUNCON_UNEXPECTED_LIGHT,
|
|
|
|
/** "No light", this can mean either no light sensed at all (not aimed at
|
|
* screen, or screen too dark: ERROR) or no light sensed yet (when trying to
|
|
* read during rendering: BUSY)
|
|
*/
|
|
GUNCON_NO_LIGHT,
|
|
|
|
/** Data is not valid for some other reason (i.e.: no Guncon, read failed,
|
|
* etc...)
|
|
*/
|
|
GUNCON_OTHER_ERROR
|
|
};
|
|
|
|
/** \brief PSX Controller Interface
|
|
*
|
|
* This is the base class implementing interactions with PSX controllers. It is
|
|
* partially abstract, so it is not supposed to be instantiated directly.
|
|
*/
|
|
class PsxController {
|
|
protected:
|
|
/** \brief Size of internal communication buffer
|
|
*
|
|
* This can be sized after the longest command reply (which is 21 bytes for
|
|
* 01 42 when in DualShock 2 mode), but we're better safe than sorry.
|
|
*/
|
|
static const byte BUFFER_SIZE = 32;
|
|
|
|
/** \brief Internal communication buffer
|
|
*
|
|
* This is used to hold replies received from the controller.
|
|
*/
|
|
byte inputBuffer[BUFFER_SIZE];
|
|
|
|
/** \brief Previous (Digital) Button Status
|
|
*
|
|
* The individual bits can be identified through #PsxButton.
|
|
*/
|
|
PsxButtons previousButtonWord;
|
|
|
|
/** \brief (Digital) Button Status
|
|
*
|
|
* The individual bits can be identified through #PsxButton.
|
|
*/
|
|
PsxButtons buttonWord;
|
|
|
|
/** \brief Controller Protocol
|
|
*
|
|
* The protocol controller data was interpreted with at the last call to
|
|
* read()
|
|
*
|
|
* \sa getProtocol
|
|
*/
|
|
PsxControllerProtocol protocol;
|
|
|
|
//! \name Analog Stick Data
|
|
//! @{
|
|
byte lx; //!< Horizontal axis of left stick [0-255, L to R]
|
|
byte ly; //!< Vertical axis of left stick [0-255, U to D]
|
|
byte rx; //!< Horizontal axis of right stick [0-255, L to R]
|
|
byte ry; //!< Vertical axis of right stick [0-255, U to D]
|
|
|
|
boolean analogSticksValid; //!< True if the above were valid at the last call to read()
|
|
//! @}
|
|
|
|
/** \brief Analog Button Data
|
|
*
|
|
* \todo What's the meaning of every individual byte?
|
|
*/
|
|
byte analogButtonData[PSX_ANALOG_BTN_DATA_SIZE];
|
|
|
|
/** \brief Analog Button Data Validity
|
|
*
|
|
* True if the #analogButtonData were valid in last call to read()
|
|
*/
|
|
boolean analogButtonDataValid;
|
|
|
|
/** \brief Rumble feature enabled or disabled.
|
|
*
|
|
* True if rumble has been turned on with command 0x4d, false otherwise.
|
|
* Rumble must be enabled and 7.5v supplied to pin 3!
|
|
*/
|
|
boolean rumbleEnabled;
|
|
|
|
/** \brief requested left motor (motor 1) power.
|
|
*
|
|
* 0xff for on, 0x00 for off, motor does not support partial activation.
|
|
* Rumble must be enabled and 7.5v supplied to pin 3!
|
|
*/
|
|
byte motor1Level;
|
|
|
|
/** \brief requested right motor (motor 2) power.
|
|
*
|
|
* 0x00 to 0xFF -> 0 to 100% power.
|
|
* Rumble must be enabled and 7.5v supplied to pin 3!
|
|
*/
|
|
byte motor2Level;
|
|
|
|
/** \brief Assert the Attention line
|
|
*
|
|
* This function must be implemented by derived classes and must set the
|
|
* Attention line \a low so that the controller will pay attention to what
|
|
* we will send.
|
|
*/
|
|
virtual void attention () = 0;
|
|
|
|
/** \brief Deassert the Attention line
|
|
*
|
|
* This function must be implemented by derived classes and must set the
|
|
* Attention line \a high so that the controller will no longer pay
|
|
* attention to what we will send.
|
|
*/
|
|
virtual void noAttention () = 0;
|
|
|
|
/** \brief Transfer a single byte to/from the controller
|
|
*
|
|
* This function must be implemented by derived classes and must transfer
|
|
* a single <i>command</i> byte to the controller and read back a single
|
|
* <i>data</i> byte.
|
|
*
|
|
* \param[in] out The command byte to send the controller
|
|
* \return The data byte returned by the controller
|
|
*/
|
|
virtual byte shiftInOut (const byte out) = 0;
|
|
|
|
/** \brief Transfer several bytes to/from the controller
|
|
*
|
|
* This function transfers an array of <i>command</i> bytes to the
|
|
* controller and reads back an equally sized array of <i>data</i> bytes.
|
|
*
|
|
* \param[in] out The command bytes to send the controller
|
|
* \param[out] in The data bytes returned by the controller, must be sized
|
|
* to hold at least \a len bytes
|
|
* \param[in] len The amount of bytes to be exchanged
|
|
*/
|
|
void shiftInOut (const byte *out, byte *in, const byte len) {
|
|
#ifdef DUMP_COMMS
|
|
byte inbuf[len];
|
|
#endif
|
|
|
|
for (byte i = 0; i < len; ++i) {
|
|
byte tmp = shiftInOut (out != NULL ? out[i] : 0x5A);
|
|
#ifdef DUMP_COMMS
|
|
inbuf[i] = tmp;
|
|
#endif
|
|
if (in != NULL) {
|
|
in[i] = tmp;
|
|
}
|
|
|
|
delayMicroseconds (INTER_CMD_BYTE_DELAY); // Very important!
|
|
}
|
|
|
|
#ifdef DUMP_COMMS
|
|
Serial.print (F("<-- "));
|
|
for (byte i = 0; i < len; ++i) {
|
|
if (out && out[i] < 0x10)
|
|
Serial.print (0);
|
|
Serial.print (out ? out[i]: 0x5A, HEX);
|
|
Serial.print (' ');
|
|
}
|
|
Serial.println ();
|
|
|
|
Serial.print (F("--> "));
|
|
for (byte i = 0; i < len; ++i) {
|
|
if (inbuf[i] < 0x10)
|
|
Serial.print (0);
|
|
Serial.print (inbuf[i], HEX);
|
|
Serial.print (' ');
|
|
}
|
|
Serial.println ();
|
|
#endif
|
|
}
|
|
|
|
/** \brief Transfer several bytes to/from the controller
|
|
*
|
|
* This function transfers an array of <i>command</i> bytes to the
|
|
* controller and reads back the full reply of <i>data</i> bytes. The size
|
|
* of the reply is calculated automatically and padding bytes (0x5A) are
|
|
* appended to the outgoing message if it is shorter.
|
|
*
|
|
* The reply is stored in an internal buffer and will be valid until the
|
|
* next call to this function, so make sure to save anything if is needed.
|
|
*
|
|
* \param[out] out The data bytes returned by the controller, must be sized
|
|
* to hold at least \a len bytes
|
|
* \param[in] len The amount of bytes to be exchanged
|
|
* \return A pointer to a buffer containing the reply, whose size can be
|
|
* calculated with getReplyLength()
|
|
*/
|
|
byte *autoShift (const byte *out, const byte len) {
|
|
byte *ret = nullptr;
|
|
|
|
if (len >= 3 && len <= BUFFER_SIZE) {
|
|
// All commands have at least 3 bytes, so shift out those first
|
|
shiftInOut (out, inputBuffer, 3);
|
|
if (isValidReply (inputBuffer)) {
|
|
// Reply is good, get full length
|
|
byte replyLen = getReplyLength (inputBuffer);
|
|
|
|
// Shift out rest of command
|
|
if (len > 3) {
|
|
shiftInOut (out + 3, inputBuffer + 3, len - 3);
|
|
}
|
|
|
|
byte left = replyLen - len + 3;
|
|
//~ Serial.print ("len = ");
|
|
//~ Serial.print (replyLen);
|
|
//~ Serial.print (", left = ");
|
|
//~ Serial.println (left);
|
|
if (left == 0) {
|
|
// The whole reply was gathered
|
|
ret = inputBuffer;
|
|
} else if (len + left <= BUFFER_SIZE) {
|
|
// Part of reply is still missing and we have space for it
|
|
shiftInOut (NULL, inputBuffer + len, left);
|
|
ret = inputBuffer;
|
|
} else {
|
|
// Reply incomplete but not enough space provided
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Get reply length
|
|
*
|
|
* Calculates the length of a command reply, in bytes
|
|
*
|
|
* \param[in] buf The buffer containing the reply, must be at least 2 bytes
|
|
* long
|
|
* \return The calculated length
|
|
*/
|
|
byte getReplyLength (const byte *buf) const {
|
|
return (buf[1] & 0x0F) * 2;
|
|
}
|
|
|
|
inline boolean isValidReply (const byte *status) {
|
|
//~ return status[0] != 0xFF || status[1] != 0xFF || status[2] != 0xFF;
|
|
return status[1] != 0xFF && (status[2] == 0x5A || status[2] == 0x00);
|
|
//~ return /* status[0] == 0xFF && */ status[1] != 0xFF && status[2] == 0x5A;
|
|
}
|
|
|
|
// Green Mode controllers
|
|
inline boolean isFlightstickReply (const byte *status) {
|
|
return (status[1] & 0xF0) == 0x50;
|
|
}
|
|
|
|
inline boolean isDualShockReply (const byte *status) {
|
|
return (status[1] & 0xF0) == 0x70;
|
|
}
|
|
|
|
inline boolean isDualShock2Reply (const byte *status) {
|
|
return status[1] == 0x79;
|
|
}
|
|
|
|
inline boolean isDigitalReply (const byte *status) {
|
|
return (status[1] & 0xF0) == 0x40;
|
|
}
|
|
|
|
inline boolean isConfigReply (const byte *status) {
|
|
return (status[1] & 0xF0) == 0xF0;
|
|
}
|
|
|
|
inline boolean isNegconReply (const byte *status) {
|
|
return status[1] == 0x23;
|
|
}
|
|
|
|
inline boolean isJogconReply (const byte *status) {
|
|
return (status[1] & 0xF0) == 0xE0;
|
|
}
|
|
|
|
inline boolean isGunconReply (const byte *status) {
|
|
return status[1] == 0x63;
|
|
}
|
|
|
|
|
|
public:
|
|
/** \brief Initialize library
|
|
*
|
|
* This function shall be called before any others, it will initialize the
|
|
* communication and return if a supported controller was found. It shall
|
|
* also be called to reinitialize the communication whenever the controller
|
|
* is unplugged.
|
|
*
|
|
* Derived classes can override this function if they need to perform
|
|
* additional initializations, but shall call it on return.
|
|
*
|
|
* \return true if a supported controller was found, false otherwise
|
|
*/
|
|
virtual boolean begin () {
|
|
// Start with all analog axes at midway position
|
|
lx = ANALOG_IDLE_VALUE;
|
|
ly = ANALOG_IDLE_VALUE;
|
|
rx = ANALOG_IDLE_VALUE;
|
|
ry = ANALOG_IDLE_VALUE;
|
|
|
|
analogSticksValid = false;
|
|
memset (analogButtonData, 0, sizeof (analogButtonData));
|
|
|
|
protocol = PSPROTO_UNKNOWN;
|
|
|
|
rumbleEnabled = false;
|
|
motor1Level = 0x00;
|
|
motor2Level = 0x00;
|
|
|
|
// Some disposable readings to let the controller know we are here
|
|
for (byte i = 0; i < 5; ++i) {
|
|
read ();
|
|
delay (1);
|
|
}
|
|
|
|
return read ();
|
|
}
|
|
|
|
//! \name Configuration Mode Functions
|
|
//! @{
|
|
|
|
/** \brief Enter Configuration Mode
|
|
*
|
|
* Some controllers can be configured in several aspects. For instance,
|
|
* DualShock controllers can return analog stick data. This function puts
|
|
* the controller in configuration mode.
|
|
*
|
|
* Note that <i>Configuration Mode</i> is sometimes called <i>Escape Mode</i>.
|
|
*
|
|
* \return true if Configuration Mode was entered successfully
|
|
*/
|
|
boolean enterConfigMode () {
|
|
boolean ret = false;
|
|
|
|
unsigned long start = millis ();
|
|
do {
|
|
attention ();
|
|
byte *in = autoShift (enter_config, 4);
|
|
noAttention ();
|
|
|
|
ret = in != NULL && isConfigReply (in);
|
|
|
|
if (!ret) {
|
|
delay (COMMAND_RETRY_INTERVAL);
|
|
}
|
|
} while (!ret && millis () - start <= COMMAND_TIMEOUT);
|
|
delay (MODE_SWITCH_DELAY);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Enable (or disable) analog sticks
|
|
*
|
|
* This function enables or disables the analog sticks that were introduced
|
|
* with DualShock controllers. When they are enabled, the getLeftAnalog()
|
|
* and getRightAnalog() functions can be used to retrieve their positions.
|
|
* Also, button presses for L3 and R3 will be available through the
|
|
* buttonPressed() and similar functions.
|
|
*
|
|
* When analog sticks are enabled, the \a ANALOG led will light up (in red)
|
|
* on the controller.
|
|
*
|
|
* Note that on some third-party controllers, when analog sticks are
|
|
* disabled the analog levers will "emulate" the D-Pad and possibly the
|
|
* []/^/O/X buttons. This does not happen on official Sony controllers.
|
|
*
|
|
* This function will only work if when the controller is in Configuration
|
|
* Mode.
|
|
*
|
|
* \param[in] enabled true to enable, false to disable
|
|
* \param[in] locked If true, the \a ANALOG button on the controller will be
|
|
* disabled and the user will not be able to turn off the
|
|
* analog sticks.
|
|
* \return true if the command was ackowledged by the controller. Note that
|
|
* this does not fully guarantee that the analog sticks were enabled
|
|
* as this can only be checked after Configuration Mode is exited.
|
|
*/
|
|
boolean enableAnalogSticks (bool enabled = true, bool locked = false) {
|
|
boolean ret = false;
|
|
byte out[sizeof (set_mode)];
|
|
|
|
memcpy (out, set_mode, sizeof (set_mode));
|
|
out[3] = enabled ? 0x01 : 0x00;
|
|
out[4] = locked ? 0x03 : 0x00;
|
|
|
|
unsigned long start = millis ();
|
|
byte cnt = 0;
|
|
do {
|
|
attention ();
|
|
byte *in = autoShift (out, 5);
|
|
noAttention ();
|
|
|
|
/* We can't know if we have successfully enabled analog mode until
|
|
* we get out of config mode, so let's just be happy if we get a few
|
|
* consecutive valid replies
|
|
*/
|
|
if (in != nullptr) {
|
|
++cnt;
|
|
}
|
|
ret = cnt >= 3;
|
|
|
|
if (!ret) {
|
|
delay (COMMAND_RETRY_INTERVAL);
|
|
}
|
|
} while (!ret && millis () - start <= COMMAND_TIMEOUT);
|
|
delay (MODE_SWITCH_DELAY);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Enable (or disable) the vibration capability of the DualShock / DualShock 2
|
|
*
|
|
* This function enables or disables the rumble feature of the DualShock / DualShock 2 controllers.
|
|
* NOTE that this function does nothing on its own - the vibration on/off must be set using
|
|
* setRumble() and the controller will begin to vibrate when the read() function is
|
|
* next called.
|
|
*
|
|
* This function will only work if when the controller is in Configuration
|
|
* Mode.
|
|
*
|
|
* \param[in] enabled true to enable both motors, false to disable them.
|
|
*
|
|
* \return true if we got bytes back. Eventually we should wait for ACK from the controller.
|
|
*/
|
|
boolean enableRumble(bool enabled = true) {
|
|
boolean ret = true;
|
|
byte out[sizeof (enable_rumble)];
|
|
|
|
memcpy (out, enable_rumble, sizeof (enable_rumble));
|
|
out[3] = enabled ? 0x00 : 0xff;
|
|
out[4] = enabled ? 0x01 : 0xff;
|
|
|
|
unsigned long start = millis ();
|
|
byte cnt = 0;
|
|
do {
|
|
attention ();
|
|
byte *in = autoShift (out, 5);
|
|
noAttention ();
|
|
|
|
/* The real way to check if the command was successful is to wait for ACK.
|
|
* Currently the library doesn't support the pin, so I will just assume success.
|
|
*/
|
|
if (in != nullptr) {
|
|
++cnt;
|
|
}
|
|
ret = cnt >= 3;
|
|
|
|
if (!ret) {
|
|
delay (COMMAND_RETRY_INTERVAL);
|
|
}
|
|
} while (!ret && millis () - start <= COMMAND_TIMEOUT);
|
|
delay (MODE_SWITCH_DELAY);
|
|
|
|
rumbleEnabled = true;
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Set the requested power output of the rumble motors on DualShock / DualShock 2 controllers.
|
|
*
|
|
* This function sets internal variables that set the requested motor power of the rumble motors.
|
|
* NOTE this does nothing if rumble has not been enabled with enableRumble(), rumble motors will
|
|
* activate or deactivate to match the arguments of this function with the next call to read()
|
|
*
|
|
* \param[in] enabled true to activate motor 1, false to deactivate.
|
|
* \param[in] requested motor power of motor 2, where 0x00 to 0xFF corresponds to 0 to 100%.
|
|
*/
|
|
void setRumble(bool motor1Active = true, byte motor2Power = 0xff) {
|
|
motor1Level = motor1Active ? 0xff : 0x00;
|
|
motor2Level = motor2Power;
|
|
}
|
|
|
|
/** \brief Enable (or disable) analog buttons
|
|
*
|
|
* This function enables or disables the analog buttons that were introduced
|
|
* with DualShock 2 controllers. When they are enabled, the
|
|
* getAnalogButton() functions can be used to retrieve how deep/strongly
|
|
* they are pressed. This applies to the D-Pad buttons, []/^/O/X, L1/2 and
|
|
* R1/2
|
|
*
|
|
* This function will only work if when the controller is in Configuration
|
|
* Mode.
|
|
*
|
|
* \param[in] enabled true to enable, false to disable
|
|
* \return true if the command was ackowledged by the controller. Note that
|
|
* this does not fully guarantee that the analog sticks were enabled
|
|
* as this can only be checked after Configuration Mode is exited.
|
|
*/
|
|
boolean enableAnalogButtons (bool enabled = true) {
|
|
boolean ret = false;
|
|
byte out[sizeof (set_mode)];
|
|
|
|
memcpy (out, set_pressures, sizeof (set_pressures));
|
|
if (!enabled) {
|
|
out[3] = 0x00;
|
|
out[4] = 0x00;
|
|
out[5] = 0x00;
|
|
}
|
|
|
|
unsigned long start = millis ();
|
|
byte cnt = 0;
|
|
do {
|
|
attention ();
|
|
byte *in = autoShift (out, sizeof (set_pressures));
|
|
noAttention ();
|
|
|
|
/* We can't know if we have successfully enabled analog mode until
|
|
* we get out of config mode, so let's just be happy if we get a few
|
|
* consecutive valid replies
|
|
*/
|
|
if (in != nullptr) {
|
|
++cnt;
|
|
}
|
|
ret = cnt >= 3;
|
|
|
|
if (!ret) {
|
|
delay (COMMAND_RETRY_INTERVAL);
|
|
}
|
|
} while (!ret && millis () - start <= COMMAND_TIMEOUT);
|
|
delay (MODE_SWITCH_DELAY);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Retrieve the controller type
|
|
*
|
|
* This function retrieves the controller type. It is not 100% reliable, so
|
|
* do not rely on it for anything other than a vague indication (for
|
|
* instance, the DualShock SCPH-1200 controller gets reported as the Guitar
|
|
* Hero controller...).
|
|
*
|
|
* This function will only work if when the controller is in Configuration
|
|
* Mode.
|
|
*
|
|
* \return The (tentative) controller type
|
|
*/
|
|
PsxControllerType getControllerType () {
|
|
PsxControllerType ret = PSCTRL_UNKNOWN;
|
|
|
|
attention ();
|
|
byte *in = autoShift (type_read, 3);
|
|
noAttention ();
|
|
|
|
if (in != nullptr) {
|
|
const byte& controllerType = in[3];
|
|
if (controllerType == 0x03) {
|
|
ret = PSCTRL_DUALSHOCK;
|
|
//~ } else if (controllerType == 0x01 && in[1] == 0x42) {
|
|
//~ return 4; // ???
|
|
} else if (controllerType == 0x01 && in[1] != 0x42) {
|
|
ret = PSCTRL_GUITHERO;
|
|
} else if (controllerType == 0x0C) {
|
|
ret = PSCTRL_DSWIRELESS;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
boolean exitConfigMode () {
|
|
boolean ret = false;
|
|
|
|
unsigned long start = millis ();
|
|
do {
|
|
attention ();
|
|
//~ shiftInOut (poll, in, sizeof (poll));
|
|
//~ shiftInOut (exit_config, in, sizeof (exit_config));
|
|
byte *in = autoShift (exit_config, 4);
|
|
noAttention ();
|
|
|
|
ret = in != nullptr && !isConfigReply (in);
|
|
|
|
if (!ret) {
|
|
delay (COMMAND_RETRY_INTERVAL);
|
|
}
|
|
} while (!ret && millis () - start <= COMMAND_TIMEOUT);
|
|
delay (MODE_SWITCH_DELAY);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//! @} // Configuration Mode Functions
|
|
|
|
//! \name Polling Functions
|
|
//! @{
|
|
|
|
/** \brief Retrieve the controller protocol
|
|
*
|
|
* This function retrieves the protocol that was used to interpret
|
|
* controller data at the last call to read().
|
|
*
|
|
* \return The controller protocol
|
|
*/
|
|
PsxControllerProtocol getProtocol () const {
|
|
return protocol;
|
|
}
|
|
|
|
/** \brief Poll the controller
|
|
*
|
|
* This function polls the controller for button and stick data. It self-
|
|
* adapts to all the supported controller types and populates internal
|
|
* variables with the retrieved information, which can be later accessed
|
|
* through the inspection functions.
|
|
*
|
|
* This function must be called quite often in order to keep the controller
|
|
* alive. Most controllers have some kind of watchdog that will reset them
|
|
* if they don't get polled at least every so often (like a couple dozen
|
|
* times per seconds).
|
|
*
|
|
* If this function fails repeatedly, it can safely be assumed that the
|
|
* controller has been disconnected (or that it is not supported if it
|
|
* failed right from the beginning).
|
|
*
|
|
* \return true if the read was successful, false otherwise
|
|
*/
|
|
boolean read () {
|
|
boolean ret = false;
|
|
|
|
analogSticksValid = false;
|
|
analogButtonDataValid = false;
|
|
|
|
attention ();
|
|
byte *in = nullptr;
|
|
if(rumbleEnabled) {
|
|
byte out[sizeof (poll)];
|
|
memcpy(out, poll, sizeof(poll));
|
|
out[3] = motor1Level;
|
|
out[4] = motor2Level;
|
|
in = autoShift (out, sizeof(poll));
|
|
}
|
|
else {
|
|
in = autoShift (poll, 3);
|
|
}
|
|
noAttention ();
|
|
|
|
if (in != NULL) {
|
|
if (isConfigReply (in)) {
|
|
// We're stuck in config mode, try to get out
|
|
exitConfigMode ();
|
|
} else {
|
|
// We surely have buttons
|
|
previousButtonWord = buttonWord;
|
|
buttonWord = ((PsxButtons) in[4] << 8) | in[3];
|
|
|
|
// See if we have anything more to read
|
|
if (isDualShock2Reply (in)) {
|
|
protocol = PSPROTO_DUALSHOCK2;
|
|
} else if (isDualShockReply (in)) {
|
|
protocol = PSPROTO_DUALSHOCK;
|
|
} else if (isFlightstickReply (in)) {
|
|
protocol = PSPROTO_FLIGHTSTICK;
|
|
} else if (isNegconReply (in)) {
|
|
protocol = PSPROTO_NEGCON;
|
|
} else if (isJogconReply (in)) {
|
|
protocol = PSPROTO_JOGCON;
|
|
} else if (isGunconReply (in)) {
|
|
protocol = PSPROTO_GUNCON;
|
|
} else {
|
|
protocol = PSPROTO_DIGITAL;
|
|
}
|
|
|
|
switch (protocol) {
|
|
case PSPROTO_DUALSHOCK2:
|
|
// We also have analog button data
|
|
analogButtonDataValid = true;
|
|
for (int i = 0; i < PSX_ANALOG_BTN_DATA_SIZE; ++i) {
|
|
analogButtonData[i] = in[i + 9];
|
|
}
|
|
/* Now fall through to DualShock case, the next line
|
|
* avoids GCC warning
|
|
*/
|
|
/* FALLTHRU */
|
|
case PSPROTO_GUNCON:
|
|
/* The Guncon uses the same reply format as DualShocks,
|
|
* by just falling through we'll end up with:
|
|
* - A (Left side) -> Start
|
|
* - B (Right side) -> Cross
|
|
* - Trigger -> Circle
|
|
* - Low byte of HSYNC -> RX
|
|
* - High byte of HSYNC -> RY
|
|
* - Low byte of VSYNC -> LX
|
|
* - High byte of VSYNC -> LY
|
|
*/
|
|
case PSPROTO_DUALSHOCK:
|
|
case PSPROTO_FLIGHTSTICK:
|
|
// We have analog stick data
|
|
analogSticksValid = true;
|
|
rx = in[5];
|
|
ry = in[6];
|
|
lx = in[7];
|
|
ly = in[8];
|
|
break;
|
|
case PSPROTO_NEGCON:
|
|
// Map the twist axis to X axis of left analog
|
|
analogSticksValid = true;
|
|
lx = in[5];
|
|
|
|
// Map analog button data to their reasonable counterparts
|
|
analogButtonDataValid = true;
|
|
analogButtonData[PSAB_CROSS] = in[6];
|
|
analogButtonData[PSAB_SQUARE] = in[7];
|
|
analogButtonData[PSAB_L1] = in[8];
|
|
|
|
// Make up "missing" digital data
|
|
if (analogButtonData[PSAB_SQUARE] >= NEGCON_I_II_BUTTON_THRESHOLD) {
|
|
buttonWord &= ~PSB_SQUARE;
|
|
}
|
|
if (analogButtonData[PSAB_CROSS] >= NEGCON_I_II_BUTTON_THRESHOLD) {
|
|
buttonWord &= ~PSB_CROSS;
|
|
}
|
|
if (analogButtonData[PSAB_L1] >= NEGCON_L_BUTTON_THRESHOLD) {
|
|
buttonWord &= ~PSB_L1;
|
|
}
|
|
break;
|
|
case PSPROTO_JOGCON:
|
|
/* Map the wheel X axis of left analog, half a rotation
|
|
* per direction: byte 5 has the wheel position, it is
|
|
* 0 at startup, then we have 0xFF down to 0x80 for
|
|
* left/CCW, and 0x01 up to 0x80 for right/CW
|
|
*
|
|
* byte 6 is the number of full CW rotations
|
|
* byte 7 is 0 if wheel is still, 1 if it is rotating CW
|
|
* and 2 if rotation CCW
|
|
* byte 8 seems to stay at 0
|
|
*
|
|
* We'll want to cap the movement halfway in each
|
|
* direction, for ease of use/implementation.
|
|
*/
|
|
analogSticksValid = true;
|
|
if (in[6] < 0x80) {
|
|
// CW up to half
|
|
lx = in[5] < 0x80 ? in[5] : (0x80 - 1);
|
|
} else {
|
|
// CCW down to half
|
|
lx = in[5] > 0x80 ? in[5] : (0x80 + 1);
|
|
}
|
|
|
|
// Bring to the usual 0-255 range
|
|
lx += 0x80;
|
|
break;
|
|
default:
|
|
// We are already done
|
|
break;
|
|
}
|
|
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Check if any button has changed state
|
|
*
|
|
* \return true if any button has changed state with regard to the previous
|
|
* call to read(), false otherwise
|
|
*/
|
|
boolean buttonsChanged () const {
|
|
return ((previousButtonWord ^ buttonWord) > 0);
|
|
}
|
|
|
|
/** \brief Check if a button has changed state
|
|
*
|
|
* \return true if \a button has changed state with regard to the previous
|
|
* call to read(), false otherwise
|
|
*/
|
|
boolean buttonChanged (const PsxButtons button) const {
|
|
return (((previousButtonWord ^ buttonWord) & button) > 0);
|
|
}
|
|
|
|
/** \brief Check if a button is currently pressed
|
|
*
|
|
* \param[in] button The button to be checked
|
|
* \return true if \a button was pressed in last call to read(), false
|
|
* otherwise
|
|
*/
|
|
boolean buttonPressed (const PsxButton button) const {
|
|
return buttonPressed (~buttonWord, button);
|
|
}
|
|
|
|
/** \brief Check if a button is pressed in a Button Word
|
|
*
|
|
* \param[in] buttons The button word to check in
|
|
* \param[in] button The button to be checked
|
|
* \return true if \a button is pressed in \a buttons, false otherwise
|
|
*/
|
|
boolean buttonPressed (const PsxButtons buttons, const PsxButton button) const {
|
|
return ((buttons & static_cast<const PsxButtons> (button)) > 0);
|
|
}
|
|
|
|
/** \brief Check if a button has just been pressed
|
|
*
|
|
* \param[in] button The button to be checked
|
|
* \return true if \a button was not pressed in the previous call to read()
|
|
* and is now, false otherwise
|
|
*/
|
|
boolean buttonJustPressed (const PsxButton button) const {
|
|
return (buttonChanged (button) & buttonPressed (button));
|
|
}
|
|
|
|
/** \brief Check if a button has just been released
|
|
*
|
|
* \param[in] button The button to be checked
|
|
* \return true if \a button was pressed in the previous call to read() and
|
|
* is not now, false otherwise
|
|
*/
|
|
boolean buttonJustReleased (const PsxButton button) const {
|
|
return (buttonChanged (button) & ((~previousButtonWord & button) > 0));
|
|
}
|
|
|
|
/** \brief Check if NO button is pressed in a Button Word
|
|
*
|
|
* \param[in] buttons The button word to check in
|
|
* \return true if all buttons in \a buttons are released, false otherwise
|
|
*/
|
|
boolean noButtonPressed (const PsxButtons buttons) const {
|
|
return buttons == PSB_NONE;
|
|
}
|
|
|
|
/** \brief Check if NO button is currently pressed
|
|
*
|
|
* \return true if all buttons were released in the last call to read(),
|
|
* false otherwise
|
|
*/
|
|
boolean noButtonPressed (void) const {
|
|
return buttonWord == ~PSB_NONE;
|
|
}
|
|
|
|
/** \brief Retrieve the <em>Button Word</em>
|
|
*
|
|
* The button word contains the status of all digital buttons and can be
|
|
* retrieved so that it can be inspected later.
|
|
*
|
|
* \sa buttonPressed
|
|
* \sa noButtonPressed
|
|
*
|
|
* \return the Button Word
|
|
*/
|
|
PsxButtons getButtonWord () const {
|
|
return ~buttonWord;
|
|
}
|
|
|
|
/** \brief Retrieve button pressure depth/strength
|
|
*
|
|
* This function will return how deeply/strongly a button is pressed. It
|
|
* will only work on DualShock 2 controllers after enabling this feature
|
|
* with enableAnalogButtons().
|
|
*
|
|
* Note that button pressure depth/strength is only available for the D-Pad
|
|
* buttons, []/^/O/X, L1/2 and R1/2.
|
|
*
|
|
* \param[in] button the button the retrieve the pressure depth/strength of
|
|
* \return the pressure depth/strength [0-255, Fully released to fully
|
|
* pressed]
|
|
*/
|
|
byte getAnalogButton (const PsxAnalogButton button) const {
|
|
byte ret = 0;
|
|
|
|
if (analogButtonDataValid) {
|
|
ret = analogButtonData[button];
|
|
//~ } else if (buttonPressed (button)) { // FIXME
|
|
//~ // No analog data, assume fully pressed or fully released
|
|
//~ ret = 0xFF;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \brief Retrieve all analog button data
|
|
*/
|
|
const byte* getAnalogButtonData () const {
|
|
return analogButtonDataValid ? analogButtonData : NULL;
|
|
}
|
|
|
|
/** \brief Retrieve position of the \a left analog stick
|
|
*
|
|
* This function will return the absolute position of the left analog stick.
|
|
*
|
|
* Note that not all controllers have analog sticks, in which case this
|
|
* function will return false.
|
|
*
|
|
* \param[in] x A variable where the horizontal position will be stored
|
|
* [0-255, L to R]
|
|
* \param[in] y A variable where the vertical position will be stored
|
|
* [0-255, U to D]
|
|
* \return true if the returned position is valid, false otherwise
|
|
*/
|
|
boolean getLeftAnalog (byte& x, byte& y) const {
|
|
x = lx;
|
|
y = ly;
|
|
|
|
return analogSticksValid;
|
|
}
|
|
|
|
/** \brief Retrieve position of the \a right analog stick
|
|
*
|
|
* This function will return the absolute position of the right analog
|
|
* stick.
|
|
*
|
|
* Note that not all controllers have analog sticks, in which case this
|
|
* function will return false.
|
|
*
|
|
* \param[in] x A variable where the horizontal position will be stored
|
|
* [0-255, L to R]
|
|
* \param[in] y A variable where the vertical position will be stored
|
|
* [0-255, U to D]
|
|
* \return true if the returned position is valid, false otherwise
|
|
*/
|
|
boolean getRightAnalog (byte& x, byte& y) {
|
|
x = rx;
|
|
y = ry;
|
|
|
|
return analogSticksValid;
|
|
}
|
|
|
|
/** \brief Retrieve Guncon X/Y readings
|
|
*
|
|
* According to the Nocash PSX Specifications, the Guncon returns 16-bit X/Y
|
|
* coordinates of the screen it is aimed at.
|
|
*
|
|
* The coordinates are updated in all frames. The absolute min/max may vary
|
|
* from TV set to TV set.
|
|
*
|
|
* Vertical coordinates are counted in scanlines (ie. equal to pixels).
|
|
* Horizontal coordinates are counted in 8MHz units (which would equal a
|
|
* resolution of 385 pixels; which can be, for example, converted to 320
|
|
* pixel resolution as X=X*320/385).
|
|
*
|
|
* <em>Caution:</em> The gun only returns meaningful data when read shortly
|
|
* after begin of VBLANK (ie. AFTER rendering, but still BEFORE vsync), so
|
|
* make sure to only consider readings returning \a GUNCON_OK;
|
|
*
|
|
* \sa GunconStatus
|
|
*/
|
|
GunconStatus getGunconCoordinates (word& x, word& y) const {
|
|
GunconStatus status = GUNCON_OTHER_ERROR;
|
|
|
|
if (protocol == PSPROTO_GUNCON && analogSticksValid) {
|
|
status = GUNCON_OK;
|
|
|
|
x = (((word) ry) << 8) | rx;
|
|
y = (((word) ly) << 8) | lx;
|
|
|
|
if (x == 0x0001) {
|
|
if (y == 0x0005) {
|
|
status = GUNCON_UNEXPECTED_LIGHT;
|
|
} else if (y == 0x000A) {
|
|
status = GUNCON_NO_LIGHT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
//! @} // Polling Functions
|
|
};
|
|
|
|
#endif
|