USB SNES Controller

During my childhood I used to play many great games on the Super Nintendo (SNES). Many years later, I heard of snes9x and played a few of these games again on my PC. What always annoyed me was the controller: it was nearly impossible to play these games with the keyboard.
I bought a few gamepads then, but still, they were missing some more or less important keys compared to the original SNES controller and the real "Nintendo feeling" never came up. (See, back then, USB did not exist yet. Instead, joysticks and gamepads had this 15-pin D-sub connector that you had to plug into your soundcard, and this Game port only permitted up to 4 buttons - an SNES controller has 8!)
Many years later, the idea struck me to program a microcontroller to interface with an original SNES controller on the one hand, and provide a standard USB HID interface on the other hand, so that it could be used with any PC, any operating system and without the need for additional drivers.
The vision I had in mind was a circuit so small that I could hide it in a standard USB connector. The result would be an SNES controller with a USB plug. I did not want to modify the electronics within the controller itself so it would still be usable on a normal SNES.
Contents
Components
SNES controller
I bought an old Super Nintendo on ebay and had a look on the controllers:
You can find all details on the controller's schematic, pin assignment and communication protocol on a separate page: SNES Controller.
Microcontroller
The microcontroller I chose for this project was one of the smallest PICs which has USB support: PIC18F2450 with 28 pins.
For my project it offers the following advantages:
- USB 2.0 compliant
- Low Speed (1.5Mb/s) and Full Speed (12Mb/s)
- On-chip USB transceiver with on-chip voltage regulator
- On-chip pull-up resistors
- Low-power modes
- 16kB Flash memory
To program (flash) the microcontroller, one needs a programmer or debugger with In-Circuit Serial Programming (ICSP) support. There's either the official ones from Microchip, but since these were too expensive for this project, I chose to build my own, similar to Brenner5 from Sprut's homepage (you can find more details under PIC programmer).
Code
All code files other source files can be found in the Subversion repository, hosted by
For compiling and linking the code I use the MCC18 compiler from Microchip. It is available in a free evaluation version in which only a few optimizations have been disabled.
To compile the code, all that is needed is the directory svn/trunk/src
. Edit the Makefile
and set the correct directories there so MCC18 can be found. Then simply execute make.exe
The code is divided into three modules:
- main.c
- This module implements the main application logic: CPU initialization and SNES controller polling. In the central
while (1)
loop all controller buttons are polled cyclically every few milliseconds. The state of all buttons is recorded in the arraybuttons
. If the state of one or more buttons changed since the last poll cycle, a new HID report is generated and the usb.c module notified that the report changed. - usb.c
- In this module all USB logic is implemented. Interrupts from USB are handled here and then processed by the appropriate
process_*()
functions, depending on what endpoint is targeted. This code contains a lot ofDEBUG_OUT()
macros which, when enabled, resolve to functions calls to debug.c. These are there to help debugging and to see what is happening on the USB. - debug.c
- In debug.c are functions for serial I/O. With a serial terminal (such as the LCD Terminal) one can see what transactions, etc. appear on the USB. When
DEBUG
is undefined (see debug.h) these are all empty and don't cause any performance degradation.
Prototype
I created a first prototype on a hole pattern board for developing the code and easy debugging. The board layout was done with Eagle.
To the left (CON3) you can see a DB-9 connector for connecting the controller (in the prototype I used a connector so I could easily change the controllers). The pin assignment is as follows:
DB9 pin | wire color | signal |
---|---|---|
2 | red | Data |
3 | orange | Latch |
4 | yellow | Clock |
6 | white | +5V supply voltage |
8 | brown | Ground |
To the top (CON1) one can find the ICSP connector (In-circuit serial programming) for programming the microcontroller. This connector has the following pin assignment:
pin | signal |
---|---|
1 | +12V programming voltage (Vpp) |
2 | +5V supply voltage (Vdd) |
3 | Ground (Vss) |
4 | Data (PGD) |
5 | Clock (PGC) |
Final Version
When the experiments with the prototype were successful, I started to develop the final, miniaturized version.
All source files in Eagle format can be found in the Subversion repository in the directory svn/trunk/board
:
Schematic
The board layout for the final version was done with Eagle, the first thing was to draw the schematic:
The wirepads to the left (labeled with YELLOW, ORANGE, RED, WHITE and BROWN) are the holes where the controller wires will be soldered. The wirepads labeled with PADx are solder pads where the wires for ICSP will be soldered: PAD1=PGC, PAD2=PGD, PAD3=VPP. Ground and supply voltage must always be connected through the USB connector.
USB requires a highly-accurate clock, which can only be retrieved from a crystal or a ceramic resonator. A crystal would be much too big for my small board, so I chose a CSTCC ceramic resonator from muRata. This has the additional advantage that it already includes the two load capacitors, which again helps to save board space.
The PIC18F2450 microcontroller is also available in a QFN package. Although this is quite difficult to solder (I tried several methods) it is the smallest possible alternative.
The only other external parts are two capacitors: C1 for smoothing the supply voltage and C2, which is required for USB 3.3V generation. Note that no pull-up resistors can be found in the schematic: the PIC18F2450 contains internal pull-ups for that purpose (this fact was completely omitted from the original data sheet 39760a.pdf, it can only be found in the errata 80274a.pdf)!
PCB
As I said, I wanted the board to be so small that it could fit into the plug's casing. This made it quite difficult to route all signals, but in the end I succeeded:
The PCB layout is a double-sided layout with parts only on the top side (red), and traces on both sides, connected with vias (green). The solder pads for ICSP are on the bottom side (blue). To the right you can see five bigger holes, these are used to connect the wires of the SNES controller.
This kind of PCB is impossible to manufacture at home, so I enlisted the services of http://www.pcb-pool.com. This manufacturer also provides and Eagle rule-file, which helps you to avoid layouts that can't be manufactured. After a few days I got the results, and I can tell you, I was very satisfied:
Soldering the parts on such a small PCB is a bit tricky, especially the QFN-packaged microcontroller. I tried several approaches, but the way that worked best was the following:
- apply solder paste to all pads - the solder resist between the pads ensures that no pads or vias get short-connected that shouldn't
- place the parts on the board, the solder paste holds them in place
- carefully heat up the PCB with a soldering torch (gas burner) until you see the solder paste becoming liquid
Here you can see the populated PCB with attached ICSP connector - this of course will be removed after programming:
Finished Controller
After attaching the SNES controller, flashing the software, final tests and detaching the ICSP connector the controller is finished and ready for long gaming hours:
What is working:
- Hardware is running stable
- Controller draws about 18mA, heat dissipation is fine
- Controller is detected and working correctly in Windows
What can still be improved:
- Casing
- The original plan, to hide the electronics in the plug, proved to be too ambitious: The current PCB does fit into the plug's casing, but this results in the plug to become quite fragile. Furthermore, there is no strain relief on the controller cable any more, so any pull on the cable could cause the wires to be severed from the PCB. Also the current PCB is very dependent on one specific plug casing, which might be a problem if that casing is not available any more.
- A better solution would be to separate the electronics a few centimeters from the plug in its own casing (I got this idea from my USB headset). The problem there is that I would have to find a suitable casing.
- Solution for damaged ceramic resonators
- In some of my PCBs the ceramic resonator stopped working for some reason. The microcontroller runs fine with its internal oscillator, but does not run when switched to the external resonator. I can only assume the resonator is sensitive to heat changes and gets damaged in my solder process. Maybe improving that with a hot-air soldering torch could help.
- Pass USB compliance tests
- The USB Implementer's Forum provides a free compliance testing toolkit called USB Command Verifier (USB20CV), obtainable from http://www.usb.org/developers/tools/. USB20CV is the compliance test tool which evaluates High, Full and Low-speed USB devices for conformance to the USB Device Framework (Chapter 9), Hub device class (Chapter 11), HID class, and OTG specifications. Also included are mass storage class and USB video class specification tests. All USB peripherals are required to pass the Device Framework tests in order to gain certification.
- In first experiments, most of these tests failed. I guess this is due to mode switch commands, etc., that are not implemented (or only implemented as stubs). However it should be possible with minimal effort to get these running.
- Implement USB low-power modes
- When the PC enters suspended state where the USB is still powered, any USB device that is not configured for wakeup must also put itself into suspended state and use only a minimum amount of electric power.
- We could achieve this with several methods, for one we can disable power supply to the SNES controller (which saves about 4.2mA). We could also power down the microcontroller, or activate whatever power-saving possibilities the PIC has to offer.
- Support for NES controllers
- The communication protocol of an NES controller is identical to a SNES controller (if I remember correctly). So with some luck it should already work, but due to the lack of a controller for testing, I can't say for sure.