Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c6f4758b6 | ||
|
|
06c65f1a87 | ||
|
|
f55e9ad39b | ||
|
|
ce7400b3fb | ||
|
|
7ac0f061cf | ||
|
|
716e2cd21a |
Binary file not shown.
@@ -2,9 +2,12 @@
|
||||
#Persistent
|
||||
#include AHKHID.ahk
|
||||
|
||||
hArd := HID_Open("1ccf","4148")
|
||||
MsgBox Sorry, this script is currently broken (I now need to get it to send a proper feature report instead of an output report)
|
||||
ExitApp
|
||||
|
||||
hArd := HID_Open("1ccf","1000")
|
||||
while (hLED = -1) {
|
||||
hArd := HID_Open("1ccf","4148")
|
||||
hArd := HID_Open("1ccf","1000")
|
||||
Sleep, 5000
|
||||
}
|
||||
if 0 < 1 ; The left side of a non-expression if-statement is always the name of a variable.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Ultimate Pop'n Controller
|
||||
|
||||
USB HID controller with 17 inputs (9 buttons, coin, test, reset, service), 20 outputs (18 lights, coin blocker, coin counter), 12-key matrix numpad, high polling rate, custom dll for full cabinet 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..).
|
||||
|
||||
@@ -10,10 +10,14 @@ In combination with the PN5180-cardio project, the whole IO from a Pop'n Music c
|
||||
|
||||
# Leonardo version
|
||||
|
||||
Because I loved the ModeSwitch feature, I adapted the code so it could compile for Leonardo as well. 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, duh).
|
||||
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). 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.
|
||||
|
||||
You only need to select "Leonardo" as your board type in Arduino IDE before flashing.
|
||||
|
||||
# Demo
|
||||
|
||||
https://www.instagram.com/p/CKE9HCQFCYM/
|
||||
@@ -32,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).
|
||||
@@ -45,9 +51,9 @@ The keypad code requires the Keypad library by Mark Stanley and Alexander Brevig
|
||||
|
||||
## I/O
|
||||
|
||||
This controller has 13 buttons (9 buttons + coin + service + reset + test), 4 dip-switches (only DIP4 is used in cabinets to select between 15 or 31kHz monitor resolution on boot), 8 lights (9 buttons + 5 top neon + 4 side pillar), 2 coin outputs (coin blocker, coin counter).
|
||||
This controller has 13 buttons (9 buttons + coin + service + reset + test), 4 dip-switches (only DIP4 is used in cabinets to select between 15 or 31kHz monitor resolution on boot), 18 lights (9 buttons + 5 top neon + 4 side pillar), 2 coin outputs (coin blocker, coin counter).
|
||||
|
||||
It also has 7 pins for the 12-key numpad, following the original cabinet pinout. It is recognized as a separate keyboard, and it is mapped to the keyboard toprow rather than the numpad (due to lack of stability when sending the numlock command to the BemaniPC).
|
||||
It also has 7 pins for the 12-key numpad, following the original cabinet pinout. It is recognized as a separate keyboard, and it is mapped to the keyboard toprow rather than the numpad (due to lack of stability when sending the numlock command to the BemaniPC).
|
||||
|
||||
The 00 key is mapped to comma (for direct compatibility with spicetools) and the originally unused bottom right key is mapped to the default card scan key.
|
||||
|
||||
@@ -55,10 +61,14 @@ It also has a cool light animation on boot which you can easily adapt to your li
|
||||
|
||||
## ezusb driver
|
||||
|
||||
The I/O is HID so it can be mapped with the usual IO emulation tools, but it can also be used without emulation, just like an official IOBoard, provided you replace the original `ezusb.dll` file with the one from this repo.
|
||||
The I/O is HID so it can be mapped with the usual IO emulation tools, and even has named outputs for ease of use, but it can also be used just like an official IOBoard, provided you replace the original `ezusb.dll` file with the one from this repo.
|
||||
|
||||
This way the firmware is fully compatible with anything that works on an official cabinet (PopnForwarder (a bit silly but why not :D), DJMame, ezPSXe pad plugin...)
|
||||
|
||||
## pop'n beMouse
|
||||
|
||||
The controller will enumerate in a way that is directly compatible with the old Pop'n beMouse software (it also directly works with Pop'n Lively).
|
||||
|
||||
## Light modes
|
||||
|
||||
There are 5 different modes :
|
||||
@@ -119,9 +129,19 @@ Arduino Leonardo has 5V logic therefore one can directly connect 5V leds to it.
|
||||
|
||||

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

|
||||
|
||||
For ACK (TXLED aka PD5) and SS (RXLED aka PB0) you have to solder new headers or cables directly on the leonardo PCB (or you can use an Arduino Micro (not pro micro) which has everything broken out).
|
||||
|
||||
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.
Binary file not shown.
+96
-85
@@ -11,19 +11,21 @@ extern "C" {
|
||||
#include "ezusb.h"
|
||||
|
||||
//#define DEBUG
|
||||
//#define DEBUG_LAMP
|
||||
|
||||
HANDLE g_hid_handle;
|
||||
uint8_t g_dip_state = 0;
|
||||
// GUID_DEVCLASS_HIDCLASS
|
||||
static GUID hidclass_guid = {0x745a17a0, 0x74d3, 0x11d0, 0xb6, 0xfe, 0x00, 0xa0, 0xc9, 0x0f, 0x57, 0xda};
|
||||
|
||||
|
||||
BOOLEAN get_device_path(wchar_t *lPath)
|
||||
{
|
||||
|
||||
const GUID *guid = &hidclass_guid;
|
||||
HidD_GetHidGuid(&hidclass_guid);
|
||||
HidD_GetHidGuid(&hidclass_guid);
|
||||
// Get device interface info set handle
|
||||
// for all devices attached to system
|
||||
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // Function class devices.
|
||||
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // Function class devices.
|
||||
if(hDevInfo == INVALID_HANDLE_VALUE)
|
||||
return FALSE;
|
||||
|
||||
@@ -38,8 +40,8 @@ BOOLEAN get_device_path(wchar_t *lPath)
|
||||
|
||||
// Iterate through all the interfaces and try to match one based on
|
||||
// the device number.
|
||||
DWORD device_index = 0;
|
||||
|
||||
DWORD device_index = 0;
|
||||
|
||||
for(DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL,guid, i, &spdid); i++)
|
||||
{
|
||||
// Get the device path.
|
||||
@@ -47,29 +49,29 @@ BOOLEAN get_device_path(wchar_t *lPath)
|
||||
SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
|
||||
if(dwSize == 0 || dwSize > sizeof(buf))
|
||||
continue;
|
||||
|
||||
|
||||
pspdidd->cbSize = sizeof(*pspdidd);
|
||||
ZeroMemory((PVOID)&spdd, sizeof(spdd));
|
||||
spdd.cbSize = sizeof(spdd);
|
||||
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd,
|
||||
dwSize, &dwSize, &spdd))
|
||||
continue;
|
||||
|
||||
/* check if the device contains our wanted vid/pid */
|
||||
if ( wcsstr( pspdidd->DevicePath, L"vid_1ccf&pid_4148&mi_02" ) == NULL )
|
||||
{
|
||||
|
||||
/* check if the device contains our wanted vid/pid */
|
||||
if ( wcsstr( pspdidd->DevicePath, L"vid_1ccf&pid_1000&mi_02" ) == NULL )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("\r\nDevice found at %S\r\n", &pspdidd->DevicePath);
|
||||
printf("\r\nDevice found at %S\r\n", &pspdidd->DevicePath);
|
||||
#endif
|
||||
//copy devpath into lPath
|
||||
wcscpy(lPath, pspdidd->DevicePath);
|
||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
||||
return TRUE;
|
||||
//copy devpath into lPath
|
||||
wcscpy(lPath, pspdidd->DevicePath);
|
||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
||||
return TRUE;
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
||||
return FALSE;
|
||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,46 +83,46 @@ BOOLEAN get_device_path(wchar_t *lPath)
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
static int controller_init(){
|
||||
static uint8_t err_count = 0;
|
||||
wchar_t lPath[256];
|
||||
char path[256];
|
||||
FILE *file;
|
||||
static uint8_t err_count = 0;
|
||||
wchar_t lPath[256];
|
||||
char path[256];
|
||||
FILE *file;
|
||||
|
||||
/* first check if there's a devicepath.dat override */
|
||||
file = fopen("devicepath.dat", "r");
|
||||
/* first check if there's a devicepath.dat override */
|
||||
file = fopen("devicepath.dat", "r");
|
||||
if (file != NULL)
|
||||
{
|
||||
while ( fgets(path,256,file) != NULL )
|
||||
{
|
||||
path[strcspn(path, "\r\n")] = 0;
|
||||
g_hid_handle = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if ( g_hid_handle != INVALID_HANDLE_VALUE )
|
||||
{
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
while ( fgets(path,256,file) != NULL )
|
||||
{
|
||||
path[strcspn(path, "\r\n")] = 0;
|
||||
g_hid_handle = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if ( g_hid_handle != INVALID_HANDLE_VALUE )
|
||||
{
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
/* auto detect device path */
|
||||
if (!get_device_path(lPath))
|
||||
{
|
||||
printf("\r\nPOPN MUSIC USB not detected.\r\n");
|
||||
err_count++;
|
||||
if (err_count > 2){
|
||||
printf("Could not init device after multiple attempts. Exiting.\r\n");
|
||||
exit(1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/* auto detect device path */
|
||||
if (!get_device_path(lPath))
|
||||
{
|
||||
printf("\r\nPOPN MUSIC USB not detected.\r\n");
|
||||
err_count++;
|
||||
if (err_count > 2){
|
||||
printf("Could not init device after multiple attempts. Exiting.\r\n");
|
||||
exit(1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_hid_handle = CreateFile(lPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||
g_hid_handle = CreateFile(lPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if ( g_hid_handle == INVALID_HANDLE_VALUE )
|
||||
{
|
||||
printf("Could not open detected device (err = %x).\r\n", GetLastError());
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -140,36 +142,33 @@ static int controller_init(){
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
static int controller_read_buttons(uint32_t *pad_bits){
|
||||
DWORD uint8_tsRead = 0;
|
||||
uint8_t res[4];
|
||||
unsigned char buf[6]; // gamepad report length is 3 uint8_ts in firmware, doubled because NumInputBuffer is set to 2
|
||||
DWORD bytesRead = 0;
|
||||
uint8_t res[4];
|
||||
unsigned char buf[6]; // gamepad report length is 3 bytes in firmware, doubled because NumInputBuffer is set to 2
|
||||
|
||||
buf[0] = 0x04; // gamepad report ID is 4 in firmware
|
||||
*pad_bits = 0;
|
||||
|
||||
ReadFile(g_hid_handle, buf, 6, &uint8_tsRead, NULL);
|
||||
// uint8_tsRead should either be 6 (if it successfully read 2 reports) or 3 (only one)
|
||||
if ( uint8_tsRead != 6 && uint8_tsRead != 3 )
|
||||
ReadFile(g_hid_handle, buf, 6, &bytesRead, NULL);
|
||||
// bytesRead should either be 6 (if it successfully read 2 reports) or 3 (only one)
|
||||
if ( bytesRead != 6 && bytesRead != 3 )
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("HID read error (got %u uint8_ts)\n",uint8_tsRead);
|
||||
printf("HID read error (got %u bytes)\n",bytesRead);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* HID read ok, convert latest report uint8_ts to pop'n bitfield */
|
||||
res[3] = 0;
|
||||
res[2] = (buf[uint8_tsRead-1]<<3 | buf[uint8_tsRead-1]) & 0x41;
|
||||
res[1] = buf[uint8_tsRead-2];
|
||||
/* HID read ok, convert latest report bytes to pop'n bitfield */
|
||||
res[3] = g_dip_state; // dip switches
|
||||
res[2] = (buf[bytesRead-1]<<3 | buf[bytesRead-1]) & 0x41; // button 9 (0x01) and coin mech (0x40)
|
||||
res[1] = buf[bytesRead-2]; // button 1 to 8
|
||||
res[0] = 0;
|
||||
buf[uint8_tsRead-1] >>= 1;
|
||||
if ( buf[uint8_tsRead-1]&1 ) res[0] |= 0x80;
|
||||
buf[uint8_tsRead-1] >>= 1;
|
||||
if ( buf[uint8_tsRead-1]&1 ) res[0] |= 0x40;
|
||||
buf[bytesRead-1] >>= 1;
|
||||
if ( buf[bytesRead-1]&1 ) res[0] |= 0x80; // test
|
||||
buf[bytesRead-1] >>= 1;
|
||||
if ( buf[bytesRead-1]&1 ) res[0] |= 0x40; // service
|
||||
*pad_bits = *(uint32_t *)res;
|
||||
|
||||
/* dip switches */
|
||||
*pad_bits |= g_dip_state << 24;
|
||||
|
||||
#ifdef DEBUG
|
||||
for (int i = 0; i<32; i++){
|
||||
@@ -177,7 +176,7 @@ static int controller_read_buttons(uint32_t *pad_bits){
|
||||
if (((i+1)%4)==0) printf(" ");
|
||||
}
|
||||
printf("\n");
|
||||
if (*pad_bits & 0x8000000) printf("31kHz mode\n");
|
||||
if (*pad_bits & 0x8000000) printf("31kHz mode\r\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -188,22 +187,33 @@ static int controller_read_buttons(uint32_t *pad_bits){
|
||||
* Pop'n Music sends lights state as a int32_t bitfield with the following format :
|
||||
* bits 0 (LSB) to 4 : neons from top to bottom
|
||||
* bits 8 to 11 : side lamps (left blue, left red, right blue, right red)
|
||||
* bits 23 to 31 : button lamps from left to right
|
||||
* bits 16 to 19 : coin blocker (0x0 for on, 0xF for off)
|
||||
* bits 23 to 31 : button lamps from left to right (note: technically game sets the whole 20-23 to 0xF rather than 0x8 for lamp 1)
|
||||
*
|
||||
* @param lamp_bits[in] lamp state bitfield
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
static int controller_write_leds(int32_t lamp_bits){
|
||||
DWORD bytesWritten = 0;
|
||||
DWORD bytesWritten = 0;
|
||||
uint8_t *src = (uint8_t*) &lamp_bits; //cast as uint8_t array for fast conversion
|
||||
uint8_t buf[5]; // HID lights report length is 5 bytes in Arduino firmware
|
||||
|
||||
#ifdef DEBUG_LAMP
|
||||
for (int i = 0; i<32; i++){
|
||||
printf("%c",( ((uint32_t )lamp_bits>>(31-i))&1)?'1':'0');
|
||||
if (((i+1)%4)==0) printf(" ");
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
/* convert bitfield into HID Report format */
|
||||
buf[4] = 0;
|
||||
buf[3] = (src[1] >> 2);
|
||||
buf[2] = (src[1] << 6) | (src[0] << 1) | (src[3] >> 7);
|
||||
buf[1] = (src[3] << 1) | (src[2] >> 7);
|
||||
buf[0] = 0x05; // HID lights report id is 5 in Arduino firmware
|
||||
buf[4] = 0; // hid report padding
|
||||
buf[3] = ((src[1]&0x0F) >> 2); // right lamps
|
||||
if ( !(src[2] & 0x0F) ) buf[3] |= 0x04; // coin blocker
|
||||
if ( src[1] & 0xF0 ) buf[3] |= 0x08; // coin counter?
|
||||
buf[2] = (src[1] << 6) | (src[0] << 1) | (src[3] >> 7); // button 9, top and left lamps
|
||||
buf[1] = (src[3] << 1) | (src[2] >> 7); // button lamps 1-8
|
||||
buf[0] = 0x05; // HID lights report id
|
||||
|
||||
/* send HID Report */
|
||||
WriteFile(g_hid_handle, buf, 5, &bytesWritten, NULL);
|
||||
@@ -303,7 +313,7 @@ __declspec(dllexport) int __cdecl usbSetExtIo(int i) {
|
||||
__declspec(dllexport) int __cdecl usbStart(int i) {
|
||||
if (controller_init() == -1)
|
||||
{
|
||||
printf("Could not init device.\n");
|
||||
printf("Could not init device.\r\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -312,17 +322,17 @@ __declspec(dllexport) int __cdecl usbStart(int i) {
|
||||
2);
|
||||
if (!hidres)
|
||||
{
|
||||
printf("error %d setnuminputbuff\n",GetLastError());
|
||||
printf("Error %d setnuminputbuff\r\n",GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* read dip switches */
|
||||
uint8_t feat[2] = {0x06, 0x00};
|
||||
if (!HidD_GetFeature(g_hid_handle, &feat, 2))
|
||||
{
|
||||
printf("error %d getFeature\n",GetLastError());
|
||||
};
|
||||
g_dip_state = feat[1];
|
||||
/* read dip switches */
|
||||
uint8_t feat[2] = {0x06, 0x00};
|
||||
if (!HidD_GetFeature(g_hid_handle, &feat, 2))
|
||||
{
|
||||
printf("Cannot read dipswitches (error %d)\r\n",GetLastError());
|
||||
};
|
||||
g_dip_state = feat[1];
|
||||
|
||||
#ifdef FORCE_DIP4
|
||||
g_dip_state |= 0x08;
|
||||
@@ -335,7 +345,7 @@ __declspec(dllexport) int __cdecl usbStart(int i) {
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Managed to open device %x\n",g_hid_handle);
|
||||
printf("Dip state is %x\n", g_dip_state);
|
||||
printf("Dip state is %x\n", g_dip_state);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -360,7 +370,7 @@ __declspec(dllexport) int __cdecl usbWdtStartDone() {
|
||||
BOOL APIENTRY DllMain( HMODULE hModule,
|
||||
DWORD fdwReason,
|
||||
LPVOID lpReserved
|
||||
) {
|
||||
) {
|
||||
if (fdwReason == 1) {
|
||||
//https://blogs.msdn.microsoft.com/larryosterman/2004/06/03/little-known-win32-apis-disablethreadlibrarycalls/
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
@@ -368,3 +378,4 @@ BOOL APIENTRY DllMain( HMODULE hModule,
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
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 |
@@ -368,7 +368,7 @@ uint8_t STRING_ID_LED_Count = 9;
|
||||
#endif
|
||||
|
||||
const DeviceDescriptor PROGMEM USB_DeviceDescriptorIAD =
|
||||
D_DEVICE(0xEF,0x02,0x01,64,0x1ccf,0x4148,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
|
||||
D_DEVICE(0xEF,0x02,0x01,64,0x1ccf,0x1000,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
|
||||
|
||||
/* PluggableUSBModule IMPLEMENTATION */
|
||||
|
||||
|
||||
@@ -5,11 +5,25 @@
|
||||
#include <Keyboard.h>
|
||||
#else
|
||||
#include <EEPROM.h>
|
||||
|
||||
#define WITH_PSX 0 //enable PSX compatibility
|
||||
#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 +40,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 +111,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 +141,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 +171,7 @@ void loop() {
|
||||
|
||||
//check for HID-requested lightmode change
|
||||
POPNHID.updateLightMode();
|
||||
}
|
||||
}
|
||||
|
||||
/* LAMPS */
|
||||
uint8_t mode = POPNHID.getLightMode();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user