I designed a USB device that interfaces with two Super NES (SNES) controllers and presents their state in accordance with the USB Human Interface Device spec. The upshot of this is that it Just Works™ without having to install drivers on at least every computer made in the last 10 years. I wanted to be able to play my SNES games on my computer with an emulator, but liked the feel of a real SNES controller. I could have purchased an adapter, but I thought it would be far more rewarding to make my own.
I first decided that I wanted to make this peripheral in either late 2005 or early 2006. Matt Mullins, my good friend, fellow Linux user, and resident hardware genius liked the project idea and we joined forces with the idea that by working together we would be able to learn together, and perhaps might accomplish something that neither of us would have the skill or determination to complete alone.
It was clear even then that other folks had done this before - Lik-Sang sold their Super Smartjoy USB adapter that accomplished the same task, and there are plenty of hobbyist sites where others have made their own similar adapters. Our purpose, however, was to complete this project from scratch ourselves, as a learning exercise with a nice reward at the end. We began our research, and as a senior in high school I found documentation on the SNES controller protocol here, started reading the USB specifications, and researched what sort of hardware support various microchips had for such an important and widespread protocol.
Due to other life activities, growing skills, rules regarding soldering irons in dormitories, teamwork, lack of materials, and other lame excuses, I did not complete this project until June 2010.
My first undertaking of this project was entirely unsuccessful - I didn't even make it so far as to order parts. I found the PIC product line from Microchip.com, and saw the PIC 18F2550 had support for USB transceiving in hardware - all I would have to do is put packets in a buffer. I also considered the Atmel AT90USB, but soldering surface-mount chips scared me away at the time. Another factor contributing to the decision to use the 18F2550 was the fact that Microchip offers free samples. (Sadly, they are still in the tube in which they originally shipped. Maybe some day I'll put them to better use.) Thus, I decided I would use the 18F2550. I ran Linux, and didn't much care for having to use a toolchain that only ran on Windows, so I determined that I would write my program for the chip in assembly using gputils. (Note: if I had any sense in me, I would have realized that the 18F2550 was not supported by gputils at the time, but I did not. Go figure.)
For a variety of reasons, this project got severly backburnered:
- I read the datasheet and the Microchip programming guide, and taught myself the PIC assembly language. The only other assembly language I had worked hitherto this experiment was that of the Zilog z80, which powers my TI-86 graphing calculator. Compared to that z80 assembly, I found the PIC language confusing and obtuse.
- For reasons which I cannot recall, Matt offered to write the
code that involved USB if I took care of the SNES side. If I remember
correctly, neither of us wound up writing any related code for several
months, but I would guess that the size of such an undertaking made
it frightening to focus on. I do remember reading parts of the
USB specification, and a good chunk of the USB HID specification.
I also remember this taking a significant amount of time.
- Sidenote: this background served me extraordinarily well in my classes at Texas A&M.
- I think the difficulty in procuring or building a PIC chip programmer was also a setback. I had no fancy hardware. Matt had assorted junk, but no time to find the right parts in the workbench nor assemble a programmer. We also were working exclusively over the internet at this point, which makes hardware work hard to share. It seemed as though whenever one of us had free time, it was the other one that had the chips.
So despite both of us making efforts, and surely learning a great deal in the process, we didn't even get as far as designing a board layout, ordering parts, or writing significant amounts of code. Nonetheless, this was one of the most educational failures I have ever had.
In the interim, I went to Texas A&M for undergraduate studies, and majored in Computer Engineering and Mathematics. While there, I met Mark Browning, with whom I learned more about the Atmel AVRs, successfully programming an ATtiny261 to bitbang RS-232 (the chip has no UART) for another failed project that was supposed to design an automated golf-ball launcher. This experience taught me about the AVRs, their delightful assembly language, how easy it was to hack together a programmer, and the wonderful support of avr-libc. The fact that the toolchain worked well on both Windows and Linux was the cherry on top.
That experience, in turn, influenced design decisions made in building the LED DDR pad project, for which we used ATmega328p chips with the Arduino programming environment, which is built on GCC and avr-libc. By the time our team had completed that project, I decided that for future projects, I would use AVRs instead of PICs.
My second, more serious attempt at this project began in earnest after I graduated from Texas A&M in December 2009. I had a good deal of free time on my hands, and chose to work toward some of my personal projects. I spent some time at MIT, and helped out with the undergraduate-run 6.270 class, which uses an ATmega256-based microcontroller board (the Happy Board), along with an operating system called JoyOS. This was fun, and another activity that confirmed my pleasure in using the AVRs.
I discovered the V-USB library with a tip from Mark Browning, who had mentioned it while I was at TAMU. I remembered it when I was planning my design in Spring 2010, and could tell that this was an appropriate building block for my project. I examined their sample projects, the software library itself, and schematics. I liked what I saw, so I proceeded to build my device on these pieces.
Before ordering any parts, I designed the layout of the board and wrote the corresponding software to ensure I hadn't forgotten anything. I wrote a SNES-polling routine in assembly (because I felt I could express it more efficiently than the compiler could figure out in C). The main routine itself was written in C, though I could probably have written it in assembly for fun. USB communications were handled by the V-USB driver. Even before the chips arrived, with the help of avr-objdump I discovered that the V-USB interrupt routine wasn't making it into the interrupt vector. It turns out that wasn't so much a bug as V-USB using a deprecated #define in avr-libc (probably for compatibility with older releases) that never existed for newer Atmel devices (like my ATmega328p). I reported the issue and sent a patch to Objective Development (the company which makes the V-USB driver). Christian Starkjohann was very responsive, and answered within 8 hours that the patch would be in the next release. Thanks, Christian. You make an awesome product available for free, and you're prompt and polite too.
Oh, source code. I am still cleaning it up at the moment, and it may undergo changes, but if you really must see it now:
git clone git://git.zarvox.org/usbsnes.git
I'll get to making a proper release later. License is GPLv3, by the way.
When I got around to ordering parts, I ordered a few ATmega328p chips from Sparkfun Electronics, which had just gotten a shipment in (note: ATmega328p chips were short in global supply at the time), along with a couple soldertail sockets, in case I were to screw up with the soldering iron, or if I wanted to make more than one. Other parts (resistors, 18MHz crystal resonator, capacitors, zener diodes, and pin headers) were procured from Digikey. I forgot to get a breadboard, so after the parts came in, I went to Radioshack and bought a through-hole breadboard.
I had not brought all my soldering equipment with me from Texas to Massachusetts, so I wound up borrowing a soldering iron, solder, and diagonal cutters from an MIT student (thanks Fred!). I sliced up an extra USB cable I had lying around to connect to the board, tinned the wires, and soldered them to the board. I fear they may break if put under too much stress, but they've survived so far. One of the 6.270 instructors gave me permission to use the lab's AVR programmer. I read the datasheet on fuse bits, and was able to successfully load a program to the AVR from my laptop in April 2010. This was particularly significant because I had no multimeter or even LEDs to test the connectedness of my logic with - the device would simply work, or not. I could make the device appear as an invalid USB device or not appear at all, which told me that I was successfully loading programs to the AVR.
I bought a broken SNES on eBay, since I had no way to produce the appropriate pin spacing and tension of a real SNES controller socket. I disassembled the SNES, removing the front panel where the controllers plug in. Using the pinout from the RepairFAQ article, I soldered some wires to the appropriate contacts on the sockets, verified continuity with a multimeter, tested discontinuity, found that I had soldered two pins to each other, cursed my cheap soldering iron and aging tip, and resoldered the lot. This time, connections were correct, and all the electrical hardware was done.
The device receives USB resets from time to time, which I believe is a result of my picking resistors with too little resistance to meet spec for the USB input impedance requirements. Alternately, there could be noise in the wires, which are more succeptible to this than normal since I have about a 3-inch run where D+/D- aren't twisted and also aren't shielded.
Circuit diagrams are on my TODO list. I may also redo the project, and get a real board fabricated.
|28-pin soldertail socket||1|
|Male breakaway pin headers||6|
|18MHz crystal resonator||1|
|68 ohm resistor (5%)||2|
|1M ohm resistor||1|
|1.5k ohm resistor||1|
|3.6V Zener diode||2|
|18 pF capacitor||2|
|10 µF capacitor||1|
Well, the story in "Design Process" details my social and timeline problems pretty well, so I'll keep this section limited to to technical issues:
- Getting the chip to speak to speak to avrdude at all. The Atmel-official AVR-ISP does NOT provide power to the target chip. This is in contrast to the AVR-ISP500 from Olimex, the programmer I used in the MIT 6.270 lab while in MA, which does power the target board. Expecting that some other connection had failed on the board, I spent the longest time trying to see why I couldn't get the chip to talk to me. It wasn't until I forgot to remove the target board's USB cable that I finally accidentally happened upon the solution.
- Writing malformed USB HID descriptors. Despite reading the USB spec and the HID spec, I still didn't quite understand the full extent of what the HID descriptor was supposed to look like and what its structure was. Even after looking through lots of examples, I still don't get what the heck the "USAGE" stanza does - it seems to play multiple roles. Also, the HID Descriptor tool from the USB IF is rather confusing.
- Sending malformed USB reports. See above.
- Failure to solder the right wires together. Yep, I managed to swap two wires when connecting wires to the breakout of the 1P and 2P controller ports. I caught the problem pretty early, though, since after the issues with my previous soldering I opted to continuity-test everything immediately after construction.
- Random USB resets. These I did not actually ever solve, but I'd guess that my hardware is out of spec in a nontrivial fashion, and whenever noise hits the lines, I get a reset.
(Possible) Future Work
- Design a circuit board and have it fabbed. This thing is a horrible mess of wires, and I'm never sure if it will or won't work because I have very weak guarantess about the connectedness (or disconnectedness) of two nodes, since my soldering iron is awful. Further, this would probably help with my USB reset issues.
- Wrap the hardware in some prettier packaging. An idea suggested by Tom Zimmerman was PVC - I may opt for something involving acrylic. If I can get access to a decently cheap 3D printer, designing a case for the electronics in some CAD program would be neat. So would using a 3D printer to produce the SNES-style sockets, rather than ripping them out of broken consoles.
- Verify that the code works with two controllers at once.
- Put up photos and a video.
Photos to come later. Video too, maybe, when I have time to record and transcode something to VP8.