diff --git a/platformio.ini b/platformio.ini index a79a484..9c5e704 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ board = micro # outputs [out-debug] -build_flags = -DGAMEPAD_OUTPUT=0 +build_flags = -DGAMEPAD_OUTPUT=0 -DDEBUG=1 src_filter = + [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 = -<*> + +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 = -<*> + -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 diff --git a/src/N64.cpp b/src/N64.cpp index 0af2dac..a83b77b 100644 --- a/src/N64.cpp +++ b/src/N64.cpp @@ -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); diff --git a/src/SegaGenesis.cpp b/src/SegaGenesis.cpp new file mode 100644 index 0000000..0d6ad0c --- /dev/null +++ b/src/SegaGenesis.cpp @@ -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); + } +} diff --git a/src/util.cpp b/src/util.cpp index 16a67df..567cbec 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -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); }