Hi there, thought you lot might be interested in what I've been up to:
Controlling a Real SNES using a Raspberry Pi
The main project aims were:
A. Use a USB joystick plugged into the Pi to control a real SNES
B. Record and playback joystick inputs
To accomplish this, the Pi is connected thus:
USB Joystick -> Raspberry Pi USB
Raspberry Pi GPIO -> Real SNES controller port
How it works:
The SNES controller protocol is fairly simple, every 16.67ms (or 60Hz for NTSC, 50Hz for PAL consoles) it sends out a latch pulse to each controller, which contain 2 x 4021 shift registers. These shift registers then clock out the 16 bits of data to the console. Although the latch pulse is fairly slow, the clock is fairly quick, with 16us between clock pulses.
So far so good, but what about randomness in games? For example, how do I know that an enemy will be in the same place each time and not be in a different place due to a different number coming out of the random number generator?
Well, the good thing is that the SNES lacks a source of entropy (hardware random number generator or even a realtime clock), so most games use an absurdly simple principle:
Count the number of latches before a controller input is pressed and use that to seed a PRNG.
Also bear in mind that the SNES doesn't start sending out the latch pulses until the CPU is working correctly, this means that as long as we send out the same button presses on exactly the same latch on each poweron, then our PRNG will act exactly
the same, with the same enemy patterns, powerups, items etc.
My different approaches
I tried a few different approaches. I first looked up some benchmarks for the GPIO on the Pi and found that it might be possible to bitbang the SNES directly. That approach ended in failure, mainly due to the Pi not being able to clock out the data fast enough for the SNES when it sees the latch pulse. A lot of people were telling me it wouldn't be possible with a Pi due to the overhead of Linux and I would need to use a microprocessor instead.
The way around this was to use some chips that can handle the faster clock pulse, load up the data into the chips from the Pi between pulses and use the chips to send the data out to the SNES controller port.
I took a cheap knock-off SNES controller (which also supplied the connector I needed for the controller port) and tried to wire up the GPIO directly to the pads, but this again ended in failure due to the crapness of the PCBs, as traces were lifting everywhere.
I then tried my third approach, which was met with some success. I duplicated the hardware inside of the SNES controller (2 x 4021 shift registers) and wired up 12 GPIO pins to that (X, Y, B, A, L, R, Up, Down, Left and Right).
I also needed something to act as a buffer/level converter for the latch pulse, as the output from the SNES is 5V logic whereas the GPIO pins on the Pi are only 3.3v tolerant.
To my amazement it worked and it was detected as a proper controller the Nintendo Controller Test software!
Live input was working fine, both from a USB keyboard and a PS1 controller connected via a USB adapter, but recording and playback would lose sync after a few button presses, which was slightly disheartening. Perhaps all those people telling me that I wouldn't be able to make it work with the overhead of Linux were right and I've have to change my approach again to use a microcontroller instead.
After a bit more playing around I had a bit of a eureka moment, where I found out that I was supplying the 4021's with 3.3V instead of 5V. Also I hadn't put in a blocking diode to stop current flow back into the SNES. Once I had fixed both of these problems playback worked absolutely fine. I could play a 20 minute game of Killer Instinct, switch off the SNES, turn it back on again and watch my Pi play the game exactly the same, as if it were a video recording! Huzzah! Here's a video of the Pi playing Super Mario All-Stars:
The circuit diagram:
So I was at the stage where I could playback from my own playback format and was successful with roughly 90% of the games I tried. So it was time to look at working on using emulator movie files. These are files recorded by an emulator that are used to playback high score attempts/speed runs etc. Sometimes emulators are used to create bizarre and lightning quick videos called 'Tool Assisted Speedruns'. For more information about TAS runs, have a look here:
These would be a good source of videos to demonstrate the abilities of SNESBot. To try and get things to work first time, I went the simple route. I chose a TAS video that had previously been verifed on hardware, which was the KFC-Mario speedrun of Super Mario All-Stars: Super Mario Bros. Lost levels. Because I was intending to run on hardware, I would need to use a pretty accurate emulator, so lsnes looked like a good candidate. I found a Lua script by Ilari to output the controller data I needed for the emulator and wrote some importing code for my bot. After a few false starts, (ILari was particularly helpful in getting it working) I was at the stage where I could give this TAS video a try.
I grabbed my copy of SMAS, plugged it in and it went through the title screens, Mario started to run and then.
Nothing. He just sat there hopping on the spot and generally not doing what I wanted him to do.
This was slightly annoying, so I tried it with the console in PAL rather than NTSC, still the same. I took my SNES apart and saw the one of the legs of PPU2 had snapped off after I installed a SuperCIC mod. I believe this was causing it to run in permanent PAL mode, throwing off the timings. With a bit of delicate surgery, I managed to get it soldered back on, setup again and hit start. And I watched Mario go absolutely nuts for 33 minutes, flying impossibly through levels, then destroy Bowser and complete the game, all controlled via the Pi. A terrible video of it can be seen here:
Feeling rather pleased. my next stop was to write an importer for SNES9X emulator movie files, which hasn't worked out so well. Which is a shame, as this is a popular file format with a lot of TAS runs available. Because the TAS videos rely on very sharp timing, if the emulation isn't exactly the same as a SNES, the frames 'desync' and the Pi/SNES lose sync with each other.
This normally happens during a 'lag frame', which is a frame where the SNES CPU is too busy to poll the joystick inputs, because it's too busy drawing enemies exploding etc. The reason lsnes input files work and SNES9x files don't is down to the way they emulate these lag frames. Unfortunately atm there's not that many TAS videos in lsnes format, so I don't have much else to show off at the moment.
A Raspberry Pi + SD card + USB keyboard / joystick
2 x CD4021B CMOS shift registers
1 x buffer / level convertor for the latch pulse
Some perf/vero board to mount it all on
An old floppy cable or something to break out the GPIO pins
A knockoff/broken controller for the SNES connector
WiringPi library by gordonDragon
SNESBot software from here:
Games confirmed to be working:
Assault Suit Vulcan
Contra 3: The Alien Wars
Super Mario Kart
Street Fighter 2
Street Fighter 2: Turbo
Super Mario All-Stars
Super Mario Kart
Super Mario World 2: Yoshis Story
SuperGB JPN + Tetris DX
SuperGB JPN + Tetris
As you can see from the list above, I tried as many different genres as possible, as Super Mario isn't a great game to test with as the enemies always appear in the same space at the same time (ie they aren't PRNG controlled). Watching the SNESBot play Tetris is very funny to watch indeed.
Games that don't work
Why they don't work is a bit of a mystery to me at the moment. As the code that reads the controller interrupts isn't the same for each game, it could be that my bot isn't precise enough with the timings. Also some games use uninitialised memory as the seed for the PRNG, which means the game won't be exactly the same with the same inputs. I'll have to run through them with a SNES debugger at some point. Another thing to try which ILari pointed out would be to write a small bit of code to run on the SNES that dumps the initialised WRAM through the controller port to the Pi, so it can be incorporated into an emulator.
Netplay (depending on the RNG method used by each game)
Support other TAS video files other than lsnes
Right now the code is heavily based around using a USB PS1 controller, it would be nice to support other controllers without hacking the source.
Add support for SNES mouse via a USB mouse attached to the Pi, but this will require different interface hardware then what I have at the moment.
Autofire/special moves on a single button press