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

Merged
merged 10 commits into from
Jan 25, 2025

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 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!

@Tails86
Copy link

Tails86 commented Jan 19, 2025

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

Aha! I knew I remember seeing a reference to Linux, but I couldn't find it.

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 tested Sega Rally 2 with a real Dreamcast, and there wasn't very much deadzone on the triggers. I've been trying to locate my maple bus host adapter I created, but it seems it walked off. I haven't had a chance to really test things out yet. Thankfully it's a fairly simple circuit, so I'm putting a new one together now. What I want to try is maybe setting the range to [0,255] just for the triggers. I have a feeling the manual interpretation is using only [0, 127] portion of the triggers because it's only binding to + end?

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.

I'm not completely opposed to 1, but I'd like to limit build configurations if possible.

@kosekmi
Copy link
Author

kosekmi commented Jan 19, 2025

I tested Sega Rally 2 with a real Dreamcast, and there wasn't very much deadzone on the triggers. I've been trying to locate my maple bus host adapter I created, but it seems it walked off. I haven't had a chance to really test things out yet. Thankfully it's a fairly simple circuit, so I'm putting a new one together now. What I want to try is maybe setting the range to [0,255] just for the triggers. I have a feeling the manual interpretation is using only [0, 127] portion of the triggers because it's only binding to + end?

Thanks, I followed up in OrangeFox86/DreamcastControllerUsbPico#123

I'm not completely opposed to 1, but I'd like to limit build configurations if possible.

I gave 2. another look, the changes are straightforward - I'll push them tomorrow

@kosekmi
Copy link
Author

kosekmi commented Jan 20, 2025

@Tails86 @flyinghead
For testing purposes, I temporarily overrode the default mappings for DreamcastControllerUsb on MacOS/Windows. Will be superseded by an extension of SDL

core/sdl/dreamconn.cpp Outdated Show resolved Hide resolved
core/sdl/sdl_gamepad.h Outdated Show resolved Hide resolved
core/sdl/dreamconn.cpp Outdated Show resolved Hide resolved
@Tails86
Copy link

Tails86 commented Jan 20, 2025

I ran into lockups on subsequent execution when DreamcastControllerUsbPico was connected and not assigned to the first COM port in Windows. Deleting emu.cfg weirdly corrects it.

Besides this issue, I confirmed on my end that VMU/jump pack with DreamcastControllerUsbPico works

@kosekmi
Copy link
Author

kosekmi commented Jan 21, 2025

I ran into lockups on subsequent execution when DreamcastControllerUsbPico was connected and not assigned to the first COM port in Windows. Deleting emu.cfg weirdly corrects it.

Besides this issue, I confirmed on my end that VMU/jump pack with DreamcastControllerUsbPico works

Thanks for confirming. Lockup should be fixed in the latest commit which dynamically selects the serial device based on VID/PID

@kosekmi
Copy link
Author

kosekmi commented Jan 23, 2025

@flyinghead Thanks for fixing this, I wasn't sure why the checks failed.

Current status:

  • @Tails86 and I fixed some stuff in the DreamcastControllerUsb, and had a minor change in Flycast for Maple Messages send to the microcontroller
  • We are working on another issue in DreamcastControllerUsb where the Maple Bus occasionally becomes unresponsive; this is unrelated to the changes in Flycast
  • I am currently waiting for the PR in SDL to be merged
  • Once this is done, and we update SDL, I can remove the controller mapping workaround
  • On this note: SDL released 3.2.0 a few days ago, so the controller mapping will probably make it to 3.2.1. Do you have plans to update SDL?

@flyinghead
Copy link
Owner

We usually upgrade SDL regularly but we probably don't need to wait for a new release that includes your PR. I think we can add custom game controller mappings with SDL_GameControllerAddMapping during SDL initialization.

@kosekmi
Copy link
Author

kosekmi commented Jan 23, 2025

We usually upgrade SDL regularly but we probably don't need to wait for a new release that includes your PR. I think we can add custom game controller mappings with SDL_GameControllerAddMapping during SDL initialization.

Good point, thanks - probably here? I'll take a look tomorrow.

@flyinghead
Copy link
Owner

Yes

core/sdl/dreamconn.cpp Outdated Show resolved Hide resolved
@kosekmi
Copy link
Author

kosekmi commented Jan 24, 2025

We usually upgrade SDL regularly but we probably don't need to wait for a new release that includes your PR. I think we can add custom game controller mappings with SDL_GameControllerAddMapping during SDL initialization.

Done

@Tails86 Can you please test the latest build once the workflow completes?

@flyinghead Open questions:

  • How to handle serial device selection? While we only support 1 Controller (for now), this is solved on Windows since the first device with matching VID:PID is used. On MacOS and Linux, I couldn't figure out a way to retrieve VID/PID from serial devices, so here the first serial port is used - which can be any device. Maybe a manual override can be placed in the [input]-section of emu.cfg?
  • HowTo/Wiki: I would briefly describe the setup and prerequisites, maybe accompanied by a short FAQ section. Where can this best be placed to be referenced from both Flycast and DreamcastControllerUSB?

@flyinghead
Copy link
Owner

It can be done on linux by looking at /sys/class/tty/* but it doesn't seem easy and it's platform specific, or even specific to a kernel version.
For now, you can add an entry in emu.cfg under [input] that you can read with cfgLoadStr("input", "propertyName", "default")

@Tails86
Copy link

Tails86 commented Jan 24, 2025

@kosekmi cool, I didn't think you'd like that blocking run() before servicing the next command. Now that I'm thinking about it though, that really shouldn't be blocking much, if at all. I'll check this out after I'm done with my workday today (in about 8 hours).

@flyinghead
Copy link
Owner

The only documentation available for Flycast is https://github.com/TheArcadeStriker/flycast-wiki/wiki. I can add a link to your documentation, or add it there directly.

@Tails86
Copy link

Tails86 commented Jan 25, 2025

Please disregard the comment I just deleted. My issue was entirely because I somehow had incorrect firmware loaded. Reflashing was all I needed to do. My apologies!

Everything seems to be working for me except I have to manually map the keys (testing on Windows).

@flyinghead flyinghead merged commit 26d386f into flyinghead:dev Jan 25, 2025
15 checks passed
@kosekmi
Copy link
Author

kosekmi commented Jan 25, 2025

@flyinghead Thanks for merging!

For now, you can add an entry in emu.cfg under [input] that you can read with cfgLoadStr("input", "propertyName", "default")

Should I push this to dev, or open a new PR?

@kosekmi
Copy link
Author

kosekmi commented Jan 25, 2025

Everything seems to be working for me except I have to manually map the keys (testing on Windows).

@Tails86 The key mapping issue should be fixed in the latest build, works for me on Windows and MacOS. Can you please check if you have a manuel override active? I.e., Settings -> Controls -> Map -> Reset, or delete the mapping file in Flycast config directory.

@flyinghead
Copy link
Owner

you'll have to open a new PR for additional changes.

@Tails86
Copy link

Tails86 commented Jan 25, 2025

@Tails86 The key mapping issue should be fixed in the latest build, works for me on Windows and MacOS. Can you please check if you have a manuel override active? I.e., Settings -> Controls -> Map -> Reset, or delete the mapping file in Flycast config directory.

I'm experiencing some odd behavior with this. Here's what I'm doing.

  1. Delete all files except for flycast executable
  2. Launch flycast, enable log to file, and exit
  3. Relaunch flycast - the log only shows the following line and does not detect that this is a Dreamcast controller
00:01:084 sdl/sdl_gamepad.h:179 N[INPUT]: SDL: Opened joystick 0 on port 0: 'P4' unique_id=sdl_joystick_0
  1. Select the game folder
  2. Exit flycast
  3. Launch flycast - the controller is now being detected as a Dreamcast controller, but it isn't automatically mapping it
01:062 sdl/sdl_gamepad.h:179 N[INPUT]: SDL: Opened joystick 0 on port 0: 'P4' unique_id=sdl_joystick_0
00:01:571 sdl/dreamconn.cpp:283 N[INPUT]: Connected to DreamcastController[0]: Type:Dreamcast Controller USB, VMU:1, Rumble Pack:1
  1. Manually map controls - it now works fine for me from then on out

Resetting the mapping just sets all to the global defaults rather than the known profile's defaults.

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