How To Build A USB SNES Controller For The PC, iPad, And PS3 – Part 3: Code

01 Jan 2013

Now that we've gone over the theory, we know that we need a device capable of reading the current button state of the SNES controller, and can send keyboard and gamepad inputs in variety of standards. My go-to device for this sort of project typically is the Arduino. It's inexpensive, its programs are written in straightforward C/C++, and there's an enormous amount of community support around it. However, the Arduino has historically been very bad at handling USB HID output. Until its recent switch to the ARM platform, USB HID output was nigh impossible. While the Arduino Uno, Leonardo, and Micro can all now handle USB HID output, the library for it is still a bit nascent and cumbersome to use.

So for this project I've opted to use the Teensy. The Teensy costs $16, can flawlessly handle USB HID output, and to top it all off, is compatible with the Arduino IDE and most libraries. Once you've procured your Teensy, install the Arduino IDE and the Teensyduino add-on.

Next we need to decide how we're going to read the button input. Several guides suggest soldering a wire to every button pad on the controller, but the more common method is to have your microcontroller mimic how the SNES console would read the controller. The SNES controller protocol is fairly straightforward, and is outlined exhaustively here. In a nutshell:

The latch pin on the SNES fires roughly every 60hz. This locks the button state on the controller's IC. Six microseconds after the latch is set, the SNES sends 16 pulses along the clock pin. Which each clock pulse, the controller sends the state of one of the buttons. The last 4 clock pulses are unused, since the controller only has 12 buttons.

It wouldn't be difficult to recreate this behavior ourself, but as fate would have it, someone has already done it for us. Rob Duarte has a well coded Arduino library called NESPad, which also works with the SNES. Download and install it in your Arduino's library folder.

Now we can actually begin coding. The program's broken into three methods: Keyboard Input, Gamepad Input, and iCade Input. I'm going to show coding snippets of each of the three methods before we put it all together.

Keyboard Input

The Teensy has two keyboard methods: Basic and Manual. The basic method is useful for sending entire strings to a computer, where the manual mode is more useful for sending multiple keys simultaneously, or for holding multiple keys down. Here is an example of Teensyduino's basic mode:

void setup() { }
void loop() {
  Keyboard.print("Hello World ");
  delay(5000);
}

This code waits 5 seconds, then types out "Hello World". It's useful for a multitude of projects, but not our particular keyboard-gamepad mode. We'll instead be using Teensyduino's manual mode:

void setup() { }
void loop() {
  Keyboard.set_key1(KEY_A);
  delay(1000);
  Keyboard.set_key2(KEY_B);
  delay(1000);
  Keyboard.set_key3(KEY_C);
  delay(1000);
  Keyboard.send_now();
}

This code behaves a bit differently. Setting a key does not send it to the computer. This code waits three seconds, then sends A, B, and C all simultaneously to the computer. Let's look at another example:

void setup() { }
void loop() {
  Keyboard.set_key1(KEY_A);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key2(KEY_B);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key3(KEY_C);
  Keyboard.send_now();
  delay(1000);
}

set_key() remains set until it is cleared, so this bit of code would send A, then AB, then ABC, and then ABC on every subsequent loop. If we wanted to have the program send A, then B, then C all individually, there are two ways we could go about it:

void setup() { }
void loop() {
  Keyboard.set_key1(KEY_A);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key1(KEY_B);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key1(KEY_C);
  Keyboard.send_now();
  delay(1000);
}

This code just uses a single channel, and rewrites it for every subsequent key. Alternatively, we could use:

void setup() { }
void loop() {
  Keyboard.set_key1(KEY_A);
  Keyboard.set_key2(0);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key1(0);
  Keyboard.set_key2(KEY_B);
  Keyboard.set_key3(0);
  Keyboard.send_now();
  delay(1000);
  Keyboard.set_key1(0);
  Keyboard.set_key2(0);
  Keyboard.set_key3(KEY_C);
  Keyboard.send_now();
  delay(1000);
}

This snippet takes advantage of Keyboard.setkey(0) to clear the previous setkey uses before each send_now. We'll be taking advantage of it in our code. Further documentation on Teensyduino's keyboard input can be found here.

Gamepad Input

Gamepad input works much in the same fashion as keyboard input. We'll be using the gamepad manual mode excessively here.

void setup() {
  Joystick.useManualSend(true);
}
void loop() {
  Joystick.button(1, 1);
  Joystick.button(2, 0);
  Joystick.button(3, 0);
  Joystick.send_now();
  delay(1000);
  Joystick.button(1, 0);
  Joystick.button(2, 1);
  Joystick.button(3, 0);
  Joystick.send_now();
  delay(1000);
  Joystick.button(1, 0);
  Joystick.button(2, 0);
  Joystick.button(3, 1);
  Joystick.send_now();
  delay(1000);
  Joystick.button(1, 1);
  Joystick.button(2, 1);
  Joystick.button(3, 1);
  Joystick.send_now();
  delay(1000);
}

Button3 altogether. Further documentation can be found here.

iCade Input

The iCade standard is outlined exhaustively in Part 2 of this guide. To recap, when a button is pushed, it should instantaneously send a single character out, then when that button is released it should send a single character out. Due to this behavior, a boolean variable needs to be created for every button. When a change in button state is noticed, the boolean is checked to see if the button was previously being held down. If the button wasn't being held down, the initial button press character is sent. If it was being held down, the release key is sent.

bool previouslyHeld = false;
char keyPressed = 'a';
char keyReleased = 'b';
void setup() { }
void loop() {
bool isButtonBeingPressed = //Read Button Code Goes Here;
  if((isButtonBeingPressed == False) && (previouslyHeld == True)) {
    Keyboard.print(keyReleased);
    previouslyHeld = false;
  }
  if((isButtonBeingPressed == True) && (previouslyHeld == False)) {
    Keyboard.print(keyPressed);
    previouslyHeld = true;
  }
}

This code uses the Teensyduino Basic Keyboard mode. Due to the way the iCade handles buttons being held down, Manual Keyboard Mode isn't required.

Finished Code

The completed code integrates these three methods. Before the controller sends any keys out, it checks to see if Start, Select, and a face button are being held down. Start-Select-Y will put the controller into Keyboard Mode, Start-Select-X will put the controller into Gamepad Mode, Start-Select-A will put the controller into iCade mode with the default layout, and Start-Select-B will put the controller into the iCade mode with an alternate layout. I've exhaustively commented the code to make it more readable. The heavily commented code can be downloaded here, and a much more lightly commented version can be downloaded here.

//** Multiplatform SNES Controller **//
//** Explanatory, help comments are designated by matching //** and **//. API comments designated with a standard //

#include <SNESpad.h>

//** Reading The Button State From The SNES Controller **//
//** The latch pin on the SNES fires roughly every 60hz. This locks the button state on the controller's IC. Six microseconds after the latch is set, the SNES sends 16 pulses along the clock pin. Which each clock pulse, the controller sends the state of one of the buttons. The last 4 clock pulses are unused, since the controller only has 12 buttons. More information about the controller's internal working can be found here: http://db.gamefaqs.com/console/snes/file/snes_pinouts.txt **//
//** The Arduino/Teensy can recreate this behavior fairly easily. However, unless you plan on learning more about wheels, it's best not to reinvent them. Rob Duarte has created a very efficient Arduino library called NESPad, which thankfully also works on the SNES controller. We'll be using it for this program. It can be found here: http://code.google.com/p/nespad/ **/
// SNESpad(Latch Pin, Clock Pin, Data Pin);
SNESpad nintendo = SNESpad(2, 3, 4);

//** The iCade sends a separate key for presses and releases. The default iCade mappings for the controller are defined her. **//
// SNES Controller Mapping: A Y B X UP DOWN LEFT RIGHT L R SELECT START
char keyPress[12] = {'o', 'k', 'l', 'i', 'w', 'x', 'a', 'd', 'h', 'j', 'y', 'u'};
char keyRelease[12] = {'g', 'p', 'v', 'm', 'e', 'z', 'q', 'c', 'r', 'n', 't', 'f'};

//** state is used to hold the SNESPad library's representation of the current state of the SNES controller's buttons. SNESPad comes with several literals (SNES_A, SNES_B, SNES_START, SNES_UP, etc). If compared using a bit comparison, state &amp; SNES_A will resolve to true if the controller's A button is currently being pressed. **//
// Current state of SNES buttons.
int state = 0;

//** Fairly straight forward. The controller starts off in keyboard-emulation mode. Holding Start and Select, then tapping one of the four face buttons will chance the modes. Y = 0, X = 1, A = 2, B = 2 with different key layout **//
// 0 = Keyboard, 1 = Gamepad, 2 = iCade
int mode = 2;

//** Keyboard Mode Only: USB Keyboards can only send six signals simultaneously.
bool channelSet[] = {0, 0, 0, 0, 0, 0};

//** iCade Mode Only: When the controller detects a change in the buttons, it will check that button's corresponding 'previous' variable. If the 'previous' variable is false, that indicates that the button is being initially pushed. If the 'previous' variable is true, that indicates that the button was previously being held down, and has now been let go. **//
// iCade &quot;Hold&quot; variables.
bool previous[12];

void setup() {
  //** The Teensy has two HID modes: Basic and Manual. Basic sends the keypress/joypress as soon as the command is executed. Manual allows you to stack multiple commands to allow for more fine tune controls. In particular, manual mode excels at multiple simultaneous button presses, and held down buttons, so we'll be using it here. **/
  Joystick.useManualSend(true);
  //** Initialize all 12 previous values to false. **//
  for(int i = 0; i &lt; 12; i++) {
    previous[i] = false;
  }
}

void loop() {
  //** Read the current state of the buttons using SNESPad **//
  state = nintendo.buttons();

  //** if Keyboard Mode **//
  if(mode == 0) {
    //** Since we're in useManualSend(true) mode, we need clear out the keyboard's six set_key variables each time we iterate through loop() **//
    Keyboard.set_key1(0);
    channelState[0] = 0;
    Keyboard.set_key2(0);
    channelState[1] = 0;
    Keyboard.set_key3(0);
    channelState[2] = 0;
    Keyboard.set_key4(0);
    channelState[3] = 0;
    Keyboard.set_key5(0);
    channelState[4] = 0;
    Keyboard.set_key6(0);
    channelState[5] = 0;
  }
  //** if Joystick Mode **//
  else if(mode == 1) {
    //** Much like keyboard mode, we need to clear our the joystick's two axis and eight button inputs each time we iterate through loop() **//
    Joystick.X(512);
    Joystick.Y(512);
    Joystick.button(1, 0);
    Joystick.button(2, 0);
    Joystick.button(3, 0);
    Joystick.button(4, 0);
    Joystick.button(5, 0);
    Joystick.button(6, 0);
    Joystick.button(7, 0);
    Joystick.button(8, 0);
  }
  else {
    //** The ~ character is a bitwise 'not' in Arduino code. if(~state &amp; SNES_Y) checks to see if the Y button is not being held down. if(previous[0] == true) checks to see if the Y button was being held down in the previous loop() iteration. If a button is currently not being held down, AND it was being held down in the previous iteration of loop(), the 'release' iCade key should be sent. If a button is not being held down AND the button was not being held down in the last loop, nothing should be sent. **//
    if((~state &amp; SNES_A) &amp;&amp; (previous[0] == true)) {
      //** Unlike the Keyboard and Joypad mode, the iCade mode can function in the Teensy's basic HID mode, and use Keyboard.print() to instantaneously send characters to the iPad. **]]
      Keyboard.print(keyRelease[0]);
      previous[0] = false;
    }
    if((~state &amp; SNES_Y) &amp;&amp; (previous[1] == true)) {
      Keyboard.print(keyRelease[1]);
      previous[1] = false;
    }
    if((~state &amp; SNES_B) &amp;&amp; (previous[2] == true)) {
      Keyboard.print(keyRelease[2]);
      previous[2] = false;
    }
    if((~state &amp; SNES_X) &amp;&amp; (previous[3] == true)) {
      Keyboard.print(keyRelease[3]);
      previous[3] = false;
    }
    if((~state &amp; SNES_UP) &amp;&amp; (previous[4] == true)) {
      Keyboard.print(keyRelease[4]);
      previous[4] = false;
    }
    if((~state &amp; SNES_DOWN) &amp;&amp; (previous[5] == true)) {
      Keyboard.print(keyRelease[5]);
      previous[5] = false;
    }
    if((~state &amp; SNES_LEFT) &amp;&amp; (previous[6] == true)) {
      Keyboard.print(keyRelease[6]);
      previous[6] = false;
    }
    if((~state &amp; SNES_RIGHT) &amp;&amp; (previous[7] == true)) {
      Keyboard.print(keyRelease[7]);
      previous[7] = false;
    }
    if((~state &amp; SNES_L) &amp;&amp; (previous[8] == true)) {
      Keyboard.print(keyRelease[8]);
      previous[8] = false;
    }
    if((~state &amp; SNES_R) &amp;&amp; (previous[9] == true)) {
      Keyboard.print(keyRelease[9]);
      previous[9] = false;
    }
    if((~state &amp; SNES_SELECT) &amp;&amp; (previous[10] == true)) {
      Keyboard.print(keyRelease[10]);
      previous[10] = false;
    }
    if((~state &amp; SNES_START) &amp;&amp; (previous[11] == true)) {
      Keyboard.print(keyRelease[11]);
      previous[11] = false;
    }
  }

  //** Now that all of our buttons have been reset and cleared, here is where we set them based on their current state. **//

  // Cycle through button states.
  //** Is the A button being held down? **//
  if(state &amp; SNES_A) {
    //** Is the controller in keyboard mode? **//
    if(mode == 0) {
      //** Send the 'A' key to the computer. **/
      setChannel(KEY_A);
    }
    //** Is the controller in gamepad mode? **//
    else if(mode == 1) {
      //** Send the JOY_1 command to the computer. **/
      Joystick.button(1, 1);
    }
    //** Is the controller in iCade mode, and was the button previously not held down? **//
    else if(mode == 2 &amp;&amp; previous[0] == false) {
      //** Immediately send the 'o' key to the iPad. **//
      Keyboard.print(keyPress[0]);
      //** Mark that the button is currently being held. **//
      previous[0] = true;
    }
  }
  if(state &amp; SNES_Y) {
    if(mode == 0) {
      setChannel(KEY_Y);
    }
    else if(mode == 1) {
      Joystick.button(3, 1);
    }
    else if(mode == 2 &amp;&amp; previous[1] == false) {
      Keyboard.print(keyPress[1]);
      previous[1] = true;
    }
  }
  if(state &amp; SNES_B) {
    if(mode == 0) {
      setChannel(KEY_B);
    }
    else if(mode == 1) {
      Joystick.button(2, 1);
    }
    else if(mode == 2 &amp;&amp; previous[2] == false) {
      Keyboard.print(keyPress[2]);
      previous[2] = true;
    }
  }
  if(state &amp; SNES_X) {
    if(mode == 0) {
      setChannel(KEY_X);
    }
    else if(mode == 1) {
      Joystick.button(0, 1);
    }
    else if(mode == 2 &amp;&amp; previous[3] == false) {
      Keyboard.print(keyPress[3]);
      previous[3] = true;
    }
  }
  if(state &amp; SNES_UP) {
    if(mode == 0) {
      setChannel(KEY_UP);
    }
    else if(mode == 1) {
      Joystick.Y(0);
    }
    else if(mode == 2 &amp;&amp; previous[4] == false) {
      Keyboard.print(keyPress[4]);
      previous[4] = true;
    }
  }
  if(state &amp; SNES_DOWN) {
    if(mode == 0) {
      setChannel(KEY_DOWN);
    }
    else if(mode == 1) {
      Joystick.Y(1023);
    }
    else if(mode == 2 &amp;&amp; previous[5] == false) {
      Keyboard.print(keyPress[5]);
      previous[5] = true;
    }
  }
  if(state &amp; SNES_LEFT) {
    if(mode == 0) {
      setChannel(KEY_LEFT);
    }
    else if(mode == 1) {
      Joystick.X(0);
    }
    else if(mode == 2 &amp;&amp; previous[6] == false) {
      Keyboard.print(keyPress[6]);
      previous[6] = true;
    }
  }
  if(state &amp; SNES_RIGHT) {
    if(mode == 0) {
      setChannel(KEY_RIGHT);
    }
    else if(mode == 1) {
      Joystick.X(1023);
    }
    else if(mode == 2 &amp;&amp; previous[7] == false) {
      Keyboard.print(keyPress[7]);
      previous[7] = true;
    }
  }
  if(state &amp; SNES_L) {
    if(mode == 0) {
      setChannel(KEY_Q);
    }
    else if(mode == 1) {
      Joystick.button(6, 1);
    }
    else if(mode == 2 &amp;&amp; previous[8] == false) {
      Keyboard.print(keyPress[8]);
      previous[8] = true;
    }
  }
  if(state &amp; SNES_R) {
    if(mode == 0) {
      setChannel(KEY_P);
    }
    else if(mode == 1) {
      Joystick.button(7, 1);
    }
    else if(mode == 2 &amp;&amp; previous[9] == false) {
      Keyboard.print(keyPress[9]);
      previous[9] = true;
    }
  }
  if(state &amp; SNES_SELECT) {
    if(mode == 0) {
      setChannel(KEY_TAB);
    }
    else if(mode == 1) {
      Joystick.button(8, 1);
    }
    else if(mode == 2 &amp;&amp; previous[10] == false) {
      Keyboard.print(keyPress[10]);
      previous[10] = true;
    }
  }
  if(state &amp; SNES_START) {
    if(mode == 0) {
      setChannel(KEY_ENTER);
    }
    else if(mode == 1) {
      Joystick.button(9, 1);
    }
    else if(mode == 2 &amp;&amp; previous[11] == false) {
      Keyboard.print(keyPress[11]);
      previous[11] = true;
    }
  }
  //** End Key Setting **//

  //** Mode Switching Code. The controller starts off in keyboard-emulation mode. Holding Start and Select, then tapping one of the four face buttons will chance the modes. Y = 0, X = 1, A = 2, B = 2 with different key layout. **//
  if((state &amp; SNES_START) &amp;&amp; (state &amp; SNES_SELECT) &amp;&amp; (state &amp;SNES_Y)) {
    mode = 0; //Keyboard Mode
  }
  else if((state &amp; SNES_START) &amp;&amp; (state &amp; SNES_SELECT) &amp;&amp; (state &amp;SNES_X)) {
    mode = 1; //Joypad Mode
  }
  else if((state &amp; SNES_START) &amp;&amp; (state &amp; SNES_SELECT) &amp;&amp; (state &amp;SNES_A)) {
    mode = 2; //iCade Mode 1

    //** Start+Select+B uses a different iCade button layout than Start+Select+A. This chunk of code resets it to the default layout. **//
    // Restore default iControlPad style controls
    keyPress[1] = 'k';
    keyPress[3] = 'i';
    keyPress[10] = 'y';
    keyPress[11] = 'u';

    keyRelease[1] = 'p';
    keyRelease[3] = 'm';
    keyRelease[10] = 't';
    keyRelease[11] = 'f';
  }
  else if((state &amp; SNES_START) &amp;&amp; (state &amp; SNES_SELECT) &amp;&amp; (state &amp;SNES_B)) {
    mode = 2; //iCade Mode 2

    //** Certain games use iCade button layouts which are incompatible with the default. This second iCade setting lets you create custom layouts just for them. **//
    // Swap Start/Select with Y/X.
    // Certain games use the top two left buttons as the main buttons for their games.
    // This tweak makes games like Super Crate Box playable.
    keyPress[10] = 'k';
    keyPress[11] = 'i';
    keyPress[1] = 'y';
    keyPress[3] = 'u';

    keyRelease[10] = 'p';
    keyRelease[11] = 'm';
    keyRelease[1] = 't';
    keyRelease[3] = 'f';
  }
  //** If in keyboard mode, send the current button presses as keyboard presses. **//
  else if(mode == 0) {
    Keyboard.send_now();
  }
  //** If in Joypad mode, send the current button presses as joypad presses. **//
  else if(mode == 1) {
    Joystick.send_now();
  }
  //** iCade mode's button presses are sent using the USB HID basic mode, and so there is no need for a send_now() command.
}

//** USB keyboards can send six key signals at the same time. setChannel receives a byte code of a keyboard key, and attempts to assign it to an empty channel. If seven or more keys are being held down, all signals past the first six will not be sent. A full list of keyboard byte codes can be viewed here: http://www.pjrc.com/teensy/usb_keyboard.html **//
void setChannel(byte key) {
  bool buttonSet = false;
  for(int i = 0; i &lt; 6; i++) {
    if(buttonSet == false) {
      if(channelSet[i] == false) {
        if(i == 0) {
          Keyboard.set_key1(key);
          channelSet[i] = true;
          buttonSet = true;
        }
        else if(i == 1) {
          Keyboard.set_key2(key);
          channelSet[i] = true;
          buttonSet = true;
        }
        else if(i == 2) {
          Keyboard.set_key3(key);
          channelSet[i] = true;
          buttonSet = true;
        }
        else if(i == 3) {
          Keyboard.set_key4(key);
          channelSet[i] = true;
          buttonSet = true;
        }
        else if(i == 4) {
          Keyboard.set_key5(key);
          channelSet[i] = true;
          buttonSet = true;
        }
        else if(i == 5) {
          Keyboard.set_key6(key);
          channelSet[i] = true;
          buttonSet = true;
        }
      }
    }
  }
}

In Part 4 of this guide, I'll explain how to physically build the finished product.

comments powered by Disqus