A laser cut Dreamcast Pop'n Music controller and integrated memory card using the Raspberry Pi Pico's Programmable IO

Dreamcast Pop'n Music Controller
Using Raspbery Pi Pico (RP2040)

Intro

This is a homebrew controller for playing the Pop'n Music games on the Sega Dreamcast using a Raspberry Pi Pico. It emulates both the controller and a memory card (using the Raspberry Pi Pico's flash for storage).

Image of controller Please excuse the kid mess in this photo (and my own mess) :)

Software

The physical construction was done a couple years ago but I put off doing the software. The Maple (Dreamcast controller) bus is quite a fast bus so a bit tricky to do with a low end microcontroller (although can be done). When the Raspbery Pi Pico was announced the Programmable IO seemed like it would be perfect for implementing this which spurred me into action to get this finished off. As the RP2040 (chip in the Pico) is fairly new I thought I'd write up my first impressions.

Programmable IO (PIO)

I started off with using the PIO to send Maple packets including automatically adding the start and end sync pulses. This wasn't really needed as sending is quite straight forward and could easily be done with bit banging but I was keen to get to grips with the PIO with a simple case first. Working with more than one output pin independently was a bit clunky (having to use side channels) but it was fairly simple to write. Sending a packet then became completely trivial, just a case of starting a DMA to the PIO. Was really fire and forget once working which is really cool.

Recieving, the bit I was excited for using PIO for, turned out to be a harder problem. The Maple bus (that Dreamcast controllers use to communicate with the console) is a two wire protocol (http://mc.pp.se/dc/maplewire.html) where the two wires take turns at being the clock. This makes it challenging to work with in the PIO as...

  • You can select a pin to branch on but only one per PIO state machine (but Maple needs two)
  • You can read inputs to a register but with no way of masking (that I can see) so is pretty useless :(

So you are limited pretty much to only using the wait instructions to watch certain pins. This would work to some extent but would be error prone if it got out of sync and there'd be no way of detecting the end of packet condition (without using the CPU to detect and reset the PIO but that defeats some of the point of using the PIO in the first place).

It all became a bit like a Zachtronics puzzle (which admittedly was fun). I did come up with an elegant way in the end but wasn't doing the decoding in the PIO which I was disappointed about. The solution I came up with was to have two state machines waiting on either pin changing and signalling another state machine to shift in the current state of both pins. This means it clocked in data only when there was a change on either of the pins. We can pack four transitions into a byte before pushing that out the FIFO. Limiting the number of transitions means we can still detect there's been an end of packet sequence even when there's some data left in the shift register waiting to be pushed (which it won't due to the bus now being idle).

Overall I was slightly disappointed with the PIO. I feel it would have been much more useful for input from multiple pins if there was just some masking of the input. Without a mask mov x, pins can't be used to compare via the jmp x!=y instruction because x ends up with a bunch of noise from all the other pins you aren't interested in.

Disclaimer: This is first time I've used the PIO so could be missing something. The datasheet is a bit unclear about whether pins with lower indexes get rotated in or not. Could be a way of getting some masking if you use certain pins

DMA

The DMA engine seems really cool but has some quirks. For transmitting, like the PIO, it works great and the documentation/libraries are really good (IMO). However for recieving I ran into some more problems. I really just wanted an endless ring buffer that'd keep recieving and the CPU could consume the data as it came in.

The first hurdle was you can't have an endlessly running DMA. This isn't a huge issue as an interrupt can restart it fairly quickly (although later on I did question if I wasn't sometimes missing data). The second issue was with the looping. Although you can tell the DMA engine to only update certain bits of the address this didn't seem to work perfectly when I was trying to read the write address back on the CPU to know how much of the ring buffer to consume. Although I had a buffer 0x8000-0x8FFF for example, reading the write address on the CPU would rarely return an errant 0x7FFF. This caused me a fair bit of confusion as my loop was just reading until the current read pointer matched the DMA write pointer, which meant it could blast right by in rare occurances due to this glitch reading garbage old data.

I'm sure it was problems with my own code but I couldn't get this ring buffer DMA to work reliably. Combined with needing to speed up the communication (due to the Dreamcast sometimes spamming super rapid requests when using the DC-X disc to load games from different regions) I decided to just ignore the DMA for recieving and use the second core in the end.

Second core

To split up the work I decided to use the second core to read directly from the reciever FIFO and do the Maple bus decoding while the other core could process the packets and send the replies. The second core was very simple to get working and the mailbox system for communicating between the two was very nice and easy to use.

I knew there might not be much time to process data (maybe as little as half a microsecond for each byte of input) so to keep up with the receiving PIO I used the large amount of RAM available to store a table (20Kb) of precalculated responses so a byte of transitions could be decoded at a time with just a simple lookup. I was a bit shocked to discover even this code couldn't keep up at first but after a bit of head scratching I realized the problem. Code is stored in flash and paged into a cache in RAM as needed. This is a slow process and if my time critical function wasn't paged in then by the time it was fetched from flash the input FIFO would be full and data could have been missed. The magic incantation I needed was to declare my function as __not_in_flash_func(core1_entry)(void) to force it to always be in RAM. For then on it was smooth sailing.

Memory Card (VMU/VMS)

After a small amount of Pop'n Music play I realised a memory card was essential. I could have plugged in a normal controller with a VMU in another port but as the Raspberry Pi Pico has 2Mb of flash, why not use that to store save games.

I originally changed the device that we report to the Dreamcast to be a controller with storage functionality. This seems perfectly valid (and might be) but it didn't appear in the system software as it seems to only expect a memory card to be a subperipheral of the controller (like a normal VMU). After reimplementing as a subperipheral it showed up and I could attempt to format it. The flash on the Pico can only be erased (needed before reprogramming) in 4Kb blocks and the Dreamcast writes 512 byte blocks (in 128 byte chunks) which means a naive approach of doing a block at a time was quite slow and inefficient. An erase/reprogram cycle also seemed to be slow enough that the Dreamcast would get upset as the responses weren't being returned fast enough for it. In the end we again used the large amount of RAM available to get us out of a bind by have the whole memory card contents stored in RAM. On startup we read the contents from flash into this RAM buffer and all writes we also initially write to RAM but record which sectors are dirty. The dirty sectors are then lazily flushed back to flash when all the memory card activity has finished. This solves the speed issue but also does a good job of amalgamating the writes.

Finally once this was working I figured it'd be nice to have a custom icon for the new memory card. As this icon is more or less a regular file it seemed easiest to just initialize the flash with a preformatted memory card with the icon file already in place on first run.

Screenshot of memory card

As an aside the flash memory can be easily read back using picotool which means this is a way to transfer Dreamcast saves back to a computer. The above screenshot is from an emulator (Reicast) but using the memory card data read back via picotool.

PWM

I used PWM for all the LEDs so I could fade them slowly after each button press. Again everything was well documented and the library really easy to use. Only slight wrinkle was as there are less PWM channels than GPIO pins so I had to be careful not to have two LEDs on pins that shared the same PWM channel. This was easily done by just making sure they were all consecutive numbered IO pins.

Development Environment

Development environment was really nice once it was set up. I started off doing the lazy way of plugging in the Pico with the button held down then dragging in the uf2 file to program when it appeared as a mass storage device. This worked but boy was it annoying. I really wish it just had a reset button. Balancing a laptop on my knee while holding a button and re-plugging in a USB cable, dragging a file then having to quickly reconnect to the USB serial to see the serial output got old fast.

I was lucky enough to be able to get three Raspberry Pi Pico's on launch day so I used the Picoprobe software to use one Pico as a USB debug/serial interface for the other. This worked quite nicely and is great to have a debugger on a microcontroller. I know this is fairly standard for ARM microcontrollers but it was the first time I'd personally had this set up. OpenOCD would often crash, the Picoprobe needing rebooting or the debugger would just getting confused and not restart the program properly but I was still impressed and was way better than any other environment I've had on a microcontroller. Compared to the lazy set up it was light years ahead. I'm sure kinks will get ironed out over time and I was using macOS which probably isn't as well tested as other platforms.

Overall impressions of the Pico

I know I've bought up some problems but I actually really like the Pico. It's new so information seems a bit thin on the ground but hopefully that'll improve in time. The Programmable IO is limited in some annoying ways but for what it's good at it's brilliant. Now I know the limitations I'm excited by a few other ideas that should play to its strengths more. The documentation and libraries are all top notch as far as I'm concerned (as a hobbiest). I have used a lot of ESP32s recently and has been my go to microcontroller for quite some years now. I'm not sure the Pico can unseat it as my favourite microcontroller just yet but it's giving it a good run for the money. I'm really looking forward to where the Pico goes next.

Physical Construction

The controller itself is made from many laser cut slices of 6mm MDF. The top layer is clear acrylic and underneath that are the graphics printed on normal paper (I used a poster printing company). The bottom layer is cork matting to make a nice non slip surface. All the layers are sandwiched together using hefty M8 bolts apart from the bottom couple layers which are glued to provide a space for the nuts. In all it's a very heavy and substatial feeling controller that should live up to a lot of punishment like the real arcade machine.

It was designed in Fusion 360 back when the free version allowed DXF export. Might not be too much use now but I've included the Fusion file in case you want to modify it and you can find some way still to export it. I've included all the exported SVG files from when I originally laser cut it. I believe the graphics were also done in Fusion and then edited in Inkscape.

Buttons are generic 100mm arcade buttons found on eBay or AliExpress. Be warned I used the shorter stemmed ones. If you can only get the longer ones then you might have to add more layers or saw them down. It seems random whether you get LED and resistor or just an LED included in the button, not to mention different colours. I removed them all in any case and replaced them with a high brightness white LED with a 82ohm resistor for consistancy.

For strain relief on the cable I left room for the wire to curl around one of the bolts a couple times. As I didn't want the wire to be cut by the thread of the bolt I asked a friend to show me how to use the lathe at the local hackspace (where I also used the laser cutter) and we machined a small tube out of Delrin with an M8 thread on the inside (so it doesn't rotate). I suppose you could get similar results with a 3D printed spacer though if you don't have access to a friendly machinist.

I did leave a slot within the shell for the electronic but I ended up just putting the Pico in one of the button wells as there was more space for Dupont connectors (for easy serviceability). I might do a PCB at some time in the future and try out the castellations for soldering the Pico as a module.

One thing I would change if I were doing it all again would be adding at least one extra button. This is needed as you must press Start to get past the start screen of at least the first Pop'n Music game. As the controller was already built at this stage I put in a slightly hacky feature where holding down a button combination (all yellow and white buttons at once) simulates a Start button press.

Comments
  • Relicensing my fork

    Relicensing my fork

    Hi Charlie! First off, let me say thanks for writing this well-organized Maple Bus transceiver code! I forked your codebase last year and have made tons of progress toward a general-purpose Dreamcast controller emulator. I did detach my fork from your repo earlier this year as its scope has grown beyond your original project quite a bit. Initially I was mostly focused on Dreamcast portablizing, but many people have reached out to me about using MaplePad for custom arcade sticks or custom DC controllers.

    I'm wondering if you're open to me relicensing my fork under CC Attribution-ShareAlike 4.0 International (CC BY-SA 4.0). It's a slightly more permissive "Free Culture" license that requires derivative works to be open-sourced, without limiting folks' ability to monetize an arcade stick or custom controller if they so choose. I think moving from NC to SA helps position the project as a true FOSS work and will encourage thoughtful reuse without giving away the farm, so to speak.

    Let me know what you think!

  • Not working in controller ports B, C or D

    Not working in controller ports B, C or D

    Hey, awesome project!!! Thank you very much for this

    This will sound strange, but I was thinking of using this project as a internal VMU for my Dreamcast, like left a Raspberry Pi Pico always connected to controller port D and use it as internal storage. The problem is that the Pop 'n Music is only being found in my Dreamcast when connected to port A. I've checked other ports and they work normally with real controllers. I know this is not the scope of this project, but would be possible to make the Pop 'n Music work in other controller ports?

    Thanks again

    soniccd123

  • flash_range_erase()

    flash_range_erase()

    You are tracking dirty sectors within flash, and erasing/updating with a granularity of FLASH_SECTOR_SIZE (4096 bytes).

    However, checking the pico_sdk, the SPI instruction used in the flash_range_erase() function in pico-sdk/src/rp2_common/hardware_flash/flash.c appears to execute an SPI 0xd8 command, which erases a BLOCK (65536 bytes), rather than a 0x20 command which would erase a SECTOR (4096 bytes). So, increments are currently 16 times larger than the example would lead us to believe.

    In this way, I expect that you would find large holes in your save data.

    I opened a thread on the Pico support forum, here: https://www.raspberrypi.org/forums/viewtopic.php?f=145&t=311700

    It is not yet clear whether they will consider a bug in the SDK, or a documentation issue in pico-examples.

    I just thought you might like to be aware.

  • Reading flash by picotool

    Reading flash by picotool

    You mention in the README that the flash should be readable by picotool, but this does not appear to be the case. For some reason, picotool reports "ERROR: Save range crosses unmapped memory" for arbitrary flash access beyond the end of the program (though I don't know why).

    Not that big a deal, but I'm wondering whether I'm missing something about how I am using picotool, or whether this should be reported as a picotool bug.

    Truth is, I'm inspired by your project, and would like to build something similar for a different retro system.

  • Would be possible to emulate two VMUs, one on each peripheral slots?

    Would be possible to emulate two VMUs, one on each peripheral slots?

    Hello,

    As said in the title, would it be possible? I'm looking at the source code thinking how could this be done. Not that is absolutely necessary, but out of curiosity, it would be cool to try. Can you give me some insight about the workings of the code to try to implement it?

    Thanks,

    Soniccd123

  • Compatibility with regular DC controller emulation

    Compatibility with regular DC controller emulation

    Hi, this is a great project, thank you for making it happen, would it work for emulating a regular DC controller instead of a Pop'n'Music one? I see you put some parts like:

    #if POPNMUSIC
    			"pop'n music controller        ",
    #else
    			"Dreamcast Controller          ",
    #endif
    

    But I don't believe just setting the flag #define POPNMUSIC 1 to 0 would make it work since there are other parts of the code that do not seem to have the regular controller counterpart, like this part:

    static ButtonInfo ButtonInfos[NUM_BUTTONS]=
    {
    	{ 16, 13, 0x0040, 0 },	// White left
    	{ 17, 12, 0x0010, 0 },	// Yellow left
    	{ 18, 11, 0x0020, 0 },	// Green left
    	{ 19, 10, 0x0080, 0 },	// Blue left
    	{ 20, 9, 0x0004, 0 },	// Red centre
    	{ 21, 8, 0x0400, 0 },	// Blue right
    	{ 22, 7, 0x0002, 0 },	// Green right
    	{ 26, 6, 0x0200, 0 },	// Yellow right
    	{ 27, 5, 0x0001, 0 }	// White right
    };
    

    Can you bring some guidance about what stuff would have to be modified in the code of this project in order to make the raspberry pico work as a generic DC pad? I would love to use it as a JAMMA to DC adapter, that's why I'm asking. Thanks a lot!!

Rmaxcut finds an approximate solution to a weighted max-cut problem via random perturbation.

Rmaxcut finds an approximate solution to a weighted max-cut problem via random perturbation. Each line in an input file consists of the first nodeID, the second nodeID and an integer weight.

Apr 28, 2021
K-Closest Points and Maximum Clique Pruning for Efficient and Effective 3-D Laser Scan Matching (RA-L 2022)
K-Closest Points and Maximum Clique Pruning for Efficient and Effective 3-D Laser Scan Matching (RA-L 2022)

KCP The official implementation of KCP: K-Closest Points and Maximum Clique Pruning for Efficient and Effective 3D Laser Scan Matching, accepted for p

Oct 1, 2022
A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Using Pro-micro control.
A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Using Pro-micro control.

N.A.G.E.K.I. A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Using Pro-micro control. 中文版

Nov 18, 2022
A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Using Pro-micro control.
A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Using Pro-micro control.

N.A.G.E.K.I. PLEASE CHECK Main Project A cheap,simple,Ongeki controller Use Keyboard Simulation and Mouse Simulation to controller the ongeki game. Us

Dec 30, 2021
Recognize stairs with lidar. Project the laser points to X-Z plane and use least squares for linear fitting.
Recognize stairs with lidar. Project the laser points to X-Z plane and use least squares for linear fitting.

stairs_recogniton Recognize stairs with lidar. Project the laser points to X-Z plane and use least squares for linear fitting. Dependencies PCL 1.8 Ei

Nov 25, 2022
Minipops alternative firmware for Music Thing Modular Radio Music

RMoxy Minipops drummer alternative firmware for Music Thing Modular Radio Music module The repository for the Radio Music module by Tom Whitwell Kits

Nov 6, 2022
Crazy Taxi Dreamcast Restoration 2.0
Crazy Taxi Dreamcast Restoration 2.0

Crazy Taxi Dreamcast Restoration 2.0 This plugin for Crazy Taxi restores the original licensed brands from the Arcade and Dreamcast versions. The orig

Nov 26, 2022
SuperTux Milestone 1 ported to the Dreamcast (again)

- An introduction for SuperTux - http://super-tux.sf.net/ Last update: April 26, 2004 Dreamcast port by Headshotnoby Turns out this game was alread

Jun 7, 2022
A FAT filesystem with SPI driver for SD card on Raspberry Pi Pico
A FAT filesystem with SPI driver for SD card on Raspberry Pi Pico

no-OS-FatFS-SD-SPI-RPi-Pico Simple library for SD Cards on the Pico At the heart of this library is ChaN's FatFs - Generic FAT Filesystem Module. It a

Nov 29, 2022
Next gen. of NekoCal: An open-source hackable and programmable e-paper display

NekoInk NekoInk is an open-source, programmable, and versatile E-paper display platform. It offers connectivity options to various type of E-paper scr

Nov 16, 2022
ESP32 based USB C Programmable Power Supply
ESP32 based USB C Programmable Power Supply

ESP32 USB-C Power Supply The idea for this ESP32 usb-c power supply project came to me when I discovered that components exist that communicate to par

Nov 26, 2022
A multi-bank MRAM based memory card for Roland instruments
A multi-bank MRAM based memory card for Roland instruments

Roland compatible multi-bank MRAM memory card (click to enlarge) This is a replacement memory card for old Roland instruments of the late 80s and earl

Nov 25, 2022
custom esp8266 controller for driving the pwm led controller

room8266 custom esp8266 controller for driving the pwm led controller designed to drive this: https://github.com/austinscreations/PWM-LED-Controller t

Nov 1, 2021
Prueba del Raspberry PI PICO con un display Raspberry PI TFT 3.5"

Raspberry-PI-PICO-display-RPI35 Prueba del Raspberry PI PICO con un display Raspberry PI TFT 3.5" Con ayuda de la libreria https://github.com/khoih-pr

Nov 10, 2021
C library designed for the TI MSP432P401R microprocessor and the TI Educational Booster Pack, to easily play and control songs with the integrated Piezo Buzzer.

MusicLib C library designed for the TI MSP432P401R microprocessor and the TI Educational Booster Pack, to easily play and control songs with the integ

Nov 24, 2021
A demonstration PoC for CVE-2022-21877 (storage spaces controller memory leak)
A demonstration PoC for CVE-2022-21877 (storage spaces controller memory leak)

POC CVE-2022-21877 This repository contains a POC for the CVE-2022-21877, found by Quang Linh, working at STAR Labs. This is an information leak found

Mar 8, 2022
Nov 16, 2022
C.impl is a small portable C interpreter integrated with a line text editor

C.impl C.impl is a small portable C interpreter integrated with a line text editor, originally developed for the ELLO 1A computer: http://ello.cc The

Nov 21, 2022
Optimization-Based GNSS/INS Integrated Navigation System
Optimization-Based GNSS/INS Integrated Navigation System

OB_GINS Optimization-Based GNSS/INS Integrated Navigation System We open-source OB_GINS, an optimization-based GNSS/INS integrated navigation system.

Nov 24, 2022