Recently Google announced that they were closing their project hosting site Google code. Back in the days before I understood how to properly use version control I thought it'd be a nice place to store the source code and documentation for a N64 controller I was making. There's a tool for exporting to github but the controller source is an old version and not worth keeping, however The documentation is a bit of a different matter. There's not too many sources out there which describe the N64 controller protocol including the memory commands properly and I thought it might be a good idea to rewrite my documentation so it could be accessible.

Physical Layer

N64 controllers connect to the console with a three core cable including 3.3V power, ground, and a bidirectional data line (the internet is lousy with pinouts). The single data line is tied high on the N64 side (and optionally at the controller) and the controller or console can drive the line low. Its important to remember that the data line is never driven high but instead when both the console and controller are in high impedance mode, the line is pulled high. This arrangement is commonly known as an open collector output.

A 0 bit is sent by pulling the data line low for 3µs then letting the line go high for 1µs. Conversely, a 1 bit is sent by pulling the line low for 1µs and letting it go high for 3µs. The console will end communication with a stop bit identical to a 1 bit, however the controller's stop bit has the data line pulled low for 2µs and then let's the bus go high.

Data layer

All communication between the console and controller begins with a command byte sent by the console followed by any required axillary information. The controller then responds within 20ms with the requested data and both sets of communication are terminated with a stop bit. The command bytes are:

0x00

The controller responds with three bytes to identify itself. For a normal controller the first two bytes are always 0x05, 0x00. The last byte is:

  • 0x01 if there is a controller pack plugged in.
  • 0x02 if there is no controller pack.
  • 0x04 if the previous controller read/write address CRC showed an error.

0x01

The controller responds with four bytes of button and joystick data

Byte Data
1 A, B, Z, Start, D up, D down, D left, D right
2 Joy reset, 0, L, R, C up, C down, C left, C right
3 Joystick x ordinate
4 Joystick y ordinate

The joystick ordinates are given as 2s complement values between -0x50 and 0x50. Providing values outside of this range can lead to strange behaviour, such as mario changing directions in super mario 64.

0xFF

From the N64 patents this seems to be a reset command which recalibrates the controller's joystick. The official N64 controller uses optical rotary encoders which give a quadrature encoded output, similar to old mouses. In this arrangement there's no way to know which position the joystick starts in, only how it's changed over time. This is why if you hold the joystick to one side as the console powers on, the joystick will have a constant drift. The 0xFF command resets the controllers internal registers for the joystick ordinates to centred. The controller also responds with the same output as the 0x00 command.

0x02

A read command followed by an additional two bytes from the N64 which give an address and a CRC for that address in the last 5 bits of the second byte. The controller responds with a 32 byte data block beginning at the given address, followed by a CRC of the data block. Details of the data CRC are given below.

0x03

A write command with the same addressing as the previous command. The address bytes are followed by a 32 byte block of data and the controller responds with a data CRC.

Controller memory space

The two bye address from the controller gives an address space between 0x0000 - 0xFFFF, or 64kB of space. The lowest 5 bits for controller read and writes are implicitly all 0 and the 5 bit address CRC calculated by the console is given in their place. All read and writes are therefore aligned on 32 byte boundaries. Memory packs, which all have 32kB of space, are mapped between 0x0000 - 0x7FFF. The rumble packs I've seen simply latch the last data value in a write to any address between 0x8000 and 0xFFFF, though I've read resources which give the rumble pack an address of 0xC000. If the lsb of the latched value is 0 the rumble pack is turned off and vice versa. To test if a rumble or memory pack is attached, some games do a test write to a rumble pack address and read back to see if the value has been latched. If it wasn't, the attached peripheral is likely the memory pack. Other peripherals do exist, one popular example being the transfer pak. From the limited experience I've had with one (borrowing from a friend), and a look at some N64 development manuals, it seems like the transfer pak just allows access to the gameboy cartridge memory which maps ROM and RAM within the address space of the N64 controller. Importantly, the transfer pak only supports MBC1, MBC3, and MBC5 within the functions of the N64 development kit, but there's no reason one can't use the given commands to access an MBC2 game that I'm aware of. You'd need to track down what addresses the transfer pak uses to switch power on and off, which I never managed to find.

CRC

To find the Data CRC I sent a write to a controller with the last bit set, and it was then simple to find the CRC polynomial:

  • Polynomial length: 8 bits with an implicit 9th 1 bit
  • Polynomial value: 0x85
  • Initial Value: 0x00
  • Post XOR: 0x00 with a pack connected, 0xFF without
  • As with all CRC calculations, the message is augmented with a 0x00 byte.In summary it's a fairly simple 8 bit CRC that wasn't too difficult to work out however calculation of the CRC using the poly is quite computationally expensive, especially when running on a microcontroller. It requires a XOR, shift and most crucially knowledge of the next 7 bits which means it's difficult to calculate while the message is being received/sent. While I didn't do measurements to determine how quickly the CRC response was needed from the controller, I had some problems with the latency of an implementation that computed the CRC after the message had been received.

    For embedded systems with limited power, an alternative approach to CRC calculation is to precompute the effect each byte will have on the CRC to create a 256 entry lookup table. Then, each byte of the CRC only requires a table lookup and a XOR with the CRC. The lookup table was calculated with this short program and resulted in this table. The given table is already formatted as an assembly lookup table to go in the 16 bit program memory space of a pic18 micro, but it can be easily reformatted.

    The same approach was reused to find the address CRC:

  • Polynomial length: 5 bits with an implicit 6th 1 bit
  • Polynomial value: 0x15
  • Initial Value: 0x00
  • Post XOR: 0x00

    Hardware

    I made two N64 controllers of my own with this challenge. The first was for a contest on the BenHeck forums, and I thought it'd be fun to come out with something so small people would wonder how it was done. I omitted the Dpad and L button and made use of a 4 way tact switch for the C buttons and had just enough space to cram the electronics into the smallest rectangular jiffy box from Jaycar.

    A rather blurry shot of the tiny controller I built, around a 16f PIC.

    A rather blurry shot of the tiny controller I built, around a 16f PIC.

    The second controller replaces the board in an official N64 controller and uses a pot joystick from a gamecube controller. It has a built in memory pack and rumble pack which you can switch between with a button combination. While it's the same form factor, it's amazing the difference having a proper joystick makes. A picture would show you what looked like a stock controller, so here's a video of the innards working before being prettied up and stuffed in the controller shell.