Ok, so what am I working on. Part two of my N64 modernisation project is to arrange it so that USB controllers can be used with the Nintendo 64.
The reasoning here is pretty simple. N64 controllers have a design flaw. I don't fully understand it, but the gist is that there's a magic powder inside the analog sticks that gives them their "springiness" and makes them return to centre. As the controllers wear, the powder escapes and it gets to the point where the sticks won't return to centre anymore as well as losing their sensitivity. Mine have held up pretty well, mostly because I've gone to great pains to take care of them, but they're fifteen years old now and they're starting to show it.
Obviously these controllers aren't manufactured anymore. Its not enough to buy used ones, for obvious reasons, and new ones are difficult to come by. Not impossible - I've seen them on eBay and in shops like Gametraders and Cash Converters, and I guess I wouldn't need to buy many of them, but still, they aren't exactly cheap or plentiful.
A better option is USB gamepads. As you'd expect from any PC peripheral, they're common as dirt and the good ones (eg Logitech or XBox) are comfortable, sturdy and responsive. So my thought has been to arrange it so that these sticks can be used with the N64.
First stop: Google. There's lots of projects where people have interfaced N64 controllers to something else (USB, parallel, Gamecube/Wii) or interfaced similar controllers (Gamecube) to the N64. As far as I've been able to tell though, nobody has ever got a USB stick going on a N64. Great, new territory - seems I can't avoid it.
I've spent the last couple of weeks researching and thinking and while I haven't yet done any testing with real hardware, I think I have a rough concept for how an interface might work. There's three aspects to it:
- Acting as a USB host controller and HID class implementation.
- Translating USB gamepad data into equivalent N64 button/position data
- Speaking the proprietary N64 controller protocol.
The translation is fairly straight forward. The gamepads I'm interested in (I'll be using a Logitech Dual Action pad for my testing) have (at least) two sticks, a direction pad, four buttons and two shoulder buttons. There's enough here to map to the N64 layout, which is one stick, a direction pad, two buttons (A and B), a second directional pad (C) and two shoulder buttons. The left shoulder maps to the Z trigger or the real left shoulder, as they were never used together on the N64 due to the structure of the controller. The second stick on the Logitech pad will map to the C buttons, with some threshold to determine if the analog stick is considered "pushed" or not.
The USB side is interesting. Its pretty easy to build a AVR-based USB device. V-USB is a very good software stack to turn an AVR into a USB device controller, or you can use one of the numerous chips from FTDI. For a USB host however, the options are far less compelling. As far as I'm able to tell, V-USB does not implement a USB host controller at all. I had intended to use the FTDI Vinculum VDIP1, but as I mentioned previously, the cost of shipping is prohibitive. After some more searching yesterday I found SIAM32, a software USB host controller implemented by a student project team at Cornell. I think some combination of their code and the V-USB code should be enough to implement a minimal host controller and HID class, which is all I need.
On the other side is the N64 controller protocol. Although its proprietary, its long been studied and is pretty well understood. The most useful sources of information have been tzanger's n64dev page and Micah Dowty's Cube64 project.
Its a command based protocol. The N64 sends a command, and the controller sends the response. There's no provision for the controller to initiate a data send - the N64 regularly polls the controller by sending a "get status" command, to which the controller response by sending a data packet containing the current state of the buttons and stick. There's also commands in there ask the controller if its there and what peripherals it has attached (such as a rumble or memory pak), as well as reading and writing to the memory card. Its pretty simple really, which is good - I like simple.
The connection to the N64 has three lines - +3.3V, ground and data. Obviously the power lines play no part in the communication. All that happens on the data line.
The most difficult thing about the data protocol itself is its strict timing requirements, as it typical of a serial protocol without a seperate clock. The line begins in a high (logical 1) state, held there by a pull-up resistor in the controller itself (the line is never explicitly driven high by either end).
A single byte consists of eight data bits and one stop bit. A single bit is 4 microseconds wide. To start the bit, the sender pulls the line low for 1us. The next two microseconds are either high or low, depending on the value of the bit. The final microsecond is high, then it goes again. After all eight bits are sent, the final 4us are all high to signal the end of the byte. See tzanger's page - it has some diagrams that make it easier to follow.
What this means is that however I implement this I need to be able to sample or transition the data line every microsecond. At 16MHz, that means I need to do something every sixteen cycles. Most instructions on the AVR take a single cycle to execute, so there's plenty of time to do things in between, but because I need to be able to respond to the N64 sending data within 2us, its pretty much impossible to run the USB host out of the same AVR.
So my interface has two AVRs - one doing the N64 comms, the other managing USB. This complicates things as now some mechanism is required for the two AVRs to communicate with each other.
This is the bit I'm not quite sure about. I originally thought to have the AVR ports tied together such that the USB AVR could just chuck the current state on the port and the N64 AVR could read it whenever it wanted. This is no good though because the entire controller state is 32 bits wide - sixteen for the button state, eight for the analog stick X axis and eight more for the Y axis. I don't really have the bandwidth available to do it that way, not even with the larger AVRs, which would be overkill in every other way anyway.
I've been thinking about perhaps using eight lines and sending the data a byte at a time, but at that point I've now got the two AVRs needing to coordinate communication when they could both be interrupted at any moment, breaking the whole thing. It might work if I allowed the transfer to be interrupted and in that case the N64 AVR will just use the last button state, but then this means that the USB AVR would have to be constantly streaming the current state rather than just sending updates when transitions occur. If it didn't then a state transition could be lost if the transfer is interrupted.
There's always the option of putting four eight-bit latches in between the two AVRs and storing the state there, as they can effectively be thought of as memories with seperate read and write channels. This however means slower access (external memories access instructions take two cycles in most cases instead of one) which might present timing problems, as well as requiring more board space.
I need to study the AVR datasheets to figure out if any of the peripherals it comes with can help me out. I'm sure a simple solution will present itself, I just have to find it. Fortunately the need for it is quite a way off. The USB and N64 comms need to be developed first, and they need to be done in isolation to ensure they work correctly.
So that's where I'm at. So far I'm just getting my development environment setup. This week I've built myself an AVR programmer which is working nicely, so next I need to write a few basic programs and make sure my laptop is setup properly and I know what I'm doing. Then the real work can begin :)