diff --git a/.gitignore b/.gitignore index e4d266c..c358396 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # Ignore VS Code folders .vscode .build +# Ignore HEX files +*.hex diff --git a/NESControllersUSB/NESControllersUSB.ino b/NESControllersUSB/NESControllersUSB.ino index 0800fec..a4f72b6 100644 --- a/NESControllersUSB/NESControllersUSB.ino +++ b/NESControllersUSB/NESControllersUSB.ino @@ -27,6 +27,8 @@ // Additionally serial number is used to differentiate arduino projects to have different button maps! const char *gp_serial = "NES to USB"; +//#define DEBUG + #define GAMEPAD_COUNT 2 // NOTE: No more than TWO gamepads are possible at the moment due to a USB HID issue. #define GAMEPAD_COUNT_MAX 4 // NOTE: For some reason, can't have more than two gamepads without serial breaking. Can someone figure out why? // (It has something to do with how Arduino handles HID devices) @@ -67,6 +69,12 @@ uint8_t gp = 0; // Timing uint32_t microsButtons = 0; +#ifdef DEBUG +uint32_t microsStart = 0; +uint32_t microsEnd = 0; +uint8_t counter = 0; +#endif + void setup() { // Setup latch and clock pins (2,3 or PD1, PD0) @@ -77,7 +85,13 @@ void setup() DDRF &= ~B11110000; // inputs PORTF |= B11110000; // enable internal pull-ups - delay(500); + #ifdef DEBUG + Serial.begin(115200); + delay(2000); + #endif + + // Short delay to let controllers stabilize + delay(50); } void loop() { while(1) @@ -85,6 +99,10 @@ void loop() { while(1) // See if enough time has passed since last button read if((micros() - microsButtons) > BUTTON_READ_DELAY) { + #ifdef DEBUG + microsStart = micros(); + #endif + // Pulse latch sendLatch(); @@ -109,6 +127,14 @@ void loop() { while(1) } microsButtons = micros(); + + #ifdef DEBUG + microsEnd = micros(); + if(counter < 20) { + Serial.println(microsEnd-microsStart); + counter++; + } + #endif } }} diff --git a/NESControllersUSB/images/snes-usb-adapter-wiring.png b/NESControllersUSB/images/snes-usb-adapter-wiring.png index a443ebf..7086577 100644 Binary files a/NESControllersUSB/images/snes-usb-adapter-wiring.png and b/NESControllersUSB/images/snes-usb-adapter-wiring.png differ diff --git a/NeoGeoControllerUSB/NeoGeoControllerUSB.ino b/NeoGeoControllerUSB/NeoGeoControllerUSB.ino index e8c4248..2cd3923 100644 --- a/NeoGeoControllerUSB/NeoGeoControllerUSB.ino +++ b/NeoGeoControllerUSB/NeoGeoControllerUSB.ino @@ -23,7 +23,7 @@ #include "Gamepad.h" -#define DEBOUNCE 0 // 1=Diddly-squat-Delay-Debouncing™ activated, 0=Debounce deactivated +#define DEBOUNCE 1 // 1=Diddly-squat-Delay-Debouncing™ activated, 0=Debounce deactivated #define DEBOUNCE_TIME 10 // Debounce time in milliseconds //#define DEBUG // Enables debugging (sends debug data to usb serial) diff --git a/SNESControllersUSB/Gamepad.cpp b/SNESControllersUSB/Gamepad.cpp index 77c9c24..2bd717b 100644 --- a/SNESControllersUSB/Gamepad.cpp +++ b/SNESControllersUSB/Gamepad.cpp @@ -33,10 +33,10 @@ static const uint8_t _hidReportDescriptor[] PROGMEM = { 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) - 0x29, 0x18, // USAGE_MAXIMUM (Button 24) + 0x29, 0x08, // USAGE_MAXIMUM (Button 8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x18, // REPORT_COUNT (24) + 0x95, 0x08, // REPORT_COUNT (8) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs) diff --git a/SNESControllersUSB/Gamepad.h b/SNESControllersUSB/Gamepad.h index 8f36620..6a1ab76 100644 --- a/SNESControllersUSB/Gamepad.h +++ b/SNESControllersUSB/Gamepad.h @@ -31,7 +31,7 @@ extern const char* gp_serial; typedef struct { - uint32_t buttons : 24; + uint8_t buttons; int8_t X; int8_t Y; } GamepadReport; diff --git a/SNESControllersUSB/SNESControllersUSB.ino b/SNESControllersUSB/SNESControllersUSB.ino index b975855..95d764f 100644 --- a/SNESControllersUSB/SNESControllersUSB.ino +++ b/SNESControllersUSB/SNESControllersUSB.ino @@ -27,20 +27,21 @@ // Additionally serial number is used to differentiate arduino projects to have different button maps! const char *gp_serial = "NES/SNES to USB"; -#define GAMEPAD_COUNT 2 // NOTE: No more than TWO gamepads are possible at the moment due to a USB HID issue. -#define GAMEPAD_COUNT_MAX 4 // NOTE: For some reason, can't have more than two gamepads without serial breaking. Can someone figure out why? - // (It has something to do with how Arduino handles HID devices) +#define DEBUG + +#define GAMEPAD_COUNT 2 // NOTE: To have more than 2 two gamepads you need to disable the CDC of the Arduino, there is a specific project for that. +#define GAMEPAD_COUNT_MAX 2 #define BUTTON_READ_DELAY 20 // Delay between button reads in µs -#define MICROS_LATCH 10 // 12µs according to specs (8 seems to work fine) -#define MICROS_CLOCK 5 // 6µs according to specs (4 seems to work fine) -#define MICROS_PAUSE 5 // 6µs according to specs (4 seems to work fine) +#define CYCLES_LATCH 128 // 12µs according to specs (8 seems to work fine) (1 cycle @ 16MHz takes 62.5ns so 62.5ns * 128 = 8000ns = 8µs) +#define CYCLES_CLOCK 64 // 6µs according to specs (4 seems to work fine) +#define CYCLES_PAUSE 64 // 6µs according to specs (4 seems to work fine) #define UP 0x01 #define DOWN 0x02 #define LEFT 0x04 #define RIGHT 0x08 -#define NTT_CONTROL_BIT 0x20000000 +#define DELAY_CYCLES(n) __builtin_avr_delay_cycles(n) // Wire it all up according to the following table: // @@ -58,39 +59,49 @@ const char *gp_serial = "NES/SNES to USB"; enum ControllerType { NONE, NES, - SNES, - NTT + SNES }; // Set up USB HID gamepads Gamepad_ Gamepad[GAMEPAD_COUNT]; // Controllers -uint32_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0}; -uint32_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0}; -uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000}; +uint8_t buttons[GAMEPAD_COUNT_MAX][2] = {{0,0},{0,0}}; +uint8_t buttonsPrev[GAMEPAD_COUNT_MAX][2] = {{0,0},{0,0}}; +uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000}; ControllerType controllerType[GAMEPAD_COUNT_MAX] = {NONE,NONE}; -uint32_t btnBits[32] = {0x10,0x40,0x400,0x800,UP,DOWN,LEFT,RIGHT,0x20,0x80,0x100,0x200, // Standard SNES controller - 0x10000000,0x20000000,0x40000000,0x80000000,0x1000,0x2000,0x4000,0x8000, // NTT Data Keypad (NDK10) - 0x10000,0x20000,0x40000,0x80000,0x100000,0x200000,0x400000,0x800000, - 0x1000000,0x2000000,0x4000000,0x8000000}; +uint8_t btnByte[12] = {0,0,0,0,1,1,1,1,0,0,0,0}; +uint8_t btnBits[12] = {0x01,0x04,0x40,0x80,UP,DOWN,LEFT,RIGHT,0x02,0x08,0x10,0x20}; uint8_t gp = 0; -uint8_t buttonCount = 32; +uint8_t buttonCount = 12; // Timing uint32_t microsButtons = 0; +#ifdef DEBUG +uint32_t microsStart = 0; +uint32_t microsEnd = 0; +uint8_t counter = 0; +#endif + void setup() { // Setup latch and clock pins (2,3 or PD1, PD0) DDRD |= B00000011; // output PORTD &= ~B00000011; // low - // Setup data pins (A0-A3 or PF7-PF4) + // Setup data pins A0-A3 (PF7-PF4) DDRF &= ~B11110000; // inputs PORTF |= B11110000; // enable internal pull-ups + DDRC &= ~B01000000; // input + PORTC |= B01000000; // enable internal pull-up - delay(500); + #ifdef DEBUG + Serial.begin(115200); + delay(2000); + #endif + + delay(3000); detectControllerTypes(); } @@ -99,13 +110,18 @@ void loop() { while(1) // See if enough time has passed since last button read if((micros() - microsButtons) > BUTTON_READ_DELAY) { + + #ifdef DEBUG + microsStart = micros(); + #endif + // Pulse latch sendLatch(); for(uint8_t btn=0; btn> 4); // First 4 bits are the axes - Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & DOWN) >> 1) - (buttons[gp] & UP); - Gamepad[gp]._GamepadReport.X = ((buttons[gp] & RIGHT) >> 3) - ((buttons[gp] & LEFT) >> 2); - buttonsPrev[gp] = buttons[gp]; + Gamepad[gp]._GamepadReport.buttons = buttons[gp][0]; + Gamepad[gp]._GamepadReport.Y = ((buttons[gp][1] & DOWN) >> 1) - (buttons[gp][1] & UP); + Gamepad[gp]._GamepadReport.X = ((buttons[gp][1] & RIGHT) >> 3) - ((buttons[gp][1] & LEFT) >> 2); + buttonsPrev[gp][0] = buttons[gp][0]; + buttonsPrev[gp][1] = buttons[gp][1]; Gamepad[gp].send(); } } - + microsButtons = micros(); + + #ifdef DEBUG + microsEnd = micros(); + if(counter < 20) { + Serial.println(microsEnd-microsStart); + counter++; + } + #endif + } }} @@ -154,26 +176,21 @@ void detectControllerTypes() for(uint8_t btn=0; btn + * + * 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 . + * + */ +#include "Gamepad.h" + +static const uint8_t _hidReportDescriptor[] PROGMEM = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x04, // USAGE (Joystick) (Maybe change to gamepad? I don't think so but...) + 0xa1, 0x01, // COLLECTION (Application) + 0xa1, 0x00, // COLLECTION (Physical) + + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x18, // USAGE_MAXIMUM (Button 24) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x18, // REPORT_COUNT (24) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x01, // USAGE (pointer) + 0xa1, 0x00, // COLLECTION (Physical) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0xff, // LOGICAL_MINIMUM (-1) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION +}; + +Gamepad_::Gamepad_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1) +{ + epType[0] = EP_TYPE_INTERRUPT_IN; + PluggableUSB().plug(this); +} + +int Gamepad_::getInterface(uint8_t* interfaceCount) +{ + *interfaceCount += 1; // uses 1 + HIDDescriptor hidInterface = { + D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), + D_HIDREPORT(sizeof(_hidReportDescriptor)), + D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) + }; + return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); +} + +int Gamepad_::getDescriptor(USBSetup& setup) +{ + // Check if this is a HID Class Descriptor request + if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; } + if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { return 0; } + + // In a HID Class Descriptor wIndex cointains the interface number + if (setup.wIndex != pluggedInterface) { return 0; } + + // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol + // due to the USB specs, but Windows and Linux just assumes its in report mode. + protocol = HID_REPORT_PROTOCOL; + + return USB_SendControl(TRANSFER_PGM, _hidReportDescriptor, sizeof(_hidReportDescriptor)); +} + +bool Gamepad_::setup(USBSetup& setup) +{ + if (pluggedInterface != setup.wIndex) { + return false; + } + + uint8_t request = setup.bRequest; + uint8_t requestType = setup.bmRequestType; + + if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) + { + if (request == HID_GET_REPORT) { + // TODO: HID_GetReport(); + return true; + } + if (request == HID_GET_PROTOCOL) { + // TODO: Send8(protocol); + return true; + } + } + + if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) + { + if (request == HID_SET_PROTOCOL) { + protocol = setup.wValueL; + return true; + } + if (request == HID_SET_IDLE) { + idle = setup.wValueL; + return true; + } + if (request == HID_SET_REPORT) + { + } + } + + return false; +} + +void Gamepad_::reset() +{ + _GamepadReport.X = 0; + _GamepadReport.Y = 0; + _GamepadReport.buttons = 0; + this->send(); +} + +void Gamepad_::send() +{ + USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &_GamepadReport, sizeof(GamepadReport)); +} + +uint8_t Gamepad_::getShortName(char *name) +{ + if(!next) + { + strcpy(name, gp_serial); + return strlen(name); + } + return 0; +} diff --git a/SNESNTTControllersUSB/Gamepad.h b/SNESNTTControllersUSB/Gamepad.h new file mode 100644 index 0000000..8f36620 --- /dev/null +++ b/SNESNTTControllersUSB/Gamepad.h @@ -0,0 +1,60 @@ +/* Gamepad.h + * + * Based on the advanced HID library for Arduino: + * https://github.com/NicoHood/HID + * Copyright (c) 2014-2015 NicoHood + * + * Copyright (c) 2020 Mikael Norrgård + * + * 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 . + * + */ + +#pragma once + +#include "HID.h" + +extern const char* gp_serial; + +typedef struct { + uint32_t buttons : 24; + int8_t X; + int8_t Y; +} GamepadReport; + + +class Gamepad_ : public PluggableUSBModule +{ + private: + uint8_t reportId; + + protected: + int getInterface(uint8_t* interfaceCount); + int getDescriptor(USBSetup& setup); + uint8_t getShortName(char *name); + bool setup(USBSetup& setup); + + uint8_t epType[1]; + uint8_t protocol; + uint8_t idle; + + public: + GamepadReport _GamepadReport; + Gamepad_(void); + void reset(void); + void send(); +}; diff --git a/SNESNTTControllersUSB/README.md b/SNESNTTControllersUSB/README.md new file mode 100644 index 0000000..cfecc4e --- /dev/null +++ b/SNESNTTControllersUSB/README.md @@ -0,0 +1,17 @@ +# DaemonBite SNES/NES USB Controller adapter with NTT Datapad support +## Introduction +With this simple to build adapter you can connect SNES and NES gamepads to a PC, Raspberry PI, MiSTer FPGA etc. The Arduino Pro Micro has very low lag when configured as a USB gamepad and it is plug n' play once it has been programmed. The controller type is auto-detected. + +The NTT Data Keypad controller for SNES is also supported in this version. For a faster version, use the project in the SNESControllersUSB folder. + +## Parts you need +- Arduino Pro Micro (ATMega32U4) +- Male end of SNES or NES controller extension cable +- Heat shrink tube (Ø ~20mm) +- Micro USB cable + +## Wiring +![Assemble1](images/snes-usb-adapter-wiring.png) + +## License +This project is licensed under the GNU General Public License v3.0. diff --git a/SNESNTTControllersUSB/SNESNTTControllersUSB.ino b/SNESNTTControllersUSB/SNESNTTControllersUSB.ino new file mode 100644 index 0000000..4807bd1 --- /dev/null +++ b/SNESNTTControllersUSB/SNESNTTControllersUSB.ino @@ -0,0 +1,243 @@ +/* DaemonBite (S)NES Controllers to USB Adapter with NTT Datapad support + * Author: Mikael Norrgård + * + * Copyright (c) 2020 Mikael Norrgård + * + * 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 . + * + */ + +#include "Gamepad.h" + +// ATT: 20 chars max (including NULL at the end) according to Arduino source code. +// Additionally serial number is used to differentiate arduino projects to have different button maps! +const char *gp_serial = "NES/SNES to USB"; + +#define DEBUG + +#define GAMEPAD_COUNT 1 // NOTE: To have more than 2 two gamepads you need to disable the CDC of the Arduino. +#define GAMEPAD_COUNT_MAX 4 +#define BUTTON_READ_DELAY 20 // Delay between button reads in µs +#define CYCLES_LATCH 128 // 12µs according to specs (8 seems to work fine) (1 cycle @ 16MHz takes 62.5ns so 62.5ns * 128 = 8000ns = 8µs) +#define CYCLES_CLOCK 64 // 6µs according to specs (4 seems to work fine) +#define CYCLES_PAUSE 64 // 6µs according to specs (4 seems to work fine) + +#define UP 0x01 +#define DOWN 0x02 +#define LEFT 0x04 +#define RIGHT 0x08 + +#define NTT_CONTROL_BIT 0x20000000 + +#define DELAY_CYCLES(n) __builtin_avr_delay_cycles(n) + +// Wire it all up according to the following table: +// +// NES SNES Arduino Pro Micro +// -------------------------------------- +// VCC VCC (All gamepads) +// GND GND (All gamepads) +// OUT0 (LATCH) 2 (PD1, All gamepads) +// CUP (CLOCK) 3 (PD0, All gamepads) +// D1 (GP1: DATA) A0 (PF7, Gamepad 1) +// D1 (GP2: DATA) A1 (PF6, Gamepad 2) +// D1 (GP3: DATA) A2 (PF5, Gamepad 3, not currently used) +// D1 (GP4: DATA) A3 (PF4, Gamepad 4, not currently used) + +enum ControllerType { + NONE, + NES, + SNES, + NTT +}; + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint32_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint32_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000}; +ControllerType controllerType[GAMEPAD_COUNT_MAX] = {NONE,NONE,NONE,NONE}; +uint32_t btnBits[32] = {0x10,0x40,0x400,0x800,UP,DOWN,LEFT,RIGHT,0x20,0x80,0x100,0x200, // Standard SNES controller + 0x10000000,0x20000000,0x40000000,0x80000000,0x1000,0x2000,0x4000,0x8000, // NTT Data Keypad (NDK10) + 0x10000,0x20000,0x40000,0x80000,0x100000,0x200000,0x400000,0x800000, + 0x1000000,0x2000000,0x4000000,0x8000000}; +uint8_t gp = 0; +uint8_t buttonCount = 32; + +// Timing +uint32_t microsButtons = 0; + +#ifdef DEBUG +uint32_t microsStart = 0; +uint32_t microsEnd = 0; +uint8_t counter = 0; +#endif + +void setup() +{ + // Setup latch and clock pins (2,3 or PD1, PD0) + DDRD |= B00000011; // output + PORTD &= ~B00000011; // low + + // Setup data pins A0-A3 (PF7-PF4) + DDRF &= ~B11110000; // inputs + PORTF |= B11110000; // enable internal pull-ups + DDRC &= ~B01000000; // input + PORTC |= B01000000; // enable internal pull-up + + #ifdef DEBUG + Serial.begin(115200); + delay(4000); + #endif + + delay(500); + detectControllerTypes(); +} + +void loop() { while(1) +{ + // See if enough time has passed since last button read + if((micros() - microsButtons) > BUTTON_READ_DELAY) + { + + #ifdef DEBUG + microsStart = micros(); + #endif + + // Pulse latch + sendLatch(); + + for(uint8_t btn=0; btn> 4); // First 4 bits are the axes + Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & DOWN) >> 1) - (buttons[gp] & UP); + Gamepad[gp]._GamepadReport.X = ((buttons[gp] & RIGHT) >> 3) - ((buttons[gp] & LEFT) >> 2); + buttonsPrev[gp] = buttons[gp]; + Gamepad[gp].send(); + } + } + + microsButtons = micros(); + + #ifdef DEBUG + microsEnd = micros(); + if(counter < 20) { + Serial.println(microsEnd-microsStart); + counter++; + } + #endif + + } +}} + +void detectControllerTypes() +{ + uint8_t buttonCountNew = 0; + + // Read the controllers a few times to detect controller type + for(uint8_t i=0; i<4; i++) + { + // Pulse latch + sendLatch(); + + // Read all buttons + for(uint8_t btn=0; btn