Line protocolThe console/controller communication is performed using a one-wire, half-duplex serial protocol. The data line idles high. Data is transmitted at a 250KHz baud rate, with a '1' bit represented by 1uS low, 3uS high, and a '0' bit represented by 3uS low, 1uS high. Data is transmitted MSB first. Transmissions begin at the first negative edge and continue until a stop bit is transmitted. Stop bits are only 3uS, which is important to pay attention to, especially when operating in host (i.e. console) mode, because the controller can (and often will) respond immediately. The console transmits a stop bit as 1uS low, 2uS high, the controller transmits as 2uS low, 1uS high.
It is possible to implement this communication on a microcontroller by (ab)using a standard UART module. Although this line protocol is not standard UART, it is possible to very closely mimic it by converting 2 bits of controller data into 1 byte of UART data. Unfortunately, due to the start and stop bits which result in a 10-bit-per-byte transmission, it is not possible to get the exact duty cycle we need, so instead of 1uS and 3uS, we get 0.8uS and 3.2uS, respectively. However, this seems to still work just fine. In order to wire this up, we need to tie the UART's Rx and Tx pins together using a Schottky diode, as shown here Once everything is wired up, data can be sent and received using the following values:
0b00 | 0x08N64 | UART 0b01 | 0xE8
0b10 | 0x0F
0b11 | 0xEF
(Note that the N64 data is MSB first, UART data is LSB first)
One thing to be careful of when doing this is that because the Rx and Tx lines are connected together, any data sent out from the UART will also be received back, so if you are using Rx interrupts, they should be disabled during transmission, and the Rx buffer should be cleared at the end of the transmission before re-enabling them (or, if you can disable the Rx function entirely, that works too). You also need to be very careful about the timing when doing so, because stop bits are only 3uS (2 low, 1 high) which means you can't just read out the Rx buffer normally. On the PIC18F25K42, I did this by waiting for the TX buffer to be empty, indicating that the stop bit had been loaded into the Tx shift register, then waiting by manually polling the Tx pin until it went low, then high. Only then did I re-enable the UART's Rx buffer and interrupt. The timing is pretty tight because the controller can respond immediately after the 1uS high period, but I was able to make it work.
Data protocolThe console polls the controller by sending a 1-byte command. Some commands are followed by parameters, which may vary in length. The controller response varies, depending on the command.
Command 0x00 [Status]The controller responds with 3 bytes. For a standard gamepad, the first two bytes are 0x05, 0x00. The third byte contains bit flags. 0x04 indicates an address CRC error in the last communication. This flag is cleared upon transmission, so it is only reported once. 0x02 indicates a controller back has been removed. If no pak is inserted at power-on, the value is 0[1]. 0x01 indicates a controller pak (or rumble pak) is inserted. When a pak is inserted or removed, the new flag is set before the current one is cleared, so they are both set for one polling cycle. [1] This may be due to a glitch in my testing setup, other documentation claims this bit is set to 1 at power-on. It may also be due to an as-of-yet undocumented command which my test bench consistently triggers at start-up. When I start up my test bench, I receive 0x00 for the third status byte, even after unplugging and re-plugging the controller itself (without resetting the test bench). Then, once I insert and remove a controller pak, I start to receive 0x02, even after unplugging/re-plugging the controller (again, without resetting the test bench). So, the state of this bit is actually latched even through a power cycle of the controller itself. Really weird... I've tried sending multiple stop bits in a row and holding the data line low for >1ms to try and recreate what might be happening to that line on startup, but so far I'm unable to recreate the result intentionally through any other means than resetting my test bench.
Command 0x01 [Poll]The controller responds with 4 bytes containing the button and axis states.
[1] Reset is '1' when L+R+Start are all pressed. When Reset is '1', L and R are both reported as '1' but Start is reported as '0'. [2] Unknown, always '0' for normal gamepads Command 0x02 [Read]
|
Hardware Projects > N64 >