diff --git a/PCEngineControllerUSB/Gamepad.cpp b/PCEngineControllerUSB/Gamepad.cpp new file mode 100644 index 0000000..5ec534e --- /dev/null +++ b/PCEngineControllerUSB/Gamepad.cpp @@ -0,0 +1,157 @@ +/* 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/PCEngineControllerUSB/Gamepad.h b/PCEngineControllerUSB/Gamepad.h new file mode 100644 index 0000000..6e6513a --- /dev/null +++ b/PCEngineControllerUSB/Gamepad.h @@ -0,0 +1,63 @@ +/* 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 +#include "HID.h" + +extern const char* gp_serial; + +// The numbers after colon are bit fields, meaning how many bits the field uses. +// Remove those if there are problems +typedef struct { + uint8_t buttons : 4; + 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/PCEngineControllerUSB/PCEngineControllerUSB.ino b/PCEngineControllerUSB/PCEngineControllerUSB.ino new file mode 100644 index 0000000..70495e2 --- /dev/null +++ b/PCEngineControllerUSB/PCEngineControllerUSB.ino @@ -0,0 +1,148 @@ +/* DaemonBite PC Engine / TurboGrafx-16 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 // Set to 1 or 2 depending if you want to make a 1 or 2 port adapter +#define SELECT_PAUSE 20 // How many microseconds to wait after setting select/enable lines? +#define FRAME_TIME 16667 // The time of one "frame" in µs (used for turbo functionality) + + +#define UP 0x01 +#define DOWN 0x04 +#define LEFT 0x08 +#define RIGHT 0x02 +#define UP_SH 0 +#define DOWN_SH 2 +#define LEFT_SH 3 +#define RIGHT_SH 1 + +// 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 = "PC Engine to USB"; + +/* ------------------------------------------------------------------------- +PC Engine / TurboGrafx-16 controller wiring + +PC Engine (P1) Arduino Pro Micro +-------------------------------------- +1 VCC VCC +2 UP/I 3 PD0 +3 RIGHT/II 2 PD1 +4 DOWN/SELECT RXI PD2 +5 LEFT/START TXO PD3 +6 DSELECT 15 PB1 (Shared with P2) +7 ENABLE 14 PB3 (Shared with P2) +8 GND GND + +PC Engine (P2) Arduino Pro Micro +-------------------------------------- +1 VCC VCC +2 UP/I A3 PF4 +3 RIGHT/II A2 PF5 +4 DOWN/SELECT A1 PF6 +5 LEFT/START A0 PF7 +6 DSELECT 15 PB1 (Shared with P1) +7 ENABLE 14 PB3 (Shared with P1) +8 GND GND + +------------------------------------------------------------------------- */ + +// Set up USB HID gamepads +Gamepad_ Gamepad[GAMEPAD_COUNT]; + +// Controllers +uint8_t buttons[2][2] = {{0,0},{0,0}}; +uint8_t buttonsPrev[2][2] = {{0,0},{0,0}}; +uint8_t gp = 0; + +// Turbo timing +uint32_t microsNow = 0; +uint32_t microsEnable = 0; + +void setup() +{ + // Set D0-D3 as inputs and enable pull-up resistors (port1 data pins) + DDRD &= ~B00001111; + PORTD |= B00001111; + + // Set F4-F7 as inputs and enable pull-up resistors (port2 data pins) + DDRF &= ~B11110000; + PORTF |= B11110000; + + // Set B1 and B3 as outputs and set them LOW + PORTB &= ~B00001010; + DDRB |= B00001010; + + // Wait for the controller(s) to settle + delay(100); +} + +void loop() { while(1) +{ + // Handle clock for turbo functionality + microsNow = micros(); + if((microsNow-microsEnable) >= FRAME_TIME) + { + PORTB |= B00001000; // Set enable pin HIGH to increase clock for turbo + delayMicroseconds(SELECT_PAUSE); // Wait a while... + PORTB &= ~B00001000; // Set enable pin LOW again + microsEnable = microsNow; + } + + // Clear button data + buttons[0][0]=0; buttons[0][1]=0; + buttons[1][0]=0; buttons[1][1]=0; + + // Read all button and axes states + PORTB |= B00000010; // Set SELECT pin HIGH + delayMicroseconds(SELECT_PAUSE); // Wait a while... + buttons[0][0] = PIND & B00001111; // Read DPAD for controller 1 + if(GAMEPAD_COUNT==2) + buttons[1][0] = (PINF & B11110000) >> 4; // Read DPAD for controller 2 + PORTB &= ~B00000010; // Set SELECT pin LOW + delayMicroseconds(SELECT_PAUSE); // Wait a while... + buttons[0][1] = PIND & B00001111; // Read buttons for controller 1 + if(GAMEPAD_COUNT==2) + buttons[1][1] = (PINF & B11110000) >> 4; // Read buttons for controller 2 + + // Invert the readings so a 1 means a pressed button + buttons[0][0] = ~buttons[0][0]; buttons[0][1] = ~buttons[0][1]; + buttons[1][0] = ~buttons[1][0]; buttons[1][1] = ~buttons[1][1]; + + // Send data to USB if values have changed + for(gp=0; gp> DOWN_SH) - ((buttons[gp][0] & UP) >> UP_SH); + Gamepad[gp]._GamepadReport.X = ((buttons[gp][0] & RIGHT) >> RIGHT_SH) - ((buttons[gp][0] & LEFT) >> LEFT_SH); + buttonsPrev[gp][0] = buttons[gp][0]; + buttonsPrev[gp][1] = buttons[gp][1]; + Gamepad[gp].send(); + } + } + +}} \ No newline at end of file diff --git a/PCEngineControllerUSB/README.md b/PCEngineControllerUSB/README.md new file mode 100644 index 0000000..487a67f --- /dev/null +++ b/PCEngineControllerUSB/README.md @@ -0,0 +1,40 @@ +# DaemonBite PC Engine / TurboGrafx-16 Controllers To USB Adapter + +## Introduction +This is a simple to build adapter for connecting PC Engine / TurboGrafx-16 controllers to USB. + +NOTE: This adapter is in BETA and not yet properly tested. + +The input lag for this adapter is minimal (should be less 1ms average connected to MiSTer). + +## Parts you need +- Arduino Pro Micro (ATMega32U4) +- Female connector end of controller extension cable or... +- Female DIN 8-pin and/or Female Mini-DIN 8-pin connector +- Micro USB cable + +## Wiring +| PC Engine (P1) | Arduino Pro Micro | +| ------ | ------ | +| 1 VCC | VCC | +| 2 UP/I | 3 PD0 | +| 3 RIGHT/II | 2 PD1 | +| 4 DOWN/SELECT | RXI PD2 | +| 5 LEFT/START | TXO PD3 | +| 6 DSELECT | 15 PB1 (Shared with P2) | +| 7 ENABLE | 14 PB3 (Shared with P2) | +| 8 GND | GND | + +| PC Engine (P2) | Arduino Pro Micro | +| ------ | ------ | +| 1 VCC | VCC | +| 2 UP/I | A3 PF4 | +| 3 RIGHT/II | A2 PF5 | +| 4 DOWN/SELECT | A1 PF6 | +| 5 LEFT/START | A0 PF7 | +| 6 DSELECT | 15 PB1 (Shared with P1) | +| 7 ENABLE | 14 PB3 (Shared with P1) | +| 8 GND | GND | + +## License +This project is licensed under the GNU General Public License v3.0. diff --git a/SaturnControllerUSB/SaturnControllerUSB.ino b/SaturnControllerUSB/SaturnControllerUSB.ino index dfa1693..96bb8ed 100644 --- a/SaturnControllerUSB/SaturnControllerUSB.ino +++ b/SaturnControllerUSB/SaturnControllerUSB.ino @@ -100,7 +100,7 @@ void setup() PORTD |= B10010000; // Set B1 and B3 as outputs and set them HIGH (select pins) - PORTD |= B00001010; + PORTB |= B00001010; DDRB |= B00001010; // Wait for the controller(s) to settle diff --git a/SegaTwoControllersUSB/SegaTwoControllersUSB.ino b/SegaTwoControllersUSB/SegaTwoControllersUSB.ino index bd14777..7a49ced 100644 --- a/SegaTwoControllersUSB/SegaTwoControllersUSB.ino +++ b/SegaTwoControllersUSB/SegaTwoControllersUSB.ino @@ -67,12 +67,12 @@ void setup() Gamepad[gp].reset(); } -void loop() +void loop() { while(1) { controllers.readState(); sendState(0); sendState(1); -} +}} void sendState(byte gp) {