Completed, working, and tested Sega Saturn support

This commit is contained in:
Travis Burtrum 2021-02-01 22:15:44 -05:00
parent ff5273b844
commit 4f6a0693e6
3 changed files with 313 additions and 181 deletions

View File

@ -327,6 +327,7 @@ lib_deps = ${in-gc-micro.lib_deps}, ${out-switchusb.lib_deps}
[in-saturn] [in-saturn]
src_filter = -<*> +<SegaSaturn.cpp> src_filter = -<*> +<SegaSaturn.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=6 build_flags = ${common.build_flags} -DGAMEPAD_INPUT=6
lib_deps = watterott/digitalWriteFast @ 1.0.0
[env:esp32-saturn-bt] [env:esp32-saturn-bt]
extends = esp32, in-saturn, out-bt extends = esp32, in-saturn, out-bt
@ -348,23 +349,23 @@ extends = micro, in-saturn, out-debug
src_filter = ${in-saturn.src_filter} ${out-debug.src_filter} src_filter = ${in-saturn.src_filter} ${out-debug.src_filter}
build_flags = ${in-saturn.build_flags} ${out-debug.build_flags} build_flags = ${in-saturn.build_flags} ${out-debug.build_flags}
# micro doesn't have enough pins for both radio and 2 gamepads
# so these must be built with only support for 1 gamepad
[env:micro-saturn-radio] [env:micro-saturn-radio]
extends = micro, in-saturn, out-radio extends = micro, in-saturn, out-radio
src_filter = ${in-saturn.src_filter} ${out-radio.src_filter} src_filter = ${in-saturn.src_filter} ${out-radio.src_filter}
build_flags = ${in-saturn.build_flags} ${out-radio.build_flags} -DGAMEPAD_COUNT=1 build_flags = ${in-saturn.build_flags} ${out-radio.build_flags}
lib_deps = ${in-saturn.lib_deps}, ${out-radio.lib_deps}
[env:micro-saturn-usbradio] [env:micro-saturn-usbradio]
extends = micro, in-saturn, out-usbradio extends = micro, in-saturn, out-usbradio
src_filter = ${in-saturn.src_filter} ${out-usbradio.src_filter} src_filter = ${in-saturn.src_filter} ${out-usbradio.src_filter}
build_flags = ${in-saturn.build_flags} ${out-usbradio.build_flags} -DGAMEPAD_COUNT=1 build_flags = ${in-saturn.build_flags} ${out-usbradio.build_flags}
lib_deps = ${in-saturn.lib_deps}, ${out-usbradio.lib_deps}
[env:micro-saturn-switchusb] [env:micro-saturn-switchusb]
extends = micro, in-saturn, out-switchusb extends = micro, in-saturn, out-switchusb
src_filter = ${in-saturn.src_filter} ${out-switchusb.src_filter} src_filter = ${in-saturn.src_filter} ${out-switchusb.src_filter}
build_flags = ${in-saturn.build_flags} ${out-switchusb.build_flags} build_flags = ${in-saturn.build_flags} ${out-switchusb.build_flags}
lib_deps = ${in-saturn.lib_deps}, ${out-switchusb.lib_deps}
# wii input # wii input
@ -466,7 +467,7 @@ src_filter = ${ohm.src_filter}
extends = micro, ohm extends = micro, ohm
src_filter = ${ohm.src_filter} src_filter = ${ohm.src_filter}
[env:native] #[env:native]
src_filter = -<*> +<test_N64Esp32.cpp> #src_filter = -<*> +<test_N64Esp32.cpp>
platform = native #platform = native
test_ignore = test_embedded #test_ignore = test_embedded

View File

@ -1,48 +1,3 @@
/* DaemonBite Saturn USB Adapter
* Author: Mikael Norrgård <mick@daemonbite.com>
*
* Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#include "Arduino.h"
#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif
#include "gamepad/Gamepad.h"
#include "pins.h"
// How many microseconds to wait after setting select lines? (2µs is enough according to the Saturn developer's manual)
// 20µs is a "safe" value that seems to work for original Saturn controllers and Retrobit wired controllers
#define SELECT_PAUSE 20
// Uncomment to support the Retro Bit 2.4GHz wireless controller (this will increase lag a lot)
//#define RETROBIT_WL
#define UP 0x01
#define DOWN 0x02
#define LEFT 0x04
#define RIGHT 0x08
/* ------------------------------------------------------------------------- /* -------------------------------------------------------------------------
Saturn controller socket (looking face-on at the front of the socket): Saturn controller socket (looking face-on at the front of the socket):
___________________ ___________________
@ -54,175 +9,281 @@ ___________________
/ 9 8 7 6 5 4 3 2 1 \ / 9 8 7 6 5 4 3 2 1 \
|___________________| |___________________|
Saturn (P1) Arduino Pro Micro Saturn
-------------------------------------- -----------------
1 VCC VCC - black 1 VCC - black
2 DATA1 2 PD1 - white 2 DATA1 - white
3 DATA0 3 PD0 - gray 3 DATA0 - gray
4 SEL1 15 PB1 (Shared with P2) - blue 4 SEL1 - blue
5 SEL0 14 PB3 (Shared with P2) - green 5 SEL0 - green
6 TL (5V) 4 PD4 - yellow 6 TL (5V) - yellow
7 DATA3 TXO PD3 - orange 7 DATA3 - orange
8 DATA2 RXI PD2 - red 8 DATA2 - red
9 GND GND - brown 9 GND - brown
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 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_WL in after the adapter has been connected to USB and the RETROBIT_WL
define needs to be uncommented. define needs to be uncommented.
------------------------------------------------------------------------- */ ------------------------------------------------------------------------- */
#include "Arduino.h"
#include <digitalWriteFast.h>
//#define digitalWriteFast digitalWrite
//#define digitalReadFast digitalRead
#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif
#include "gamepad/Gamepad.h"
// How many microseconds to wait after setting select lines? (2µs is enough according to the Saturn developer's manual)
// 20µs is a "safe" value that seems to work for original Saturn controllers and Retrobit wired controllers
#define SELECT_PAUSE 10
// Uncomment to support the Retro Bit 2.4GHz wireless controller (this will increase lag a lot)
//#define RETROBIT_WL
#include "pins.h"
// pins
#define P1_2 OR_PIN_2
#define P1_3 OR_PIN_3
#define P1_6 OR_PIN_4
#define P1_7 OR_PIN_1
#define P1_8 OR_PIN_11
#define PX_4 OR_PIN_6
#define PX_5 OR_PIN_5
#if GAMEPAD_COUNT == 2
#define P2_2 OR_PIN_20
#define P2_3 OR_PIN_21
#define P2_6 OR_PIN_10
#define P2_7 OR_PIN_18
#define P2_8 OR_PIN_19
#endif
// Set up USB HID gamepads // Set up USB HID gamepads
GAMEPAD_CLASS gamepad; GAMEPAD_CLASS gamepad;
// Controllers ScratchGamepad currentGamepad;
uint8_t buttons[2][2] = {{0, 0}, {0, 0}};
uint8_t buttonsPrev[2][2] = {{0, 0}, {0, 0}};
uint8_t gp = 0;
// Read R, X, Y, Z // Read R, X, Y, Z
void read1() { void read1() {
PORTB &= ~B00001010; // Set select outputs to 00 // Set select outputs to 00
digitalWriteFast(PX_4, LOW);
digitalWriteFast(PX_5, LOW);
delayMicroseconds(SELECT_PAUSE); delayMicroseconds(SELECT_PAUSE);
buttons[0][1] |= (PIND & 0x0f) << 4; if (!digitalReadFast(P1_3)) {
if (GAMEPAD_COUNT == 2) // Z
buttons[1][1] |= (PINF & 0xf0); currentGamepad.press(0, BUTTON_R);
}
if (!digitalReadFast(P1_2)) {
// Y
currentGamepad.press(0, BUTTON_X);
}
if (!digitalReadFast(P1_8)) {
// X
currentGamepad.press(0, BUTTON_L);
}
if (!digitalReadFast(P1_7)) {
// R
currentGamepad.press(0, BUTTON_R2);
}
#if GAMEPAD_COUNT == 2
if (!digitalReadFast(P2_3)) {
// Z
currentGamepad.press(1, BUTTON_R);
}
if (!digitalReadFast(P2_2)) {
// Y
currentGamepad.press(1, BUTTON_X);
}
if (!digitalReadFast(P2_8)) {
// X
currentGamepad.press(1, BUTTON_L);
}
if (!digitalReadFast(P2_7)) {
// R
currentGamepad.press(1, BUTTON_R2);
}
#endif
} }
// Read ST, A, C, B // Read ST, A, C, B
void read2() { void read2() {
PORTB ^= B00001010; // Toggle select outputs (01->10 or 10->01) // Toggle select outputs (01->10 or 10->01)
digitalWriteFast(PX_4, HIGH);
digitalWriteFast(PX_5, LOW);
delayMicroseconds(SELECT_PAUSE); delayMicroseconds(SELECT_PAUSE);
buttons[0][1] |= (PIND & 0x0f); if (!digitalReadFast(P1_3)) {
if (GAMEPAD_COUNT == 2) // B
buttons[1][1] |= (PINF & 0xf0) >> 4; currentGamepad.press(0, BUTTON_B);
}
if (!digitalReadFast(P1_2)) {
// C
currentGamepad.press(0, BUTTON_A);
}
if (!digitalReadFast(P1_8)) {
// A
currentGamepad.press(0, BUTTON_Y);
}
if (!digitalReadFast(P1_7)) {
// ST
currentGamepad.press(0, BUTTON_START);
}
#if GAMEPAD_COUNT == 2
if (!digitalReadFast(P2_3)) {
// B
currentGamepad.press(1, BUTTON_B);
}
if (!digitalReadFast(P2_2)) {
// C
currentGamepad.press(1, BUTTON_A);
}
if (!digitalReadFast(P2_8)) {
// A
currentGamepad.press(1, BUTTON_Y);
}
if (!digitalReadFast(P2_7)) {
// ST
currentGamepad.press(1, BUTTON_START);
}
#endif
} }
// Read DR, DL, DD, DU // Read DR, DL, DD, DU
void read3() { void read3() {
PORTB ^= B00000010; // Set select outputs to 10 from 11 (toggle) // Set select outputs to 10 from 11 (toggle)
digitalWriteFast(PX_4, LOW);
digitalWriteFast(PX_5, HIGH);
delayMicroseconds(SELECT_PAUSE); delayMicroseconds(SELECT_PAUSE);
buttons[0][0] |= (PIND & 0x0f); if (!digitalReadFast(P1_3)) {
if (GAMEPAD_COUNT == 2) // UP
buttons[1][0] |= (PINF & 0xf0) >> 4; currentGamepad.pressDpad(0, DPAD_BIT_UP);
}
if (!digitalReadFast(P1_2)) {
// DOWN
currentGamepad.pressDpad(0, DPAD_BIT_DOWN);
}
if (!digitalReadFast(P1_8)) {
// LEFT
currentGamepad.pressDpad(0, DPAD_BIT_LEFT);
}
if (!digitalReadFast(P1_7)) {
// RIGHT
currentGamepad.pressDpad(0, DPAD_BIT_RIGHT);
}
#if GAMEPAD_COUNT == 2
if (!digitalReadFast(P2_3)) {
// UP
currentGamepad.pressDpad(1, DPAD_BIT_UP);
}
if (!digitalReadFast(P2_2)) {
// DOWN
currentGamepad.pressDpad(1, DPAD_BIT_DOWN);
}
if (!digitalReadFast(P2_8)) {
// LEFT
currentGamepad.pressDpad(1, DPAD_BIT_LEFT);
}
if (!digitalReadFast(P2_7)) {
// RIGHT
currentGamepad.pressDpad(1, DPAD_BIT_RIGHT);
}
#endif
} }
// Read L, *, *, * // Read L, *, *, *
void read4() { void read4() {
PORTB |= B00001010; // Set select outputs to 11 // Set select outputs to 11
digitalWriteFast(PX_4, HIGH);
digitalWriteFast(PX_5, HIGH);
delayMicroseconds(SELECT_PAUSE); delayMicroseconds(SELECT_PAUSE);
buttons[0][0] |= (PIND & 0x0f) << 4; if (!digitalReadFast(P1_7)) {
if (GAMEPAD_COUNT == 2) // L
buttons[1][0] |= (PINF & 0xf0); currentGamepad.press(0, BUTTON_L2);
}
#if GAMEPAD_COUNT == 2
if (!digitalReadFast(P2_7)) {
// L
currentGamepad.press(1, BUTTON_L2);
}
#endif
} }
void setup() { void setup() {
// Set D0-D3 as inputs and enable pull-up resistors (port1 data pins) gamepad.begin();
DDRD &= ~B00001111;
PORTD |= B00001111;
// Set F4-F7 as inputs and enable pull-up resistors (port2 data pins) // Set P1 data pins as inputs and enable pull-up resistors
DDRF &= ~B11110000; pinMode(P1_3, INPUT_PULLUP);
PORTF |= B11110000; pinMode(P1_2, INPUT_PULLUP);
pinMode(P1_7, INPUT_PULLUP);
pinMode(P1_8, INPUT_PULLUP);
digitalWrite(P1_3, HIGH);
digitalWrite(P1_2, HIGH);
digitalWrite(P1_7, HIGH);
digitalWrite(P1_8, HIGH);
// Set D4 and D7 as inputs and enable pull-up resistors (port1/2 TL) // Set P1 TL as input and enable pull-up resistor
DDRD &= ~B10010000; pinMode(P1_6, INPUT_PULLUP);
PORTD |= B10010000; digitalWrite(P1_6, HIGH);
// Set B1 and B3 as outputs and set them HIGH (select pins) #if GAMEPAD_COUNT == 2
PORTB |= B00001010; // Set P2 data pins as inputs and enable pull-up resistors
DDRB |= B00001010; pinMode(P2_3, INPUT_PULLUP);
pinMode(P2_2, INPUT_PULLUP);
pinMode(P2_7, INPUT_PULLUP);
pinMode(P2_8, INPUT_PULLUP);
digitalWrite(P2_3, HIGH);
digitalWrite(P2_2, HIGH);
digitalWrite(P2_7, HIGH);
digitalWrite(P2_8, HIGH);
// Set P2 TL as input and enable pull-up resistor
pinMode(P2_6, INPUT_PULLUP);
digitalWrite(P2_6, HIGH);
#endif
// Set P1+P2 select pins as outputs and set them HIGH
pinMode(PX_4, OUTPUT);
pinMode(PX_5, OUTPUT);
digitalWrite(PX_4, HIGH);
digitalWrite(PX_5, HIGH);
// Wait for the controller(s) to settle // Wait for the controller(s) to settle
delay(100); delay(100);
} }
void controllerChanged(const int c) {
// if start and down are held at the same time, send menu and only menu
gamepad.buttons(c, buttons[gp][1] | ((buttons[gp][0] & 0x80) << 1));
/* todo:
if (controllers.down(c, SC_BTN_START) && ((buttons[gp][0] & DOWN) >> 1)) {
gamepad.buttons(c, 0);
gamepad.press(c, BUTTON_MENU);
gamepad.setHatSync(c, DPAD_CENTERED);
return;
}
*/
if (((buttons[gp][0] & DOWN) >> 1)) {
if (((buttons[gp][0] & RIGHT) >> 3)) {
gamepad.setHatSync(c, DPAD_DOWN_RIGHT);
} else if (((buttons[gp][0] & LEFT) >> 2)) {
gamepad.setHatSync(c, DPAD_DOWN_LEFT);
} else {
gamepad.setHatSync(c, DPAD_DOWN);
}
} else if ((buttons[gp][0] & UP)) {
if (((buttons[gp][0] & RIGHT) >> 3)) {
gamepad.setHatSync(c, DPAD_UP_RIGHT);
} else if (((buttons[gp][0] & LEFT) >> 2)) {
gamepad.setHatSync(c, DPAD_UP_LEFT);
} else {
gamepad.setHatSync(c, DPAD_UP);
}
} else if (((buttons[gp][0] & RIGHT) >> 3)) {
gamepad.setHatSync(c, DPAD_RIGHT);
} else if (((buttons[gp][0] & LEFT) >> 2)) {
gamepad.setHatSync(c, DPAD_LEFT);
} else {
gamepad.setHatSync(c, DPAD_CENTERED);
}
}
void loop() { void loop() {
while (1) { // Read all button and axes states
// Clear button data read3();
buttons[0][0] = 0; read2();
buttons[0][1] = 0; read1();
buttons[1][0] = 0; read4();
buttons[1][1] = 0;
// Read all button and axes states // Send data to USB if values have changed
read3(); for (uint8_t gp = 0; gp < GAMEPAD_COUNT; gp++) {
read2(); if (currentGamepad.changed(gp, gamepad)) {
read1(); const auto hat = gamepad.getHat(gp);
read4(); if (hat == DPAD_DOWN && gamepad.isPressed(gp, BUTTON_START)) {
gamepad.buttons(gp, 0);
// Invert the readings so a 1 means a pressed button gamepad.press(gp, BUTTON_MENU);
buttons[0][0] = ~buttons[0][0]; gamepad.setHatSync(gp, DPAD_CENTERED);
buttons[0][1] = ~buttons[0][1]; currentGamepad.changed(gp, gamepad);
buttons[1][0] = ~buttons[1][0]; return;
buttons[1][1] = ~buttons[1][1];
// Send data to USB if values have changed
for (gp = 0; gp < GAMEPAD_COUNT; gp++) {
// Has any buttons changed state?
if (buttons[gp][0] != buttonsPrev[gp][0] || buttons[gp][1] != buttonsPrev[gp][1]) {
/*
Gamepad[gp]._GamepadReport.buttons = buttons[gp][1] | ((buttons[gp][0] & 0x80)<<1);
Gamepad[gp]._GamepadReport.Y = ((buttons[gp][0] & DOWN) >> 1) - (buttons[gp][0] & UP);
Gamepad[gp]._GamepadReport.X = ((buttons[gp][0] & RIGHT) >> 3) - ((buttons[gp][0] & LEFT) >> 2);
Gamepad[gp].send();
*/
controllerChanged(gp);
buttonsPrev[gp][0] = buttons[gp][0];
buttonsPrev[gp][1] = buttons[gp][1];
} }
gamepad.setHatSync(gp, hat);
} }
// Clear button data
currentGamepad.reset(gp);
}
#ifdef RETROBIT_WL #ifdef RETROBIT_WL
// This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter // This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter
delay(17); delay(17);
#endif #endif
}
} }

View File

@ -4,11 +4,12 @@
#include <Arduino.h> #include <Arduino.h>
#define ARDUINO_ARCH_ESP32 1
#include "HIDTypes.h" #include "HIDTypes.h"
#if GAMEPAD_OUTPUT == 5 // nintendo switch #if GAMEPAD_OUTPUT == 5 // nintendo switch
#include "HID-Project.h"
#define BUTTON_A NSButton_A #define BUTTON_A NSButton_A
#define BUTTON_B NSButton_B #define BUTTON_B NSButton_B
#define BUTTON_MENU NSButton_Home #define BUTTON_MENU NSButton_Home
@ -143,6 +144,11 @@
#define BUTTON_32 2147483648 #define BUTTON_32 2147483648
#endif #endif
#define DPAD_BIT_UP 1
#define DPAD_BIT_DOWN 2
#define DPAD_BIT_LEFT 4
#define DPAD_BIT_RIGHT 8
#ifndef GAMEPAD_REPORT_ARRAY_ADD #ifndef GAMEPAD_REPORT_ARRAY_ADD
// this is used by radio gamepad to send additional info // this is used by radio gamepad to send additional info
#define GAMEPAD_REPORT_ARRAY_ADD 0 #define GAMEPAD_REPORT_ARRAY_ADD 0
@ -246,6 +252,7 @@ static const uint8_t _hidReportDescriptor[] PROGMEM = {
class AbstractGamepad { class AbstractGamepad {
public: public:
uint32_t _buttons[GAMEPAD_COUNT]; uint32_t _buttons[GAMEPAD_COUNT];
uint8_t _dpad[GAMEPAD_COUNT];
uint8_t gamepadReport[GAMEPAD_REPORT_LEN + GAMEPAD_REPORT_ARRAY_ADD]; uint8_t gamepadReport[GAMEPAD_REPORT_LEN + GAMEPAD_REPORT_ARRAY_ADD];
AbstractGamepad() { AbstractGamepad() {
@ -320,14 +327,77 @@ class AbstractGamepad {
return ((b & _buttons[cIdx]) > 0); return ((b & _buttons[cIdx]) > 0);
} }
virtual void dpad(const uint8_t cIdx, uint8_t b) {
_dpad[cIdx] = b;
}
virtual void pressDpad(const uint8_t cIdx, uint8_t b) {
dpad(cIdx, _dpad[cIdx] | b);
}
virtual void releaseDpad(const uint8_t cIdx, uint8_t b) {
dpad(cIdx, _dpad[cIdx] & ~b);
}
virtual bool isDpadPressed(const uint8_t cIdx, uint8_t b) {
return ((b & _dpad[cIdx]) > 0);
}
virtual void sync(const uint8_t cIdx) { virtual void sync(const uint8_t cIdx) {
sendHidReport(cIdx, &gamepadReport, GAMEPAD_REPORT_LEN); sendHidReport(cIdx, &gamepadReport, GAMEPAD_REPORT_LEN);
} }
virtual void sendHidReport(const uint8_t cIdx, const void* d, int len); // actually sends report virtual void reset(const uint8_t cIdx) {
_buttons[cIdx] = 0;
_dpad[cIdx] = 0;
}
virtual char getHat(const uint8_t cIdx) {
// bit positions are B0000RLDU
switch (_dpad[cIdx]) {
case B00000000:
return DPAD_CENTER;
case B00000001:
return DPAD_UP;
case B00000010:
return DPAD_DOWN;
case B00000100:
return DPAD_LEFT;
case B00001000:
return DPAD_RIGHT;
case B00001001:
return DPAD_UP_RIGHT;
case B00001010:
return DPAD_DOWN_RIGHT;
case B00000110:
return DPAD_DOWN_LEFT;
case B00000101:
return DPAD_UP_LEFT;
}
return DPAD_CENTER;
}
// actually sends report
virtual void sendHidReport(const uint8_t cIdx, const void* d, int len) {
}
virtual ~AbstractGamepad() { virtual ~AbstractGamepad() {
} }
}; };
class ScratchGamepad : public AbstractGamepad {
public:
ScratchGamepad() : AbstractGamepad() {
}
virtual boolean changed(const uint8_t cIdx, AbstractGamepad& current) {
if (_buttons[cIdx] != current._buttons[cIdx] || _dpad[cIdx] != current._dpad[cIdx]) {
current._dpad[cIdx] = _dpad[cIdx];
current._buttons[cIdx] = _buttons[cIdx];
return true;
}
return false;
}
};
#endif // GAMEPAD_COMMON_H #endif // GAMEPAD_COMMON_H