Browse Source

Completed, working, and tested Sega Saturn support

master
Travis Burtrum 5 months ago
parent
commit
4f6a0693e6
3 changed files with 311 additions and 179 deletions
  1. +10
    -9
      platformio.ini
  2. +229
    -168
      src/SegaSaturn.cpp
  3. +72
    -2
      src/gamepad/common.h

+ 10
- 9
platformio.ini View File

@ -327,6 +327,7 @@ lib_deps = ${in-gc-micro.lib_deps}, ${out-switchusb.lib_deps}
[in-saturn]
src_filter = -<*> +<SegaSaturn.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=6
lib_deps = watterott/digitalWriteFast @ 1.0.0
[env:esp32-saturn-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}
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]
extends = micro, in-saturn, out-radio
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]
extends = micro, in-saturn, out-usbradio
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]
extends = micro, in-saturn, out-switchusb
src_filter = ${in-saturn.src_filter} ${out-switchusb.src_filter}
build_flags = ${in-saturn.build_flags} ${out-switchusb.build_flags}
lib_deps = ${in-saturn.lib_deps}, ${out-switchusb.lib_deps}
# wii input
@ -466,7 +467,7 @@ src_filter = ${ohm.src_filter}
extends = micro, ohm
src_filter = ${ohm.src_filter}
[env:native]
src_filter = -<*> +<test_N64Esp32.cpp>
platform = native
test_ignore = test_embedded
#[env:native]
#src_filter = -<*> +<test_N64Esp32.cpp>
#platform = native
#test_ignore = test_embedded

+ 229
- 168
src/SegaSaturn.cpp View File

@ -1,228 +1,289 @@
/* 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/>.
*
*/
/* -------------------------------------------------------------------------
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
-----------------
1 VCC - black
2 DATA1 - white
3 DATA0 - gray
4 SEL1 - blue
5 SEL0 - green
6 TL (5V) - yellow
7 DATA3 - orange
8 DATA2 - red
9 GND - brown
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
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"
#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
#define SELECT_PAUSE 10
// 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
#include "pins.h"
/* -------------------------------------------------------------------------
Saturn controller socket (looking face-on at the front of the socket):
___________________
/ 1 2 3 4 5 6 7 8 9 \
|___________________|
// 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
Saturn controller plug (looking face-on at the front of the controller plug):
___________________
/ 9 8 7 6 5 4 3 2 1 \
|___________________|
#define PX_4 OR_PIN_6
#define PX_5 OR_PIN_5
Saturn (P1) Arduino Pro Micro
--------------------------------------
1 VCC VCC - black
2 DATA1 2 PD1 - white
3 DATA0 3 PD0 - gray
4 SEL1 15 PB1 (Shared with P2) - blue
5 SEL0 14 PB3 (Shared with P2) - green
6 TL (5V) 4 PD4 - yellow
7 DATA3 TXO PD3 - orange
8 DATA2 RXI PD2 - red
9 GND 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
#if GAMEPAD_COUNT == 2
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
define needs to be uncommented.
------------------------------------------------------------------------- */
#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
GAMEPAD_CLASS gamepad;
// Controllers
uint8_t buttons[2][2] = {{0, 0}, {0, 0}};
uint8_t buttonsPrev[2][2] = {{0, 0}, {0, 0}};
uint8_t gp = 0;
ScratchGamepad currentGamepad;
// Read R, X, Y, Z
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);
buttons[0][1] |= (PIND & 0x0f) << 4;
if (GAMEPAD_COUNT == 2)
buttons[1][1] |= (PINF & 0xf0);
if (!digitalReadFast(P1_3)) {
// Z
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
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);
buttons[0][1] |= (PIND & 0x0f);
if (GAMEPAD_COUNT == 2)
buttons[1][1] |= (PINF & 0xf0) >> 4;
if (!digitalReadFast(P1_3)) {
// B
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
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);
buttons[0][0] |= (PIND & 0x0f);
if (GAMEPAD_COUNT == 2)
buttons[1][0] |= (PINF & 0xf0) >> 4;
if (!digitalReadFast(P1_3)) {
// UP
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, *, *, *
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);
buttons[0][0] |= (PIND & 0x0f) << 4;
if (GAMEPAD_COUNT == 2)
buttons[1][0] |= (PINF & 0xf0);
if (!digitalReadFast(P1_7)) {
// L
currentGamepad.press(0, BUTTON_L2);
}
#if GAMEPAD_COUNT == 2
if (!digitalReadFast(P2_7)) {
// L
currentGamepad.press(1, BUTTON_L2);
}
#endif
}
void setup() {
// Set D0-D3 as inputs and enable pull-up resistors (port1 data pins)
DDRD &= ~B00001111;
PORTD |= B00001111;
gamepad.begin();
// Set P1 data pins as inputs and enable pull-up resistors
pinMode(P1_3, INPUT_PULLUP);
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 F4-F7 as inputs and enable pull-up resistors (port2 data pins)
DDRF &= ~B11110000;
PORTF |= B11110000;
// Set P1 TL as input and enable pull-up resistor
pinMode(P1_6, INPUT_PULLUP);
digitalWrite(P1_6, HIGH);
// Set D4 and D7 as inputs and enable pull-up resistors (port1/2 TL)
DDRD &= ~B10010000;
PORTD |= B10010000;
#if GAMEPAD_COUNT == 2
// Set P2 data pins as inputs and enable pull-up resistors
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 B1 and B3 as outputs and set them HIGH (select pins)
PORTB |= B00001010;
DDRB |= B00001010;
// 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
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() {
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 < 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];
// Read all button and axes states
read3();
read2();
read1();
read4();
// Send data to USB if values have changed
for (uint8_t gp = 0; gp < GAMEPAD_COUNT; gp++) {
if (currentGamepad.changed(gp, gamepad)) {
const auto hat = gamepad.getHat(gp);
if (hat == DPAD_DOWN && gamepad.isPressed(gp, BUTTON_START)) {
gamepad.buttons(gp, 0);
gamepad.press(gp, BUTTON_MENU);
gamepad.setHatSync(gp, DPAD_CENTERED);
currentGamepad.changed(gp, gamepad);
return;
}
gamepad.setHatSync(gp, hat);
}
// Clear button data
currentGamepad.reset(gp);
}
#ifdef RETROBIT_WL
// This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter
delay(17);
// This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter
delay(17);
#endif
}
}

+ 72
- 2
src/gamepad/common.h View File

@ -4,11 +4,12 @@
#include <Arduino.h>
#define ARDUINO_ARCH_ESP32 1
#include "HIDTypes.h"
#if GAMEPAD_OUTPUT == 5 // nintendo switch
#include "HID-Project.h"
#define BUTTON_A NSButton_A
#define BUTTON_B NSButton_B
#define BUTTON_MENU NSButton_Home
@ -143,6 +144,11 @@
#define BUTTON_32 2147483648
#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
// this is used by radio gamepad to send additional info
#define GAMEPAD_REPORT_ARRAY_ADD 0
@ -246,6 +252,7 @@ static const uint8_t _hidReportDescriptor[] PROGMEM = {
class AbstractGamepad {
public:
uint32_t _buttons[GAMEPAD_COUNT];
uint8_t _dpad[GAMEPAD_COUNT];
uint8_t gamepadReport[GAMEPAD_REPORT_LEN + GAMEPAD_REPORT_ARRAY_ADD];
AbstractGamepad() {
@ -320,14 +327,77 @@ class AbstractGamepad {
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) {
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() {
}
};
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

Loading…
Cancel
Save