diff --git a/NESControllersUSB/Gamepad.cpp b/NESControllersUSB/Gamepad.cpp new file mode 100644 index 0000000..15505e5 --- /dev/null +++ b/NESControllersUSB/Gamepad.cpp @@ -0,0 +1,156 @@ +/* Gamepad.cpp + * + * 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 . + * + */ +#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, 0x04, // USAGE_MAXIMUM (Button 4) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x04, // REPORT_COUNT (4) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x95, 0x01, // REPORT_COUNT (1) ; pad out the bits into a number divisible by 8 + 0x75, 0x04, // REPORT_SIZE (4) + 0x81, 0x03, // INPUT (Const,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/NESControllersUSB/Gamepad.h b/NESControllersUSB/Gamepad.h new file mode 100644 index 0000000..6a1ab76 --- /dev/null +++ b/NESControllersUSB/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 { + uint8_t buttons; + 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/NESControllersUSB/NESControllersUSB.ino b/NESControllersUSB/NESControllersUSB.ino new file mode 100644 index 0000000..3d059f1 --- /dev/null +++ b/NESControllersUSB/NESControllersUSB.ino @@ -0,0 +1,131 @@ +/* DaemonBite NES Controllers to USB Adapter + * 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 to USB"; + +#define GAMEPAD_COUNT 1 // 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 BUTTON_COUNT 8 // Standard NES controller has four buttons and four axes, totalling 8 +#define BUTTON_READ_DELAY 20 // Delay between button reads in µs +#define MICROS_LATCH 8 // 12µs according to specs (8 seems to work fine) +#define MICROS_CLOCK 4 // 6µs according to specs (4 seems to work fine) +#define MICROS_PAUSE 4 // 6µs according to specs (4 seems to work fine) + +#define UP 0x01 +#define DOWN 0x02 +#define LEFT 0x04 +#define RIGHT 0x08 + +// 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) + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint8_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint8_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000}; +uint8_t btnBits[BUTTON_COUNT] = {0x20,0x10,0x40,0x80,UP,DOWN,LEFT,RIGHT}; +uint8_t gp = 0; + +// Timing +uint32_t microsButtons = 0; + +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) + DDRF &= ~B11110000; // inputs + PORTF |= B11110000; // enable internal pull-ups + + delay(500); +} + +void loop() { while(1) +{ + // See if enough time has passed since last button read + if((micros() - microsButtons) > BUTTON_READ_DELAY) + { + // 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(); + } +}} + +void sendLatch() +{ + // Send a latch pulse to the NES controller(s) + PORTD |= B00000010; // Set HIGH + delayMicroseconds(MICROS_LATCH); + PORTD &= ~B00000010; // Set LOW + delayMicroseconds(MICROS_PAUSE); +} + +void sendClock() +{ + // Send a clock pulse to the NES controller(s) + PORTD |= B10000001; // Set HIGH + delayMicroseconds(MICROS_CLOCK); + PORTD &= ~B10000001; // Set LOW + delayMicroseconds(MICROS_PAUSE); +} diff --git a/NESControllersUSB/README.md b/NESControllersUSB/README.md new file mode 100644 index 0000000..56ce7fd --- /dev/null +++ b/NESControllersUSB/README.md @@ -0,0 +1,15 @@ +# DaemonBite SNES/NES USB Controller adapter +## Introduction +With this simple to build adapter you can connect 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. + +## Parts you need +- Arduino Pro Micro (ATMega32U4) +- Male end of 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/NESControllersUSB/images/snes-usb-adapter-wiring.png b/NESControllersUSB/images/snes-usb-adapter-wiring.png new file mode 100644 index 0000000..a443ebf Binary files /dev/null and b/NESControllersUSB/images/snes-usb-adapter-wiring.png differ diff --git a/SNESControllersUSB/README.md b/SNESControllersUSB/README.md index 56ce7fd..6fa7dcd 100644 --- a/SNESControllersUSB/README.md +++ b/SNESControllersUSB/README.md @@ -1,10 +1,10 @@ # DaemonBite SNES/NES USB Controller adapter ## Introduction -With this simple to build adapter you can connect 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. +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 NTT Data Keypad controller for SNES is also supported. The controller type is auto-detected. ## Parts you need - Arduino Pro Micro (ATMega32U4) -- Male end of NES controller extension cable +- Male end of SNES or NES controller extension cable - Heat shrink tube (Ø ~20mm) - Micro USB cable