Serial Interface with a Game Cube Controller using Bidirectional Dataline

Thread Starter

ybahman

Joined Aug 7, 2010
2
Hey Guys! This is my first post so forgive me if I do anything out of the norm!


So here's my goal, I built a 4x4x4 LED cube and controlled it with a pic18f4520, that was pretty cool at first, but it isn't terribly interesting. So to spice things up a bit I decided I'd implement a 3D Snake game on it. And I still think that'd be pretty cool, but nobody wants to play any game with just a few buttons attached to a PCB. No no no, that'd be boring. So I figured why not use a controller that is widely loved (and that I had lying around) the Game Cube controller! But it just wont work! AH!


It uses a 3.43v bidirectional dataline to communicate with the gamecube. so, using a voltage regulator and a pull up resistor I pulled up the line (which is port A0) to 3.43 volts. Then I toggle it from input to output in order to simulate an open-drian output. I've tested this, and it seems to work just fine.

So the GameCube itself sends a 24 bit (+ 1 termination bit) signal to the controller as a status update, then the controller responds with a 64 bit (+ 1 termination bit) update.

Each bit takes place over 4 microseconds, and is divided into 3 parts:
1 microsecond low ( 0 volts )
2 microseconds data ( 0 or 3.43 volts )
1 microsecond high (3.43 volts)

So the low and high bits look like this:
Low: _ _ _ ¯ (4 microseconds)

High: _ ¯ ¯ ¯ (4 microseconds)

If you're interested the protocol is documented here.

I'm running a 10 MHz Crystal (with 15 pf caps) with PLL enabled, so I have 10 cycles for 1 microsecond, so the timing shouldn't be too difficult. But I just cant get the gamecube controller to respond! Here's the code I've written so far. All it does is send the 24 bit signal, and monitor to see if anything pulls A0 low, and if anything does (which it should if the controller responds!) it lights an LED. But it wont light :(

I posted my code below! If anyone has any insight or any more questions or anything I'd love to hear back! Thanks for taking the time!

Yashar

Rich (BB code):
#include <p18f4520.h>
 
 void assembly(void);
 void main(void)
 {
     unsigned int i,j,check;
     check=0;
 
     // Set all outputs to Digital
     ADCON1 = 0xFF;
     CMCON=0x07;
 
     // Initialize pins
     LATD = 0x00;
     TRISD = 0x00;
     LATA = 0x00;
     TRISA = 0x01;
 
     while(1)
     {
         assembly();
 
         LATDbits.LATD0 = ~LATDbits.LATD0; //toggle this LED for fun
 
         // Just a delay that ALSO checks if anything drives RA0 low
         // but mostly it's just a delay to see the LED toggle 
         for ( i = 0; i < 30000; i++)
         {
             for(j=0; j<5; j++) 
             {
                 if(PORTAbits.RA0 == 0)
                 {
                     check=1;
                 }
             }
         }
         if (check==1) LATDbits.LATD1=1;
     
 
 
     }
 }
 
 void assembly(void)
 {
 _asm
     //1 uS = 10 cycles
     //3 uS = 30 cycles
     //9 NOPs + instruction to set next bit should be 10 cycles
     //We want to send 0100 0000 0000 0011 0000 0011 <- The last bit is high because I want some rumble action!
     //But don't forget to TERMINATE by sending a 1 at the end of the 24 bit stream.
 
 
 //First 0100
     CALL sendZero, 0
     CALL sendOne, 0
     CALL sendZero, 0
     CALL sendZero, 0
 
 // Then 0000
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
 
 // And 0000
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
 
 // Followed by 0011
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendOne, 0
     CALL sendOne, 0
 
 // Proceded with 0000
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendZero, 0
 
 // Finally 0011
     CALL sendZero, 0
     CALL sendZero, 0
     CALL sendOne, 0
     CALL sendOne, 0
     
     
 // Terminated with 1, After termination A0 will be an input, and should be high.
     CALL sendOne, 0
 
 
 
 // Check to see if anything drives A0 low.     
 // This is just to see if we get any thing back
 
     MOVLW 0xFF         
     MOVWF 0x0B, 0
    
     loopB:    
         MOVLW 0xFF     
         MOVWF 0x0A, 0   
         
         loopA:  
             BTFSS PORTA, 0, 0    // Skip next instrucion if A0 is high
             BSF LATD, 1, 0
             DECFSZ 0x0A, 1, 0
         BRA loopA
        
         DECFSZ 0x0B, 1, 0
     BRA loopB
 
 
 
 
 GOTO fin        // This ends the ASM before Functions
 
 //============================================================================
 //========================Function Declarations===============================
 //============================================================================
 
 sendZero:
     BCF TRISA, 0, 0     // 1  - Set A0 as output (low) for 3 uSeconds
     CALL delay29, 0     // 29 = 2(call) + 25(NOP) + 2(return)
     BSF TRISA, 0, 0     // 1  - Set A0 as input (high) for 1 uSecond
     CALL delay5, 0      // 5  = 2(call) + 1(NOP) + 2(return)
     // A0 has been high for 5 cycles already. Next we will return from
     // this function (2 clock cycles) and call another one (2 clock cycles)
     // then set change A0 again (1 clock cycle). So 5 clock cycles remain.
 
 sendOne:
     BCF TRISA, 0, 0     // 1  - Set A0 as output (low) for 1 uSeconds
     CALL delay9, 0      // 9  = 2(call) + 5(NOP) + 2(return)
     BSF TRISA, 0, 0     // 1  - Set A0 as input (high) for 3 uSecond
     CALL delay25, 0     // 25 = 2(call) + 21(NOP) + 2(return)
     // A0 has been high for 25 cycles already. Next we will return from
     // this function (2 clock cycles) and call another one (2 clock cycles)
     // then set change A0 again (1 clock cycle). So 5 clock cycles remain.
 
 
 delay29:                // Including the CALL this is a 29 cycle delay
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
 delay25:
     NOP                 // 1  // 5
 
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1  // 10
 
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1  // 15
 
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1  // 20
 delay9:
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
     NOP                 // 1
 delay5:
     NOP                 // 1  // 25
 
     RETURN 0            // 2
 
 
 fin:
 _endasm
 }
 

Thread Starter

ybahman

Joined Aug 7, 2010
2
OKAY So just incase you gamecube modders were wondering and are crawling the internet for information. I found my problem and it was SUCH a rookie mistake! I forgot the RETURN after the sendOne and sendZero functions!

For some reason I was thinking that BCF and BSF completed after 1 clock cycle, but they should complete on the rising edge of the clock cycle.

Let me go through sendOne (with the return statement) as an example and let's figure out the timing correctly:

sendOne:
(1) BCF TRISA, 0, 0 // 1 - Set A0 as output (low) for 1 uSeconds
(2) CALL delay9, 0 // 9 = 2(call) + 5(NOP) + 2(return)
(3) BSF TRISA, 0, 0 // 1 - Set A0 as input (high) for 3 uSecond
(4) CALL delay25, 0 // 25 = 2(call) + 21(NOP) + 2(return)
(5) RETURN // 2 - This is always followed by a CALL sendOne or CALL sendZero
//^ Line numbers added
// A0 has been high for 28 cycles already. The next instruction will be a
// CALL sendOne or CALL sendZero which will last 2 clock cycles then A0
// will be set low on the following clock cycle. So 2 more clock cycles remain.



At (1), A0 = 0. This is executed on the RISING edge. Then 0.05 uS later the clock falls which lats for another 0.05 uS. A total of 0.1 uS.

At (2), the CALL statement takes 2 clock cycles to execute (according to the datasheet) which means 0.2 uS. Then in delay9 there are 5 NOPs for 0.5 uS and the RETURN which takes 2 clock cycles to execute (again according to datasheet) statement for another 0.2 uS. 0.2 + 0.5 + 0.2 = 0.9 uS delay total

*The clock is now low and A0 has been low for the appropriate amount of time: 1.0 uS.*

At (3), A0 = 1. So on the following RISING edge, A0 switches from 0 to 1. Then stays at 1 for the rest of the instruction. For a total of 0.1 uS.

At (4), the CALL statement takes 2 clock cycles, 0.2 uS. Then 21 NOPs for 2.1 uS. Then RETURN for 0.2 uS. For a total of 2.5 uS.

At (5), (which I just added) there is a RETURN statement (2 clock cycles), which will be followed by a CALL statement for the next bit (2 more clock cycles) before the next time A0 is set low again. That mean another 0.4 uS delay.

*The clock is now low and A0 has been high for 0.1 + 2.5 + 0.4 = 3.0 uS.*

The next rising edge will set A0 low again to start the process over again from (1). I think with the added return statement the timing will work out. the problem was in my comment when I included the BCF instruction as time towards A0 being high, when really A0 is low during that instruction!

Sorry for being so explicit, but I think when it comes to timing, it's a good practice! Once again I'll try it when I get home today, I was just so excited I had to post again! lol


Also, in the datasheet the CALL / RETURN statements have something called fastmode which has something to do with shadow registers but I have no idea what that means so I'm not going to use it now.



OKAY! It works! woot! check it out on this youtube video!

http://www.youtube.com/watch?v=z9C5a_LTAb0

There's proof! W00t! Now I gotta write the Snake game!
 
Top