mirror of
https://github.com/pothos/arduino-n64-controller-library
synced 2024-11-21 16:25:11 -05:00
Extract N64Interface
This commit is contained in:
parent
d670348f48
commit
ac161c6d00
@ -1,41 +1,17 @@
|
|||||||
|
|
||||||
#include "N64Controller.h"
|
#include "N64Controller.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "pins_arduino.h"
|
|
||||||
|
|
||||||
#define NOP asm volatile ("nop")
|
|
||||||
#define NOP5 asm volatile ("nop\nnop\nnop\nnop\nnop\n")
|
|
||||||
#define NOP30 asm volatile ("nop\nnop\nnop\nnop\nnop\n" \
|
|
||||||
"nop\nnop\nnop\nnop\nnop\n" \
|
|
||||||
"nop\nnop\nnop\nnop\nnop\n" \
|
|
||||||
"nop\nnop\nnop\nnop\nnop\n" \
|
|
||||||
"nop\nnop\nnop\nnop\nnop\n" \
|
|
||||||
"nop\nnop\nnop\nnop\nnop\n")
|
|
||||||
|
|
||||||
// these two macros set arduino pin 2 to input or output, which with an
|
|
||||||
// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or
|
|
||||||
// low. These operations translate to 1 op code, which takes 2 cycles
|
|
||||||
#define N64_PIND_HIGH DDRD &= ~pincode
|
|
||||||
#define N64_PIND_LOW DDRD |= pincode
|
|
||||||
#define N64_PIND_QUERY (PIND & pincode)
|
|
||||||
|
|
||||||
#define N64_PINB_HIGH DDRB &= ~pincode
|
|
||||||
#define N64_PINB_LOW DDRB |= pincode
|
|
||||||
#define N64_PINB_QUERY (PINB & pincode)
|
|
||||||
|
|
||||||
N64Controller::N64Controller(unsigned char serialPin) {
|
N64Controller::N64Controller(unsigned char serialPin) {
|
||||||
if(serialPin > 13)
|
if(serialPin > 13)
|
||||||
serialPin = 2;
|
serialPin = 2;
|
||||||
n64_PIN = serialPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
void N64Controller::begin() {
|
|
||||||
// Communication with N64 controller controller on this pin
|
// Communication with N64 controller controller on this pin
|
||||||
// Don't remove these lines, we don't want to push +5V to the controller
|
// Don't remove these lines, we don't want to push +5V to the controller
|
||||||
digitalWrite(n64_PIN, LOW);
|
digitalWrite(serialPin, LOW);
|
||||||
pinMode(n64_PIN, INPUT);
|
pinMode(serialPin, INPUT);
|
||||||
n64_first_register = true;
|
bool n64_first_register = true;
|
||||||
switch (n64_PIN) {
|
char n64_pincode;
|
||||||
|
switch (serialPin) {
|
||||||
case 0: n64_pincode = 0x01;
|
case 0: n64_pincode = 0x01;
|
||||||
break;
|
break;
|
||||||
case 1: n64_pincode = 0x02;
|
case 1: n64_pincode = 0x02;
|
||||||
@ -65,279 +41,20 @@ void N64Controller::begin() {
|
|||||||
case 13: n64_pincode = 0x20; n64_first_register = false;
|
case 13: n64_pincode = 0x20; n64_first_register = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
n64_pincode = 0x04; n64_PIN = 2;
|
n64_pincode = 0x04;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n64_first_register) {
|
if(n64_first_register) {
|
||||||
N64_init_PIND(n64_pincode);
|
interface = new N64Interface_PIND(n64_pincode);
|
||||||
} else {
|
} else {
|
||||||
N64_init_PINB(n64_pincode);
|
interface = new N64Interface_PINB(n64_pincode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void N64Controller::N64_init_PIND(char pincode) {
|
void N64Controller::begin() {
|
||||||
// Initialize the gamecube controller by sending it a null byte.
|
interface->init();
|
||||||
// This is unnecessary for a standard controller, but is required for the
|
|
||||||
// Wavebird.
|
|
||||||
unsigned char initialize = 0x00;
|
|
||||||
noInterrupts();
|
|
||||||
N64_PIND_send(pincode, &initialize, 1);
|
|
||||||
|
|
||||||
// Stupid routine to wait for the gamecube controller to stop
|
|
||||||
// sending its response. We don't care what it is, but we
|
|
||||||
// can't start asking for status if it's still responding
|
|
||||||
int x;
|
|
||||||
for (x=0; x<64; x++) {
|
|
||||||
// make sure the line is idle for 64 iterations, should
|
|
||||||
// be plenty.
|
|
||||||
if (!N64_PIND_QUERY)
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for the gamecube controller's status. We do this
|
|
||||||
// to get the 0 point for the control stick.
|
|
||||||
unsigned char command[] = {0x01};
|
|
||||||
N64_PIND_send(pincode, command, 1);
|
|
||||||
// read in data and dump it to N64_raw_dump
|
|
||||||
N64_PIND_get(pincode);
|
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void N64Controller::N64_init_PINB(char pincode) {
|
|
||||||
unsigned char initialize = 0x00;
|
|
||||||
noInterrupts();
|
|
||||||
N64_PINB_send(pincode, &initialize, 1);
|
|
||||||
int x;
|
|
||||||
for (x=0; x<64; x++) {
|
|
||||||
if (!N64_PINB_QUERY)
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
unsigned char command[] = {0x01};
|
|
||||||
N64_PINB_send(pincode, command, 1);
|
|
||||||
N64_PINB_get(pincode);
|
|
||||||
interrupts();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This sends the given byte sequence to the controller
|
|
||||||
* length must be at least 1
|
|
||||||
* Oh, it destroys the buffer passed in as it writes it
|
|
||||||
*/
|
|
||||||
void N64Controller::N64_PIND_send(char pincode, unsigned char *buffer, char length) {
|
|
||||||
// Send these bytes
|
|
||||||
char bits;
|
|
||||||
|
|
||||||
// This routine is very carefully timed by examining the assembly output.
|
|
||||||
// Do not change any statements, it could throw the timings off
|
|
||||||
//
|
|
||||||
// We get 16 cycles per microsecond, which should be plenty, but we need to
|
|
||||||
// be conservative. Most assembly ops take 1 cycle, but a few take 2
|
|
||||||
//
|
|
||||||
// I use manually constructed for-loops out of gotos so I have more control
|
|
||||||
// over the outputted assembly. I can insert nops where it was impossible
|
|
||||||
// with a for loop
|
|
||||||
|
|
||||||
asm volatile (";Starting outer for loop");
|
|
||||||
outer_loop:
|
|
||||||
{
|
|
||||||
asm volatile (";Starting inner for loop");
|
|
||||||
bits=8;
|
|
||||||
inner_loop:
|
|
||||||
{
|
|
||||||
// Starting a bit, set the line low
|
|
||||||
asm volatile (";Setting line to low");
|
|
||||||
N64_PIND_LOW; // 1 op, 2 cycles
|
|
||||||
|
|
||||||
asm volatile (";branching");
|
|
||||||
if (*buffer >> 7) {
|
|
||||||
asm volatile (";Bit is a 1");
|
|
||||||
// 1 bit
|
|
||||||
// remain low for 1us, then go high for 3us
|
|
||||||
// nop block 1
|
|
||||||
NOP5;
|
|
||||||
|
|
||||||
asm volatile (";Setting line to high");
|
|
||||||
N64_PIND_HIGH;
|
|
||||||
|
|
||||||
// nop block 2
|
|
||||||
// we'll wait only 2us to sync up with both conditions
|
|
||||||
// at the bottom of the if statement
|
|
||||||
NOP30;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
asm volatile (";Bit is a 0");
|
|
||||||
// 0 bit
|
|
||||||
// remain low for 3us, then go high for 1us
|
|
||||||
// nop block 3
|
|
||||||
NOP30; NOP5; NOP;
|
|
||||||
|
|
||||||
asm volatile (";Setting line to high");
|
|
||||||
N64_PIND_HIGH;
|
|
||||||
|
|
||||||
// wait for 1us
|
|
||||||
asm volatile ("; end of conditional branch, need to wait 1us more before next bit");
|
|
||||||
|
|
||||||
}
|
|
||||||
// end of the if, the line is high and needs to remain
|
|
||||||
// high for exactly 16 more cycles, regardless of the previous
|
|
||||||
// branch path
|
|
||||||
|
|
||||||
asm volatile (";finishing inner loop body");
|
|
||||||
--bits;
|
|
||||||
if (bits != 0) {
|
|
||||||
// nop block 4
|
|
||||||
// this block is why a for loop was impossible
|
|
||||||
NOP5; NOP; NOP; NOP; NOP;
|
|
||||||
|
|
||||||
// rotate bits
|
|
||||||
asm volatile (";rotating out bits");
|
|
||||||
*buffer <<= 1;
|
|
||||||
|
|
||||||
goto inner_loop;
|
|
||||||
} // fall out of inner loop
|
|
||||||
}
|
|
||||||
asm volatile (";continuing outer loop");
|
|
||||||
// In this case: the inner loop exits and the outer loop iterates,
|
|
||||||
// there are /exactly/ 16 cycles taken up by the necessary operations.
|
|
||||||
// So no nops are needed here (that was lucky!)
|
|
||||||
--length;
|
|
||||||
if (length != 0) {
|
|
||||||
++buffer;
|
|
||||||
goto outer_loop;
|
|
||||||
} // fall out of outer loop
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a single stop (1) bit
|
|
||||||
// nop block 5
|
|
||||||
asm volatile ("nop\nnop\nnop\nnop\n");
|
|
||||||
N64_PIND_LOW;
|
|
||||||
// wait 1 us, 16 cycles, then raise the line
|
|
||||||
// 16-2=14
|
|
||||||
// nop block 6
|
|
||||||
NOP5; NOP5; NOP; NOP; NOP; NOP;
|
|
||||||
|
|
||||||
N64_PIND_HIGH;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void N64Controller::N64_PINB_send(char pincode, unsigned char *buffer, char length) {
|
|
||||||
char bits;
|
|
||||||
asm volatile (";Starting outer for loop");
|
|
||||||
outer_loop:
|
|
||||||
{
|
|
||||||
asm volatile (";Starting inner for loop");
|
|
||||||
bits=8;
|
|
||||||
inner_loop:
|
|
||||||
{
|
|
||||||
asm volatile (";Setting line to low");
|
|
||||||
N64_PINB_LOW;
|
|
||||||
asm volatile (";branching");
|
|
||||||
if (*buffer >> 7) {
|
|
||||||
asm volatile (";Bit is a 1");
|
|
||||||
NOP5;
|
|
||||||
asm volatile (";Setting line to high");
|
|
||||||
N64_PINB_HIGH;
|
|
||||||
NOP30;
|
|
||||||
} else {
|
|
||||||
asm volatile (";Bit is a 0");
|
|
||||||
NOP30; NOP5; NOP;
|
|
||||||
|
|
||||||
asm volatile (";Setting line to high");
|
|
||||||
N64_PINB_HIGH;
|
|
||||||
asm volatile ("; end of conditional branch, need to wait 1us more before next bit");
|
|
||||||
}
|
|
||||||
asm volatile (";finishing inner loop body");
|
|
||||||
--bits;
|
|
||||||
if (bits != 0) {
|
|
||||||
NOP5; NOP; NOP; NOP; NOP;
|
|
||||||
asm volatile (";rotating out bits");
|
|
||||||
*buffer <<= 1;
|
|
||||||
goto inner_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
asm volatile (";continuing outer loop");
|
|
||||||
--length;
|
|
||||||
if (length != 0) {
|
|
||||||
++buffer;
|
|
||||||
goto outer_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NOP; NOP; NOP; NOP;
|
|
||||||
N64_PINB_LOW;
|
|
||||||
NOP5; NOP5; NOP; NOP; NOP; NOP;
|
|
||||||
N64_PINB_HIGH;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void N64Controller::N64_PIND_get(char pincode)
|
|
||||||
{
|
|
||||||
// listen for the expected 8 bytes of data back from the controller and
|
|
||||||
// blast it out to the N64_raw_dump array, one bit per byte for extra speed.
|
|
||||||
// Afterwards, call translate_raw_data() to interpret the raw data and pack
|
|
||||||
// it into the N64_status struct.
|
|
||||||
asm volatile (";Starting to listen");
|
|
||||||
unsigned char timeout;
|
|
||||||
char bitcount = 32;
|
|
||||||
char *bitbin = N64_raw_dump;
|
|
||||||
|
|
||||||
// Again, using gotos here to make the assembly more predictable and
|
|
||||||
// optimization easier (please don't kill me)
|
|
||||||
read_loop:
|
|
||||||
timeout = 0x3f;
|
|
||||||
// wait for line to go low
|
|
||||||
while (N64_PIND_QUERY) {
|
|
||||||
if (!--timeout)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// wait approx 2us and poll the line
|
|
||||||
NOP30;
|
|
||||||
*bitbin = N64_PIND_QUERY;
|
|
||||||
++bitbin;
|
|
||||||
--bitcount;
|
|
||||||
if (bitcount == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// wait for line to go high again
|
|
||||||
// it may already be high, so this should just drop through
|
|
||||||
timeout = 0x3f;
|
|
||||||
while (!N64_PIND_QUERY) {
|
|
||||||
if (!--timeout)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
goto read_loop;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void N64Controller::N64_PINB_get(char pincode)
|
|
||||||
{
|
|
||||||
asm volatile (";Starting to listen");
|
|
||||||
unsigned char timeout;
|
|
||||||
char bitcount = 32;
|
|
||||||
char *bitbin = N64_raw_dump;
|
|
||||||
read_loop:
|
|
||||||
timeout = 0x3f;
|
|
||||||
while (N64_PINB_QUERY) {
|
|
||||||
if (!--timeout)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NOP30;
|
|
||||||
*bitbin = N64_PINB_QUERY;
|
|
||||||
++bitbin;
|
|
||||||
--bitcount;
|
|
||||||
if (bitcount == 0)
|
|
||||||
return;
|
|
||||||
timeout = 0x3f;
|
|
||||||
while (!N64_PINB_QUERY) {
|
|
||||||
if (!--timeout)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
goto read_loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void N64Controller::print_N64_status()
|
void N64Controller::print_N64_status()
|
||||||
{
|
{
|
||||||
// bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright
|
// bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright
|
||||||
@ -386,15 +103,8 @@ void N64Controller::print_N64_status()
|
|||||||
|
|
||||||
void N64Controller::update() {
|
void N64Controller::update() {
|
||||||
unsigned char command[] = {0x01};
|
unsigned char command[] = {0x01};
|
||||||
if (n64_first_register) {
|
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
N64_PIND_send(n64_pincode, command, 1);
|
interface->send(command, 1);
|
||||||
N64_PIND_get(n64_pincode);
|
interface->get();
|
||||||
interrupts();
|
interrupts();
|
||||||
} else {
|
|
||||||
noInterrupts();
|
|
||||||
N64_PINB_send(n64_pincode, command, 1);
|
|
||||||
N64_PINB_get(n64_pincode);
|
|
||||||
interrupts();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
#ifndef N64Controller_h
|
#ifndef N64Controller_h
|
||||||
#define N64Controller_h
|
#define N64Controller_h
|
||||||
|
|
||||||
|
#include "N64Interface.h"
|
||||||
|
|
||||||
#define A_IDX 0
|
#define A_IDX 0
|
||||||
#define B_IDX 1
|
#define B_IDX 1
|
||||||
#define Z_IDX 2
|
#define Z_IDX 2
|
||||||
@ -46,47 +48,34 @@ class N64Controller {
|
|||||||
void update(); // then update always and get button info
|
void update(); // then update always and get button info
|
||||||
// consider to have a delay instead of
|
// consider to have a delay instead of
|
||||||
// calling update all the time in a loop
|
// calling update all the time in a loop
|
||||||
inline bool D_up() { return (N64_raw_dump[D_UP_IDX]) > 0; };
|
inline bool D_up() { return (interface->raw_dump[D_UP_IDX]) > 0; };
|
||||||
inline bool D_down() { return (N64_raw_dump[D_DOWN_IDX]) > 0; };
|
inline bool D_down() { return (interface->raw_dump[D_DOWN_IDX]) > 0; };
|
||||||
inline bool D_left() { return (N64_raw_dump[D_LEFT_IDX]) > 0; };
|
inline bool D_left() { return (interface->raw_dump[D_LEFT_IDX]) > 0; };
|
||||||
inline bool D_right() { return (N64_raw_dump[D_RIGHT_IDX]) > 0; };
|
inline bool D_right() { return (interface->raw_dump[D_RIGHT_IDX]) > 0; };
|
||||||
inline bool Start() { return (N64_raw_dump[START_IDX]) > 0; };
|
inline bool Start() { return (interface->raw_dump[START_IDX]) > 0; };
|
||||||
inline bool A() { return (N64_raw_dump[A_IDX]) > 0; };
|
inline bool A() { return (interface->raw_dump[A_IDX]) > 0; };
|
||||||
inline bool B() { return (N64_raw_dump[B_IDX]) > 0; };
|
inline bool B() { return (interface->raw_dump[B_IDX]) > 0; };
|
||||||
inline bool Z() { return (N64_raw_dump[Z_IDX]) > 0; };
|
inline bool Z() { return (interface->raw_dump[Z_IDX]) > 0; };
|
||||||
inline bool L() { return (N64_raw_dump[L_IDX]) > 0; };
|
inline bool L() { return (interface->raw_dump[L_IDX]) > 0; };
|
||||||
inline bool R() { return (N64_raw_dump[R_IDX]) > 0; };
|
inline bool R() { return (interface->raw_dump[R_IDX]) > 0; };
|
||||||
inline bool C_up() { return (N64_raw_dump[C_UP_IDX]) > 0; };
|
inline bool C_up() { return (interface->raw_dump[C_UP_IDX]) > 0; };
|
||||||
inline bool C_down() { return (N64_raw_dump[C_DOWN_IDX]) > 0; };
|
inline bool C_down() { return (interface->raw_dump[C_DOWN_IDX]) > 0; };
|
||||||
inline bool C_left() { return (N64_raw_dump[C_LEFT_IDX]) > 0; };
|
inline bool C_left() { return (interface->raw_dump[C_LEFT_IDX]) > 0; };
|
||||||
inline bool C_right() { return (N64_raw_dump[C_RIGHT_IDX]) > 0; };
|
inline bool C_right() { return (interface->raw_dump[C_RIGHT_IDX]) > 0; };
|
||||||
inline char axis_x() { return axis(X_IDX); };
|
inline char axis_x() { return axis(X_IDX); };
|
||||||
inline char axis_y() { return axis(Y_IDX); };
|
inline char axis_y() { return axis(Y_IDX); };
|
||||||
|
|
||||||
void print_N64_status();
|
void print_N64_status();
|
||||||
private:
|
private:
|
||||||
void set_up();
|
N64Interface * interface;
|
||||||
char n64_PIN; // might also be set by constructor or begin()
|
|
||||||
char n64_pincode;
|
|
||||||
bool n64_first_register; // PIN0-7: DDRD PIN8-13: DDRB
|
|
||||||
|
|
||||||
void N64_init_PIND(char pincode);
|
|
||||||
void N64_PIND_send(char pincode, unsigned char *buffer, char length);
|
|
||||||
void N64_PIND_get(char pincode);
|
|
||||||
|
|
||||||
void N64_init_PINB(char pincode);
|
|
||||||
void N64_PINB_send(char pincode, unsigned char *buffer, char length);
|
|
||||||
void N64_PINB_get(char pincode);
|
|
||||||
|
|
||||||
inline char axis(int index) {
|
inline char axis(int index) {
|
||||||
char value = 0;
|
char value = 0;
|
||||||
for (char i=0; i<8; i++) {
|
for (char i=0; i<8; i++) {
|
||||||
value |= N64_raw_dump[index+i] ? (0x80 >> i) : 0;
|
value |= interface->raw_dump[index+i] ? (0x80 >> i) : 0;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
char N64_raw_dump[33]; // 1 received bit per byte
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
278
N64Controller/N64Interface.cpp
Normal file
278
N64Controller/N64Interface.cpp
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
#include "N64Interface.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <pins_arduino.h>
|
||||||
|
|
||||||
|
#define NOP asm volatile ("nop")
|
||||||
|
#define NOP5 asm volatile ("nop\nnop\nnop\nnop\nnop\n")
|
||||||
|
#define NOP30 asm volatile ("nop\nnop\nnop\nnop\nnop\n" \
|
||||||
|
"nop\nnop\nnop\nnop\nnop\n" \
|
||||||
|
"nop\nnop\nnop\nnop\nnop\n" \
|
||||||
|
"nop\nnop\nnop\nnop\nnop\n" \
|
||||||
|
"nop\nnop\nnop\nnop\nnop\n" \
|
||||||
|
"nop\nnop\nnop\nnop\nnop\n")
|
||||||
|
|
||||||
|
// these two macros set arduino pin 2 to input or output, which with an
|
||||||
|
// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or
|
||||||
|
// low. These operations translate to 1 op code, which takes 2 cycles
|
||||||
|
#define N64_PIND_HIGH DDRD &= ~pincode
|
||||||
|
#define N64_PIND_LOW DDRD |= pincode
|
||||||
|
#define N64_PIND_QUERY (PIND & pincode)
|
||||||
|
|
||||||
|
#define N64_PINB_HIGH DDRB &= ~pincode
|
||||||
|
#define N64_PINB_LOW DDRB |= pincode
|
||||||
|
#define N64_PINB_QUERY (PINB & pincode)
|
||||||
|
|
||||||
|
void N64Interface_PINB::init() {
|
||||||
|
unsigned char initialize = 0x00;
|
||||||
|
noInterrupts();
|
||||||
|
send(&initialize, 1);
|
||||||
|
int x;
|
||||||
|
for (x=0; x<64; x++) {
|
||||||
|
if (!N64_PINB_QUERY)
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
unsigned char command[] = {0x01};
|
||||||
|
send(command, 1);
|
||||||
|
get();
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void N64Interface_PINB::send(unsigned char * buffer, char length) {
|
||||||
|
char bits;
|
||||||
|
asm volatile (";Starting outer for loop");
|
||||||
|
outer_loop:
|
||||||
|
{
|
||||||
|
asm volatile (";Starting inner for loop");
|
||||||
|
bits=8;
|
||||||
|
inner_loop:
|
||||||
|
{
|
||||||
|
asm volatile (";Setting line to low");
|
||||||
|
N64_PINB_LOW;
|
||||||
|
asm volatile (";branching");
|
||||||
|
if (*buffer >> 7) {
|
||||||
|
asm volatile (";Bit is a 1");
|
||||||
|
NOP5;
|
||||||
|
asm volatile (";Setting line to high");
|
||||||
|
N64_PINB_HIGH;
|
||||||
|
NOP30;
|
||||||
|
} else {
|
||||||
|
asm volatile (";Bit is a 0");
|
||||||
|
NOP30; NOP5; NOP;
|
||||||
|
|
||||||
|
asm volatile (";Setting line to high");
|
||||||
|
N64_PINB_HIGH;
|
||||||
|
asm volatile ("; end of conditional branch, need to wait 1us more before next bit");
|
||||||
|
}
|
||||||
|
asm volatile (";finishing inner loop body");
|
||||||
|
--bits;
|
||||||
|
if (bits != 0) {
|
||||||
|
NOP5; NOP; NOP; NOP; NOP;
|
||||||
|
asm volatile (";rotating out bits");
|
||||||
|
*buffer <<= 1;
|
||||||
|
goto inner_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
asm volatile (";continuing outer loop");
|
||||||
|
--length;
|
||||||
|
if (length != 0) {
|
||||||
|
++buffer;
|
||||||
|
goto outer_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NOP; NOP; NOP; NOP;
|
||||||
|
N64_PINB_LOW;
|
||||||
|
NOP5; NOP5; NOP; NOP; NOP; NOP;
|
||||||
|
N64_PINB_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void N64Interface_PINB::get() {
|
||||||
|
asm volatile (";Starting to listen");
|
||||||
|
unsigned char timeout;
|
||||||
|
char bitcount = 32;
|
||||||
|
char *bitbin = raw_dump;
|
||||||
|
read_loop:
|
||||||
|
timeout = 0x3f;
|
||||||
|
while (N64_PINB_QUERY) {
|
||||||
|
if (!--timeout)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NOP30;
|
||||||
|
*bitbin = N64_PINB_QUERY;
|
||||||
|
++bitbin;
|
||||||
|
--bitcount;
|
||||||
|
if (bitcount == 0)
|
||||||
|
return;
|
||||||
|
timeout = 0x3f;
|
||||||
|
while (!N64_PINB_QUERY) {
|
||||||
|
if (!--timeout)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goto read_loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void N64Interface_PIND::init() {
|
||||||
|
// Initialize the gamecube controller by sending it a null byte.
|
||||||
|
// This is unnecessary for a standard controller, but is required for the
|
||||||
|
// Wavebird.
|
||||||
|
unsigned char initialize = 0x00;
|
||||||
|
noInterrupts();
|
||||||
|
send(&initialize, 1);
|
||||||
|
|
||||||
|
// Stupid routine to wait for the gamecube controller to stop
|
||||||
|
// sending its response. We don't care what it is, but we
|
||||||
|
// can't start asking for status if it's still responding
|
||||||
|
int x;
|
||||||
|
for (x=0; x<64; x++) {
|
||||||
|
// make sure the line is idle for 64 iterations, should
|
||||||
|
// be plenty.
|
||||||
|
if (!N64_PIND_QUERY)
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query for the gamecube controller's status. We do this
|
||||||
|
// to get the 0 point for the control stick.
|
||||||
|
unsigned char command[] = {0x01};
|
||||||
|
send(command, 1);
|
||||||
|
// read in data and dump it to N64_raw_dump
|
||||||
|
get();
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sends the given byte sequence to the controller
|
||||||
|
* length must be at least 1
|
||||||
|
* Oh, it destroys the buffer passed in as it writes it
|
||||||
|
*/
|
||||||
|
void N64Interface_PIND::send(unsigned char * buffer, char length) {
|
||||||
|
// Send these bytes
|
||||||
|
char bits;
|
||||||
|
|
||||||
|
// This routine is very carefully timed by examining the assembly output.
|
||||||
|
// Do not change any statements, it could throw the timings off
|
||||||
|
//
|
||||||
|
// We get 16 cycles per microsecond, which should be plenty, but we need to
|
||||||
|
// be conservative. Most assembly ops take 1 cycle, but a few take 2
|
||||||
|
//
|
||||||
|
// I use manually constructed for-loops out of gotos so I have more control
|
||||||
|
// over the outputted assembly. I can insert nops where it was impossible
|
||||||
|
// with a for loop
|
||||||
|
|
||||||
|
asm volatile (";Starting outer for loop");
|
||||||
|
outer_loop:
|
||||||
|
{
|
||||||
|
asm volatile (";Starting inner for loop");
|
||||||
|
bits=8;
|
||||||
|
inner_loop:
|
||||||
|
{
|
||||||
|
// Starting a bit, set the line low
|
||||||
|
asm volatile (";Setting line to low");
|
||||||
|
N64_PIND_LOW; // 1 op, 2 cycles
|
||||||
|
|
||||||
|
asm volatile (";branching");
|
||||||
|
if (*buffer >> 7) {
|
||||||
|
asm volatile (";Bit is a 1");
|
||||||
|
// 1 bit
|
||||||
|
// remain low for 1us, then go high for 3us
|
||||||
|
// nop block 1
|
||||||
|
NOP5;
|
||||||
|
|
||||||
|
asm volatile (";Setting line to high");
|
||||||
|
N64_PIND_HIGH;
|
||||||
|
|
||||||
|
// nop block 2
|
||||||
|
// we'll wait only 2us to sync up with both conditions
|
||||||
|
// at the bottom of the if statement
|
||||||
|
NOP30;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
asm volatile (";Bit is a 0");
|
||||||
|
// 0 bit
|
||||||
|
// remain low for 3us, then go high for 1us
|
||||||
|
// nop block 3
|
||||||
|
NOP30; NOP5; NOP;
|
||||||
|
|
||||||
|
asm volatile (";Setting line to high");
|
||||||
|
N64_PIND_HIGH;
|
||||||
|
|
||||||
|
// wait for 1us
|
||||||
|
asm volatile ("; end of conditional branch, need to wait 1us more before next bit");
|
||||||
|
|
||||||
|
}
|
||||||
|
// end of the if, the line is high and needs to remain
|
||||||
|
// high for exactly 16 more cycles, regardless of the previous
|
||||||
|
// branch path
|
||||||
|
|
||||||
|
asm volatile (";finishing inner loop body");
|
||||||
|
--bits;
|
||||||
|
if (bits != 0) {
|
||||||
|
// nop block 4
|
||||||
|
// this block is why a for loop was impossible
|
||||||
|
NOP5; NOP; NOP; NOP; NOP;
|
||||||
|
|
||||||
|
// rotate bits
|
||||||
|
asm volatile (";rotating out bits");
|
||||||
|
*buffer <<= 1;
|
||||||
|
|
||||||
|
goto inner_loop;
|
||||||
|
} // fall out of inner loop
|
||||||
|
}
|
||||||
|
asm volatile (";continuing outer loop");
|
||||||
|
// In this case: the inner loop exits and the outer loop iterates,
|
||||||
|
// there are /exactly/ 16 cycles taken up by the necessary operations.
|
||||||
|
// So no nops are needed here (that was lucky!)
|
||||||
|
--length;
|
||||||
|
if (length != 0) {
|
||||||
|
++buffer;
|
||||||
|
goto outer_loop;
|
||||||
|
} // fall out of outer loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a single stop (1) bit
|
||||||
|
// nop block 5
|
||||||
|
asm volatile ("nop\nnop\nnop\nnop\n");
|
||||||
|
N64_PIND_LOW;
|
||||||
|
// wait 1 us, 16 cycles, then raise the line
|
||||||
|
// 16-2=14
|
||||||
|
// nop block 6
|
||||||
|
NOP5; NOP5; NOP; NOP; NOP; NOP;
|
||||||
|
|
||||||
|
N64_PIND_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void N64Interface_PIND::get() {
|
||||||
|
// listen for the expected 8 bytes of data back from the controller and
|
||||||
|
// blast it out to the N64_raw_dump array, one bit per byte for extra speed.
|
||||||
|
// Afterwards, call translate_raw_data() to interpret the raw data and pack
|
||||||
|
// it into the N64_status struct.
|
||||||
|
asm volatile (";Starting to listen");
|
||||||
|
unsigned char timeout;
|
||||||
|
char bitcount = 32;
|
||||||
|
char *bitbin = raw_dump;
|
||||||
|
|
||||||
|
// Again, using gotos here to make the assembly more predictable and
|
||||||
|
// optimization easier (please don't kill me)
|
||||||
|
read_loop:
|
||||||
|
timeout = 0x3f;
|
||||||
|
// wait for line to go low
|
||||||
|
while (N64_PIND_QUERY) {
|
||||||
|
if (!--timeout)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// wait approx 2us and poll the line
|
||||||
|
NOP30;
|
||||||
|
*bitbin = N64_PIND_QUERY;
|
||||||
|
++bitbin;
|
||||||
|
--bitcount;
|
||||||
|
if (bitcount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// wait for line to go high again
|
||||||
|
// it may already be high, so this should just drop through
|
||||||
|
timeout = 0x3f;
|
||||||
|
while (!N64_PIND_QUERY) {
|
||||||
|
if (!--timeout)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goto read_loop;
|
||||||
|
}
|
33
N64Controller/N64Interface.h
Normal file
33
N64Controller/N64Interface.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef N64Interface_h
|
||||||
|
#define N64Interface_h
|
||||||
|
|
||||||
|
class N64Interface {
|
||||||
|
public:
|
||||||
|
virtual void init();
|
||||||
|
virtual void send(unsigned char * buffer, char length);
|
||||||
|
virtual void get();
|
||||||
|
|
||||||
|
char raw_dump[33];
|
||||||
|
|
||||||
|
protected:
|
||||||
|
N64Interface(unsigned char pincode) : pincode(pincode) {};
|
||||||
|
unsigned char pincode;
|
||||||
|
};
|
||||||
|
|
||||||
|
class N64Interface_PINB : public N64Interface {
|
||||||
|
public:
|
||||||
|
N64Interface_PINB(unsigned char pincode) : N64Interface(pincode) {};
|
||||||
|
virtual void init();
|
||||||
|
virtual void send(unsigned char * buffer, char length);
|
||||||
|
virtual void get();
|
||||||
|
};
|
||||||
|
|
||||||
|
class N64Interface_PIND : public N64Interface {
|
||||||
|
public:
|
||||||
|
N64Interface_PIND(unsigned char pincode) : N64Interface(pincode) {};
|
||||||
|
virtual void init();
|
||||||
|
virtual void send(unsigned char * buffer, char length);
|
||||||
|
virtual void get();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user