Merge pull request #1 from CrazyRedMachine/PSX_usbemani

(leonardo) playstation compatibility
This commit was merged in pull request #1.
This commit is contained in:
CrazyRedMachine
2021-11-12 00:22:59 +01:00
committed by GitHub
6 changed files with 346 additions and 11 deletions
+14 -4
View File
@@ -2,7 +2,7 @@
# Ultimate Pop'n Controller
USB HID controller with 17 inputs (9 buttons, coin, test, reset, service), 20 named outputs (18 lights, coin blocker, coin counter), 4 features (dip switches), 12-key matrix numpad, high polling rate, custom dll for full cabinet compatibility, Pop'n beMouse and Lively native compatibility, and nice programmable features.
USB HID controller with 13 inputs (9 buttons, coin, test, reset, service), 20 named outputs (18 lights, coin blocker, coin counter), 4 features (dip switches), 12-key matrix numpad, high polling rate, custom dll for full cabinet compatibility, Pop'n beMouse and Lively native compatibility, and nice programmable features.
The goal was to replace the official IO Board from my pop'n cabinet with a DIY controller instead, for stability reasons (Konami IO boards are prone to failure, most Pop'n Music cabinets in the wild have broken lamps...) but also for QoL improvements (this allows me to use my panel system-wide and not only in-game, which means I can control a multiboot menu, play with emulators etc..).
@@ -12,7 +12,7 @@ In combination with the PN5180-cardio project, the whole IO from a Pop'n Music c
This code was originally written for Arduino Due but is also compatible with Leonardo without any change required.
Due to lack of gpio, in this case there's only 11 buttons and 9 lights, no keypad support, and reactive mode won't include AC light simulation (there's no side or top lamps, but you might want to have a look at the ambilight branch for ws2812b side/top lamps).
Due to lack of gpio, in this case there's only 11 buttons and 9 lights, no keypad support, and reactive mode won't include AC light simulation (there's no side or top lamps, but you might want to have a look at the ambilight branch for ws2812b side/top lamps). However, there is **Playstation compatibility** as well.
I'm also taking advantage of the Leonardo EEPROM. On manually switching, the resulting lightmode is stored in the EEPROM so that it persists on controller disconnect/reconnect.
@@ -36,6 +36,8 @@ The keypad code uses the Keypad library by Mark Stanley and Alexander Brevig.
Switch debouncing is done with Bounce2 library by Thomas O Fredericks.
Playstation compatibility uses parts of progmem's excellent https://github.com/progmem/re-usbemani/ project
# Supported devices and requirements
This code was designed for Arduino Due. It will compile for Leonardo as well but some features are stripped due to lack of gpio (only 11 buttons and 9 lights, no keypad, lower polling rate).
@@ -127,9 +129,17 @@ Arduino Leonardo has 5V logic therefore one can directly connect 5V leds to it.
![pinout](https://github.com/CrazyRedMachine/UltimatePopnController/blob/master/pinout_leonardo.png?raw=true)
Arduino Leonardo version is also compatible with Playstation and Playstation 2 (it can be made to be plugged directly to the controller port, please refer to the PSX branch for more information).
## Playstation compatibility
## Donation
Arduino Leonardo version is also compatible with Playstation and Playstation 2 (it can be made to be plugged directly to the controller port, using the following pinout).
![pinout_psx](https://github.com/CrazyRedMachine/UltimatePopnController/blob/master/pinout_leonardo_psx.png?raw=true)
LEDs will be dimmer due to 3.3v power. Using the 7V rumble motor line to Vin instead, and using NPN transistors like 2N2222A on MISO and ACK lines to prevent backfeeding voltage into the console will solve the issue (set INVERT_CIPO and INVERT_ACK to 1 in `ps2.c`).
**BEWARE: DO NOT PLUG USB AND PSX AT THE SAME TIME, THIS CAN DAMAGE YOUR CONSOLE**
# Donation
If this project helps you and you want to give back, you can help me with my future projects.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

+45 -7
View File
@@ -5,11 +5,23 @@
#include <Keyboard.h>
#else
#include <EEPROM.h>
#define WITH_PSX 1
#if WITH_PSX == 1
#include "ps2.h"
#endif
#endif
#include "POPNHID.h"
#if defined(ARDUINO_ARCH_SAM)
/* 1 frame (as declared in POPNHID.cpp) on highspeed USB spec is 125µs */
#define REPORT_DELAY 125
#define MILLIDEBOUNCE 15
#define REPORT_DELAY 120
#else
/* 1 frame (as declared in POPNHID.cpp) on fullspeed USB spec is 1ms */
#define REPORT_DELAY 995
#endif
#define MILLIDEBOUNCE 5
POPNHID_ POPNHID;
/* Buttons + Lights declarations */
@@ -26,6 +38,8 @@ const byte ButtonCount = sizeof(ButtonPins) / sizeof(ButtonPins[0]);
const byte LightCount = sizeof(LightPins) / sizeof(LightPins[0]);
Bounce buttons[ButtonCount];
uint16_t buttonsState = 0;
#if defined(ARDUINO_ARCH_SAM)
/* Keypad declarations */
const byte ROWS = 4;
@@ -95,6 +109,24 @@ void setup() {
if (lightMode > 4)
lightMode = 2;
POPNHID.setLightMode(lightMode);
#if WITH_PSX == 1
PS2_MapInput(&buttonsState, (1<<0), PS2_TRIANGLE);
PS2_MapInput(&buttonsState, (1<<1), PS2_CIRCLE);
PS2_MapInput(&buttonsState, (1<<2), PS2_R1);
PS2_MapInput(&buttonsState, (1<<3), PS2_CROSS);
PS2_MapInput(&buttonsState, (1<<4), PS2_L1);
PS2_MapInput(&buttonsState, (1<<5), PS2_SQUARE);
PS2_MapInput(&buttonsState, (1<<6), PS2_R2);
PS2_MapInput(&buttonsState, (1<<7), PS2_UP);
PS2_MapInput(&buttonsState, (1<<8), PS2_L2);
PS2_MapInput(&buttonsState, (1<<9), PS2_SELECT);
PS2_MapInput(&buttonsState, (1<<10), PS2_START);
PS2_AlwaysInput(PS2_LEFT|PS2_DOWN|PS2_RIGHT);
PS2_Init();
#endif
#endif
//boot animation
uint16_t anim[] = {1, 4, 16, 64, 256, 128, 32, 8, 2};
@@ -107,21 +139,27 @@ void setup() {
/* LOOP */
unsigned long lastReport = 0;
uint32_t prevButtonsState = 0;
uint16_t prevButtonsState = 0;
bool modeChanged = false;
void loop() {
/* BUTTONS */
uint32_t buttonsState = 0;
buttonsState = 0;
for (int i = 0; i < ButtonCount; i++) {
buttons[i].update();
int value = buttons[i].read();
if (value != HIGH){
buttonsState |= (uint32_t)1 << i;
buttonsState |= (uint16_t)1 << i;
} else {
buttonsState &= ~((uint32_t)1 << i);
buttonsState &= ~((uint16_t)1 << i);
}
}
#if defined(ARDUINO_ARCH_AVR)
#if WITH_PSX == 1
PS2_Task();
#endif
#endif
/* USB DATA */
if ( ( (micros() - lastReport) >= REPORT_DELAY) )
{
@@ -131,7 +169,7 @@ void loop() {
//check for HID-requested lightmode change
POPNHID.updateLightMode();
}
}
/* LAMPS */
uint8_t mode = POPNHID.getLightMode();
+237
View File
@@ -0,0 +1,237 @@
#include "ps2.h"
#if defined(ARDUINO_ARCH_AVR)
/* USER CUSTOMIZABLE SETTINGS */
#define ACK_PORT PORTD
#define ACK_DDR DDRD
#define ACK_PIN 5 // PD5 (TXLED)
/* RECOMMENDED DO NOT CHANGE */
#define INVERT_CIPO 0 // Set to 1 if CIPO is open-drain via transistor (recommended)
#define INVERT_ACK 0 // Set to 1 if ACK is open-drain via transistor (recommended)
/* END OF USER CUSTOMIZABLE SETTINGS */
// Stores a constructed packet for the PS2.
uint16_t Data = 0;
// List of available PS2 inputs.
PS2_InputList_t *PS2Input = NULL;
// Current PS2 state.
void (*PS2Handler)(uint8_t) = NULL;
void PS2_Acknowledge(void) {
// Burn a few cycles before acknowledging
asm volatile(
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
);
#if INVERT_ACK == 0
ACK_DDR |= (1<<ACK_PIN);
#else
ACK_DDR &= ~(1<<ACK_PIN);
#endif
// 40 cycles of delay should give us the same delay as a real PS1 controller
asm volatile(
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
);
#if INVERT_ACK == 0
ACK_DDR &= ~(1<<ACK_PIN);
#else
ACK_DDR |= (1<<ACK_PIN);
#endif
}
void PS2_Listen(uint8_t in);
void PS2_Addressed(uint8_t in);
void PS2_HeaderFinished(uint8_t in);
void PS2_LowerSent(uint8_t in);
uint8_t memory_card_timeout = 0;
void PS2_MemoryCardTimeout(uint8_t in) {
if (memory_card_timeout) --memory_card_timeout;
if (!memory_card_timeout)
PS2Handler = PS2_Listen;
}
void PS2_MemoryCardID2(uint8_t in) {
// If we receive a 0x01 here, revert back to the listener
--memory_card_timeout;
if (in == 0x01) {
memory_card_timeout = 0;
PS2_Listen(in);
return;
}
PS2Handler = PS2_MemoryCardTimeout;
}
void PS2_MemoryCardID1(uint8_t in) {
// If we receive a 0x01 here, revert back to the listener
--memory_card_timeout;
if (in == 0x01) {
memory_card_timeout = 0;
PS2_Listen(in);
return;
}
PS2Handler = PS2_MemoryCardID2;
}
// Memory card addressed; ignore input
void PS2_MemoryCardAddressed(uint8_t in) {
memory_card_timeout = 8;
if (in == 'R')
memory_card_timeout = 138;
if (in == 'W')
memory_card_timeout = 136;
PS2Handler = PS2_MemoryCardID1;
}
// Idle state.
void PS2_Listen(uint8_t in) {
if (in == 0x81) {
DDRB &= ~0x08;
PS2Handler = PS2_MemoryCardAddressed;
}
// Report as a digital controller when addressed
if (in == 0x01) {
DDRB |= 0x08;
#if INVERT_CIPO == 1
SPDR = ~0x41;
#else
SPDR = 0x41;
#endif
PS2Handler = PS2_Addressed;
PS2_Acknowledge();
}
}
// When polling is requested, begin responding
void PS2_Addressed(uint8_t in) {
if (in == 0x42) {
#if INVERT_CIPO == 1
SPDR = ~0x5A;
#else
SPDR = 0x5A;
#endif
PS2Handler = PS2_HeaderFinished;
PS2_Acknowledge();
}
}
// After end-of-header sent, send the first byte
void PS2_HeaderFinished(uint8_t in) {
uint8_t *data = (uint8_t *)&Data;
#if INVERT_CIPO == 1
SPDR = ~(*data);
#else
SPDR = (*data);
#endif
PS2Handler = PS2_LowerSent;
PS2_Acknowledge();
}
// After first byte sent, send the second and go back to listening.
void PS2_LowerSent(uint8_t in) {
uint8_t *data = (uint8_t *)&Data + 1;
#if INVERT_CIPO == 1
SPDR = ~(*data);
#else
SPDR = (*data);
#endif
PS2Handler = PS2_Listen;
PS2_Acknowledge();
}
void PS2_Init(void) {
cli();
#if INVERT_ACK == 0
ACK_PORT &= ~(1<<ACK_PIN);
#else
ACK_PORT |= (1<<ACK_PIN);
#endif
PS2_Acknowledge();
// Set MISO as an output pin
DDRB |= 0x08;
// Setup data on falling edge, sample on rising edge (SPI mode 3)
SPCR = (1 << CPOL) | (1 << CPHA)
// Transmit LSB first
| (1 << DORD)
// Enable interrupts for SPI
| (1 << SPIE)
// Enable SPI
| (1 << SPE);
// Set the first byte up
#if INVERT_CIPO == 1
SPDR = 0x00; // 0xFF;
#else
SPDR = 0xFF;
#endif
PS2Handler = PS2_Listen;
sei();
}
// Update the stored data packet
void PS2_Task(void) {
if (PINB & 0x01)
{
#if INVERT_CIPO == 1
SPDR = 0x00;
#else
SPDR = 0xFF;
#endif
}
PS2_InputList_t *map = PS2Input;
uint16_t new_data = 0;
while(map) {
if (!map->input || (*map->input & map->mask))
new_data |= map->buttons;
map = map->parent;
}
Data = ~new_data;
}
void PS2_MapInput(uint16_t *input, uint16_t mask, PS2_INPUT buttons) {
PS2_InputList_t *child = calloc(1, sizeof(PS2_InputList_t));
child->input = input,
child->mask = mask,
child->buttons = buttons,
child->parent = PS2Input;
PS2Input = child;
}
void PS2_AlwaysInput(PS2_INPUT buttons) {
PS2_InputList_t *child = calloc(1, sizeof(PS2_InputList_t));
child->input = NULL,
child->mask = 0,
child->buttons = buttons,
child->parent = PS2Input;
PS2Input = child;
}
// When a transfer is complete, determine what to do next
ISR(SPI_STC_vect) {
uint8_t input = SPDR;
if (input == 0x01 && (!memory_card_timeout)) PS2Handler = PS2_Listen;
PS2Handler(SPDR);
}
#endif
+50
View File
@@ -0,0 +1,50 @@
#if defined(ARDUINO_ARCH_AVR)
#include <stdlib.h>
#include <stdint.h>
#include <avr/interrupt.h>
typedef enum {
PS2_NC = 0,
PS2_SELECT = (1 << 0),
PS2_L3 = (1 << 1),
PS2_R3 = (1 << 2),
PS2_START = (1 << 3),
PS2_UP = (1 << 4),
PS2_RIGHT = (1 << 5),
PS2_DOWN = (1 << 6),
PS2_LEFT = (1 << 7),
PS2_L2 = (1 << 8),
PS2_R2 = (1 << 9),
PS2_L1 = (1 << 10),
PS2_R1 = (1 << 11),
PS2_TRIANGLE = (1 << 12),
PS2_CIRCLE = (1 << 13),
PS2_CROSS = (1 << 14),
PS2_SQUARE = (1 << 15),
} PS2_INPUT;
typedef struct PS2_InputList_t PS2_InputList_t;
struct PS2_InputList_t {
uint16_t *input; // Input source; NULL is always true
uint16_t mask; // Mask to check; (*input & mask)
PS2_INPUT buttons; // OR the following if true
PS2_InputList_t *parent;
};
#ifdef __cplusplus
extern "C"{
#endif
void PS2_Init(void);
void PS2_Task(void);
void PS2_MapInput(uint16_t *input, uint16_t mask, PS2_INPUT buttons);
void PS2_AlwaysInput(PS2_INPUT buttons);
#ifdef __cplusplus
}
#endif
#endif