From 849a3e694154f79a907987c5129a78c371f78cca Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 22 Dec 2020 01:45:58 -0500 Subject: [PATCH] Add Nintendo Gamecube input support --- platformio.ini | 43 +++++++++++- readme.md | 6 +- src/N64Micro.cpp | 159 +++++++++++++++++++++++++++---------------- src/gamepad/common.h | 17 ++++- src/util.cpp | 32 +++++++-- 5 files changed, 185 insertions(+), 72 deletions(-) diff --git a/platformio.ini b/platformio.ini index 03e2af2..b73b326 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ # $board-$input-$output # supported values: # $board: micro, esp32 -# $input: snes, genesis, psx, n64, radio, debug +# $input: snes, genesis, psx, n64, gc, radio, debug # $output: radio, usb, usbradio, bt, switchusb, debug # please note not all boards are compatible with all inputs/outputs # for example esp32 can only do bt @@ -194,16 +194,19 @@ build_flags = ${in-n64-micro.build_flags} ${out-debug.build_flags} extends = micro, in-n64-micro, out-radio src_filter = ${in-n64-micro.src_filter} ${out-radio.src_filter} build_flags = ${in-n64-micro.build_flags} ${out-radio.build_flags} +lib_deps = ${in-n64-micro.lib_deps}, ${out-radio.lib_deps} [env:micro-n64-usbradio] extends = micro, in-n64-micro, out-usbradio src_filter = ${in-n64-micro.src_filter} ${out-usbradio.src_filter} build_flags = ${in-n64-micro.build_flags} ${out-usbradio.build_flags} +lib_deps = ${in-n64-micro.lib_deps}, ${out-usbradio.lib_deps} [env:micro-n64-switchusb] extends = micro, in-n64-micro, out-switchusb src_filter = ${in-n64-micro.src_filter} ${out-switchusb.src_filter} build_flags = ${in-n64-micro.build_flags} ${out-switchusb.build_flags} +lib_deps = ${in-n64-micro.lib_deps}, ${out-switchusb.lib_deps} # genesis input @@ -253,7 +256,7 @@ build_flags = ${in-genesis.build_flags} ${out-switchusb.build_flags} [in-psx] src_filter = -<*> + -build_flags = ${common.build_flags} -DGAMEPAD_INPUT=3 -DGAMEPAD_COUNT=4 +build_flags = ${common.build_flags} -DGAMEPAD_INPUT=5 -DGAMEPAD_COUNT=4 [env:esp32-psx-bt] extends = esp32, in-psx, out-bt @@ -290,6 +293,42 @@ extends = micro, in-psx, out-switchusb src_filter = ${in-psx.src_filter} ${out-switchusb.src_filter} build_flags = ${in-psx.build_flags} ${out-switchusb.build_flags} +# gc input + +[in-gc-micro] +src_filter = -<*> + +build_flags = ${common.build_flags} -DGAMEPAD_INPUT=5 -DGAMEPAD_COUNT=1 -DGAMECUBE +lib_deps = https://github.com/OpenRetroPad/Nintendo + +[env:micro-gc-usb] +extends = micro, in-gc-micro, out-usb +src_filter = ${in-gc-micro.src_filter} ${out-usb.src_filter} +build_flags = ${in-gc-micro.build_flags} ${out-usb.build_flags} + +[env:micro-gc-debug] +extends = micro, in-gc-micro, out-debug +src_filter = ${in-gc-micro.src_filter} ${out-debug.src_filter} +build_flags = ${in-gc-micro.build_flags} ${out-debug.build_flags} + +[env:micro-gc-radio] +extends = micro, in-gc-micro, out-radio +src_filter = ${in-gc-micro.src_filter} ${out-radio.src_filter} +build_flags = ${in-gc-micro.build_flags} ${out-radio.build_flags} +lib_deps = ${in-gc-micro.lib_deps}, ${out-radio.lib_deps} + +[env:micro-gc-usbradio] +extends = micro, in-gc-micro, out-usbradio +src_filter = ${in-gc-micro.src_filter} ${out-usbradio.src_filter} +build_flags = ${in-gc-micro.build_flags} ${out-usbradio.build_flags} +lib_deps = ${in-gc-micro.lib_deps}, ${out-usbradio.lib_deps} + +[env:micro-gc-switchusb] +extends = micro, in-gc-micro, out-switchusb +src_filter = ${in-gc-micro.src_filter} ${out-switchusb.src_filter} +build_flags = ${in-gc-micro.build_flags} ${out-switchusb.build_flags} +lib_deps = ${in-gc-micro.lib_deps}, ${out-switchusb.lib_deps} + + # debug input [in-debug] diff --git a/readme.md b/readme.md index 9993537..66b4113 100644 --- a/readme.md +++ b/readme.md @@ -3,12 +3,16 @@ OpenRetroPad Adapt various input devices to various output devices. +Currently supported inputs: SNES/NES, Sega Genesis/Megadrive/Atari, Playstation (and PS2) Digital and Dual shock, Nintendo 64, Nintendo Gamecube + +Currently supported outputs: bluetooth-hid gamepad, usb-hid gamepad, nintendo switch usb gamepad, wireless usb-hid gamepad over radio + Build using [PlatformIO](https://platformio.org/) using `pio run` or `pio run -e $board-$input-$output` for a specific target/env. env's are laid out like `$board-$input-$output` supported values: * $board: micro, esp32 - * $input: snes, genesis, psx, n64, radio, debug + * $input: snes, genesis, psx, n64, gc, radio, debug * $output: radio, usb, usbradio, switchusb, bt, debug * please note not all boards are compatible with all inputs/outputs, for example esp32 can only do bt, micro can only do radio or usb diff --git a/src/N64Micro.cpp b/src/N64Micro.cpp index cee83a2..e244099 100644 --- a/src/N64Micro.cpp +++ b/src/N64Micro.cpp @@ -12,7 +12,7 @@ PIN # USAGE VCC +3.3V ONLY */ -#define DATA_PIN 2 +#define DATA_PIN 7 // how often to poll, 100? 14? polling must not occur faster than every 20 ms #define POLL_DELAY 14 @@ -23,23 +23,41 @@ PIN # USAGE //#define DEBUG +#ifdef GAMECUBE +#define AXIS_CENTER_IN 135 +#define AXIS_MAX_IN 230 +#define AXIS_MIN_IN 30 + +#define TRIGGER_MAX_IN 236 +#define TRIGGER_MIN_IN 36 + +#else // N64 #define AXIS_CENTER_IN 0 -#define AXIS_MAX_IN 100 -#define AXIS_MIN_IN -100 +#define AXIS_MAX_IN 80 +#define AXIS_MIN_IN -80 +#endif #include "gamepad/Gamepad.h" #include "util.cpp" -// Define a N64 Controller -CN64Controller N64Controller(DATA_PIN); +#ifdef GAMECUBE +typedef CGamecubeController NintendoController; +typedef Gamecube_Report_t ControllerReport; +#else // N64 +typedef CN64Controller NintendoController; +typedef N64_Report_t ControllerReport; +#endif -#define REPORT_SIZE 4 -uint8_t oldReport[REPORT_SIZE]; +// Define a Controller +NintendoController controller(DATA_PIN); + +#define NINTENDO_REPORT_SIZE 4 +uint8_t oldReport[NINTENDO_REPORT_SIZE]; GAMEPAD_CLASS gamepad; #ifdef DEBUG -void print_n64_report(N64_Report_t &controller, N64_Status_t &n64_status) { +void print_report(ControllerReport &controller) { Serial.print("buttons: "); Serial.print(controller.a ? "A" : "-"); Serial.print(controller.b ? "B" : "-"); @@ -47,56 +65,61 @@ void print_n64_report(N64_Report_t &controller, N64_Status_t &n64_status) { Serial.print(controller.l ? "L" : "-"); Serial.print(controller.r ? "R" : "-"); Serial.print(controller.start ? "S" : "-"); +#ifdef GAMECUBE + Serial.print(controller.y ? "Y" : "-"); + Serial.print(controller.x ? "X" : "-"); +#endif Serial.print(" DPAD: "); Serial.print(controller.dup ? "U" : "-"); Serial.print(controller.ddown ? "D" : "-"); Serial.print(controller.dleft ? "L" : "-"); Serial.print(controller.dright ? "R" : "-"); + Serial.print(" Y: "); + Serial.print(controller.yAxis); + Serial.print(" YT: "); + Serial.print(-translateAxis(controller.yAxis)); + Serial.print(" X: "); + Serial.print(controller.xAxis); + Serial.print(" XT: "); + Serial.print(translateAxis(controller.xAxis)); +#ifdef GAMECUBE + Serial.print(" CY: "); + Serial.print(controller.cyAxis); + Serial.print(" CYT: "); + Serial.print(-translateAxis(controller.cyAxis)); + Serial.print(" CX: "); + Serial.print(controller.cxAxis); + Serial.print(" CXT: "); + Serial.print(translateAxis(controller.cxAxis)); + Serial.print(" LEFT: "); + Serial.print(controller.left); + Serial.print(" TLEFT: "); + Serial.print(translateTrigger(controller.left)); + Serial.print(" RIGHT: "); + Serial.print(controller.right); + Serial.print(" TRIGHT: "); + Serial.print(translateTrigger(controller.right)); +#else // N64 Serial.print(" C: "); Serial.print(controller.cup ? "U" : "-"); Serial.print(controller.cdown ? "D" : "-"); Serial.print(controller.cleft ? "L" : "-"); Serial.print(controller.cright ? "R" : "-"); - Serial.print(" Y: "); - Serial.print(controller.yAxis); - Serial.print(" YT: "); - Serial.print(translateAxis(-controller.yAxis)); - Serial.print(" X: "); - Serial.print(controller.xAxis); - Serial.print(" XT: "); - Serial.print(translateAxis(controller.xAxis)); +#endif Serial.println(); - /* - // Print device information - Serial.print(F("Device: ")); - switch (n64_status.device) { - case NINTENDO_DEVICE_N64_NONE: - Serial.println(F("No N64 Controller found!")); - return; - break; - case NINTENDO_DEVICE_N64_WIRED: - Serial.println(F("Original Nintendo N64 Controller")); - break; - - default: - Serial.print(F("Unknown ")); - Serial.println(n64_status.device, HEX); - break; - } -*/ } #endif void setup() { #ifdef DEBUG Serial.begin(115200); - if (N64Controller.begin()) { - Serial.println(F("N64 begin() success.")); + if (controller.begin()) { + Serial.println(F("controller.begin() success.")); } else { - Serial.println(F("N64 begin() fail.")); + Serial.println(F("controller.begin() fail.")); } #else - N64Controller.begin(); + controller.begin(); #endif gamepad.begin(); @@ -105,25 +128,24 @@ void setup() { void loop() { delay(POLL_DELAY); // Try to read the controller data - if (N64Controller.read()) { + if (controller.read()) { // Print Controller information - auto controller = N64Controller.getReport(); + auto report = controller.getReport(); - if (memcmp(oldReport, controller.raw8, REPORT_SIZE)) { - memcpy(oldReport, controller.raw8, REPORT_SIZE); + if (memcmp(oldReport, report.raw8, NINTENDO_REPORT_SIZE)) { + memcpy(oldReport, report.raw8, NINTENDO_REPORT_SIZE); } else { // nothing changed return; } #ifdef DEBUG - auto status = N64Controller.getStatus(); - print_n64_report(controller, status); + print_report(report); #endif uint8_t c = 0; // for now just do 1 pad gamepad.buttons(c, 0); - if (controller.start) { - if (controller.ddown) { + if (report.start) { + if (report.ddown) { // then only send menu, nothing else gamepad.press(c, BUTTON_MENU); gamepad.setHatSync(c, DPAD_CENTER); @@ -131,36 +153,53 @@ void loop() { } gamepad.press(c, BUTTON_START); } - if (controller.a) { + if (report.a) { gamepad.press(c, BUTTON_A); } - if (controller.b) { + if (report.b) { gamepad.press(c, BUTTON_B); } - if (controller.z) { + if (report.z) { gamepad.press(c, BUTTON_TR); } - if (controller.l) { + if (report.l) { gamepad.press(c, BUTTON_L); } - if (controller.r) { + if (report.r) { gamepad.press(c, BUTTON_R); } - auto hat = calculateDpadDirection(controller.dup, controller.ddown, controller.dleft, controller.dright); - auto cHat = dpadToAxis(calculateDpadDirection(controller.cup, controller.cdown, controller.cleft, controller.cright)); - gamepad.setAxis(c, translateAxis(controller.xAxis), translateAxis(-controller.yAxis), cHat.x, cHat.y, 0, 0, hat); + auto hat = calculateDpadDirection(report.dup, report.ddown, report.dleft, report.dright); +#ifdef GAMECUBE + if (report.y) { + gamepad.press(c, BUTTON_Y); + } + if (report.x) { + gamepad.press(c, BUTTON_X); + } + gamepad.setAxis(c, + translateAxis(report.xAxis), + -translateAxis(report.yAxis), + translateAxis(report.cxAxis), + -translateAxis(report.cyAxis), + translateTrigger(report.left), + translateTrigger(report.right), + hat); +#else // N64 + auto cHat = dpadToAxis(calculateDpadDirection(report.cup, report.cdown, report.cleft, report.cright)); + gamepad.setAxis(c, translateAxis(report.xAxis), -translateAxis(report.yAxis), cHat.x, cHat.y, 0, 0, hat); +#endif } else { // Add debounce if reading failed - delay(5000); + delay(100); #ifdef DEBUG - Serial.println(F("Error reading N64 controller.")); - if (N64Controller.begin()) { - Serial.println(F("N64 begin() success.")); + Serial.println(F("Error reading controller.")); + if (controller.begin()) { + Serial.println(F("controller.begin() success.")); } else { - Serial.println(F("N64 begin() fail.")); + Serial.println(F("controller.begin() fail.")); } #else - N64Controller.begin(); + controller.begin(); #endif } } diff --git a/src/gamepad/common.h b/src/gamepad/common.h index 78db177..faed89b 100644 --- a/src/gamepad/common.h +++ b/src/gamepad/common.h @@ -37,6 +37,10 @@ #define AXIS_MAX 255 #define AXIS_MIN 0 +// not supported by horipad yet... +#define TRIGGER_MAX 255 +#define TRIGGER_MIN 0 + #else #define BUTTON_A 1 @@ -64,9 +68,12 @@ #define DPAD_UP_LEFT 8 #define AXIS_CENTER 0 -#define AXIS_MAX 32768 +#define AXIS_MAX 32767 #define AXIS_MIN -32767 +#define TRIGGER_MAX 255 +#define TRIGGER_MIN 0 + #endif // nintendo switch // aliases @@ -87,11 +94,17 @@ #define AXIS_CENTER_IN 0 #endif #ifndef AXIS_MAX_IN -#define AXIS_MAX_IN 32768 +#define AXIS_MAX_IN 32767 #endif #ifndef AXIS_MIN_IN #define AXIS_MIN_IN -32767 #endif +#ifndef TRIGGER_MAX_IN +#define TRIGGER_MAX_IN 127 +#endif +#ifndef TRIGGER_MIN_IN +#define TRIGGER_MIN_IN -127 +#endif #if 0 #define BUTTON_1 1 diff --git a/src/util.cpp b/src/util.cpp index 7e6e53a..bc4e39e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -62,18 +62,36 @@ struct Axis dpadToAxis(uint8_t dpad) { return axis(AXIS_CENTER, AXIS_CENTER); } -/* -long map(long x, long in_min, long in_max, long out_min, long out_max) { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +inline long translate(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } -*/ -inline int16_t translateAxis(int16_t v) { +inline int16_t translateAxis(long v) { + // pin to max/min + if (v <= AXIS_MIN_IN) { + return AXIS_MIN; + } else if (v >= AXIS_MAX_IN) { + return AXIS_MAX; + } // don't map at all if translation isn't required... #if AXIS_CENTER_IN == AXIS_CENTER && AXIS_MIN_IN == AXIS_MIN && AXIS_MAX_IN == AXIS_MAX return v; // noop #else - //return v == AXIS_CENTER_IN ? AXIS_CENTER : map(v, AXIS_MIN_IN, AXIS_MAX_IN, AXIS_MIN, AXIS_MAX); - return v == AXIS_CENTER_IN ? AXIS_CENTER : (v - AXIS_MIN_IN) * (AXIS_MAX - AXIS_MIN) / (AXIS_MAX_IN - AXIS_MIN_IN) + AXIS_MIN; + return v == AXIS_CENTER_IN ? AXIS_CENTER : translate(v, AXIS_MIN_IN, AXIS_MAX_IN, AXIS_MIN, AXIS_MAX); +#endif +} + +inline uint8_t translateTrigger(long v) { + // pin to max/min + if (v <= TRIGGER_MIN_IN) { + return TRIGGER_MIN; + } else if (v >= TRIGGER_MAX_IN) { + return TRIGGER_MAX; + } + // don't map at all if translation isn't required... +#if TRIGGER_MIN_IN == TRIGGER_MIN && TRIGGER_MAX_IN == TRIGGER_MAX + return v; // noop +#else + return translate(v, TRIGGER_MIN_IN, TRIGGER_MAX_IN, TRIGGER_MIN, TRIGGER_MAX); #endif }