This commit is contained in:
FluffyRedLobster 2022-05-22 08:10:10 +01:00 committed by GitHub
commit 8addc49c81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 447 additions and 0 deletions

View File

@ -0,0 +1,194 @@
/* DaemonBite 3DO Controllers to USB Adapter
*
* Based on DaemonBite NES / SNES Adapter by Mikael Norrgård <mick@daemonbite.com>
*
* Author: Chris Chaplin
*
* Copyright (c) 2022 Chris Chaplin
*
* 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/>.
*
*/
// The bulk of the information on this comes from http://kaele.com/~kashima/games/3do-e.html
// and https://github.com/fluxcorenz/UPCB/blob/master/3do.h
//
// Controller DB9 pins (looking face-on to the end of the plug):
//
// 5 4 3 2 1
// 9 8 7 6
//
// Wire it all up according to the following table:
//
// 3DO-DB9 Arduino Pro Micro
// ---------------------------------------------
// 1 GND GND
// 2 VCC VCC
// 3 Audio.1(1v-pp)
// 4 Audio.1(1v-pp)
// 5 VCC VCC
// 6 LATCH 2 (PD1)
// 7 CLOCK 3 (PD0)
// 8 GND GND
// 9 DATA A0 (PF7)
#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 = "3DO to USB";
//#define DEBUG
#define GAMEPAD_COUNT 1 // NOTE: No more than ONE gamepad is possible at the moment due to the author only having one pad to test with. 3DO gamepads are daisychained (see linked info) so this will need more work to support.
#define GAMEPAD_COUNT_MAX 1 // NOTE: Currently set to 1 due to the above. The loops from the (S)NES code to support more than one pad have been left in but don't do anything.
#define BUTTON_COUNT 11 // Panasonic FZ-JP1 controller has seven buttons and four axes, totalling 11
#define BUTTON_READ_DELAY 88 // Delay between button reads in µs (11 buttons x 8µs clock cycle). Any less than this and buttons reset themselves
#define MICROS_CLOCK 4
#define BUTTONS 0
#define AXES 1
#define UP 0x01
#define DOWN 0x02
#define LEFT 0x04
#define RIGHT 0x08
// Set up USB HID gamepads
Gamepad_ Gamepad[GAMEPAD_COUNT];
// Controller
uint8_t buttons[GAMEPAD_COUNT_MAX][2] = {{0,0}};
uint8_t buttonsPrev[GAMEPAD_COUNT_MAX][2] = {{0,0}};
uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000};
uint8_t btnByte[BUTTON_COUNT] = {1,1,1,1,0,0,0,0,0,0,0};
uint8_t btnBits[BUTTON_COUNT] = {UP,DOWN,LEFT,RIGHT,0x01,0x02,0x04,0x08,0x10,0x20,0x40};
uint8_t gp = 0;
uint8_t buttonCount = 11; // Panasonic FZ-JP1 has seven buttons and four axes, totalling 11
// 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; // Sets bits 0&1 as outputs
PORTD = B00000010; // Clock (PD0) low, Latch (PD1) high
// Setup data pins (A0)
DDRF &= ~B00000000; // inputs
PORTF |= B10000000; // enable internal pull-ups
#ifdef DEBUG
Serial.begin(115200);
delay(2000);
#endif
// Short delay to let controllers stabilize
delay(200);
}
void loop() { while(1)
{
// See if enough time has passed since last button read
if((micros() - microsButtons) > BUTTON_READ_DELAY)
{
// Reset the button and axes state
for(gp=0; gp<GAMEPAD_COUNT; gp++) {
buttons[gp][BUTTONS] = 0;
buttons[gp][AXES] = 0;
}
// Get the Clock and Latch lines in the right state for the controller to start sending data
pullClock(); //pull clock line high ready it can be dropped in sync with the latch
dropLatch(); //drop latch low, controller will start sending data
// We don't care about the first two (three?) bits from the controller
uint8_t clockpulse = 0;
while(clockpulse<4) {
sendClock();
clockpulse++;
}
// Loop through the number of configured buttons and sample the pin state for each
for(uint8_t btn=0; btn<BUTTON_COUNT; btn++)
{
for(gp=0; gp<GAMEPAD_COUNT; gp++) {
if((PINF & gpBit[gp])==0) {
buttons[gp][btnByte[btn]] |= btnBits[btn];
}
sendClock(); // Send a clock pulse and loop around to get the next button until we've got them all
}
}
// Finished getting button and axes data so pull the latch back up
pullLatch();
// Set the USB gamepad based on the button and axes data gathered from the data line
for(gp=0; gp<GAMEPAD_COUNT; gp++)
{
// Has any buttons changed state?
// Note checked added for BUTTONS = B00000000 to avoid all buttons reporting as pressed if controller disconnected
if (buttons[gp][BUTTONS] != B00000000 && (buttons[gp][BUTTONS] != buttonsPrev[gp][BUTTONS] || buttons[gp][AXES] != buttonsPrev[gp][AXES]))
{
Gamepad[gp]._GamepadReport.buttons = ~buttons[gp][BUTTONS]; // 3DO controller buttons are low when pressed, so invert
Gamepad[gp]._GamepadReport.Y = ((buttons[gp][AXES] & DOWN) >> 1) - (buttons[gp][AXES] & UP);
Gamepad[gp]._GamepadReport.X = ((buttons[gp][AXES] & RIGHT) >> 3) - ((buttons[gp][AXES] & LEFT) >> 2);
buttonsPrev[gp][BUTTONS] = buttons[gp][BUTTONS];
buttonsPrev[gp][AXES] = buttons[gp][AXES];
Gamepad[gp].send();
}
}
microsButtons = micros();
}
}}
void dropLatch()
{
PORTD &= ~B00000010; // Set LOW PD1
}
void pullLatch()
{
PORTD |= B00000010; // Set HIGH PD1
}
void pullClock()
{
PORTD |= B00000001; // Set HIGH PD0
delayMicroseconds(MICROS_CLOCK);
}
void sendClock()
{
// Send a clock pulse to the 3DO controller
PORTD &= ~B00000001; // Set LOW PD0
delayMicroseconds(MICROS_CLOCK);
PORTD |= B00000001; // Set HIGH PD0
delayMicroseconds(MICROS_CLOCK);
}

View File

@ -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 <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, 0x07, // USAGE_MAXIMUM (Button 7)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x07, // REPORT_COUNT (7)
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, 0x01, // 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,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 <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 "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();
};

View File

@ -0,0 +1,37 @@
# DaemonBite 3DO USB Controller adapter
## Introduction
With this simple to build adapter you can connect a 3DO gamepad (specifically tested using a Panasonic FZ-JP1 controller) 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.
Only one controller is currently supported.
The headphone jack is not supported.
## Parts you need
- Arduino Pro Micro (ATMega32U4)
- Male DB9 plug
- Wire
- Heat shrink tube (Ø ~20mm)
- Micro USB cable
## Wiring
Male DB9 socket pins (look at the pins in the socket):
| DB9 |
|-----------|
|\1 2 3 4 5/|
| \6 7 8 9/ |
3DO-DB9 | Arduino Pro Micro
---------------------------|------------------
1 GND | GND
2 VCC(5v) | VCC
3 Audio.1(1v-pp) |
4 Audio.1(1v-pp) |
5 VCC(5v) | VCC
6 P/S (Shift/Load) | 2 (PD1)
7 CLK(125KHz) (Shift/Clock)| 3 (PD0)
8 GND | GND
9 DATA | A0 (PF7)
## License
This project is licensed under the GNU General Public License v3.0.