1
0
mirror of https://github.com/MickGyver/DaemonBite-Retro-Controllers-USB synced 2024-12-26 01:38:49 -05:00

PC Engine / Turbografx-16 adapter, Saturn adapter bug fix

This commit is contained in:
MickGyver 2020-06-28 11:12:55 +03:00
parent 87a96ab0f8
commit 227a10814c
6 changed files with 411 additions and 3 deletions

View File

@ -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 <http://daemonbite.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#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;
}

View File

@ -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 <http://daemonbite.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <Arduino.h>
#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();
};

View File

@ -0,0 +1,148 @@
/* DaemonBite PC Engine / TurboGrafx-16 controllers to USB Adapter
* Author: Mikael Norrgård <mick@daemonbite.com>
*
* Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#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<GAMEPAD_COUNT; gp++)
{
// Has any buttons changed state?
if (buttons[gp][0] != buttonsPrev[gp][0] || buttons[gp][1] != buttonsPrev[gp][1] )
{
Gamepad[gp]._GamepadReport.buttons = buttons[gp][1];
Gamepad[gp]._GamepadReport.Y = ((buttons[gp][0] & DOWN) >> 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();
}
}
}}

View File

@ -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.

View File

@ -100,7 +100,7 @@ void setup()
PORTD |= B10010000; PORTD |= B10010000;
// Set B1 and B3 as outputs and set them HIGH (select pins) // Set B1 and B3 as outputs and set them HIGH (select pins)
PORTD |= B00001010; PORTB |= B00001010;
DDRB |= B00001010; DDRB |= B00001010;
// Wait for the controller(s) to settle // Wait for the controller(s) to settle

View File

@ -67,12 +67,12 @@ void setup()
Gamepad[gp].reset(); Gamepad[gp].reset();
} }
void loop() void loop() { while(1)
{ {
controllers.readState(); controllers.readState();
sendState(0); sendState(0);
sendState(1); sendState(1);
} }}
void sendState(byte gp) void sendState(byte gp)
{ {