diff --git a/SNESControllersUSB/Gamepad.cpp b/SNESControllersUSB/Gamepad.cpp new file mode 100644 index 0000000..eddab2b --- /dev/null +++ b/SNESControllersUSB/Gamepad.cpp @@ -0,0 +1,150 @@ +/* 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 . + * + */ + +#pragma once + +#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, 0x08, // USAGE_MAXIMUM (Button 8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x08, // REPORT_COUNT (8) + 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) +{ + return 0; +} diff --git a/SNESControllersUSB/Gamepad.h b/SNESControllersUSB/Gamepad.h new file mode 100644 index 0000000..341347a --- /dev/null +++ b/SNESControllersUSB/Gamepad.h @@ -0,0 +1,74 @@ +/* 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" + +// The numbers after colon are bit fields, meaning how many bits the field uses. +// Remove those if there are problems +typedef struct { + union + { + struct { + bool b0: 1 ; + bool b1: 1 ; + bool b2: 1 ; + bool b3: 1 ; + bool b4: 1 ; + bool b5: 1 ; + bool b6: 1 ; + bool b7: 1 ; + }; + 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/SNESControllersUSB/README.md b/SNESControllersUSB/README.md new file mode 100644 index 0000000..b208d78 --- /dev/null +++ b/SNESControllersUSB/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/nes-usb-adapter-wiring.png) + +## License +This project is licensed under the GNU General Public License v3.0. diff --git a/SNESControllersUSB/SNESControllersUSB.ino b/SNESControllersUSB/SNESControllersUSB.ino new file mode 100644 index 0000000..7651324 --- /dev/null +++ b/SNESControllersUSB/SNESControllersUSB.ino @@ -0,0 +1,141 @@ +/* DaemonBite (S)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" + +#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 SNES 0 +#define NES 1 +#define GPTYPE NES // NOTE: Set gamepad type here (NES or SNES)! :) +#define BUTTON_READ_DELAY 300 // Button read delay in µs + +#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) +// D1 (GP4: DATA) A3 (PF4, Gamepad 4) + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint16_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint16_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0}; +uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000}; +uint16_t btnBitsSnes[12] = {0x200,0x800,0x8000,0x4000,UP,DOWN,LEFT,RIGHT,0x100,0x400,0x1000,0x2000}; +uint16_t btnBitsNes[8] = {0x100,0x200,0x8000,0x4000,UP,DOWN,LEFT,RIGHT}; +uint16_t *btnBits; +uint8_t gp = 0; +uint8_t gpType = GPTYPE; +uint8_t buttonCount = 0; + +// Timing +long microsNow = 0; +long 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 + + if(gpType == SNES) { + buttonCount = 12; + btnBits = btnBitsSnes; + } + else { + buttonCount = 8; + btnBits = btnBitsNes; + } +} + +void loop() +{ + // Get current time + microsNow = micros(); + + // See if enough time has passed since last button read + if(microsNow > microsButtons+BUTTON_READ_DELAY) + { + // Pulse latch + sendLatch(); + + for(uint8_t btn=0; btn> 8; + 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(); + } + } +} + +void sendLatch() +{ + // Send a latch pulse to (S)NES controller(s) + PORTD |= B00000010; // Set HIGH + delayMicroseconds(12); + PORTD &= ~B00000010; // Set LOW + delayMicroseconds(6); +} + +void sendClock() +{ + // Send a clock pulse to (S)NES controller(s) + PORTD |= B10000001; // Set HIGH + delayMicroseconds(6); + PORTD &= ~B10000001; // Set LOW + delayMicroseconds(6); +}