diff --git a/SaturnControllerUSB/Gamepad.cpp b/SaturnControllerUSB/Gamepad.cpp new file mode 100644 index 0000000..77bf764 --- /dev/null +++ b/SaturnControllerUSB/Gamepad.cpp @@ -0,0 +1,157 @@ +/* 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, 0x09, // USAGE_MAXIMUM (Button 9) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x09, // REPORT_COUNT (9) + 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, 0x07, // REPORT_SIZE (7) + 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/SaturnControllerUSB/Gamepad.h b/SaturnControllerUSB/Gamepad.h new file mode 100644 index 0000000..1b7d948 --- /dev/null +++ b/SaturnControllerUSB/Gamepad.h @@ -0,0 +1,63 @@ +/* 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; + +// The numbers after colon are bit fields, meaning how many bits the field uses. +// Remove those if there are problems +typedef struct { + uint16_t buttons : 9; + 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/SaturnControllerUSB/README.md b/SaturnControllerUSB/README.md new file mode 100644 index 0000000..c3a2c9d --- /dev/null +++ b/SaturnControllerUSB/README.md @@ -0,0 +1,55 @@ +# DaemonBite Saturn Controller To USB Adapter +## Introduction +This is a simple to build adapter for connecting SEGA Saturn controllers to USB. Currently it supports normal digital Saturn controllers, 3D controllers 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 Saturn controller extension cable +- Heat shrink tube (Ø ~20mm) +- Micro USB cable + +## Wiring +Saturn controller socket (looking face-on at the front of the socket): +___________________ +/ 1 2 3 4 5 6 7 8 9 \ +|___________________| + +Saturn controller plug (looking face-on at the front of the controller plug): +___________________ +/ 9 8 7 6 5 4 3 2 1 \ +|___________________| + +| Saturn (P1) | Arduino Pro Micro | +| ------ | ------ | +| 1 VCC | VCC | +| 2 DATA1 | 2 PD1 | +| 3 DATA0 | 3 PD0 | +| 4 SEL1 | 15 PB1 (Shared with P2) | +| 5 SEL0 | 14 PB3 (Shared with P2) | +| 6 TL (5V) | 4 PD4 | +| 7 DATA3 | TXO PD3 | +| 8 DATA2 | RXI PD2 | +| 9 GND | GND | + +| Saturn (P2) | Arduino Pro Micro +| ------ | ------ | +| 1 VCC | VCC | +| 2 DATA1 | A2 PF5 | +| 3 DATA0 | A3 PF4 | +| 4 SEL1 | 15 PB1 (Shared with P1) | +| 5 SEL0 | 14 PB3 (Shared with P1) | +| 6 TL (5V) | 6 PD7 | +| 7 DATA3 | A0 PF7 | +| 8 DATA2 | A1 PF6 | +| 9 GND | GND | + +## How to assemble (please ignore the switch) +Check the SegaControllerUSB readme for an idea how to assemble. + +## Retro Bit 2.4GHz wireless controller +The Retro Bit 2.4GHz wireless controller is officially not supported but you can enable support for it in the code (with a huge increase in lag as a side effect).The receiver of the Retro Bit 2.4GHz controller needs to be plugged in after the adapter has been connected to USB and the RETROBIT define needs to be uncommented. + +## License +This project is licensed under the GNU General Public License v3.0. diff --git a/SaturnControllerUSB/SaturnControllerUSB.ino b/SaturnControllerUSB/SaturnControllerUSB.ino new file mode 100644 index 0000000..2eb21da --- /dev/null +++ b/SaturnControllerUSB/SaturnControllerUSB.ino @@ -0,0 +1,185 @@ +/* DaemonBite Saturn USB Adapter + * Author: Mikael Norrgård + * + * 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" + +#define GAMEPAD_COUNT 1 // Set to 1 or 2 depending if you want to make a 1 or 2 port adapter +#define SELECT_PAUSE 3 // How many microseconds to wait after setting select lines? (2µs is enough according to the Saturn developer's manual) +//#define RETROBIT // Uncomment to support the Retro Bit 2.4GHz controller (this will increase lag a lot) + +#define UP 0x01 +#define DOWN 0x02 +#define LEFT 0x04 +#define RIGHT 0x08 + +// 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 = "SATURN to USB"; + +/* ------------------------------------------------------------------------- +Saturn controller socket (looking face-on at the front of the socket): +___________________ +/ 1 2 3 4 5 6 7 8 9 \ +|___________________| + +Saturn controller plug (looking face-on at the front of the controller plug): +___________________ +/ 9 8 7 6 5 4 3 2 1 \ +|___________________| + +Saturn (P1) Arduino Pro Micro +-------------------------------------- +1 VCC VCC +2 DATA1 2 PD1 +3 DATA0 3 PD0 +4 SEL1 15 PB1 (Shared with P2) +5 SEL0 14 PB3 (Shared with P2) +6 TL (5V) 4 PD4 +7 DATA3 TXO PD3 +8 DATA2 RXI PD2 +9 GND GND + +Saturn (P2) Arduino Pro Micro +-------------------------------------- +1 VCC VCC +2 DATA1 A2 PF5 +3 DATA0 A3 PF4 +4 SEL1 15 PB1 (Shared with P1) +5 SEL0 14 PB3 (Shared with P1) +6 TL (5V) 6 PD7 +7 DATA3 A0 PF7 +8 DATA2 A1 PF6 +9 GND GND + +NOTE: The receiver of the Retro Bit 2.4GHz controller needs to be plugged + in after the adapter has been connected to USB and the RETROBIT + define needs to be uncommented. +------------------------------------------------------------------------- */ + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint8_t buttons[2][2] = {{0,0},{0,0}}; +uint8_t buttonsPrev[2][2] = {{0,0},{0,0}}; +uint8_t gp = 0; + +void setup() +{ + // Set D0-D3 as inputs and enable pull-up resistors (port1 data pins) + DDRD &= ~B00001111; + PORTD |= B00001111; + + // Set F4-F7 as inputs and enable pull-up resistors (port2 data pins) + DDRF &= ~B11110000; + PORTF |= B11110000; + + // Set D4 and D7 as inputs and enable pull-up resistors (port1/2 TL) + DDRD &= ~B10010000; + PORTD |= B10010000; + + // Set B1 and B3 as outputs and set them HIGH (select pins) + PORTD |= B00001010; + DDRB |= B00001010; + + // Wait for the controller(s) to settle + delay(100); +} + +void loop() { while(1) +{ + // Clear button data + buttons[0][0]=0; buttons[0][1]=0; + buttons[1][0]=0; buttons[1][1]=0; + + // Read all button and axes states + read3(); + read2(); + read1(); + read4(); + + // Invert the readings so a 1 means a pressed button + buttons[0][0] = ~buttons[0][0]; buttons[0][1] = ~buttons[0][1]; + buttons[1][0] = ~buttons[1][0]; buttons[1][1] = ~buttons[1][1]; + + // Send data to USB if values have changed + for(gp=0; gp> 1) - (buttons[gp][0] & UP); + Gamepad[gp]._GamepadReport.X = ((buttons[gp][0] & RIGHT) >> 3) - ((buttons[gp][0] & LEFT) >> 2); + buttonsPrev[gp][0] = buttons[gp][0]; + buttonsPrev[gp][1] = buttons[gp][1]; + Gamepad[gp].send(); + } + } + + #ifdef RETROBIT + // This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter + delay(17); + #endif + +}} + +// Read R, X, Y, Z +void read1() +{ + PORTB &= ~B00001010; // Set select outputs to 00 + delayMicroseconds(SELECT_PAUSE); + buttons[0][1] |= (PIND & 0x0f) << 4; + if(GAMEPAD_COUNT == 2) + buttons[1][1] |= (PINF & 0xf0); +} + +// Read ST, A, C, B +void read2() +{ + PORTB ^= B00001010; // Toggle select outputs (01->10 or 10->01) + delayMicroseconds(SELECT_PAUSE); + buttons[0][1] |= (PIND & 0x0f); + if(GAMEPAD_COUNT == 2) + buttons[1][1] |= (PINF & 0xf0)>>4; +} + +// Read DR, DL, DD, DU +void read3() +{ + PORTB ^= B00000010; // Set select outputs to 10 from 11 (toggle) + delayMicroseconds(SELECT_PAUSE); + buttons[0][0] |= (PIND & 0x0f); + if(GAMEPAD_COUNT == 2) + buttons[1][0] |= (PINF & 0xf0) >> 4; +} + +// Read L, *, *, * +void read4() +{ + PORTB |= B00001010; // Set select outputs to 11 + delayMicroseconds(SELECT_PAUSE); + buttons[0][0] |= (PIND & 0x0f) << 4; + if(GAMEPAD_COUNT == 2) + buttons[1][0] |= (PINF & 0xf0); +} \ No newline at end of file