Taking Control: Nintendo gamepads simplify input

If you’re looking to add human input to your project, you’ll quickly discover how much work goes into designing useful controllers: from finding suitable buttons, joysticks, and pads, to engineering and prototyping an ergonomic enclosure that’s comfortable to hold and interact with, there’s a lot that goes into good controllers. Rather than reinvent the wheel, a popular option is to use off-the-shelf input devices. Treehopper has built-in library support for the four most popular Nintendo controllers: the Wii Nunchuk, the Wii Classic Controller, plus the NES and Super NES (SNES) controllers. Let’s look at the considerations of adding these to your project.

Choosing a Controller

NES Controller

The Nintendo Entertainment System (NES) controller has an iconic modernist design that strikes a strong contrast between the bright, rounded A and B buttons, plus the gray (or white, in modern reproductions), cubist enclosure and face decals. The “start” and “select” type is almost unreadable against the gray background, and the design’s sharp lines are anything but ergonomic, but the controller is still completely usable in the hand with a bit of practice. The inputs are quite spartan, with nothing more than a D-pad and four buttons, but this is plenty for basic rovers and other simple projects.

Electrically, the NES controller functions as a simple shift register — in addition to power and ground, it has a PS pin that asynchronously programs the latches with the current button presses on a falling edge, plus a clock and data pin used to synchronously read out the data. The 4 buttons and 4-input D-pad make full use of all 8 bits.

The NES controller uses a 7-pin proprietary connector that you’re not going to find through traditional electronics distributors. There appears to be a company selling them, however, for prototypes, it’s probably easier to just cut the connector off and use something else (or directly solder the wires to your board). We would never destroy an original NES controller, but we have no issue tearing into a modern reproduction — of which we found a two-pack on Amazon (there are many other sellers on eBay and Amazon, in case these go out of stock).

We cut off the connector, and then proceeded to determine the wiring. One of our controllers had something inside rattling around, so we ended up taking the controller apart anyway. Once inside, we discovered the PCB designer had nicely labeled all connections for us — thanks! Interestingly, while these controllers appear from the outside to be simple shift registers, this particular model had a full microcontroller inside (notice the programming pads). We’re not sure which MCU this is, but it’s not unfathomable that a mass-produced die-bond MCU is cheaper to lay down than a lesser-used (and much less complicated) parallel-in shift register.

Our particular pin out turned out to be

ColorFunction
RedPower (3.3V)
WhiteGround
YellowPS (attach to your chip-select pin)
BlueCK (attach to SCK)
YellowDAT (attach to MISO)

Your wire colors may be different, so make sure to test. If you don’t want to take the controller apart, or it’s unlabeled inside, cut off the connector (leaving a bit of the cable intact), then do a continuity test against it, using a known pin-out of the connector.

Super NES Controller

The Super NES controller takes the same shift-register approach that the NES controller uses — but expands the channel count to 16.

Nintendo Wii Controllers

Nintendo Wii is a total reboot of the platform; instead of using shift registers, the controller has a microcontroller that exposes an I2C-based API for collecting digital and analog inputs, along with the accelerometer data. This also allows several different controller designs to be used — the Wii can negotiate with the attached controller to determine its type. Treehopper supports the two most popular controllers: the Wii Nunchuk and the Wii Classic Controller. There are many clones of these available on Amazon and eBay. Treehopper’s libraries are based on information published by wiibrew — this is an invaluable resource to people interested in hacking Wii hardware.

Software Considerations

All of these controllers implement IPollable, so the easiest way to use them is to construct a Poller<IPollable> of the sensor, and then just hook into the individual events you’re interested in (button presses/releases, D-pad state changes, and analog joystick movement). Poller<IPollable> implements IDisposable (or whatever is appropriate for the language) for cleaning up the polling thread, so make sure you call that explicitly when you’re done with the poller.

Multiple Controllers

Right now, the NES and SNES libraries only support a single device, as they call directly into Spi to gather data from the shift registers. In the future, we’ll go back and implement a “chainable” class these controllers will inherit from (similar to ChainableShiftRegisterOutput for output-type shift registers).

// set up a poller to fire events. Dispose of the object when you're done.
this.poller = new Poller<SuperNesController>(new SuperNesController(board.Spi, board.Pins[5])))
// NES and SNES controllers
poller.Sensor.A.OnPressed += A_OnPressed;
poller.Sensor.B.OnPressed += B_OnPressed;
poller.Sensor.Start.OnPressed += Start_OnPressed;
poller.Sensor.Select.OnPressed += Select_OnPressed;
poller.Sensor.DPadStateChanged += Sensor_DPadChanged;

// SNES only
poller.Sensor.L.OnPressed += L_OnPressed;
poller.Sensor.R.OnPressed += R_OnPressed;
poller.Sensor.X.OnPressed += X_OnPressed;
poller.Sensor.Y.OnPressed += Y_OnPressed;

...

private void Y_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("Y pressed");
}

private void X_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("X pressed");
}

private void R_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("Right trigger pressed");
}

private void L_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("Left trigger pressed");
}

private void Sensor_DPadChanged(object sender, DPadStateEventArgs e)
{
Console.WriteLine("DPad: " + e.NewValue);
}

private void A_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("A pressed");
}

private void B_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("B pressed");
}

private void Start_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("Start pressed");
}

private void Select_OnPressed(object sender, Button.ButtonPressedEventArgs e)
{
Console.WriteLine("Select pressed");
}

// set up a poller to fire events. Dispose of the object when you're done.
this.poller = new Poller<NesController>(new NesController(board.spi, board.pins[5])))
// NES and SNES controllers
poller.sensor.a.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("A pressed");
}
});

poller.sensor.b.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("B pressed");
}
});

poller.sensor.start.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("Start pressed");
}
});

poller.sensor.select.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("Select pressed");
}
});

poller.sensor.addDPadChangedEventHandler(new DPadStateEventHandler() {
@Override
public void DPadChanged(Object sender, DPad.DPadStateEventArgs e) {
System.out.println("D-Pad: " + e.newValue);
}
});

// SNES only
poller.sensor.l.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("L pressed");
}
});

poller.sensor.r.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("R pressed");
}
});

poller.sensor.x.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("X pressed");
}
});

poller.sensor.y.addOnPressedEventHandler(new Button.OnPressedEventHandler() {
@Override
public void ButtonPressed(Object sender, Button.ButtonPressedEventArgs e) {
System.out.println("Y pressed");
}
});

// set up a poller to fire events.
this.poller = Poller<NesController>(NesController(board.spi, board.pins[5])))
// NES and SNES controllers
poller.sensor.a.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "A pressed";
};

poller.sensor.b.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "B pressed";
};

poller.sensor.start.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "Start pressed";
};

poller.sensor.select.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "Select pressed";
};

poller.sensor.onDpadStateChanged += [](Dpad& sender, Dpad::DpadStateChanged e)
{
cout << "D-Pad state: " << e.newState.ToString();
};

// SNES only
poller.sensor.x.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "X pressed";
};

poller.sensor.y.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "Y pressed";
};

poller.sensor.l.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "L pressed";
};

poller.sensor.r.onPressed += [](Button& sender, Button::OnPressedEventArgs e)
{
cout << "R pressed";
};

Leave a comment