Skip to content

Commit a565642

Browse files
Merge pull request #88 from herzmx/neogeo2usb
Add NEOGEO+ to USB adapter support
2 parents 1b50d87 + 25a0338 commit a565642

File tree

11 files changed

+1186
-1
lines changed

11 files changed

+1186
-1
lines changed

Makefile

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ CONSOLE_wifi2usb := joypad_wifi2usb
7979
CONSOLE_snes2usb := joypad_snes2usb
8080
CONSOLE_n642usb := joypad_n642usb
8181
CONSOLE_gc2usb := joypad_gc2usb
82+
CONSOLE_neogeo2usb := joypad_neogeo2usb
83+
CONSOLE_neogeo2usb_rp2040zero := joypad_neogeo2usb_rp2040zero
8284
CONSOLE_controller_fisherprice := joypad_controller_fisherprice
8385
CONSOLE_controller_fisherprice_analog := joypad_controller_fisherprice_analog
8486
CONSOLE_controller_alpakka := joypad_controller_alpakka
@@ -113,6 +115,8 @@ APP_wifi2usb_pico2_w := pico2_w wifi2usb wifi2usb_pico2_w WiFi USB
113115
APP_snes2usb_kb2040 := kb2040 snes2usb snes2usb_kb2040 SNES USB
114116
APP_n642usb_kb2040 := kb2040 n642usb n642usb_kb2040 N64 USB
115117
APP_gc2usb_kb2040 := kb2040 gc2usb gc2usb_kb2040 GameCube USB
118+
APP_neogeo2usb_kb2040 := kb2040 neogeo2usb neogeo2usb_kb2040 NEOGEO USB
119+
APP_neogeo2usb_rp2040zero := rp2040zero neogeo2usb_rp2040zero neogeo2usb_rp2040zero NEOGEO USB
116120
APP_controller_fisherprice_kb2040 := kb2040 controller_fisherprice controller_fisherprice_kb2040 GPIO USB
117121
APP_controller_fisherprice_analog_kb2040 := kb2040 controller_fisherprice_analog controller_fisherprice_analog_kb2040 GPIO/ADC USB
118122
APP_controller_alpakka_pico := pico controller_alpakka controller_alpakka_pico GPIO/I2C USB
@@ -185,7 +189,9 @@ help:
185189
@echo " make snes2usb_kb2040 - SNES -> USB HID (KB2040)"
186190
@echo " make n642usb_kb2040 - N64 -> USB HID (KB2040)"
187191
@echo " make gc2usb_kb2040 - GameCube -> USB HID (KB2040)"
188-
@echo " make controller_fisherprice_kb2040 - GPIO -> USB HID (KB2040)"
192+
@echo " make neogeo2usb_kb2040 - NEOGEO -> USB HID (KB2040)"
193+
@echo " make neogeo2usb_rp2040zero - NEOGEO -> USB HID (KB2040)"
194+
@echo " make controller_fisherprice_kb2040 - GPIO -> USB HID (RP2040-Zero)"
189195
@echo " make controller_alpakka_pico - GPIO/I2C -> USB HID (Pico)"
190196
@echo " make controller_macropad - 12 keys -> USB HID (MacroPad RP2040)"
191197
@echo ""
@@ -396,6 +402,14 @@ n642usb_kb2040:
396402
gc2usb_kb2040:
397403
$(call build_app,gc2usb_kb2040)
398404

405+
.PHONY: neogeo2usb_kb2040
406+
neogeo2usb_kb2040:
407+
$(call build_app,neogeo2usb_kb2040)
408+
409+
.PHONY: neogeo2usb_rp2040zero
410+
neogeo2usb_rp2040zero:
411+
$(call build_app,neogeo2usb_rp2040zero)
412+
399413
.PHONY: controller_fisherprice_kb2040
400414
controller_fisherprice_kb2040:
401415
$(call build_app,controller_fisherprice_kb2040)
@@ -629,6 +643,14 @@ flash-n642usb_kb2040:
629643
flash-gc2usb_kb2040:
630644
@$(MAKE) --no-print-directory _flash_app APP_NAME=gc2usb_kb2040
631645

646+
.PHONY: flash-neogeo2usb_kb2040
647+
flash-neogeo2usb_kb2040:
648+
@$(MAKE) --no-print-directory _flash_app APP_NAME=neogeo2usb_kb2040
649+
650+
.PHONY: flash-neogeo2usb_rp2040zero
651+
flash-neogeo2usb_rp2040zero:
652+
@$(MAKE) --no-print-directory _flash_app APP_NAME=neogeo2usb_rp2040zero
653+
632654
.PHONY: flash-controller_fisherprice_kb2040
633655
flash-controller_fisherprice_kb2040:
634656
@$(MAKE) --no-print-directory _flash_app APP_NAME=controller_fisherprice_kb2040

docs/apps/NEO2USB.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# NEOGEO to USB Adapter
2+
3+
neogeo2usb app turns a KB2040 or RP2040 Zero into a USB controller adapter. Attach DB15/NEOGEO stick and get [USB Output Interface](./USB2USB.md)
4+
5+
## Features
6+
7+
### HotKeys
8+
9+
* **D-Pad Mode Switching (Hold for 2 seconds):**
10+
* `S1 (Coin) + S2 (Start) + Down`: **Digital D-Pad Mode** (Default). Directions act as standard digital inputs on the D-Pad.
11+
* `S1 (Coin) + S2 (Start) + Left`: **Left Analog Stick Mode**. Directions are remapped to the Left Analog Stick axes.
12+
* `S1 (Coin) + S2 (Start) + Right`: **Right Analog Stick Mode**. Directions are remapped to the Right Analog Stick axes.
13+
14+
* **System Actions:**
15+
* `S1 (Coin) + S2 (Start)`: **Home / Guide Button**.
16+
17+
18+
## Hardware Requirements
19+
20+
- **Board**: Adafruit KB2040 (default), RP2040-Zero
21+
- **Protocol**: Parallel **Active-Low** logic direct from GPIO
22+
- **Internal Pull-ups**: Firmware enables internal RP2040 pull-up resistors; no external resistors are required.
23+
- **Connector**: DB15 male connector.
24+
25+
### Wiring
26+
27+
| KB2040 | RP2040 Zero | DB15 Port | NEOGEO | Joypad |
28+
| :--- | :--- | :--- | :--- | :--- |
29+
| GND | GND | Pin 1 | Ground | - |
30+
| GPIO 9 | GPIO 29 | Pin 2 | Button 6 / K3 | R2 |
31+
| GPIO 7 | GPIO 28 | Pin 3 | Coin | S1 |
32+
| GPIO 5 | GPIO 27 | Pin 4 | Button 4 / K1 | B1 |
33+
| GPIO 3 | GPIO 4 | Pin 5 | Button 2 / P2 | B4 |
34+
| GPIO 18 | GPIO 3 | Pin 6 | Right | D-Pad Right |
35+
| GPIO 19 | GPIO 2 | Pin 7 | Down | D-Pad Down |
36+
| N/C | N/C | Pin 8 | - | - |
37+
| N/C | N/C | Pin 9 | - | - |
38+
| GPIO 8 | GPIO 13 | Pin 10 | Button 5 / K2 | B2 |
39+
| GPIO 6 | GPIO 12 | Pin 11 | Start | S2 |
40+
| GPIO 4 | GPIO 11 | Pin 12 | Button 3 / P3 | R1 |
41+
| GPIO 2 | GPIO 10 | Pin 13 | Button 1 / P1 | B3 |
42+
| GPIO 20 | GPIO 9 | Pin 14 | Left | D-Pad Left |
43+
| GPIO 10 | GPIO 8 | Pin 15 | Up | D-Pad Up |
44+
45+
### RP2040 Zero Wiring Reference
46+
47+
It is possible to connect the RP2040 Zero directly to a DB15 connector by taking advantage of the board design.
48+
49+
**Installation Steps:**
50+
51+
* Solder pins 10 through 15 of the DB15 directly to GPIO pins 14 through 9 on the bottom of the RP2040 Zero. To minimize risk, solder to the base of the DB15 pins; it is possible to solder in the center, but it requires special care.
52+
53+
* Connect the remaining GPIOs and GND line, using wires as shown in the front and back reference images.
54+
55+
| Front | Back |
56+
| :---: | :---: |
57+
| ![NEOGEO-2-USB RP2040 Zero Front](../images/neogeo2usb_rp2040_zero_front.png) | ![USB-2-NEOGEO RP2040 Zero Back](../images/usb2neogeo_rp2040_zero_back.png) |
58+
59+
60+
## Troubleshooting
61+
62+
**Controller not detected:**
63+
64+
- Check DB15 connections
65+
- Ensure the DB15 connectos is GND on Pin 1.
66+
- Check data pin assignment in firmware
67+
68+
69+
## Product Links
70+
71+
- [GitHub Releases](https://github.com/joypad-ai/joypad-os/releases) - Latest firmware
67.5 KB
Loading

src/CMakeLists.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,42 @@ target_link_libraries(joypad_gc2usb PRIVATE pico_stdlib pico_multicore hardware_
914914
joypad_target_common(joypad_gc2usb)
915915
pico_generate_pio_header(joypad_gc2usb ${CMAKE_CURRENT_LIST_DIR}/lib/joybus-pio/src/joybus.pio)
916916

917+
# --- NEGEO2USB (NEOGEO -> USB HID) ---
918+
add_executable(joypad_neogeo2usb)
919+
target_compile_definitions(joypad_neogeo2usb PRIVATE CONFIG_NEOGEO2USB=1 CONFIG_USB=1 DISABLE_USB_HOST=1 BUTTON_USER_GPIO=${BOARD_BUTTON_GPIO})
920+
target_sources(joypad_neogeo2usb PUBLIC ${CORE_SOURCES} ${USB_DEVICE_SOURCES}
921+
${CMAKE_CURRENT_SOURCE_DIR}/native/host/arcade/arcade_host.c
922+
${CMAKE_CURRENT_SOURCE_DIR}/apps/neogeo2usb/app.c
923+
)
924+
target_include_directories(joypad_neogeo2usb PUBLIC
925+
${CMAKE_CURRENT_SOURCE_DIR}/apps/neogeo2usb
926+
${CMAKE_CURRENT_SOURCE_DIR}/usb/usbd
927+
${CMAKE_CURRENT_SOURCE_DIR}/native/host/arcade
928+
)
929+
target_link_libraries(joypad_neogeo2usb PRIVATE pico_stdlib pico_multicore hardware_pio pico_rand tinyusb_device tinyusb_board)
930+
joypad_target_common(joypad_neogeo2usb)
931+
932+
# NEGEO2USB — RP2040-Zero (WS2812 on GP16, auto from board def)
933+
add_executable(joypad_neogeo2usb_rp2040zero)
934+
target_compile_definitions(joypad_neogeo2usb_rp2040zero PRIVATE
935+
CONFIG_NEOGEO2USB=1
936+
CONFIG_USB=1
937+
DISABLE_USB_HOST=1
938+
USE_BOOTSEL_BUTTON=1
939+
PICO_RP2040_ZERO_BUILD=1
940+
)
941+
target_sources(joypad_neogeo2usb_rp2040zero PUBLIC ${CORE_SOURCES} ${USB_DEVICE_SOURCES}
942+
${CMAKE_CURRENT_SOURCE_DIR}/native/host/arcade/arcade_host.c
943+
${CMAKE_CURRENT_SOURCE_DIR}/apps/neogeo2usb/app.c
944+
)
945+
target_include_directories(joypad_neogeo2usb_rp2040zero PUBLIC
946+
${CMAKE_CURRENT_SOURCE_DIR}/apps/neogeo2usb
947+
${CMAKE_CURRENT_SOURCE_DIR}/usb/usbd
948+
${CMAKE_CURRENT_SOURCE_DIR}/native/host/arcade
949+
)
950+
target_link_libraries(joypad_neogeo2usb_rp2040zero PRIVATE pico_stdlib pico_multicore hardware_pio pico_rand tinyusb_device tinyusb_board)
951+
joypad_target_common(joypad_neogeo2usb_rp2040zero)
952+
917953
# ============================================================================
918954
# CONTROLLER APPS (GPIO/pad input -> USB output)
919955
# ============================================================================

src/apps/neogeo2usb/app.c

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// app.c - NEOGEO2USB App Entry Point
2+
// NEOGEO native controller input to USB HID gamepad output adapter
3+
//
4+
// This app polls native NEOGEO controllers and routes input to USB device output.
5+
6+
#include "app.h"
7+
#include "profiles.h"
8+
#include "core/router/router.h"
9+
#include "core/services/players/manager.h"
10+
#include "core/services/profiles/profile.h"
11+
#include "core/input_interface.h"
12+
#include "core/output_interface.h"
13+
#include "core/services/button/button.h"
14+
#include "usb/usbd/usbd.h"
15+
#include "native/host/arcade/arcade_host.h"
16+
#include <stdio.h>
17+
18+
static arcade_config_t arcade_config[ARCADE_MAX_PORTS] = {
19+
[0] = {
20+
.pin_du = NEOGEO_PIN_DU,
21+
.pin_dd = NEOGEO_PIN_DD,
22+
.pin_dl = NEOGEO_PIN_DL,
23+
.pin_dr = NEOGEO_PIN_DR,
24+
25+
// Action Buttons
26+
.pin_p1 = NEOGEO_PIN_B1,
27+
.pin_p2 = NEOGEO_PIN_B2,
28+
.pin_p3 = NEOGEO_PIN_B3,
29+
.pin_p4 = GPIO_DISABLED,
30+
.pin_k1 = NEOGEO_PIN_B4,
31+
.pin_k2 = NEOGEO_PIN_B5,
32+
.pin_k3 = NEOGEO_PIN_B6,
33+
.pin_k4 = GPIO_DISABLED,
34+
35+
// Meta Buttons
36+
.pin_s1 = NEOGEO_PIN_S1,
37+
.pin_s2 = NEOGEO_PIN_S2,
38+
.pin_a1 = GPIO_DISABLED,
39+
.pin_a2 = GPIO_DISABLED,
40+
41+
// Extra Buttons
42+
.pin_l3 = GPIO_DISABLED,
43+
.pin_r3 = GPIO_DISABLED,
44+
.pin_l4 = GPIO_DISABLED,
45+
.pin_r4 = GPIO_DISABLED,
46+
}
47+
};
48+
49+
// ============================================================================
50+
// BUTTON EVENT HANDLER
51+
// ============================================================================
52+
53+
static void on_button_event(button_event_t event)
54+
{
55+
switch (event) {
56+
case BUTTON_EVENT_CLICK:
57+
printf("[app:usb2neogeo] Button click - current mode: %s\n",
58+
usbd_get_mode_name(usbd_get_mode()));
59+
break;
60+
61+
case BUTTON_EVENT_DOUBLE_CLICK: {
62+
// Double-click to cycle USB output mode
63+
printf("[app:usb2neogeo] Double-click - switching USB output mode...\n");
64+
tud_task();
65+
sleep_ms(50);
66+
tud_task();
67+
68+
usb_output_mode_t next = usbd_get_next_mode();
69+
printf("[app:usb2neogeo] Switching to %s\n", usbd_get_mode_name(next));
70+
usbd_set_mode(next);
71+
break;
72+
}
73+
74+
case BUTTON_EVENT_TRIPLE_CLICK:
75+
// Triple-click to reset to default HID mode
76+
printf("[app:usb2neogeo] Triple-click - resetting to HID mode...\n");
77+
if (!usbd_reset_to_hid()) {
78+
printf("[app:usb2neogeo] Already in HID mode\n");
79+
}
80+
break;
81+
82+
default:
83+
break;
84+
}
85+
}
86+
87+
// ============================================================================
88+
// APP INPUT INTERFACES
89+
// ============================================================================
90+
91+
static const InputInterface* input_interfaces[] = {
92+
&arcade_input_interface,
93+
};
94+
95+
const InputInterface** app_get_input_interfaces(uint8_t* count)
96+
{
97+
*count = sizeof(input_interfaces) / sizeof(input_interfaces[0]);
98+
return input_interfaces;
99+
}
100+
101+
// ============================================================================
102+
// APP OUTPUT INTERFACES
103+
// ============================================================================
104+
105+
static const OutputInterface* output_interfaces[] = {
106+
&usbd_output_interface,
107+
};
108+
109+
const OutputInterface** app_get_output_interfaces(uint8_t* count)
110+
{
111+
*count = sizeof(output_interfaces) / sizeof(output_interfaces[0]);
112+
return output_interfaces;
113+
}
114+
115+
// ============================================================================
116+
// APP INITIALIZATION
117+
// ============================================================================
118+
119+
void app_init(void)
120+
{
121+
printf("[app:neogeo2usb] Initializing NEOGEO2USB v%s\n", APP_VERSION);
122+
123+
// Initialize button service
124+
button_init();
125+
button_set_callback(on_button_event);
126+
127+
// Initialize Arcade host driver input
128+
arcade_host_init_pins(&arcade_config[0]);
129+
130+
// Configure router for NEOGEO → USB routing
131+
router_config_t router_cfg = {
132+
.mode = ROUTING_MODE,
133+
.merge_mode = MERGE_MODE,
134+
.max_players_per_output = {
135+
[OUTPUT_TARGET_USB_DEVICE] = USB_OUTPUT_PORTS,
136+
},
137+
.merge_all_inputs = false,
138+
.transform_flags = TRANSFORM_NONE,
139+
.mouse_drain_rate = 8,
140+
};
141+
router_init(&router_cfg);
142+
143+
// Add route: Native NEOGEO → USB Device
144+
router_add_route(INPUT_SOURCE_NATIVE_ARCADE, OUTPUT_TARGET_USB_DEVICE, 0);
145+
146+
// Configure player management
147+
player_config_t player_cfg = {
148+
.slot_mode = PLAYER_SLOT_MODE,
149+
.max_slots = MAX_PLAYER_SLOTS,
150+
.auto_assign_on_press = AUTO_ASSIGN_ON_PRESS,
151+
};
152+
players_init_with_config(&player_cfg);
153+
154+
// Initialize profile system with button combos (Select+Start=Home)
155+
static const profile_config_t profile_cfg = {
156+
.output_profiles = { NULL },
157+
.shared_profiles = &neogeo2usb_profile_set,
158+
};
159+
profile_init(&profile_cfg);
160+
161+
printf("[app:neogeo2usb] Initialization complete\n");
162+
printf("[app:neogeo2usb] Routing: NEOGEO → USB HID Gamepad\n");
163+
printf("[app:neogeo2usb] NEOGEO pins: B1=%d B2=%d B3=%d B4=%d B5=%d B6=%d\n",
164+
arcade_config[0].pin_p1, arcade_config[0].pin_p2, arcade_config[0].pin_p3,
165+
arcade_config[0].pin_k1, arcade_config[0].pin_k2, arcade_config[0].pin_k3);
166+
printf("[app:neogeo2usb] NEOGEO pins: DU=%d DD=%d DL=%d DR=%d S1=%d S2=%d\n",
167+
arcade_config[0].pin_du, arcade_config[0].pin_dd, arcade_config[0].pin_dl,
168+
arcade_config[0].pin_dr, arcade_config[0].pin_s1, arcade_config[0].pin_s2);
169+
}
170+
171+
// ============================================================================
172+
// APP TASK
173+
// ============================================================================
174+
175+
void app_task(void)
176+
{
177+
button_task();
178+
}

0 commit comments

Comments
 (0)