Serial Interface with a Game Cube Controller using Bidirectional Dataline

Discussion in 'Embedded Systems and Microcontrollers' started by ybahman, Aug 8, 2010.

  1. ybahman

    Thread Starter New Member

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

    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![​IMG]

    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

    Code ( (Unknown Language)):
    1.  
    2. #include <p18f4520.h>
    3.  
    4.  void assembly(void);
    5.  void main(void)
    6.  {
    7.      unsigned int i,j,check;
    8.      check=0;
    9.  
    10.      // Set all outputs to Digital
    11.      ADCON1 = 0xFF;
    12.      CMCON=0x07;
    13.  
    14.      // Initialize pins
    15.      LATD = 0x00;
    16.      TRISD = 0x00;
    17.      LATA = 0x00;
    18.      TRISA = 0x01;
    19.  
    20.      while(1)
    21.      {
    22.          assembly();
    23.  
    24.          LATDbits.LATD0 = ~LATDbits.LATD0; //toggle this LED for fun
    25.  
    26.          // Just a delay that ALSO checks if anything drives RA0 low
    27.          // but mostly it's just a delay to see the LED toggle
    28.          for ( i = 0; i < 30000; i++)
    29.          {
    30.              for(j=0; j<5; j++)
    31.              {
    32.                  if(PORTAbits.RA0 == 0)
    33.                  {
    34.                      check=1;
    35.                  }
    36.              }
    37.          }
    38.          if (check==1) LATDbits.LATD1=1;
    39.      
    40.  
    41.  
    42.      }
    43.  }
    44.  
    45.  void assembly(void)
    46.  {
    47.  _asm
    48.      //1 uS = 10 cycles
    49.      //3 uS = 30 cycles
    50.      //9 NOPs + instruction to set next bit should be 10 cycles
    51.      //We want to send 0100 0000 0000 0011 0000 0011 <- The last bit is high because I want some rumble action!
    52.      //But don't forget to TERMINATE by sending a 1 at the end of the 24 bit stream.
    53.  
    54.  
    55.  //First 0100
    56.      CALL sendZero, 0
    57.      CALL sendOne, 0
    58.      CALL sendZero, 0
    59.      CALL sendZero, 0
    60.  
    61.  // Then 0000
    62.      CALL sendZero, 0
    63.      CALL sendZero, 0
    64.      CALL sendZero, 0
    65.      CALL sendZero, 0
    66.  
    67.  // And 0000
    68.      CALL sendZero, 0
    69.      CALL sendZero, 0
    70.      CALL sendZero, 0
    71.      CALL sendZero, 0
    72.  
    73.  // Followed by 0011
    74.      CALL sendZero, 0
    75.      CALL sendZero, 0
    76.      CALL sendOne, 0
    77.      CALL sendOne, 0
    78.  
    79.  // Proceded with 0000
    80.      CALL sendZero, 0
    81.      CALL sendZero, 0
    82.      CALL sendZero, 0
    83.      CALL sendZero, 0
    84.  
    85.  // Finally 0011
    86.      CALL sendZero, 0
    87.      CALL sendZero, 0
    88.      CALL sendOne, 0
    89.      CALL sendOne, 0
    90.      
    91.      
    92.  // Terminated with 1, After termination A0 will be an input, and should be high.
    93.      CALL sendOne, 0
    94.  
    95.  
    96.  
    97.  // Check to see if anything drives A0 low.    
    98.  // This is just to see if we get any thing back
    99.  
    100.      MOVLW 0xFF        
    101.      MOVWF 0x0B, 0
    102.    
    103.      loopB:    
    104.          MOVLW 0xFF    
    105.          MOVWF 0x0A, 0  
    106.          
    107.          loopA:  
    108.              BTFSS PORTA, 0, 0    // Skip next instrucion if A0 is high
    109.              BSF LATD, 1, 0
    110.              DECFSZ 0x0A, 1, 0
    111.          BRA loopA
    112.        
    113.          DECFSZ 0x0B, 1, 0
    114.      BRA loopB
    115.  
    116.  
    117.  
    118.  
    119.  GOTO fin        // This ends the ASM before Functions
    120.  
    121.  //============================================================================
    122.  //========================Function Declarations===============================
    123.  //============================================================================
    124.  
    125.  sendZero:
    126.      BCF TRISA, 0, 0     // 1  - Set A0 as output (low) for 3 uSeconds
    127.      CALL delay29, 0     // 29 = 2(call) + 25(NOP) + 2(return)
    128.      BSF TRISA, 0, 0     // 1  - Set A0 as input (high) for 1 uSecond
    129.      CALL delay5, 0      // 5  = 2(call) + 1(NOP) + 2(return)
    130.      // A0 has been high for 5 cycles already. Next we will return from
    131.      // this function (2 clock cycles) and call another one (2 clock cycles)
    132.      // then set change A0 again (1 clock cycle). So 5 clock cycles remain.
    133.  
    134.  sendOne:
    135.      BCF TRISA, 0, 0     // 1  - Set A0 as output (low) for 1 uSeconds
    136.      CALL delay9, 0      // 9  = 2(call) + 5(NOP) + 2(return)
    137.      BSF TRISA, 0, 0     // 1  - Set A0 as input (high) for 3 uSecond
    138.      CALL delay25, 0     // 25 = 2(call) + 21(NOP) + 2(return)
    139.      // A0 has been high for 25 cycles already. Next we will return from
    140.      // this function (2 clock cycles) and call another one (2 clock cycles)
    141.      // then set change A0 again (1 clock cycle). So 5 clock cycles remain.
    142.  
    143.  
    144.  delay29:                // Including the CALL this is a 29 cycle delay
    145.      NOP                 // 1
    146.      NOP                 // 1
    147.      NOP                 // 1
    148.      NOP                 // 1
    149.  delay25:
    150.      NOP                 // 1  // 5
    151.  
    152.      NOP                 // 1
    153.      NOP                 // 1
    154.      NOP                 // 1
    155.      NOP                 // 1
    156.      NOP                 // 1  // 10
    157.  
    158.      NOP                 // 1
    159.      NOP                 // 1
    160.      NOP                 // 1
    161.      NOP                 // 1
    162.      NOP                 // 1  // 15
    163.  
    164.      NOP                 // 1
    165.      NOP                 // 1
    166.      NOP                 // 1
    167.      NOP                 // 1
    168.      NOP                 // 1  // 20
    169.  delay9:
    170.      NOP                 // 1
    171.      NOP                 // 1
    172.      NOP                 // 1
    173.      NOP                 // 1
    174.  delay5:
    175.      NOP                 // 1  // 25
    176.  
    177.      RETURN 0            // 2
    178.  
    179.  
    180.  fin:
    181.  _endasm
    182.  }
    183.  
    184.  
     
  2. ybahman

    Thread Starter New Member

    Aug 7, 2010
    2
    0
    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. [​IMG]


    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!
     
Loading...