Skip to content

Gamecube

NicoHood edited this page May 27, 2022 · 28 revisions

gamecube

Features

  • IDE 1.6 compatible
  • 16MHz compatible (8/20 possible soon)
  • Host mode
  • Controller status readable
  • Controller report readable
  • Controller rumble usable
  • Device mode (todo)

Hardware

The Gamecube controller uses 3.3V logic (bidirectional), 3.3V power, 5V rumble power. Make sure to use a logic level converter (it's 1€) to not burn your controller. This is needed compared to Brownans version, Using the In/Output method will cause weird delay in the sending and the pulse is 2cycles off sometimes. This is needed for more accurate controller timings since the in/out version is 2 cycles off.

A 1k resistor on the 3.3V side is needed to pull up the line. See schematic below. The rumble draws about 30-40mA from what I've measured. Currently the library only works for 16MHz, but 20MHz could be possible and 8MHz with some tricks.

You better get an extension cable and cut it half. I used a Lioncast cable and the colors were (can be different to yours! On another cable I used brown and white were switched! Also see this table for other Controllers):

1 Yellow: 5V
2 White:  Data
3 Red:    Gnd
4 Brown:  Gnd
5 Green:  NC
6 Blue:   3.3V
7 Black:  Gnd

cable

Cut the extension cable and solder some jumper wires to connect them to a breadboard. Make sure they don't connect with each other like in the picture. You can use shrink tube around each wire.

cable

Set up your breadboard like this with a logic level converter. It is important that you use a logic level converter or it might damage you controller. Also make sure to choose a TX line of the logic converter, otherwise it won't work!

The data pin configuration on the picture below does not match the example sketches, dont't get confused of that. Breadboard

Software

Checkout the examples on how to access and initialize the controller. There is also an example for the HID Project to create a simple Gamecube to USB adapter like this. Make sure to select the correct USB-Core and if Windows does not recognizes the controller have a look here:

HID

Be aware that every reading turns off interrupts. A logic bit pulse is 4uS long.

4 uS * 8 * (1 + 3) = 128 microseconds to read the status.
4 uS * 8 * (3 + 8) = 352 microseconds to read the data.
bool begin(const uint8_t pin, Gamecube_Status_t &status)

Initializes the controller and updates the passed in status report. The status report tells you the controller type and the rumble state. Normally you don't have to do this, but for a Wavebird Controller it seems to be necessary. Returns true if initialization was successful, otherwise false.

bool end(const uint8_t pin)

Sends a rumble off signal to the controller and discards any incoming data. Mainly does the same as read, but discards the data. Returns true if operation was successful, otherwise false.

bool read(const uint8_t pin, Gamecube_Data_t &report, const bool rumble = false)

Reads in the new controller state to the passed in report. Rumble will be turned on/off by the passed in bool. If you don't pass the rumble boot it, it will by default not rumble. Returns true if reading was successful, otherwise false. If it fails, it still might have updated some bytes of the report.

dpad directions
#define NINTENDO_GAMECUBE_DPAD_CENTERED 0
#define NINTENDO_GAMECUBE_DPAD_UP (1 << 3)
#define NINTENDO_GAMECUBE_DPAD_UP_RIGHT (NINTENDO_GAMECUBE_DPAD_UP | NINTENDO_GAMECUBE_DPAD_RIGHT)
#define NINTENDO_GAMECUBE_DPAD_RIGHT (1 << 1)
#define NINTENDO_GAMECUBE_DPAD_DOWN_RIGHT (NINTENDO_GAMECUBE_DPAD_DOWN | NINTENDO_GAMECUBE_DPAD_RIGHT)
#define NINTENDO_GAMECUBE_DPAD_DOWN (1 << 2)
#define NINTENDO_GAMECUBE_DPAD_DOWN_LEFT (NINTENDO_GAMECUBE_DPAD_DOWN | NINTENDO_GAMECUBE_DPAD_LEFT)
#define NINTENDO_GAMECUBE_DPAD_LEFT (1 << 0)
#define NINTENDO_GAMECUBE_DPAD_UP_LEFT (NINTENDO_GAMECUBE_DPAD_UP | NINTENDO_GAMECUBE_DPAD_LEFT)
Gamecube_Data_t
typedef union{
	// 8 bytes of datareport that we get from the controller
	uint8_t whole8[];
	uint16_t whole16[];
	uint32_t whole32[];

	struct{
		uint8_t buttons0;
		union{
			uint8_t buttons1;
			uint8_t dpad : 4;
		};
	};

	struct {
		// first data byte (bitfields are sorted in LSB order)
		uint8_t a : 1;
		uint8_t b : 1;
		uint8_t x : 1;
		uint8_t y : 1;
		uint8_t start : 1;
		uint8_t high0 : 1;
		uint8_t errlatch : 1;
		uint8_t errstat : 1;

		// second data byte
		uint8_t dleft : 1;
		uint8_t dright : 1;
		uint8_t ddown : 1;
		uint8_t dup : 1;
		uint8_t z : 1;
		uint8_t r : 1;
		uint8_t l : 1;
		uint8_t high1 : 1;

		// 3rd-8th data byte
		uint8_t xAxis;
		uint8_t yAxis;
		uint8_t cxAxis;
		uint8_t cyAxis;
		uint8_t left;
		uint8_t right;
	};
} Gamecube_Data_t;
Gamecube_Status_t
typedef union{
	// 3 bytes of statusreport that we get from the controller
	uint8_t whole8[];
	uint16_t whole16[];
	struct {
		// device information
		uint16_t device;

		// controller status (only rumble is known)
		uint8_t status0 : 3;
		uint8_t rumble : 1;
		uint8_t status1 : 4;
	};
} Gamecube_Status_t;

Development

Here I just refer to other documentations, its explained pretty good. Logical pulses are 3uS+1uS (0) or 1uS+3uS (1) in 8bit packs + one stop bit. The Arduino sends the controller a command and the controller responds to that. This all happens on the same data line bidirectional.

pulse

I started with Brownans version and improved it over the time. Now the library is written in assembler to ensure the most accurate timings and compiler independence.

While developing I used a logic analyser which is very important. I also had to look at the assembly output of the previous C code. You have to look at the AVR Instruction Set to count the cycles and understand what each command does.

Controller init (0x00)

init

Controller read (0x40, 0x03, 0x00 | rumble)

rumble

How to manually get assembler output you may use this command. Or you can also use the provided .bat file. You have to edit the Arduino IDE path in the .bat file and start the .bat as administrator.

avr-objdump -S /tmp/buildf8dd8eac4ada9a26435d6b023098223f.tmp/isrtest.ino.elf > dump.txt

Known Bugs

Ports next to the input can crosstalk maybe. This should be 0.2uS or so, not really important. But this is a general hardware bug of the AVR uCs. Just want to note that. This happens if you do not pull up/down the other pin.

crosstalk

Links