API for interfacing with a game cube controller

Dependencies:   mbed

Gamecube Controller Interface

This is a library to interface the Gamecube Controller to the mbed. The goal of this project is to read input data from the controller.

Introduction

This library lets you read data from a Gamecube Controller. It allows you to use two pins to get 6+ buttons, a D-pad (up/down/left/right), two variable triggers, and two joysticks. The controller was stripped of its connector and this wiki shows the subsequent steps to wire the controller to the mbed and code needed to read data through a serial connection. Assembly was used because the timing for the controller needs to have microsecond accuracy. The shortest wait found using wait_us(...) was 1.5us.

Pinout

ColorFunction
Yellow5V power supply (rumble)
RedData: bi-directional / 3.3V
GreenGround
WhiteGround
Blue3.3V power supply

*Note this pinout is for the latest Gamecube controller /media/uploads/christopherjwang/6.jpg

Wiring

The Gamecube controller was cut to expose the wires. Seperate them and solder other wires on to them for stability and consistency.

/media/uploads/christopherjwang/2.jpg

Color (Gamecube)mbed Pin
YellowVOUT
Redp9 and p10
GreenGround
WhiteGround
BlueVU

/media/uploads/christopherjwang/bro.png

Gamecube Controller Protocol

The Gamecube controller uses 3.3V logic (bidirectional), 3.3V to power the controller, and 5V for the rumble motor.

The 5V power used by the rumble motor is always on, and the motor is controlled by a command sent to the controller.

The controller uses one bi-directional data line to communicate with the console. This is an active high 3.3V logic signal, using a 10K pull-up resistor to 3.3V to hold the line high. Communication is initiated by the console sending a 24-bit string to the controller, after which the controller responds with 64-bits of button state and joystick data.

The transfer speed is fast at around 4 microseconds per bit. .A low bit is signaled by a 3us low followed by 1us high, and a high bit is signaled by 1us low followed by 3us high.

Here is a 0 followed by a 1

/media/uploads/christopherjwang/bro2.png

The last bit of the command is the 'rumble' control. Setting this bit to one enables the rumble motor, and clearing it disables the motor.

Polling for Data

When the Gamecube or the controller sends a string of bits, it terminates it with a single (high) stop bit. Therefore, in order to send the string 00000000, the Gamecube would send 000000001.

There is a typical interval of about 6ms between successive updates. The real time update time would depend on the game being played or the video frame update rate. The sequence starts with a 24-bit command from the console:

0100 0000 0000 0011 0000 000 (0*) *rumble

After the 24-bit command, the controller responds with a string of bits that contain the state of all the buttons along with joystick position data. The sequence of the returned data is shown in the table below. The buttons are listed in transmission order, from left to right.

Byte 0000StartYXBA
Byte 11LRZD-UpD-DownD-RightD-Left
Byte 2Joystick X Value (8 bit)
Byte 3Joystick Y Value (8 bit)
Byte 4C-Stick X Value (8 bit)
Byte 5C-Stick Y Value (8 bit)
Byte 6Left Button Value (8 bit)
Byte 7Right Button Value (8 bit)

Example Code

Basic program to use with library

//
#include "mbed.h"
#include "Gamecube.h"
 
Serial pc1(USBTX, USBRX); // tx, rx
DigitalOut myled(LED1);
int main() {
    Gamecube g(p9); // gamecube controller connected to pin 9
    int device_id = g.get_device_id();
    if (device_id != NINTENDO_DEVICE_GC_WIRED) { // device id for controller is 0x0900
        pc1.printf("this device is not a nintendo gamecube wired controller!, it returned an ID of %d\n", device_id);    
    }
    while(1) {
        g.update();
        pc1.printf("A: %d \n\rB: %d \n\rX: %d \n\rY: %d \n\rL: %d \n\rR: %d \n\rZ: %d \n\rSTART: %d \n\rD_UP: %d \n\rD_LEFT: %d \n\rD_DOWN: %d \n\rD_RIGHT: %d \n\rJOYSTICK_X: %d \n\rJOYSTICK_Y: %d \n\rC_STICK_X: %d \n\rC_STICK_Y: %d \n\rLEFT_TRIGGER: %d \n\rRIGHT_TRIGGER: %d \n\r", g.A, g.B, g.X, g.Y, g.L, g.R, g.Z, g.START, g.D_UP, g.D_LEFT, g.D_DOWN, g.D_RIGHT, g.JOYSTICK_X, g.JOYSTICK_Y, g.C_STICK_X, g.C_STICK_Y, g.LEFT_TRIGGER, g.RIGHT_TRIGGER);
        wait(1);
        
        g.rumble(true); // rumble working
        myled = 1;
        wait(1);
        myled = 0;
        g.rumble(false);
        wait(.2);
    }
}

Functions

Functions

    // constructor
    Gamecube(PinName _data_line); 

    // gets the device id
    int get_device_id();

    // call this to update public variables in header file
    void update(void);

Library

Import programgamecube_controller

API for interfacing with a game cube controller

Demo

Committer:
christopherjwang
Date:
Sun Dec 06 19:16:05 2015 +0000
Revision:
1:a93f71ee6778
Parent:
0:7434770d9fc9
MVP, hard coded at p9 and p10

Who changed what in which revision?

UserRevisionLine numberNew contents of line
christopherjwang 0:7434770d9fc9 1 #include "Gamecube.h"
christopherjwang 0:7434770d9fc9 2 extern "C" void gc_asm_write_read(uint32_t *buff, uint8_t len, uint32_t *read_buff, uint8_t read_buff_len);
christopherjwang 0:7434770d9fc9 3
christopherjwang 0:7434770d9fc9 4 #ifdef DEBUG
christopherjwang 0:7434770d9fc9 5 Serial pc(USBTX, USBRX); // tx, rx
christopherjwang 0:7434770d9fc9 6 #endif
christopherjwang 0:7434770d9fc9 7 DigitalIn my_in(p10);
christopherjwang 0:7434770d9fc9 8 DigitalOut p(p19);
christopherjwang 0:7434770d9fc9 9
christopherjwang 0:7434770d9fc9 10 void reverse_array(uint32_t *arr, uint8_t count) {
christopherjwang 0:7434770d9fc9 11 int temp, i;
christopherjwang 0:7434770d9fc9 12 for (i = 0; i < count/2; ++i) {
christopherjwang 0:7434770d9fc9 13 temp = arr[count-i-1];
christopherjwang 0:7434770d9fc9 14 arr[count-i-1] = arr[i];
christopherjwang 0:7434770d9fc9 15 arr[i] = temp;
christopherjwang 0:7434770d9fc9 16 }
christopherjwang 0:7434770d9fc9 17 }
christopherjwang 0:7434770d9fc9 18
christopherjwang 0:7434770d9fc9 19 //TODO: translate pin name to assembly layer, it is currently hardcoded at p9 and p10!!!
christopherjwang 0:7434770d9fc9 20 Gamecube::Gamecube(PinName _data_line)
christopherjwang 0:7434770d9fc9 21 :data_line(_data_line)
christopherjwang 0:7434770d9fc9 22 {
christopherjwang 0:7434770d9fc9 23
christopherjwang 0:7434770d9fc9 24 }
christopherjwang 0:7434770d9fc9 25
christopherjwang 0:7434770d9fc9 26 int Gamecube::get_device_id() {
christopherjwang 0:7434770d9fc9 27 uint32_t start_sequence[8] = {0, 0, 0, 0, 0, 0, 0, 0};
christopherjwang 0:7434770d9fc9 28 int num_bits = 2 * 8;
christopherjwang 0:7434770d9fc9 29 uint32_t data[num_bits];
christopherjwang 0:7434770d9fc9 30 int ret_val;
christopherjwang 0:7434770d9fc9 31
christopherjwang 0:7434770d9fc9 32 gc_write_read(start_sequence, 8, data, num_bits);
christopherjwang 0:7434770d9fc9 33
christopherjwang 0:7434770d9fc9 34 for (int i=0; i < num_bits; i++) {
christopherjwang 0:7434770d9fc9 35 ret_val <<= 1;
christopherjwang 0:7434770d9fc9 36 ret_val |= data[i];
christopherjwang 0:7434770d9fc9 37 }
christopherjwang 0:7434770d9fc9 38
christopherjwang 0:7434770d9fc9 39 #ifdef DEBUG
christopherjwang 0:7434770d9fc9 40 for (int i=0; i < num_bits; i++) {
christopherjwang 0:7434770d9fc9 41 pc.printf("%d", data[i]!=0);
christopherjwang 0:7434770d9fc9 42 if (i>0 && (i+1)%8 == 0) {
christopherjwang 0:7434770d9fc9 43 pc.printf(" ");
christopherjwang 0:7434770d9fc9 44 }
christopherjwang 0:7434770d9fc9 45 }
christopherjwang 0:7434770d9fc9 46 pc.printf("\n\r");
christopherjwang 0:7434770d9fc9 47 #endif
christopherjwang 0:7434770d9fc9 48
christopherjwang 0:7434770d9fc9 49 return ret_val;
christopherjwang 0:7434770d9fc9 50 }
christopherjwang 0:7434770d9fc9 51
christopherjwang 0:7434770d9fc9 52 void Gamecube::rumble(bool r) {
christopherjwang 0:7434770d9fc9 53 _rumble = r;
christopherjwang 0:7434770d9fc9 54 uint32_t status_sequence[] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, _rumble};
christopherjwang 0:7434770d9fc9 55
christopherjwang 0:7434770d9fc9 56 // passing in a variable for data because the assembly can't waste the time between writing and reading at the current moment
christopherjwang 0:7434770d9fc9 57 //TODO: implement assembly such that an empty data array doesn't have to be passed in
christopherjwang 0:7434770d9fc9 58 uint32_t data[1];
christopherjwang 0:7434770d9fc9 59 gc_write_read(status_sequence, 24, data, 0);
christopherjwang 0:7434770d9fc9 60 }
christopherjwang 0:7434770d9fc9 61
christopherjwang 0:7434770d9fc9 62 void Gamecube::update(void) {
christopherjwang 0:7434770d9fc9 63 int num_bits = 8 * 8;
christopherjwang 0:7434770d9fc9 64 uint32_t data[num_bits];
christopherjwang 0:7434770d9fc9 65 uint32_t status_sequence[] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, _rumble};
christopherjwang 0:7434770d9fc9 66
christopherjwang 0:7434770d9fc9 67 gc_write_read(status_sequence, 24, data, num_bits);
christopherjwang 0:7434770d9fc9 68
christopherjwang 0:7434770d9fc9 69 // data[0] data[1] data[2] are not used
christopherjwang 0:7434770d9fc9 70 START = data[3] == 1;
christopherjwang 0:7434770d9fc9 71 Y = data[4] == 1;
christopherjwang 0:7434770d9fc9 72 X = data[5] == 1;
christopherjwang 0:7434770d9fc9 73 B = data[6] == 1;
christopherjwang 0:7434770d9fc9 74 A = data[7] == 1;
christopherjwang 0:7434770d9fc9 75
christopherjwang 0:7434770d9fc9 76 //data[8] is always 1
christopherjwang 0:7434770d9fc9 77 L = data[9] == 1;
christopherjwang 0:7434770d9fc9 78 R = data[10] == 1;
christopherjwang 0:7434770d9fc9 79 Z = data[11] == 1;
christopherjwang 0:7434770d9fc9 80 D_UP = data[12] == 1;
christopherjwang 0:7434770d9fc9 81 D_DOWN = data[13] == 1;
christopherjwang 0:7434770d9fc9 82 D_RIGHT = data[14] == 1;
christopherjwang 0:7434770d9fc9 83 D_LEFT = data[15] == 1;
christopherjwang 0:7434770d9fc9 84
christopherjwang 0:7434770d9fc9 85 JOYSTICK_X = 0;
christopherjwang 0:7434770d9fc9 86 JOYSTICK_Y = 0;
christopherjwang 0:7434770d9fc9 87 C_STICK_X = 0;
christopherjwang 0:7434770d9fc9 88 C_STICK_Y = 0;
christopherjwang 0:7434770d9fc9 89 LEFT_TRIGGER = 0;
christopherjwang 0:7434770d9fc9 90 RIGHT_TRIGGER = 0;
christopherjwang 0:7434770d9fc9 91
christopherjwang 0:7434770d9fc9 92 for (int i=0; i < 8; i++) {
christopherjwang 0:7434770d9fc9 93 JOYSTICK_X <<= 1;
christopherjwang 0:7434770d9fc9 94 JOYSTICK_X |= data[i+16];
christopherjwang 0:7434770d9fc9 95 JOYSTICK_Y <<= 1;
christopherjwang 0:7434770d9fc9 96 JOYSTICK_Y |= data[i+24];
christopherjwang 0:7434770d9fc9 97
christopherjwang 0:7434770d9fc9 98 C_STICK_X <<= 1;
christopherjwang 0:7434770d9fc9 99 C_STICK_X |= data[i+32];
christopherjwang 0:7434770d9fc9 100 C_STICK_Y <<= 1;
christopherjwang 0:7434770d9fc9 101 C_STICK_Y |= data[i+40];
christopherjwang 0:7434770d9fc9 102
christopherjwang 0:7434770d9fc9 103 LEFT_TRIGGER <<= 1;
christopherjwang 0:7434770d9fc9 104 LEFT_TRIGGER |= data[i+48];
christopherjwang 0:7434770d9fc9 105 RIGHT_TRIGGER <<= 1;
christopherjwang 0:7434770d9fc9 106 RIGHT_TRIGGER |= data[i+56];
christopherjwang 0:7434770d9fc9 107 }
christopherjwang 0:7434770d9fc9 108
christopherjwang 0:7434770d9fc9 109 #ifdef DEBUG
christopherjwang 0:7434770d9fc9 110 for (int i=0; i < num_bits; i++) {
christopherjwang 0:7434770d9fc9 111 pc.printf("%d", data[i]!=0);
christopherjwang 0:7434770d9fc9 112 if (i>0 && (i+1)%8 == 0) {
christopherjwang 0:7434770d9fc9 113 pc.printf(" ");
christopherjwang 0:7434770d9fc9 114 }
christopherjwang 0:7434770d9fc9 115 }
christopherjwang 0:7434770d9fc9 116 pc.printf("\n\r");
christopherjwang 0:7434770d9fc9 117 #endif
christopherjwang 0:7434770d9fc9 118 }
christopherjwang 0:7434770d9fc9 119
christopherjwang 0:7434770d9fc9 120 void Gamecube::gc_write_read(uint32_t* wbuff, uint8_t wbuff_len, uint32_t *rbuff, uint8_t rbuff_len) {
christopherjwang 0:7434770d9fc9 121 __disable_irq();
christopherjwang 0:7434770d9fc9 122 gc_asm_write_read(wbuff, wbuff_len, rbuff, rbuff_len);
christopherjwang 0:7434770d9fc9 123 __enable_irq();
christopherjwang 0:7434770d9fc9 124 }