Saturn adapter added

This commit is contained in:
MickGyver 2020-05-15 10:11:55 +03:00
parent 0b79c943a6
commit 69c1ff98e5
4 changed files with 460 additions and 0 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, 0x09, // USAGE_MAXIMUM (Button 9)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x09, // REPORT_COUNT (9)
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, 0x07, // REPORT_SIZE (7)
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 {
uint16_t buttons : 9;
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,55 @@
# DaemonBite Saturn Controller To USB Adapter
## Introduction
This is a simple to build adapter for connecting SEGA Saturn controllers to USB. Currently it supports normal digital Saturn controllers, 3D controllers are not supported.
The input lag for this adapter is minimal (about 0.75ms average connected to MiSTer).
## Parts you need
- Arduino Pro Micro (ATMega32U4)
- Male end of a Saturn controller extension cable
- Heat shrink tube (Ø ~20mm)
- Micro USB cable
## Wiring
Saturn controller socket (looking face-on at the front of the socket):
___________________
/ 1 2 3 4 5 6 7 8 9 \
|___________________|
Saturn controller plug (looking face-on at the front of the controller plug):
___________________
/ 9 8 7 6 5 4 3 2 1 \
|___________________|
| Saturn (P1) | Arduino Pro Micro |
| ------ | ------ |
| 1 VCC | VCC |
| 2 DATA1 | 2 PD1 |
| 3 DATA0 | 3 PD0 |
| 4 SEL1 | 15 PB1 (Shared with P2) |
| 5 SEL0 | 14 PB3 (Shared with P2) |
| 6 TL (5V) | 4 PD4 |
| 7 DATA3 | TXO PD3 |
| 8 DATA2 | RXI PD2 |
| 9 GND | GND |
| Saturn (P2) | Arduino Pro Micro
| ------ | ------ |
| 1 VCC | VCC |
| 2 DATA1 | A2 PF5 |
| 3 DATA0 | A3 PF4 |
| 4 SEL1 | 15 PB1 (Shared with P1) |
| 5 SEL0 | 14 PB3 (Shared with P1) |
| 6 TL (5V) | 6 PD7 |
| 7 DATA3 | A0 PF7 |
| 8 DATA2 | A1 PF6 |
| 9 GND | GND |
## How to assemble (please ignore the switch)
Check the SegaControllerUSB readme for an idea how to assemble.
## Retro Bit 2.4GHz wireless controller
The Retro Bit 2.4GHz wireless controller is officially not supported but you can enable support for it in the code (with a huge increase in lag as a side effect).The receiver of the Retro Bit 2.4GHz controller needs to be plugged in after the adapter has been connected to USB and the RETROBIT define needs to be uncommented.
## License
This project is licensed under the GNU General Public License v3.0.

View File

@ -0,0 +1,185 @@
/* DaemonBite Saturn 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 1 // Set to 1 or 2 depending if you want to make a 1 or 2 port adapter
#define SELECT_PAUSE 3 // How many microseconds to wait after setting select lines? (2µs is enough according to the Saturn developer's manual)
//#define RETROBIT // Uncomment to support the Retro Bit 2.4GHz controller (this will increase lag a lot)
#define UP 0x01
#define DOWN 0x02
#define LEFT 0x04
#define RIGHT 0x08
// 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 = "SATURN to USB";
/* -------------------------------------------------------------------------
Saturn controller socket (looking face-on at the front of the socket):
___________________
/ 1 2 3 4 5 6 7 8 9 \
|___________________|
Saturn controller plug (looking face-on at the front of the controller plug):
___________________
/ 9 8 7 6 5 4 3 2 1 \
|___________________|
Saturn (P1) Arduino Pro Micro
--------------------------------------
1 VCC VCC
2 DATA1 2 PD1
3 DATA0 3 PD0
4 SEL1 15 PB1 (Shared with P2)
5 SEL0 14 PB3 (Shared with P2)
6 TL (5V) 4 PD4
7 DATA3 TXO PD3
8 DATA2 RXI PD2
9 GND GND
Saturn (P2) Arduino Pro Micro
--------------------------------------
1 VCC VCC
2 DATA1 A2 PF5
3 DATA0 A3 PF4
4 SEL1 15 PB1 (Shared with P1)
5 SEL0 14 PB3 (Shared with P1)
6 TL (5V) 6 PD7
7 DATA3 A0 PF7
8 DATA2 A1 PF6
9 GND GND
NOTE: The receiver of the Retro Bit 2.4GHz controller needs to be plugged
in after the adapter has been connected to USB and the RETROBIT
define needs to be uncommented.
------------------------------------------------------------------------- */
// 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;
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 D4 and D7 as inputs and enable pull-up resistors (port1/2 TL)
DDRD &= ~B10010000;
PORTD |= B10010000;
// Set B1 and B3 as outputs and set them HIGH (select pins)
PORTD |= B00001010;
DDRB |= B00001010;
// Wait for the controller(s) to settle
delay(100);
}
void loop() { while(1)
{
// 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
read3();
read2();
read1();
read4();
// 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] | ((buttons[gp][0] & 0x80)<<1);
Gamepad[gp]._GamepadReport.Y = ((buttons[gp][0] & DOWN) >> 1) - (buttons[gp][0] & UP);
Gamepad[gp]._GamepadReport.X = ((buttons[gp][0] & RIGHT) >> 3) - ((buttons[gp][0] & LEFT) >> 2);
buttonsPrev[gp][0] = buttons[gp][0];
buttonsPrev[gp][1] = buttons[gp][1];
Gamepad[gp].send();
}
}
#ifdef RETROBIT
// This delay is needed for the retro bit 2.4GHz wireless controller, making it more or less useless with this adapter
delay(17);
#endif
}}
// Read R, X, Y, Z
void read1()
{
PORTB &= ~B00001010; // Set select outputs to 00
delayMicroseconds(SELECT_PAUSE);
buttons[0][1] |= (PIND & 0x0f) << 4;
if(GAMEPAD_COUNT == 2)
buttons[1][1] |= (PINF & 0xf0);
}
// Read ST, A, C, B
void read2()
{
PORTB ^= B00001010; // Toggle select outputs (01->10 or 10->01)
delayMicroseconds(SELECT_PAUSE);
buttons[0][1] |= (PIND & 0x0f);
if(GAMEPAD_COUNT == 2)
buttons[1][1] |= (PINF & 0xf0)>>4;
}
// Read DR, DL, DD, DU
void read3()
{
PORTB ^= B00000010; // Set select outputs to 10 from 11 (toggle)
delayMicroseconds(SELECT_PAUSE);
buttons[0][0] |= (PIND & 0x0f);
if(GAMEPAD_COUNT == 2)
buttons[1][0] |= (PINF & 0xf0) >> 4;
}
// Read L, *, *, *
void read4()
{
PORTB |= B00001010; // Set select outputs to 11
delayMicroseconds(SELECT_PAUSE);
buttons[0][0] |= (PIND & 0x0f) << 4;
if(GAMEPAD_COUNT == 2)
buttons[1][0] |= (PINF & 0xf0);
}