I Built a Board That Turns an SFC (SNES) Controller into a USB Gamepad

Editor’s note: This is an English translation of an article originally published in Japanese in January 2022 (read the original — Japanese article). For the latest information on the product covered here, see its product page. Some details — the shops, software versions, and games shown — reflect that time.

This time I made a board that turns a Super Famicom (SFC) controller — the SNES outside Japan — into a USB gamepad.

In this article, I’ll walk through the build process of the USB-gamepad conversion board for the SFC controller, step by step.
I’ve published the design data on github, so please give building your own a try too.

TOC

A Quick Note

The board featured in this article has been turned into a product, and it’s sold at the shops below.

If you’re interested, please take a look.

Now, on to the main story.

Here’s the Spec I’m Aiming For

I’m making a USB gamepad board that fits perfectly inside the SFC controller’s housing.
The SFC controller doesn’t have many buttons, so to make it work with modern games too, I’ll let you change the button assignment by holding the Start or Select button.

Drawing the Schematic

Here’s what the overall schematic looks like.

Schematic of the USB gamepad conversion board for the SFC controller

Basically, the circuit connects the button pads directly to the I/O ports of the PIC16F1459, which is the main microcontroller.
On the PIC16F1459, some I/O ports seem to have an internal pull-up resistor and some don’t, so just in case I added a land for a pull-up resistor on every button pad.

The Main Microcontroller: PIC16F1459

For the main microcontroller, I’m using the PIC microcontroller PIC16F1459.
This PIC is what’s commonly called a USB microcontroller — it can act as a USB device.

Another nice thing is that it has a lot of I/O ports.
It can cover all of the SFC controller’s buttons without building a key matrix or similar circuitry, which keeps the circuit simple.

Drawing the PCB Artwork

This is the heart of the project.
I measured the actual dimensions of the SFC controller’s housing and board, and designed the artwork so it fits snugly into the housing with the button positions lined up.
That said, getting a board to fit the SFC housing perfectly on the first try is hard, so I went through a cycle of manufacturing a board, fine-tuning it, making it again, and so on.

And here’s the artwork I ended up with.

USB gamepad conversion board for the SFC controller — front

USB gamepad conversion board for the SFC controller — back

The little piece sticking out at the bottom-left is the board for the L and R buttons.

Getting the Boards Made

I ordered the board manufacturing from Allpcb.
I mostly went with the default options, but for the surface finish you need to select gold plating.

This is to keep the button pads from oxidizing.
With a solder-leveler finish or bare exposed copper, the button pads would oxidize and stop responding, so gold plating prevents that oxidation.

And here are the boards that arrived.

The SFC controller USB gamepad board as delivered — front

The SFC controller USB gamepad board as delivered — back

The gold plating makes for a shiny, good-looking board.
I made the logo by leaving the solder resist open so the plating shows through, and it turned out pretty cool.

The gold-plated logo

Mounting the Components

Let’s get the components mounted right away.
And here’s the board after assembly.

Here’s what it looks like after assembly

If I were distributing these I’d build them more neatly, but this one’s just for me, so this is good enough.

Writing the Program

The full source code for the USB gamepad board is published on GitHub, so in this article I’ll just cover a few key points.

This program is built on top of the Microchip Libraries for Applications (MLA) provided by Microchip.

The MLA includes sample project files for each PIC microcontroller.
For example, the USB Joystick sample project for the PIC16F1459 I’m using should be in the following path:

mla\v2018_11_26\apps\usb\device\hid_joystick\firmware\low_pin_count_usb_development_kit_pic16f1459.x

Configuring the PIC’s Ports and Initializing the Timer

The PIC’s port configuration and timer initialization are done near the start of main() in main.c.

    /* set all ports input*/
TRISA = 0x30;
TRISB = 0xf0;
TRISC = 0xff;
/* enabling internal pull up*/
OPTION_REGbits.nWPUEN = 0;
WPUA = 0x30;
WPUB = 0xf0;
/* all ports used as degital ports.*/
ANSELA = 0x00;
ANSELB = 0x00;
ANSELC = 0x00;
/* initializing timer0 and interruption*/
OPTION_REGbits.PS = 0b111;        // clock divided by 256
OPTION_REGbits.PSA = 0;         // enabling prescaler
OPTION_REGbits.TMR0SE = 0;      // edge select (rise)
OPTION_REGbits.TMR0CS = 0;      // clock source select (internal)
OPTION_REGbits.INTEDG = 1;      // interrupt edge select (rise)
//    INTCONbits.TMR0IF = 0;          // reset timer0 interrupt flag
//    INTCONbits.TMR0IE = 1;          // enabling peripheral interrupts
INTCONbits.GIE = 1;             // enabling interrupts
// setting initial value of timer 0.
// 1 clock = 256/16MHz = 16us 
// the timer interrupt happens every 16us x 0xff(255) = 4080us .
// 
// set the timer0 value so that timer interruption happens every 4000us.
// 80us / 16us = 5 clocks
TMR0bits.TMR0 = (uint8_t)5;

It’s all just as the comments say.
I set all the I/O ports as inputs, enable the internal pull-ups on every port that has one, and since the ports default to analog, I switch them all to digital.
Next, I set the parameters for Timer0.

The Data Type Used for USB Communication

The data type for the packet sent over USB is declared in app_device_joystick.h like this:

typedef union _INTPUT_CONTROLS_TYPEDEF
{
struct
{
struct
{
uint8_t y:1;
uint8_t b:1;
uint8_t a:1;
uint8_t x:1;
uint8_t L1:1;
uint8_t R1:1;
uint8_t L2:1;
uint8_t R2:1;//            
uint8_t select:1;
uint8_t start:1;
uint8_t left_stick:1;
uint8_t right_stick:1;
uint8_t home:1;
uint8_t :3;    //filler            
} buttons;
struct
{
uint8_t hat_switch:4;
uint8_t :4;//filler
} hat_switch;
struct
{
uint8_t X;
uint8_t Y;
uint8_t Z;
uint8_t Rz;
} analog_stick;
} members;
uint8_t val[7];
} INPUT_CONTROLS;

By storing values in these variables according to which buttons are pressed, you can drive the USB gamepad.
For example, when the “Y” button is pressed, I set the variable y to 1 and send it to the PC over USB.
That’s how the PC knows the “Y” button was pressed on the USB gamepad.

The Button-Handling Code

The button-handling code is written in my_app_device_gamepad.c.
This code handles:

  • Monitoring the button states
  • Handling Start/Select long-presses and managing the state

—that’s what this section does.

For monitoring button states, I just prepare a bit of simple code like the following for each button.

gamepad_input->members.buttons.select = BUTTON_IsPressed(BUTTON_SELECT);

For handling the Start/Select long-press, I use Timer0, which I initialized earlier on the PIC.
Below is the function for handling the Start long-press.

void ChangeSWMode_Button_Start(void){
uint16_t cnt_timer =0;
if (BUTTON_IsPressed(BUTTON_START)){
INTCONbits.TMR0IF = 0;          // reset timer0 interrupt flag
TMR0bits.TMR0 = (uint8_t)5;
cnt_timer = 0;
while(BUTTON_IsPressed(BUTTON_START)){
if(INTCONbits.TMR0IF){          // INTCONbits.TMR0IF happens every 4ms
cnt_timer++;
if(cnt_timer >=500){        // 2s
flags.sw_flag = ~(flags.sw_flag);
cnt_timer =0;
while(BUTTON_IsPressed(BUTTON_START));
}
INTCONbits.TMR0IF = 0;
TMR0bits.TMR0 = (uint8_t)5;
}
}
}
return;
}

USB HID report descriptors

There isn’t much material on USB HID report descriptors beyond the official documentation, and the spec itself is hard to follow, so this was quite a pain.
The relevant part is the following section in usb_descriptors.c.

const struct{uint8_t report[HID_RPT01_SIZE];}hid_rpt01={{
0x05,0x01,        //USAGE_PAGE (Generic Desktop)
0x09,0x05,        //USAGE (Game Pad)
0xA1,0x01,        //COLLECTION (Application)
0x15,0x00,        //  LOGICAL_MINIMUM(0)
0x25,0x01,        //  LOGICAL_MAXIMUM(1)
0x35,0x00,        //  PHYSICAL_MINIMUM(0)
0x45,0x01,        //  PHYSICAL_MAXIMUM(1)
0x75,0x01,        //  REPORT_SIZE(1)
0x95,0x0D,        //  REPORT_COUNT(13)
0x05,0x09,        //  USAGE_PAGE(Button)
0x19,0x01,        //  USAGE_MINIMUM(Button 1)
0x29,0x0D,        //  USAGE_MAXIMUM(Button 13)
0x81,0x02,        //  INPUT(Data,Var,Abs)
0x95,0x03,        //  REPORT_COUNT(3)
0x81,0x01,        //  INPUT(Cnst,Ary,Abs)
0x05,0x01,        //  USAGE_PAGE(Generic Desktop)
0x25,0x07,        //  LOGICAL_MAXIMUM(7)
0x46,0x3B,0x01,   //  PHYSICAL_MAXIMUM(315)
0x75,0x04,        //  REPORT_SIZE(4)
0x95,0x01,        //  REPORT_COUNT(1)
0x65,0x14,        //  UNIT(Eng Rot:Angular Pos)
0x09,0x39,        //  USAGE(Hat Switch)
0x81,0x42,        //  INPUT(Data,Var,Abs,Null)
0x65,0x00,        //  UNIT(None)
0x95,0x01,        //  REPORT_COUNT(1)
0x81,0x01,        //  INPUT(Cnst,Ary,Abs)
0x26,0xFF,0x00,   //  LOGICAL_MAXIMUM(255)
0x46,0xFF,0x00,   //  PHYSICAL_MAXIMUM(255)
0x09,0x30,        //  USAGE(X)
0x09,0x31,        //  USAGE(Y)
0x09,0x32,        //  USAGE(Z)
0x09,0x35,        //  USAGE(Rz)          
0x75,0x08,        //  REPORT_SIZE(8)
0x95,0x04,        //  REPORT_COUNT(4)
0x81,0x02,        //  INPUT(Data,Var,Abs)
0xC0              //END_COLLECTION
}
};

If there’s interest, I’d like to cover how to write this in a separate article.

About the Program’s License

Most of the MLA source code provided by Microchip is licensed under Apache License 2.0.
I’ve decided to release the source code I added under Apache License 2.0 as well, so feel free to use it however you like, whether for personal or commercial use.
However, the USB Product ID (PID) is provided under a sublicense agreement with Microchip, so it cannot be used commercially.
As a file, my_usb_pid.h is the one that’s off-limits for commercial use.

Modifying the SFC Controller

Once you flash the finished program onto the board with a PICkit 3, the board is complete.

So let’s go ahead and use the finished board to turn an SFC controller into a USB gamepad.

Take this,

do this,

then this,

and… there we go.

Put the cover back on, and it’s done.

SFC controller, USB gamepad version

Let’s fire it up.

Trying It Out

Testing on Windows

First, let’s check that it works.
When I plugged it into the PC, “SFC Gamepad” showed up properly in the Control Panel, recognized.

“SFC Gamepad” appeared in “Devices and Printers”

Now, right-click the icon above → “Game controller settings” → “Properties” to bring up the gamepad test screen.
And here’s what it looks like when you move it.

Function check (testing the Select long-press feature)

Function check (testing the Start long-press feature)

It’s working solidly.
I was pretty happy when it ran for the first time.

Testing on Raspberry Pi

I also tested it on a Raspberry Pi.

To use a gamepad on the Raspberry Pi, you use the “joystick” package.
And for testing, “jstest-gtk” is easy to work with.
First, run the following command from the terminal to install these packages.

sudo apt install joystick jstest-gtk

Next, run the following command from the terminal to launch jstest-gtk.

jstest-gtk

From there, it’s the same as on Windows.

Here are the OSes I ended up testing on:

  • Raspberry PI 3
    • Raspberry Pi OS
    • Recalbox for RASPBERRY PI 3
    • RetroPie 4.7.1 for RASPBERRY PI 2/3
  • Raspberry PI 4
    • Raspberry Pi OS
    • Recalbox for RASPBERRY PI 4/400
    • RetroPie 4.7.1 for RASPBERRY PI 4/400

I haven’t checked others, but since it doesn’t use any special drivers and runs on the standard driver, it’ll probably work on them too.

Playing Games on Steam

I tried playing some Steam games with the USB-gamepad-converted SFC controller.

First up, a Bomberman battle-royale game I played a lot on the Super Famicom as a kid: “Super Bomberman R Online”.

Super Bomberman R Online

Getting to play the latest Bomberman with an SFC controller — I’m honestly moved…

“DELTARUNE”, the new game from Toby Fox, the creator of one of my favorite games, “UNDERTALE”.

DELTARUNE

Even though it’s a recent game, playing it with an SFC controller feels completely natural.

The modern 3D party game “Fall Guys”.

Fall Guys: Ultimate Knockout

I figured this one would be a stretch, but it played just fine!

“Cuphead”, with its 1930s-cartoon look.

Cuphead

It suits the SFC controller incredibly well.

Trying to Play on EPIC Games…?

EPIC Games seems to support only a limited set of controllers, so I couldn’t play…
I really wanted to play FORTNITE, too…

<Follow-up>
We’ve released Xinput-compatible firmware for this board, so you can flash it on and play Fortnite and other Epic Games titles. (Honestly, the SNES controller has too few buttons to really play Fortnite — but it does run.)
Grab the firmware from the product page.

Wrapping Up

This time, I built a board for converting a Super Famicom controller into a USB gamepad, actually modified an SFC controller with it, and played some games.

I really love the Super Famicom controller, and the urge to play modern games with it too is what kicked off this project.
In the end, I’m really happy with how it turned out.

The Gerber data for the board and the firmware source code are published on GitHub, so if you’d like, please give building one a try.

That’s it for this time.

シェアして応援お願いします

Comments

To comment

TOC