diff --git a/examples/JogconMotor/JogconMotor.ino b/examples/JogconMotor/JogconMotor.ino new file mode 100644 index 0000000..e5e7f1e --- /dev/null +++ b/examples/JogconMotor/JogconMotor.ino @@ -0,0 +1,169 @@ +/******************************************************************************* + * This file is part of PsxNewLib. * + * * + * Copyright (C) 2019-2020 by SukkoPera * + * * + * 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. * + ******************************************************************************* + * + * This sketch can send commands to a PSX Jogcon controller to move it's motor + * + * This example drives the controller through the hardware SPI port, so pins are + * fixed and depend on the board/microcontroller being used. For instance, on an + * Arduino Uno connections must be as follows: + * + * CMD: Pin 11 + * DATA: Pin 12 + * CLK: Pin 13 + * + * Any pin can be used for ATTN, but please note that most 8-bit AVRs require + * the HW SPI SS pin to be kept as an output for HW SPI to be in master mode, so + * using that pin for ATTN is a natural choice. On the Uno this would be pin 10. + * + * It also works perfectly on OpenPSX2AmigaPadAdapter boards (as it's basically + * a modified Uno). + * + */ + +#include + +const byte PIN_PS2_ATT = 10; +PsxControllerHwSpi psx; + +void setup() { + Serial.begin(115200); + while(!Serial){} + Serial.println(F("Ready!")); +} + +void loop() { + static bool haveController = false; + static uint8_t force = 8; + + if (!haveController) { + if (psx.begin()) { + Serial.println(F("Controller found!")); + haveController = true; + + delay(300); + + if (!psx.enterConfigMode ()) { + Serial.println (F("Cannot enter config mode")); + } else { + //must enable analog mode to use jogcon's paddle + if (!psx.enableAnalogSticks ()) + Serial.println (F("Cannot enable analog sticks")); + + if (!psx.enableAnalogButtons ()) + Serial.println (F("Cannot enable analog buttons")); + + //must enable rumble to use the jogcon's motor + if (!psx.enableRumble ()) + Serial.println (F("Cannot enable rumble")); + + if (!psx.exitConfigMode ()) + Serial.println (F("Cannot exit config mode")); + } + psx.read (); // Make sure the protocol is up to date + } + } else { + if(!psx.read()){ + haveController = false; + } else if (psx.getProtocol () == PSPROTO_JOGCON) {// It's a jogcon! + + //reading the "raw" values + uint8_t jogPosition = 0; + uint8_t jogRevolutions = 0; + JogconDirection jogDirection; + JogconCommand cmdResult; + + //Checking if the jog state has changed or theres as command result. + //Not required but just to not fill the serial output when controller is idle. + if (psx.getJogconData(jogPosition, jogRevolutions, jogDirection, cmdResult) && (jogDirection != JOGCON_DIR_NONE || cmdResult != JOGCON_CMD_NONE)) { + Serial.print(F("Position: ")); + Serial.print(jogPosition); + Serial.print(F("\tRevolutions: ")); + Serial.print(jogRevolutions); + + if (jogDirection != JOGCON_DIR_NONE) { + Serial.print(F("\tRotation: ")); + if (jogDirection == JOGCON_DIR_CCW) + Serial.print(F("CCW")); + else if (jogDirection == JOGCON_DIR_CW) + Serial.print(F("CW")); + else if (jogDirection == JOGCON_DIR_MAX) + Serial.print(F("MAX")); + else + Serial.print(psx.getAnalogButton(PSAB_PAD_UP), HEX); + } + + if (cmdResult != JOGCON_CMD_NONE) { + Serial.print(F("\tCmdResult: ")); + if (cmdResult == JOGCON_CMD_DROP_REVOLUTIONS) + Serial.print(F("DROP_REVOLUTIONS")); + else if (cmdResult == JOGCON_CMD_NEW_START) + Serial.print(F("NEW_START")); + else + Serial.print(psx.getAnalogButton(PSAB_PAD_UP), HEX); + } + Serial.println(); + } // end getJogconData() + + + //buttons to decrement/increment the motor's force + if (psx.buttonJustPressed (PSB_L2)) { + force--; + if (force > 15) + force = 0; + Serial.print(F("Motor force: ")); + Serial.println(force); + } else if (psx.buttonJustPressed (PSB_R2)) { //increment the motor's force + force++; + if (force > 15) + force = 15; + Serial.print(F("Motor force: ")); + Serial.println(force); + } + + + //Send command no jogcon. Will be sent on next Read() + JogconDirection newDirection = JOGCON_DIR_NONE; + JogconCommand newCommand = JOGCON_CMD_NONE; + + //buttons to change the jogcon's mode + + //rotate to start position + if (psx.buttonPressed (PSB_PAD_UP)) //PSB_TRIANGLE + newDirection = JOGCON_DIR_START; + //rotate CCW + else if (psx.buttonPressed (PSB_PAD_LEFT)) //PSB_SQUARE + newDirection = JOGCON_DIR_CCW; + //rotate CW + else if (psx.buttonPressed (PSB_PAD_RIGHT)) // PSB_CIRCLE + newDirection = JOGCON_DIR_CW; + + //set new start position (and forget the ammount of revolutions) + if (psx.buttonPressed (PSB_CROSS)) + newCommand = JOGCON_CMD_NEW_START; + //forget the ammount of revolutions + else if (psx.buttonPressed (PSB_TRIANGLE)) + newCommand = JOGCON_CMD_DROP_REVOLUTIONS; + + psx.setJogconMotorMode(newDirection, newCommand, force); + + } //end if PSPROTO_JOGCON + + } + +} diff --git a/src/PsxNewLib.h b/src/PsxNewLib.h index 587a8ce..1bc5551 100644 --- a/src/PsxNewLib.h +++ b/src/PsxNewLib.h @@ -293,6 +293,26 @@ enum GunconStatus { GUNCON_OTHER_ERROR }; +//! \brief Jogcon rotation direction +enum JogconDirection { + JOGCON_DIR_NONE = 0x0, + JOGCON_DIR_CW = 0x1, + JOGCON_DIR_CCW = 0x2, + JOGCON_DIR_START = 0x3, + //Bellow values are only used as return on getJogconData() + JOGCON_DIR_MAX = 0x4, //Position and revolutions maxed out + JOGCON_DIR_OTHER = 0xF //Using 0x0F as generic unhandled code +}; + +//! \brief Jogcon command +enum JogconCommand { + JOGCON_CMD_NONE = 0x0, + JOGCON_CMD_DROP_REVOLUTIONS = 0x80, + JOGCON_CMD_NEW_START = 0xC0, + //Bellow values are only used as return on getJogconData() + JOGCON_CMD_OTHER = 0xF0 //Using 0xF0 as generic unhandled code return +}; + /** \brief PSX Controller Interface * * This is the base class implementing interactions with PSX controllers. It is @@ -377,6 +397,12 @@ class PsxController { */ byte motor2Level; + /** \brief requested jogcon motor power and mode. + * + * Rumble must be enabled and 7.5v supplied to pin 3! + */ + byte jogconMotorLevelAndMode; + /** \brief Assert the Attention line * * This function must be implemented by derived classes and must set the @@ -584,6 +610,7 @@ class PsxController { rumbleEnabled = false; motor1Level = 0x00; motor2Level = 0x00; + jogconMotorLevelAndMode = 0x00; // Some disposable readings to let the controller know we are here for (byte i = 0; i < 5; ++i) { @@ -901,8 +928,12 @@ class PsxController { if(rumbleEnabled) { byte out[sizeof (poll)]; memcpy(out, poll, sizeof(poll)); - out[3] = motor1Level; - out[4] = motor2Level; + if (protocol == PSPROTO_JOGCON) { + out[3] = jogconMotorLevelAndMode; + } else { + out[3] = motor1Level; + out[4] = motor2Level; + } in = autoShift (out, sizeof(poll)); } else { @@ -1014,6 +1045,12 @@ class PsxController { // Bring to the usual 0-255 range lx += 0x80; + + //Stores the "raw" data to use on getJogconData(). + //Reusing the analogButtonData array. Need to make a new structure to hold those values. + analogButtonData[PSAB_PAD_RIGHT] = in[5]; + analogButtonData[PSAB_PAD_LEFT] = in[6]; + analogButtonData[PSAB_PAD_UP] = in[7]; break; default: // We are already done @@ -1229,6 +1266,87 @@ class PsxController { return status; } + + + /** \brief Set Jogcon direction, command and motor power + * + * Data will be combined into a single byte as ddccffff, where + * dd = direction (2 bits) + * cc = command (2 bits) + * ffff = force (4 bits) + * + * \param[in] direction The direction for motor rotation + * \param[in] command The Command to be sent + * \param[in] motorPower The amount of motor power. Max 15 (0x0F). + */ + void setJogconMotorMode (JogconDirection direction, JogconCommand command, const uint8_t motorPower) { + if((byte)direction > 0x3) + direction = JOGCON_DIR_NONE; + + if((byte)command == JOGCON_CMD_OTHER) + command = JOGCON_CMD_NONE; + + jogconMotorLevelAndMode = ((byte)direction << 4) | (command | (motorPower & 0x0F)); + } + + + /** \brief Retrieve Jogcon state and raw readings + * + * \param[in] position A variable where the jog position will be stored + * \param[in] revolutions A variable where the jog revolutions will be stored + * \param[in] direction A variable where the last direction will be stored + * \param[in] cmdResult A variable where the last command sent will be stored + * + * \return true if the device is Jogcon and data is valid + */ + bool getJogconData (uint8_t& position, uint8_t& revolutions, JogconDirection& direction, JogconCommand& cmdResult) const { + if (protocol == PSPROTO_JOGCON && analogSticksValid) { + position = analogButtonData[PSAB_PAD_RIGHT]; + revolutions = analogButtonData[PSAB_PAD_LEFT]; + //state = static_cast(analogButtonData[PSAB_PAD_UP]); + + //State byte contains two nibbles with data. + //Rotation state and command result + + //Last rotation direction + switch (analogButtonData[PSAB_PAD_UP] & 0x0F) { + case 0x0: + direction = JOGCON_DIR_NONE; + break; + case 0x1: + direction = JOGCON_DIR_CW; + break; + case 0x2: + direction = JOGCON_DIR_CCW; + break; + case 0x4: + direction = JOGCON_DIR_MAX; //Max value reached (overflow) + break; + default: + direction = JOGCON_DIR_OTHER; //Other - unhandled + break; + } + + //Last command result + switch (analogButtonData[PSAB_PAD_UP] & 0xF0) { + case 0x00: + cmdResult = JOGCON_CMD_NONE; + break; + case 0x80: + cmdResult = JOGCON_CMD_DROP_REVOLUTIONS; + break; + case 0xC0: + cmdResult = JOGCON_CMD_NEW_START; + break; + default: + cmdResult = JOGCON_CMD_OTHER; //Other - unhandled + break; + } + return true; + } + + return false; + } //! @} // Polling Functions };