New Sega Genesis impl, only working on micro for now

This commit is contained in:
Travis Burtrum 2020-12-08 00:50:05 -05:00
parent e48656ec91
commit b9d269c145
4 changed files with 405 additions and 41 deletions

View File

@ -39,7 +39,7 @@ board = micro
# outputs
[out-debug]
build_flags = -DGAMEPAD_OUTPUT=0
build_flags = -DGAMEPAD_OUTPUT=0 -DDEBUG=1
src_filter = +<gamepad/Debug-Gamepad>
[out-radio]
@ -165,11 +165,40 @@ extends = micro, in-n64, out-usbradio
src_filter = ${in-n64.src_filter} ${out-usbradio.src_filter}
build_flags = ${in-n64.build_flags} ${out-usbradio.build_flags}
# genesis input
[in-genesis]
src_filter = -<*> +<SegaGenesis.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=4
[env:micro-genesis-usb]
extends = micro, in-genesis, out-usb
src_filter = ${in-genesis.src_filter} ${out-usb.src_filter}
build_flags = ${in-genesis.build_flags} ${out-usb.build_flags}
[env:micro-genesis-debug]
extends = micro, in-genesis, out-debug
src_filter = ${in-genesis.src_filter} ${out-debug.src_filter}
build_flags = ${in-genesis.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-genesis-radio]
extends = micro, in-genesis, out-radio
src_filter = ${in-genesis.src_filter} ${out-radio.src_filter}
build_flags = ${in-genesis.build_flags} ${out-radio.build_flags} -DGAMEPAD_COUNT=1
[env:micro-genesis-usbradio]
extends = micro, in-genesis, out-usbradio
src_filter = ${in-genesis.src_filter} ${out-usbradio.src_filter}
build_flags = ${in-genesis.build_flags} ${out-usbradio.build_flags} -DGAMEPAD_COUNT=1
# debug input
[in-debug]
src_filter = -<*> +<Debug.cpp>
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=0
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=0 -DDEBUG=1
[env:esp32-debug-bt]
extends = esp32, in-debug, out-bt

View File

@ -295,31 +295,30 @@ void loop() {
// output received data to ique
//outputToiQue(&controller);
uint8_t c = 0; // for now just do 1 pad
gamepad.buttons(c, 0);
uint8_t c = 0; // for now just do 1 pad
gamepad.buttons(c, 0);
if (controller.buttonA) {
gamepad.press(c, BUTTON_A);
gamepad.press(c, BUTTON_A);
}
if (controller.buttonB) {
gamepad.press(c, BUTTON_B);
gamepad.press(c, BUTTON_B);
}
if (controller.buttonZ) {
gamepad.press(c, BUTTON_TR);
gamepad.press(c, BUTTON_TR);
}
if (controller.buttonL) {
gamepad.press(c, BUTTON_L);
gamepad.press(c, BUTTON_L);
}
if (controller.buttonR) {
gamepad.press(c, BUTTON_R);
gamepad.press(c, BUTTON_R);
}
if (controller.buttonStart) {
gamepad.press(c, BUTTON_START);
gamepad.press(c, BUTTON_START);
}
auto hat = calculateDpadDirection(controller.DPadUp, controller.DPadDown, controller.DPadLeft, controller.DPadRight);
auto cHat = dpadToAxis(calculateDpadDirection(controller.CUp, controller.CDown, controller.CLeft, controller.CRight));
// todo: need to scale max/min to our max/min
gamepad.setAxis(c, controller.xAxis, controller.yAxis, cHat.x, cHat.y, 0, 0, hat);
// todo: need to scale max/min to our max/min
gamepad.setAxis(c, controller.xAxis, controller.yAxis, cHat.x, cHat.y, 0, 0, hat);
// polling must not occur faster than every 20 ms
delay(14);

336
src/SegaGenesis.cpp Normal file
View File

@ -0,0 +1,336 @@
#include "Arduino.h"
#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif
#define BUTTON_COUNT 9
#include "gamepad/Gamepad.h"
enum
{
SC_BTN_UP = 1,
SC_BTN_DOWN = 2,
SC_BTN_LEFT = 4,
SC_BTN_RIGHT = 8,
SC_BTN_A = 16,
SC_BTN_B = 32,
SC_BTN_C = 64,
SC_BTN_X = 128,
SC_BTN_Y = 256,
SC_BTN_Z = 512,
SC_BTN_START = 1024,
SC_BTN_MODE = 2048,
SC_BTN_HOME = 4096,
SC_BIT_SH_UP = 0,
SC_BIT_SH_DOWN = 1,
SC_BIT_SH_LEFT = 2,
SC_BIT_SH_RIGHT = 3,
SC_PIN1_BIT = 0,
SC_PIN2_BIT = 1,
SC_PIN3_BIT = 2,
SC_PIN4_BIT = 3,
SC_PIN6_BIT = 4,
SC_PIN9_BIT = 5,
DB9_PIN1_BIT1 = 7,
DB9_PIN2_BIT1 = 6,
DB9_PIN3_BIT1 = 5,
DB9_PIN4_BIT1 = 4,
DB9_PIN6_BIT1 = 3,
DB9_PIN9_BIT1 = 1,
DB9_PIN1_BIT2 = 3,
DB9_PIN2_BIT2 = 2,
DB9_PIN3_BIT2 = 1,
DB9_PIN4_BIT2 = 0,
DB9_PIN6_BIT2 = 4,
DB9_PIN9_BIT2 = 7
};
//individual data pin for each controller
static const int DATA_PIN[GAMEPAD_COUNT][6] = {
{DB9_PIN1_BIT1, DB9_PIN2_BIT1, DB9_PIN3_BIT1, DB9_PIN4_BIT1, DB9_PIN6_BIT1, DB9_PIN9_BIT1},
#if GAMEPAD_COUNT > 1
{DB9_PIN1_BIT2, DB9_PIN2_BIT2, DB9_PIN3_BIT2, DB9_PIN4_BIT2, DB9_PIN6_BIT2, DB9_PIN9_BIT2},
#endif
};
// pressing one of these buttons on the controller... (read below)
static const int translateFromButton[BUTTON_COUNT] = {
SC_BTN_A,
SC_BTN_B,
SC_BTN_C,
SC_BTN_X,
SC_BTN_Y,
SC_BTN_Z,
SC_BTN_START,
SC_BTN_MODE,
SC_BTN_HOME,
};
// ... translates to one of these buttons over HID
static const int translateToHid[BUTTON_COUNT] = {
BUTTON_Y,
BUTTON_B,
BUTTON_A,
BUTTON_L,
BUTTON_X,
BUTTON_R,
BUTTON_START,
BUTTON_SELECT,
BUTTON_MENU,
};
const byte SC_CYCLE_DELAY = 10; // Delay (µs) between setting the select pin and reading the button pins
class SegaControllers32U4 {
public:
SegaControllers32U4(void);
void readState();
word currentState[GAMEPAD_COUNT];
// Controller previous states
word lastState[GAMEPAD_COUNT];
void poll(void (*controllerChanged)(const int controller)) {
readState();
for (int c = 0; c < GAMEPAD_COUNT; c++) {
if (currentState[c] != lastState[c]) {
controllerChanged(c);
lastState[c] = currentState[c];
}
}
}
bool down(int controller, int button) const {
return currentState[controller] & button;
}
#ifdef DEBUG
void printState(byte gp) {
auto cs = currentState[gp];
//Serial.println(cs);
//Serial.print((cs & SC_CTL_ON) ? "+" : "-");
Serial.print((cs & SC_BTN_UP) ? "U" : "0");
Serial.print((cs & SC_BTN_DOWN) ? "D" : "0");
Serial.print((cs & SC_BTN_LEFT) ? "L" : "0");
Serial.print((cs & SC_BTN_RIGHT) ? "R" : "0");
Serial.print((cs & SC_BTN_START) ? "S" : "0");
Serial.print((cs & SC_BTN_A) ? "A" : "0");
Serial.print((cs & SC_BTN_B) ? "B" : "0");
Serial.print((cs & SC_BTN_C) ? "C" : "0");
Serial.print((cs & SC_BTN_X) ? "X" : "0");
Serial.print((cs & SC_BTN_Y) ? "Y" : "0");
Serial.print((cs & SC_BTN_Z) ? "Z" : "0");
Serial.print((cs & SC_BTN_MODE) ? "M" : "0");
Serial.print((cs & SC_BTN_HOME) ? "H" : "0");
Serial.println(" sending");
}
#endif
private:
void readPort(byte c, byte reg1, byte reg2);
boolean _pinSelect;
byte _ignoreCycles[GAMEPAD_COUNT];
boolean _connected[GAMEPAD_COUNT];
boolean _sixButtonMode[GAMEPAD_COUNT];
byte _inputReg1;
byte _inputReg2;
#if GAMEPAD_COUNT > 1
byte _inputReg3;
#endif
};
SegaControllers32U4::SegaControllers32U4(void) {
// Setup input pins (A0,A1,A2,A3,14,15 or PF7,PF6,PF5,PF4,PB3,PB1)
DDRF &= ~B11110000; // input
PORTF |= B11110000; // high to enable internal pull-up
DDRB &= ~B00001010; // input
PORTB |= B00001010; // high to enable internal pull-up
// Setup input pins (TXO,RXI,2,3,4,6 or PD3,PD2,PD1,PD0,PD4,PD7)
DDRD &= ~B10011111; // input
PORTD |= B10011111; // high to enable internal pull-up
DDRC |= B01000000; // Select pins as output
DDRE |= B01000000;
PORTC |= B01000000; // Select pins high
PORTE |= B01000000;
_pinSelect = true;
for (int c = 0; c < GAMEPAD_COUNT; c++) {
currentState[c] = 0;
lastState[c] = 0;
_connected[c] = 0;
_sixButtonMode[c] = false;
_ignoreCycles[c] = 0;
}
}
void SegaControllers32U4::readState() {
// Set the select pins low/high
_pinSelect = !_pinSelect;
if (!_pinSelect) {
PORTE &= ~B01000000;
PORTC &= ~B01000000;
} else {
PORTE |= B01000000;
PORTC |= B01000000;
}
// Short delay to stabilise outputs in controller
delayMicroseconds(SC_CYCLE_DELAY);
// Read all input registers
_inputReg1 = PINF;
_inputReg2 = PINB;
#if GAMEPAD_COUNT > 1
_inputReg3 = PIND;
#endif
readPort(0, _inputReg1, _inputReg2);
#if GAMEPAD_COUNT > 1
readPort(1, _inputReg3, _inputReg3);
#endif
}
// "Normal" Six button controller reading routine, done a bit differently in this project
// Cycle TH out TR in TL in D3 in D2 in D1 in D0 in
// 0 LO Start A 0 0 Down Up
// 1 HI C B Right Left Down Up
// 2 LO Start A 0 0 Down Up (Check connected and read Start and A in this cycle)
// 3 HI C B Right Left Down Up (Read B, C and directions in this cycle)
// 4 LO Start A 0 0 0 0 (Check for six button controller in this cycle)
// 5 HI C B Mode X Y Z (Read X,Y,Z and Mode in this cycle)
// 6 LO --- --- --- --- --- Home (Home only for 8bitdo wireless gamepads)
// 7 HI --- --- --- --- --- ---
void SegaControllers32U4::readPort(byte c, byte reg1, byte reg2) {
if (_ignoreCycles[c] <= 0) {
if (_pinSelect) // Select pin is HIGH
{
if (_connected[c]) {
// Check if six button mode is active
if (_sixButtonMode[c]) {
// Read input pins for X, Y, Z, Mode
(bitRead(reg1, DATA_PIN[c][0]) == LOW) ? currentState[c] |= SC_BTN_Z : currentState[c] &= ~SC_BTN_Z;
(bitRead(reg1, DATA_PIN[c][1]) == LOW) ? currentState[c] |= SC_BTN_Y : currentState[c] &= ~SC_BTN_Y;
(bitRead(reg1, DATA_PIN[c][2]) == LOW) ? currentState[c] |= SC_BTN_X : currentState[c] &= ~SC_BTN_X;
(bitRead(reg1, DATA_PIN[c][3]) == LOW) ? currentState[c] |= SC_BTN_MODE : currentState[c] &= ~SC_BTN_MODE;
_sixButtonMode[c] = false;
_ignoreCycles[c] = 2; // Ignore the two next cycles (cycles 6 and 7 in table above)
} else {
// Read input pins for Up, Down, Left, Right, B, C
(bitRead(reg1, DATA_PIN[c][0]) == LOW) ? currentState[c] |= SC_BTN_UP : currentState[c] &= ~SC_BTN_UP;
(bitRead(reg1, DATA_PIN[c][1]) == LOW) ? currentState[c] |= SC_BTN_DOWN : currentState[c] &= ~SC_BTN_DOWN;
(bitRead(reg1, DATA_PIN[c][2]) == LOW) ? currentState[c] |= SC_BTN_LEFT : currentState[c] &= ~SC_BTN_LEFT;
(bitRead(reg1, DATA_PIN[c][3]) == LOW) ? currentState[c] |= SC_BTN_RIGHT : currentState[c] &= ~SC_BTN_RIGHT;
(bitRead(reg2, DATA_PIN[c][4]) == LOW) ? currentState[c] |= SC_BTN_B : currentState[c] &= ~SC_BTN_B;
(bitRead(reg2, DATA_PIN[c][5]) == LOW) ? currentState[c] |= SC_BTN_C : currentState[c] &= ~SC_BTN_C;
}
} else // No Mega Drive controller is connected, use SMS/Atari mode
{
// Clear current state
currentState[c] = 0;
// Read input pins for Up, Down, Left, Right, Fire1, Fire2
if (bitRead(reg1, DATA_PIN[c][0]) == LOW) {
currentState[c] |= SC_BTN_UP;
}
if (bitRead(reg1, DATA_PIN[c][1]) == LOW) {
currentState[c] |= SC_BTN_DOWN;
}
if (bitRead(reg1, DATA_PIN[c][2]) == LOW) {
currentState[c] |= SC_BTN_LEFT;
}
if (bitRead(reg1, DATA_PIN[c][3]) == LOW) {
currentState[c] |= SC_BTN_RIGHT;
}
if (bitRead(reg2, DATA_PIN[c][4]) == LOW) {
currentState[c] |= SC_BTN_A;
}
if (bitRead(reg2, DATA_PIN[c][5]) == LOW) {
currentState[c] |= SC_BTN_B;
}
}
} else // Select pin is LOW
{
// Check if a controller is connected
_connected[c] = (bitRead(reg1, DATA_PIN[c][2]) == LOW && bitRead(reg1, DATA_PIN[c][3]) == LOW);
// Check for six button mode
_sixButtonMode[c] = (bitRead(reg1, DATA_PIN[c][0]) == LOW && bitRead(reg1, DATA_PIN[c][1]) == LOW);
// Read input pins for A and Start
if (_connected[c]) {
if (!_sixButtonMode[c]) {
(bitRead(reg2, DATA_PIN[c][4]) == LOW) ? currentState[c] |= SC_BTN_A : currentState[c] &= ~SC_BTN_A;
(bitRead(reg2, DATA_PIN[c][5]) == LOW) ? currentState[c] |= SC_BTN_START : currentState[c] &= ~SC_BTN_START;
}
}
}
} else {
if (_ignoreCycles[c]-- == 2) // Decrease the ignore cycles counter and read 8bitdo home in first "ignored" cycle, this cycle is unused on normal 6-button controllers
{
(bitRead(reg1, DATA_PIN[c][0]) == LOW) ? currentState[c] |= SC_BTN_HOME : currentState[c] &= ~SC_BTN_HOME;
}
}
}
SegaControllers32U4 controllers;
GAMEPAD_CLASS gamepad;
void setup() {
Serial.begin(115200);
gamepad.begin();
}
void controllerChanged(const int c) {
#ifdef DEBUG
controllers.printState(c);
#endif
gamepad.buttons(c, 0);
// if start and select are held at the same time, send menu and only menu
if (controllers.down(c, SC_BTN_START) && controllers.down(c, SC_BTN_DOWN)) {
gamepad.press(c, BUTTON_MENU);
} else {
// actually send buttons held
for (uint8_t btn = 0; btn < BUTTON_COUNT; btn++) {
if (controllers.down(c, translateFromButton[btn])) {
gamepad.press(c, translateToHid[btn]);
}
}
}
if (controllers.down(c, SC_BTN_DOWN)) {
if (controllers.down(c, SC_BTN_RIGHT)) {
gamepad.setHatSync(c, DPAD_DOWN_RIGHT);
} else if (controllers.down(c, SC_BTN_LEFT)) {
gamepad.setHatSync(c, DPAD_DOWN_LEFT);
} else {
gamepad.setHatSync(c, DPAD_DOWN);
}
} else if (controllers.down(c, SC_BTN_UP)) {
if (controllers.down(c, SC_BTN_RIGHT)) {
gamepad.setHatSync(c, DPAD_UP_RIGHT);
} else if (controllers.down(c, SC_BTN_LEFT)) {
gamepad.setHatSync(c, DPAD_UP_LEFT);
} else {
gamepad.setHatSync(c, DPAD_UP);
}
} else if (controllers.down(c, SC_BTN_RIGHT)) {
gamepad.setHatSync(c, DPAD_RIGHT);
} else if (controllers.down(c, SC_BTN_LEFT)) {
gamepad.setHatSync(c, DPAD_LEFT);
} else {
gamepad.setHatSync(c, DPAD_CENTERED);
}
}
void loop() {
if (gamepad.isConnected()) {
controllers.poll(controllerChanged);
}
}

View File

@ -26,38 +26,38 @@ uint8_t calculateDpadDirection(const bool up, const bool down, const bool left,
}
struct Axis {
int16_t x;
int16_t y;
int16_t x;
int16_t y;
};
struct Axis axis(int16_t x, int16_t y) {
Axis axis;
axis.x = x;
axis.y = y;
return axis;
Axis axis;
axis.x = x;
axis.y = y;
return axis;
}
struct Axis dpadToAxis(uint8_t dpad) {
switch(dpad) {
case DPAD_CENTER:
return axis(AXIS_CENTER, AXIS_CENTER);
case DPAD_UP:
return axis(AXIS_CENTER, AXIS_MIN);
case DPAD_UP_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_RIGHT:
return axis(AXIS_MAX, AXIS_CENTER);
case DPAD_DOWN_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_DOWN:
return axis(AXIS_CENTER, AXIS_MAX);
case DPAD_DOWN_LEFT:
return axis(AXIS_MIN, AXIS_MAX);
case DPAD_LEFT:
return axis(AXIS_MIN, AXIS_CENTER);
case DPAD_UP_LEFT:
return axis(AXIS_MIN, AXIS_MIN);
}
// todo: panic here?
return axis(AXIS_CENTER, AXIS_CENTER);
switch (dpad) {
case DPAD_CENTER:
return axis(AXIS_CENTER, AXIS_CENTER);
case DPAD_UP:
return axis(AXIS_CENTER, AXIS_MIN);
case DPAD_UP_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_RIGHT:
return axis(AXIS_MAX, AXIS_CENTER);
case DPAD_DOWN_RIGHT:
return axis(AXIS_MAX, AXIS_MAX);
case DPAD_DOWN:
return axis(AXIS_CENTER, AXIS_MAX);
case DPAD_DOWN_LEFT:
return axis(AXIS_MIN, AXIS_MAX);
case DPAD_LEFT:
return axis(AXIS_MIN, AXIS_CENTER);
case DPAD_UP_LEFT:
return axis(AXIS_MIN, AXIS_MIN);
}
// todo: panic here?
return axis(AXIS_CENTER, AXIS_CENTER);
}