From 8403735ac354ebe1bb68d5790e764544ad267e1e Mon Sep 17 00:00:00 2001 From: Hendrik Osterholz Date: Mon, 22 Mar 2021 15:40:58 +0100 Subject: [PATCH] PSX adapter --- PSXControllerUSB/Gamepad.cpp | 156 ++++++++++++++++++++++++++ PSXControllerUSB/Gamepad.h | 60 ++++++++++ PSXControllerUSB/PSXControllerUSB.ino | 99 ++++++++++++++++ PSXControllerUSB/Psx.cpp | 97 ++++++++++++++++ PSXControllerUSB/Psx.h | 82 ++++++++++++++ PSXControllerUSB/README.md | 45 ++++++++ 6 files changed, 539 insertions(+) create mode 100644 PSXControllerUSB/Gamepad.cpp create mode 100644 PSXControllerUSB/Gamepad.h create mode 100644 PSXControllerUSB/PSXControllerUSB.ino create mode 100644 PSXControllerUSB/Psx.cpp create mode 100644 PSXControllerUSB/Psx.h create mode 100644 PSXControllerUSB/README.md diff --git a/PSXControllerUSB/Gamepad.cpp b/PSXControllerUSB/Gamepad.cpp new file mode 100644 index 0000000..7fe0edf --- /dev/null +++ b/PSXControllerUSB/Gamepad.cpp @@ -0,0 +1,156 @@ +/* Gamepad.cpp + * + * Based on the advanced HID library for Arduino: + * https://github.com/NicoHood/HID + * Copyright (c) 2014-2015 NicoHood + * + * Copyright (c) 2021 Hendrik Osterholz + * + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ +#include "Gamepad.h" + +static const uint8_t _hidReportDescriptor[] PROGMEM = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x04, // USAGE (Joystick) (Maybe change to gamepad? I don't think so but...) + 0xa1, 0x01, // COLLECTION (Application) + 0xa1, 0x00, // COLLECTION (Physical) + + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x0c, // USAGE_MAXIMUM (Button 12) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x0c, // REPORT_COUNT (12) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x95, 0x01, // REPORT_COUNT (1) ; pad out the bits into a number divisible by 8 + 0x75, 0x04, // REPORT_SIZE (4) + 0x81, 0x03, // INPUT (Const,Var,Abs) + + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x01, // USAGE (pointer) + 0xa1, 0x00, // COLLECTION (Physical) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0xff, // LOGICAL_MINIMUM (-1) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION +}; + +Gamepad_::Gamepad_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1) +{ + epType[0] = EP_TYPE_INTERRUPT_IN; + PluggableUSB().plug(this); +} + +int Gamepad_::getInterface(uint8_t* interfaceCount) +{ + *interfaceCount += 1; // uses 1 + HIDDescriptor hidInterface = { + D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), + D_HIDREPORT(sizeof(_hidReportDescriptor)), + D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) + }; + return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); +} + +int Gamepad_::getDescriptor(USBSetup& setup) +{ + // Check if this is a HID Class Descriptor request + if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; } + if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { return 0; } + + // In a HID Class Descriptor wIndex cointains the interface number + if (setup.wIndex != pluggedInterface) { return 0; } + + // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol + // due to the USB specs, but Windows and Linux just assumes its in report mode. + protocol = HID_REPORT_PROTOCOL; + + return USB_SendControl(TRANSFER_PGM, _hidReportDescriptor, sizeof(_hidReportDescriptor)); +} + +bool Gamepad_::setup(USBSetup& setup) +{ + if (pluggedInterface != setup.wIndex) { + return false; + } + + uint8_t request = setup.bRequest; + uint8_t requestType = setup.bmRequestType; + + if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) + { + if (request == HID_GET_REPORT) { + // TODO: HID_GetReport(); + return true; + } + if (request == HID_GET_PROTOCOL) { + // TODO: Send8(protocol); + return true; + } + } + + if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) + { + if (request == HID_SET_PROTOCOL) { + protocol = setup.wValueL; + return true; + } + if (request == HID_SET_IDLE) { + idle = setup.wValueL; + return true; + } + if (request == HID_SET_REPORT) + { + } + } + + return false; +} + +void Gamepad_::reset() +{ + _GamepadReport.X = 0; + _GamepadReport.Y = 0; + _GamepadReport.buttons = 0; + this->send(); +} + +void Gamepad_::send() +{ + USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &_GamepadReport, sizeof(GamepadReport)); +} + +uint8_t Gamepad_::getShortName(char *name) +{ + if(!next) + { + strcpy(name, gp_serial); + return strlen(name); + } + return 0; +} diff --git a/PSXControllerUSB/Gamepad.h b/PSXControllerUSB/Gamepad.h new file mode 100644 index 0000000..9009a65 --- /dev/null +++ b/PSXControllerUSB/Gamepad.h @@ -0,0 +1,60 @@ +/* Gamepad.h + * + * Based on the advanced HID library for Arduino: + * https://github.com/NicoHood/HID + * Copyright (c) 2014-2015 NicoHood + * + * Copyright (c) 2021 Hendrik Osterholz + * + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#pragma once + +#include "HID.h" + +extern const char* gp_serial; + +typedef struct { + uint16_t buttons; + int8_t X; + int8_t Y; +} GamepadReport; + + +class Gamepad_ : public PluggableUSBModule +{ + private: + uint8_t reportId; + + protected: + int getInterface(uint8_t* interfaceCount); + int getDescriptor(USBSetup& setup); + uint8_t getShortName(char *name); + bool setup(USBSetup& setup); + + uint8_t epType[1]; + uint8_t protocol; + uint8_t idle; + + public: + GamepadReport _GamepadReport; + Gamepad_(void); + void reset(void); + void send(); +}; diff --git a/PSXControllerUSB/PSXControllerUSB.ino b/PSXControllerUSB/PSXControllerUSB.ino new file mode 100644 index 0000000..a8c7ba5 --- /dev/null +++ b/PSXControllerUSB/PSXControllerUSB.ino @@ -0,0 +1,99 @@ +/* DaemonBite PSX USB Adapter + * Author: Hendrik Osterholz + * + * Copyright (c) 2021 Hendrik Osterholz + * + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#include "Gamepad.h" + +#define GAMEPAD_COUNT 1 // Set to 1 or 2 depending if you want to make a 1 or 2 port adapter +#define GAMEPAD_COUNT_MAX 4 + +// ATT: 20 chars max (including NULL at the end) according to Arduino source code. +// Additionally serial number is used to differentiate arduino projects to have different button maps! +const char *gp_serial = "PSX to USB"; + +#include "Psx.h" + +#define dataPin 18 +#define cmndPin 2 +#define attPin 3 +#define clockPin 4 + +/* ------------------------------------------------------------------------- +PSX controller socket (looking face-on at the front of the socket): +_________________________ +| 9 8 7 | 6 5 4 | 3 2 1 | +\_______________________/ + +PSX controller plug (looking face-on at the front of the controller plug): +_________________________ +| 1 2 3 | 4 5 6 | 7 8 9 | +\_______________________/ + +PSX Arduino Pro Micro +-------------------------------------- +1 DATA A0 PF7 +2 CMD 2 PD1 +3 +7.6V +4 GND GND +5 VCC VCC +6 ATT 3 PD0 +7 CLK 4 PD4 +8 N/C +9 ACK + +------------------------------------------------------------------------- */ + +Psx Psx; + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint16_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint16_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint8_t gp = 0; + +void setup() +{ + Psx.setupPins(dataPin, cmndPin, attPin, clockPin, 10); +} + +void loop() { while(1) +{ + // Send data to USB if values have changed + for(gp=0; gp> 4; + Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & psxDown) >> 1) - ((buttons[gp] & psxUp) >> 3); + Gamepad[gp]._GamepadReport.X = ((buttons[gp] & psxRight) >> 2) - (buttons[gp] & psxLeft); + buttonsPrev[gp] = buttons[gp]; + Gamepad[gp].send(); + } + } + +}} diff --git a/PSXControllerUSB/Psx.cpp b/PSXControllerUSB/Psx.cpp new file mode 100644 index 0000000..22939da --- /dev/null +++ b/PSXControllerUSB/Psx.cpp @@ -0,0 +1,97 @@ +/* PSX Controller Decoder Library (Psx.cpp) + Written by: Kevin Ahrendt June 22nd, 2008 + + Controller protocol implemented using Andrew J McCubbin's analysis. + http://www.gamesx.com/controldata/psxcont/psxcont.htm + + Shift command is based on tutorial examples for ShiftIn and ShiftOut + functions both written by Carlyn Maw and Tom Igoe + http://www.arduino.cc/en/Tutorial/ShiftIn + http://www.arduino.cc/en/Tutorial/ShiftOut + + This program 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. + + This program 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 this program. If not, see . +*/ +#include "Psx.h" + +Psx::Psx() +{ +} + +byte Psx::shift(byte _dataOut) // Does the actual shifting, both in and out simultaneously +{ + _temp = 0; + _dataIn = 0; + + for (_i = 0; _i <= 7; _i++) + { + + + if ( _dataOut & (1 << _i) ) digitalWrite(_cmndPin, HIGH); // Writes out the _dataOut bits + else digitalWrite(_cmndPin, LOW); + + digitalWrite(_clockPin, LOW); + + delayMicroseconds(_delay); + + _temp = digitalRead(_dataPin); // Reads the data pin + if (_temp) + { + _dataIn = _dataIn | (B10000000 >> _i); // Shifts the read data into _dataIn + } + + digitalWrite(_clockPin, HIGH); + delayMicroseconds(_delay); + } + return _dataIn; +} + + +void Psx::setupPins(byte dataPin, byte cmndPin, byte attPin, byte clockPin, byte delay) +{ + pinMode(dataPin, INPUT); + digitalWrite(dataPin, HIGH); // Turn on internal pull-up + _dataPin = dataPin; + + pinMode(cmndPin, OUTPUT); + _cmndPin = cmndPin; + + pinMode(attPin, OUTPUT); + _attPin = attPin; + digitalWrite(_attPin, HIGH); + + pinMode(clockPin, OUTPUT); + _clockPin = clockPin; + digitalWrite(_clockPin, HIGH); + + _delay = delay; +} + + +unsigned int Psx::read() +{ + digitalWrite(_attPin, LOW); + + shift(0x01); + shift(0x42); + shift(0xFF); + + _data1 = ~shift(0xFF); + _data2 = ~shift(0xFF); + + digitalWrite(_attPin, HIGH); + + _dataOut = (_data2 << 8) | _data1; + + return _dataOut; +} diff --git a/PSXControllerUSB/Psx.h b/PSXControllerUSB/Psx.h new file mode 100644 index 0000000..6b7b99c --- /dev/null +++ b/PSXControllerUSB/Psx.h @@ -0,0 +1,82 @@ +/* PSX Controller Decoder Library (Psx.h) + Written by: Kevin Ahrendt June 22nd, 2008 + + Controller protocol implemented using Andrew J McCubbin's analysis. + http://www.gamesx.com/controldata/psxcont/psxcont.htm + + Shift command is based on tutorial examples for ShiftIn and ShiftOut + functions both written by Carlyn Maw and Tom Igoe + http://www.arduino.cc/en/Tutorial/ShiftIn + http://www.arduino.cc/en/Tutorial/ShiftOut + + This program 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. + + This program 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 this program. If not, see . +*/ + + +#ifndef Psx_h +#define Psx_h + +#include "Arduino.h" + +// Button Hex Representations: +#define psxLeft 0x0001 +#define psxDown 0x0002 +#define psxRight 0x0004 +#define psxUp 0x0008 +#define psxStrt 0x0010 +#define psxSlct 0x0080 + +#define psxSqu 0x0100 +#define psxX 0x0200 +#define psxO 0x0400 +#define psxTri 0x0800 +#define psxR1 0x1000 +#define psxL1 0x2000 +#define psxR2 0x4000 +#define psxL2 0x8000 + + +class Psx +{ + public: + Psx(); + void setupPins(byte , byte , byte , byte , byte ); // (Data Pin #, CMND Pin #, ATT Pin #, CLK Pin #, Delay) + // Delay is how long the clock goes without changing state + // in Microseconds. It can be lowered to increase response, + // but if it is too low it may cause glitches and have some + // keys spill over with false-positives. A regular PSX controller + // works fine at 50 uSeconds. + + unsigned int read(); // Returns the status of the button presses in an unsignd int. + // The value returned corresponds to each key as defined above. + + private: + byte shift(byte _dataOut); + + byte _dataPin; + byte _cmndPin; + byte _attPin; + byte _clockPin; + + byte _delay; + byte _i; + boolean _temp; + byte _dataIn; + + byte _data1; + byte _data2; + unsigned int _dataOut; +}; + +#endif diff --git a/PSXControllerUSB/README.md b/PSXControllerUSB/README.md new file mode 100644 index 0000000..2ee7da4 --- /dev/null +++ b/PSXControllerUSB/README.md @@ -0,0 +1,45 @@ +# DaemonBite PSX Controller To USB Adapter +## Introduction +This is a simple to build adapter for connecting PSX controllers to USB. Currently it supports normal digital PSX controllers, analog sticks are not supported. + +The input lag for this adapter is minimal (about 0.75ms average connected to MiSTer). + +## Parts you need +- Arduino Pro Micro (ATMega32U4) +- Male end of a PSX controller extension cable +- Heat shrink tube (Ø ~20mm) +- Micro USB cable + +## Wiring +PSX controller socket (looking face-on at the front of the socket): +``` +_________________________ +| 9 8 7 | 6 5 4 | 3 2 1 | +\_______________________/ +``` + +PSX controller plug (looking face-on at the front of the controller plug): +``` +_________________________ +| 1 2 3 | 4 5 6 | 7 8 9 | +\_______________________/ +``` + +| PSX | Arduino Pro Micro | +| ------ | ------ | +| 1 DATA | A0 PF7 | +| 2 CMD | 2 PD1 | +| 3 +7.6V || +| 4 GND | GND | +| 5 VCC | VCC | +| 6 ATT | 3 PD0 | +| 7 CLK | 4 PD4 | +| 8 N/C || +| 9 ACK || + + +## License +This project is licensed under the GNU General Public License v3.0. + +## Credits +PSX Library from Arduino Playground: https://playground.arduino.cc/Main/PSXLibrary/ \ No newline at end of file