Tag

Showing blog posts sorted under the tag: sega

Using the Steam Controller on the Sega Dreamcast with Raspberry Pi Pico 2

Pi Pico 2 with Steam Controller and dongle next to a Dreamcast.

I've been scratching my head to come up with a project to play around with the Raspberry Pi Pico's PIO (programmable input/output) and eventually had the idea for some sort of converter to use non-Dreamcast peripherals on the Dreamcast. Of course, all good ideas have already been done, in this case not once or twice, but three times as far as I'm aware, and probably more.

All of these projects are based on the RP2040 chip (original Pico), while I wanted to make use of the new RP2350 (Pico 2) chip. In particular, there is a new PIO input masking feature available on the RP2350 which I think makes for a better implementation for reading the Dreamcast's Maple bus. Also, none of the previously-listed projects actually allow me to use by beloved Steam Controller. All this to say that I really wanted to try things on my own.

The Maple Bus

The Maple bus is how the Sega Dreamcast talks to peripherals on the controller port. It's a two-wire protocol where each wire takes turns being the clock and being the data. Essentially when a wire goes low, it signals that there is data on the other wire (a 1 or a 0), it allows a bit of time to read the bit, before the appropriate wire goes high in preparation for another falling edge.

Below is a sample reading of the Dreamcast send a packet to a controller. In this particular case it's Command 0x01 which is a request for information about what the peripheral is capable of. Hopefully you can see that every falling edge represents one bit of data, read from the other wire. I hope to go into more detail about the Maple protocol in a separate blog post.

PulseView reading of the Dreamcast sending a Maple packet.

Reading and Writing with Maple

To actually read and write data to the Maple bus, the RP2350's programmable input/output (PIO) sounded like the perfect solution. PIO on the RP chips are like little tiny co-processors that can run at a fixed clock rate. Each PIO block has 4 state machines, each with a few registers, and access to a shared space of up-to 32 instructions from a very limited set of instructions.

Both the reading and writing PIO programs got close to the 32 instruction limit so I ended up using two of the three PIO blocks available on the RP2350. With four state machines in each PIO block, it should - in theory - be possible to read and write from up to four controller ports on the Dreamcast. Though I'm only using one at the moment.

To read the Maple bus, I wrote a PIO program that constantly checks the value of the 2 data lines, waiting for the value to change due to a line going high or low. It takes advantage of the fact that the Maple bus only has data available during a falling edge, meaning a valid is read when the voltage on the wires either goes 11->10/01 or 10/01->00. The former is interpreted as a 1 and the latter as a 0. The PIO program then pushes this bit (eventually) back to the main C program where it can be interpreted.

Writing to the Maple bus was a lot more straightforward. A bunch of data is fed to the PIO which clocks out the start-of-packet signal then starts sending the data accordingly, before finishing with the end-of-packet signal when no more data is available in its buffer.

The whole thing ends up looking like this, on the wire, where the first packet is a request for data coming from the Dreamcast, and the second packet is the requested data being supplied by the Raspberry Pi:

PulseView reading of the Dreamcast sending a Maple packet requesting data from the controller.

The above packet is send by the Dreamcast and has a command code of 0x9, which is a 'Get Condition' command. It also has some included data (the pink bubbles) with a value of 0x00000001 which should be interpreted as 'Controller'. So, it's asking for the current status of the controller.

PulseView reading of the Pico responding to the Dreamcast with data.

This packet is sent by the Pico in response to the Get Condition command. It has a command code of 0x8 which says that this is a response packet with some data attached. The first 4 bytes are again 0x00000001 saying this is about the controller, then the remaining bytes describe which buttons are pressed and the current axis values of the joystick and triggers.

The whole request and response only takes about 125 microseconds or 0.125 milliseconds. But the controller state is requested about every 16.7 milliseconds, or once per frame at 60fps.

Getting Data from the Steam Controller

Getting USB data from the Steam Controller was surprisingly straightforward since the Pico SDK has TinyUSB as a built-in library. So, with that and the hid-controller example from the TinyUSB docs I was able to start getting the raw USB data quite quickly.

To interpret the raw data that was coming from the controller, I referenced Linux's hid-steam driver as it has mappings as to the meaning of specific bits and bytes sent by the Steam Controller.

Then it was just a matter of keeping track of what buttons are pressed, and sending that data to the Dreamcast via the Maple bus whenever it was requested.

Closing

If you own a Steam Controller, and would like to try this out for yourself, I've written a short visual guide in a previous blog post. It would be great to get feedback as I hope to built this into a standalone device with support for more kinds of controllers.

This really wouldn't have been possible without the awesome Dreamcast documentation from Marcus Comstedt. I'm no electrical engineer so his explanations of the exact workings of the Maple bus were invaluable. He also happened to write the Maple bus decoder for PulseView which generates the nice little data bubbles under the logic analyzer readouts. This project would have easily taken twice as long without that. I highly recommend checking out his stuff if you are interested in knowing more about the inner workings of the Sega Dreamcast.

There's also a little video showing the controller in action below, playing some Sonic Adventure.


Tags:


How To: Use a Steam Controller on the Sega Dreamcast

Required Hardware

  • Raspberry Pi Pico 2
  • Micro-USB to female USB-A adapter
  • A Dreamcast controller cable
  • Steam Controller with USB dongle

Controls

Controls are mapped as you would expect. The exception is the left touchpad of the Steam Controller which must be clicked to be read as DPad input on the Dreamcast.

Download

Download the pico2maple uf2 firmware file.

Holding down the BOOTSEL button while connecting the Pico to a PC should make it appear as a USB storage device. Then simply copy the pico2maple uf2 file over and the Pico should reboot itself with the new firmware.

Construction

Use a multi-meter to check which wires on the controller cable correspond to the following pins on the controller plug.

Connect the wires to the labelled pins on the Pico below by soldering or otherwise.

With everything wired up, it's simply a matter of plugging in the Steam Controller dongle to the Pico using the USB-A to Mini-USB adapter and plugging the controller cable into the Dreamcast.

Once the Dreamcast is powered on, power on the Steam Controller and it should connect and be a usable Dreamcast controller!


Tags: