From 9c4cfcbe7127deb8a468f5ec954bd9dc37055564 Mon Sep 17 00:00:00 2001 From: sorgelig Date: Mon, 17 Feb 2020 00:20:29 +0800 Subject: [PATCH] Two Paddles/Two Spinners controller added. --- PaddleTwoControllersUSB/Gamepad.cpp | 153 ++++++++++++ PaddleTwoControllersUSB/Gamepad.h | 83 +++++++ .../PaddleTwoControllersUSB.ino | 218 ++++++++++++++++++ 3 files changed, 454 insertions(+) create mode 100644 PaddleTwoControllersUSB/Gamepad.cpp create mode 100644 PaddleTwoControllersUSB/Gamepad.h create mode 100644 PaddleTwoControllersUSB/PaddleTwoControllersUSB.ino diff --git a/PaddleTwoControllersUSB/Gamepad.cpp b/PaddleTwoControllersUSB/Gamepad.cpp new file mode 100644 index 0000000..d847bce --- /dev/null +++ b/PaddleTwoControllersUSB/Gamepad.cpp @@ -0,0 +1,153 @@ +/* Gamepad.cpp + * + * Based on the advanced HID library for Arduino: + * https://github.com/NicoHood/HID + * Copyright (c) 2014-2015 NicoHood + * + * Copyright (c) 2020 Mikael Norrgård + * + * 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, 0x05, // USAGE_MAXIMUM (Button 5) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,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, 0x80, // LOGICAL_MINIMUM (-128) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 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/PaddleTwoControllersUSB/Gamepad.h b/PaddleTwoControllersUSB/Gamepad.h new file mode 100644 index 0000000..9a29c03 --- /dev/null +++ b/PaddleTwoControllersUSB/Gamepad.h @@ -0,0 +1,83 @@ +/* Gamepad.h + * + * Based on the advanced HID library for Arduino: + * https://github.com/NicoHood/HID + * Copyright (c) 2014-2015 NicoHood + * + * Copyright (c) 2020 Mikael Norrgård + * + * 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 +#include "HID.h" + +extern const char* gp_serial; + +// NOTE: To make this work on the MiSTer (or possibly other Linux distros), +// you need to edit USBDesc.h like follows. Change: +// #define ISERIAL 3 +// to +// #define ISERIAL 0 + +// The numbers after colon are bit fields, meaning how many bits the field uses. +// Remove those if there are problems +typedef struct { + union + { + struct { + bool b0: 1 ; + bool b1: 1 ; + bool b2: 1 ; + bool b3: 1 ; + bool b4: 1 ; + bool b5: 1 ; + bool b6: 1 ; + bool b7: 1 ; + }; + uint8_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); + bool setup(USBSetup& setup); + uint8_t getShortName(char *name); + + uint8_t epType[1]; + uint8_t protocol; + uint8_t idle; + + public: + GamepadReport _GamepadReport; + Gamepad_(void); + void reset(void); + void send(); +}; diff --git a/PaddleTwoControllersUSB/PaddleTwoControllersUSB.ino b/PaddleTwoControllersUSB/PaddleTwoControllersUSB.ino new file mode 100644 index 0000000..d162618 --- /dev/null +++ b/PaddleTwoControllersUSB/PaddleTwoControllersUSB.ino @@ -0,0 +1,218 @@ +/* + * A2600 Paddles/Spinners USB Adapter + * (C) Alexey Melnikov + * + * Based on project by Mikael Norrgård + * + * 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 . + * + */ + +// PADDLE: +// Paddles need a modification: connect unused 3rd pin on potentiometer (make sure it's not connected to middle pin!) +// to the ground (black wire on button). + +// SPINNER: +// Any spinner should work. Original A2600 driving controller has very low resolution, so it's better to upgrade it with some 3rd party spinner +// component with at least 80 ppr. Spinner components with clicking mechanism is not recommended as it won't be smooth. + +// Controller DB9 pins (looking face-on to the end of the plug): +// +// 5 4 3 2 1 +// 9 8 7 6 +// +// Joystick Port 1 +// DB9 Arduino Pro Micro Paddle x2 Driving x1 +// --------------------------------------------------- +// 1 TXO(1) PD3 EncA +// 2 RXI(0) PD2 EncB +// 3 3 PD0 button 1 +// 4 4 PD4 button 0 +// 5 A0 PF7 paddle 0 +// 6 6 PD7 button +// 7 VCC VCC VCC +// 8 GND GND GND +// 9 A1 PF6 paddle 1 +// +// Joystick Port 2 +// DB9 Arduino Pro Micro Driving x1 +// --------------------------------------------------- +// 1 2 PD1 EncA +// 2 7 PE6 EncB +// 3 +// 4 +// 5 +// 6 15 PB1 button +// 7 VCC VCC +// 8 GND GND +// 9 +// +// +// Note: spinners pins must support interrupts! +// + +//#define BEETLE + +// serial for special support in MiSTer +const char *gp_serial = "MiSTer PD/SP v1"; + +// pins map +#ifdef BEETLE + const int8_t encpin[2][2] = {{1,0},{3,2}}; // rotary encoder + const int8_t dbtnpin[2] = {9,15}; // driving controller button + const int8_t pbtnpin[2] = {11,10}; // paddle button + const int8_t pdlpin[2] = {A0,A1}; // paddle pot +#else + const int8_t encpin[2][2] = {{1,0},{2,7}}; // rotary encoder + const int8_t dbtnpin[2] = {6,15}; // driving controller button + const int8_t pbtnpin[2] = {4,3}; // paddle button + const int8_t pdlpin[2] = {A0,A1}; // paddle pot +#endif +//////////////////////////////////////////////////////// + +#include +#include "Gamepad.h" + +Gamepad_ Gamepad[2]; +ResponsiveAnalogRead analog[2] = {ResponsiveAnalogRead(pdlpin[0], true),ResponsiveAnalogRead(pdlpin[1], true)}; + +int8_t pdlena[2] = {0,0}; +uint16_t drvpos[2]; + +void setup() +{ + float snap = .01; + float thresh = 8.0; + + for(int idx=0; idx<2; idx++) + { + Gamepad[idx].reset(); + + pinMode(encpin[idx][0], INPUT_PULLUP); + pinMode(encpin[idx][1], INPUT_PULLUP); + pinMode(dbtnpin[idx], INPUT_PULLUP); + drv_proc(idx); + drvpos[idx] = 0; + attachInterrupt(digitalPinToInterrupt(encpin[idx][0]), idx ? drv1_isr : drv0_isr, CHANGE); + attachInterrupt(digitalPinToInterrupt(encpin[idx][1]), idx ? drv1_isr : drv0_isr, CHANGE); + + pdlena[idx] = 0; + pinMode(pbtnpin[idx], INPUT_PULLUP); + pinMode(pdlpin[idx], INPUT); + analog[idx].setSnapMultiplier(snap); + analog[idx].setActivityThreshold(thresh); + } +} + +void loop() +{ + sendState(0); + sendState(1); +} + +void drv_proc(int8_t idx) +{ + static int8_t prev[2]; + int8_t a = digitalRead(encpin[idx][0]); + int8_t b = digitalRead(encpin[idx][1]); + + int8_t spval = (b << 1) | (b^a); + int8_t diff = (prev[idx] - spval)&3; + + if(diff == 3) drvpos[idx]++; + if(diff == 1) drvpos[idx]--; + + prev[idx] = spval; +} + +void drv0_isr() +{ + drv_proc(0); +} + +void drv1_isr() +{ + drv_proc(1); +} + +void sendState(byte idx) +{ + // LEDs off + TXLED1; //RXLED1; + + analog[idx].update(); + + // paddle + int8_t newA = !digitalRead(pbtnpin[idx]); + //int8_t newB = 0; // reserved for paddles mixed in a single USB controller. + int8_t newX = 0; + int8_t newY = 0; + + // spinner + int8_t newC = !digitalRead(dbtnpin[idx]); + int8_t newR = 0; + int8_t newL = 0; + + if(newA) pdlena[idx] = 1; + if(newC) pdlena[idx] = 0; + + if(pdlena[idx]) + { + newX = (analog[idx].getValue()>>2) ^ 0x80; + } + else + { + if(!Gamepad[idx]._GamepadReport.b3 && !Gamepad[idx]._GamepadReport.b4) + { + static int prev[2] = {0,0}; + int16_t diff = drvpos[idx] - prev[idx]; + if(diff) + { + if(diff>0) + { + newR = 1; + prev[idx]++; + //Serial.println("RIGHT"); + } + else + { + newL = 1; + prev[idx]--; + //Serial.println("LEFT"); + } + } + } + } + + int8_t diff = newX - Gamepad[idx]._GamepadReport.X; + + // Only report controller state if it has changed + if (diff + || ((Gamepad[idx]._GamepadReport.b0 ^ newA) & 1) + || ((Gamepad[idx]._GamepadReport.b2 ^ newC) & 1) + || ((Gamepad[idx]._GamepadReport.b3 ^ newL) & 1) + || ((Gamepad[idx]._GamepadReport.b4 ^ newR) & 1)) + { + //if(!idx) Serial.println(newX); + Gamepad[idx]._GamepadReport.X = newX; + Gamepad[idx]._GamepadReport.b0 = newA; + Gamepad[idx]._GamepadReport.b2 = newC; + Gamepad[idx]._GamepadReport.b3 = newL; + Gamepad[idx]._GamepadReport.b4 = newR; + Gamepad[idx].send(); + } +}