Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VMUs and Rumble Packs with Original Dreamcast Controllers #1810

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

kosekmi
Copy link

@kosekmi kosekmi commented Jan 16, 2025

This PR enables the usage of VMUs and Rumble Packs with original Dreamcast Controllers connected to MacOS/Linux/Windows using the DreamcastControllerUsbPico Raspberry Pi Pico-based USB-Adapter.

By default, the USB-Adapter provides the following devices:

  • USB HID with support for analog triggers (e.g., to be used by any emulator/game)
  • USB MSC exposing the VMU contents as a removable media (e.g., to copy savegames between emulators and actual hardware)
  • USB CDC serial device for communication with the Maple Bus (i.e., enabling arbitrary MapleIO)

Using the USB CDC serial device, this PR piggybacks on the handler for DreamConn+ Controllers as discussed here. It currently provides a working implementation of VMUs and Rumble Packs using MapleIO with support for MacOS/Linux/Windows.

Demo: https://youtu.be/Nj4dRMZ_jB0

Usage:

  • Wire up DreamcastControllerUsbPico for Host Mode 1 Player
  • Deploy the Flycast-compatible precompiled binary on the microcontroller
  • Plugin the microcontroller
  • Linux: there might be some permission issues since the USB serial device (most likely /dev/ttyACM*) is owned by root. Change permission of the device to your user, or startup Flycast with superuser permissions
  • Startup Flycast
  • MacOS and Windows: the button mappings need to be manually configured in Flycasts' Controls Tab (see also todos). Make sure that Left Trigger is assigned to 2+, and Right Trigger to 5+ (sometimes they register as 2-/5- for some reason)

Notes:

  • Controller needs to be reconnected if devices (e.g., VMU/Rumble pack) are added to the controller
  • Games need to be restarted if a Controller is reconnected to re-initialize MapleIO communication. Not sure if this is identical for DreamConn+
  • When MapleIO disconnects (e.g., a game is closed), VMU LCD shows the last state until new LCD data is received
  • VMUs can not be used for actual savegames during emulation. Yet, VMU binary files can be copied over to/from the VMU using the USB Mass Storage device
  • Supports 1 Controller only, primarily because I only have 1 Controller to test this with

Todos:

  • I probably broke some DreamConn+ stuff which I cannot test as I do not own that device. Maybe @chrisvcpp can pitch in and verify DreamConn+ support
  • MapleIO on the serial port is performed asynchronously to optimize performance. For some reason, the completion handler is not called when the async_write() finishes. As such, the disconnection of a serial device can currently not be detected reliably since the device will report being open despite being disconnected. As a consequence, the DreamConn instance is not deconstructed correctly until a new game is started
  • Depending on OS, USB port, and other serial devices connected, the serial device handler (e.g., /dev/ttyACM0, COM1) changes. Currently, the first serial device found is used. This should probably be exposed to a config file or the UI for proper handling
  • On MacOS and Windows, the default button mapping of the USB HID does not match the buttons a, y, start, the dpad, and the analog triggers. Even if manually configured, the checkKeyCombo() is not working since leftTrigger and rightTrigger seem to be not correctly wired within the SDLGamepad handler - I couldn't figure out why. This can probably be hardcoded? The mapping is:
[analog]
bind0 = 0-:btn_analog_left
bind1 = 0+:btn_analog_right
bind2 = 1-:btn_analog_up
bind3 = 1+:btn_analog_down
bind4 = 2+:btn_trigger_left
bind5 = 5+:btn_trigger_right

[digital]
bind0 = 0:btn_a
bind1 = 1:btn_b
bind2 = 3:btn_x
bind3 = 4:btn_y
bind4 = 11:btn_start
bind5 = 256:btn_dpad1_up
bind6 = 257:btn_dpad1_down
bind7 = 258:btn_dpad1_left
bind8 = 259:btn_dpad1_right

Credits go to @Tails86 for DreamcastControllerUsbPico, @flyinghead for Flycast, and @chrisvcpp for DreamConn+. Thanks to @Marcel43367 for helping on serial device debugging!

@chrisvcpp
Copy link

I can try it with the DreamConn S for possible issues. Can you please provide a Windows build?

@flyinghead
Copy link
Owner

you can grab it here: https://github.com/flyinghead/flycast/actions/runs/12806671980/artifacts/2440333360

@Tails86
Copy link

Tails86 commented Jan 16, 2025

Thanks for your work on this to get this integrated with DreamcastControllerUsbPico! Using the CDC interface is certainly the easiest way to go about it 🤣 It was originally only meant to be a debug tool, but I don't see any harm in using it this way! Keep it simple, right? I did get the PID/VID assigned through pidcodes.github.com, so no one else should be using that address.

FYI The storage device implementation is very basic, essentially a proof-of-concept, and I never hardened it to allow for random access of data (only copy or paste of an entire vmu). It would be interesting to see an emulator working directly off of the data somehow though. I'd be interested in helping with that if it seems like a useful feature.

@programmerta
Copy link

Awesome work! This is really cool!

@chrisvcpp
Copy link

Just tested with DreamConn S, and everything seems to be working fine.

@kosekmi
Copy link
Author

kosekmi commented Jan 17, 2025

Just tested with DreamConn S, and everything seems to be working fine.

@chrisvcpp Thanks for checking!

Using the CDC interface is certainly the easiest way to go about it 🤣 It was originally only meant to be a debug tool, but I don't see any harm in using it this way! Keep it simple, right?

@Tails86 This was the low hanging fruit :D My initial idea was to use a Pi Pico with WIFI and extend DreamcastControllerUsbPico with a TCP Server to piggyback on the existing TCP-to-localhost stuff from DreamConn - but the CDC makes it way easier.

On MacOS and Windows, the default button mapping of the USB HID does not match the buttons a, y, start, the dpad, and the analog triggers. Even if manually configured, the checkKeyCombo() is not working since leftTrigger and rightTrigger seem to be not correctly wired within the SDLGamepad handler - I couldn't figure out why.

@Tails86 Maybe this can also be addressed by DreamcastControllerUsbPico. Unfortunately I don't have a Debug Kit available so I couldn't figure out why the USB HID seems to be exposed differently on MacOS/Windows vs Linux (Linux has the "correct" mapping). But I guess this is related to how the OSes interpret the HID - any thoughts on this?

@Tails86
Copy link

Tails86 commented Jan 17, 2025

@Tails86 Maybe this can also be addressed by DreamcastControllerUsbPico. Unfortunately I don't have a Debug Kit available so I couldn't figure out why the USB HID seems to be exposed differently on MacOS/Windows vs Linux (Linux has the "correct" mapping). But I guess this is related to how the OSes interpret the HID - any thoughts on this?

I'm using a descriptor that is very similar descriptor to what TinyUSB recommends.
Mine:
https://github.com/OrangeFox86/DreamcastControllerUsbPico/blob/main/src/hal/Usb/Client/Hid/usb_descriptors.c#L52C9-L52C36
TinyUSB:
https://github.com/hathach/tinyusb/blob/880aae4be2556704abd4dae9c707c9fa87603cf1/src/class/hid/hid_device.h#L355

The only tweak I made is the minimum is -128 instead of -127 to better match up with the Dreamcast controller. It seemed like TinyUSB followed a standard recommended by the Linux community, so that tracks.

Conversion from Dreamcast controller input happens here:
https://github.com/OrangeFox86/DreamcastControllerUsbPico/blob/main/src/hal/Usb/Client/Hid/UsbGamepadDreamcastControllerObserver.cpp#L7

Button indices are defined here:
https://github.com/OrangeFox86/DreamcastControllerUsbPico/blob/main/src/hal/Usb/Client/Hid/UsbGamepad.h#L11-L54

I can of course tweak the descriptor and/or conversions to be whatever is most compatible. Do you have any idea what layout this needs to be? I'm sort of wondering if the analog trigger range [-128,127] is a problem.

@kosekmi
Copy link
Author

kosekmi commented Jan 17, 2025

@Tails86 thanks for the pointers!

It seemed like TinyUSB followed a standard recommended by the Linux community, so that tracks.

For future reference, its stated here https://github.com/hathach/tinyusb/blob/880aae4be2556704abd4dae9c707c9fa87603cf1/src/class/hid/hid.h

I'm sort of wondering if the analog trigger range [-128,127] is a problem.

By default on Linux, or when manually configuring the Controller on MacOS/Windows, the triggers work fine. Sega Rally 2 has a nice test for this - there is an option menu for configuring the deadzone. To my feeling, the triggers need to be pressed quite a bit before they are recognized. I am not sure if this is the default behaviour of the controller on the original DC, or something which is due to DreamcastControllerUsbPico. I briefly tested a trigger range of [-127, 127] which did not change my perception of this. I currently cannot test this with a real DC - maybe you have something available to compare this with?

I can of course tweak the descriptor and/or conversions to be whatever is most compatible. Do you have any idea what layout this needs to be?

The problem seems to be that MacOS and Windows use different input event codes, hence those OS by default do not or do only falsely recognize the buttons/axis. So it seems that there is no one-fits-all solution from what I could find.

To my mind, the button mapping issue can be worked around by either

  1. Changing the event codes emitted by the microcontroller, leading to separate binaries for OSs, or
  2. Changing the default input mapping in Flycast for our PID/VID on MacOS/Windows in sdl_gamepad.h.

From what I can tell, for 1. we need to figure out the correct input event codes and probably do some heavy lifting in DreamcastControllerUsbPico - what is your take on this @Tails86?
Variant 2. seems quite straightforward to me - if this is a viable option to you @flyinghead I could make the necessary changes which extend to at least sdl.cpp to hand down TYPE_DREAMCASTCONTROLLERUSB through the stack.

@kosekmi
Copy link
Author

kosekmi commented Jan 17, 2025

The storage device implementation is very basic, essentially a proof-of-concept, and I never hardened it to allow for random access of data (only copy or paste of an entire vmu). It would be interesting to see an emulator working directly off of the data somehow though. I'd be interested in helping with that if it seems like a useful feature.

I second that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants