How to Read Inputs
The complexity of video game controllers has increased tremendously since the first, simple Atari controller was introduced 50 years ago. In this article, Joseph explains how to read inputs from a retro video game controller, and focuses specifically on the Super Nintendo Entertainment System (SNES) controller as an example.
If you are a gamer, especially an older one, you have seen how far game controllers have come. Starting with Atari in 1972, controllers were simple, consisting of one or two buttons and a joystick. As the years marched on, companies such as Nintendo, Sega, Sony, and Microsoft added more functionality to their controllers, resulting in more exotic interfaces. From pressure-sensitive buttons to dual analog joysticks, the electronics inside them became more complicated. Now, some even have a way to plug in a headset to communicate with other players in a live, online game. Video game electronics thus have evolved considerably. A typical old school controller is shown in Figure 1, and Figure 2 shows where we are today.
![FIGURE 1
An early Atari controller [1]. It consisted of only a joystick with a single button, and had a wired connection.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/03/387_Corleto_F1.jpg?resize=520%2C298&ssl=1)
An early Atari controller [1]. It consisted of only a joystick with a single button, and had a wired connection.
If you are a gamer and also tinker with electronics, reading inputs from a game controller becomes an interesting little project. Even though the older generation of consoles is long out of production, some first-party controllers are still being sold in decent condition. Even more newly manufactured third-party controllers fill in the gap to replace the first-party controller shortage (if, like me, you feel guilty hacking up the original hardware). The price tag for these controllers is very low, usually in the $5-$20 range.
![FIGURE 2
A Sony Dual Sense controller [2]. There are digital inputs in the form of push buttons, analog inputs in the form of joysticks and triggers, adaptive triggers to adjust tension, haptic feedback with rumble motors, a built-in microphone, a jack for headset, a touchpad, and it can be wired or wireless.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/03/387_Corleto_F2.jpg?resize=520%2C351&ssl=1)
A Sony Dual Sense controller [2]. There are digital inputs in the form of push buttons, analog inputs in the form of joysticks and triggers, adaptive triggers to adjust tension, haptic feedback with rumble motors, a built-in microphone, a jack for headset, a touchpad, and it can be wired or wireless.
CHOOSING A CONTROLLER
You might be tempted to choose the most modern controller for the fanciest interface for your project. This is not a bad thing, but there are a few reasons why it might not be the best idea.
The first is cost. New controllers are expensive compared to the retro-styled ones. The prices starts at $70 and can climb up to $230!
Second is the interface complexity. Newer controllers tend to have USB and/or wireless connections. If you can’t find a library to read the controller inputs, it will be harder than you think to understand the details of how USB or BLE work, on top of the custom report structure for the controller defined by the vendor. For this reason, modern controllers are beyond the scope of this article. But if you are dead set on using one, I suggest using an embedded computer, such as a Raspberry Pi, and making use of the Pygame module, which has a nice interface that removes all the electronics complexity for you.
Third, you likely don’t need it. If your project only needs a simple directional scheme and a button, an uncomplicated Atari controller will do. If you need more buttons and prefer a directional pad, a Super Nintendo or Sega Genesis controller would be better suited.
— ADVERTISMENT—
—Advertise Here—
UNDERSTANDING THE PINOUT & HARDWARE
Each retro controller usually has its own unique way of communication. Early designs are nothing more than buttons mapped to a pin on the output connector. For example, the joystick controller for the Atari 2600 has five buttons that switched a common signal, which was circuit ground, terminated to a DB9 connector. Figure 3 shows the input in detail.
![FIGURE 3
Pinout of the Atari 2600 joystick controller [3]. Each input has its own signal wire.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/03/387_Corleto_F3.jpg?resize=443%2C206&ssl=1)
Pinout of the Atari 2600 joystick controller [3]. Each input has its own signal wire.
The interface is as simple as it gets. It just requires that each signal is tied to a digital input pin on a microcontroller. You will need to provide an external pull-up resistor to detect when the signal goes high or low, or configure the digital input to enable an internal pull-up, if your microcontroller supports it (most do). Other than that, simply poll each signal to detect a state change, or have an interrupt trigger when one of these pin signals change.
Now that we are warmed up with a simple controller, let’s try to understand the inner workings of something a little more complicated. If video game controller manufacturers relied on having one signal per pin, the connector would start to get bulky and expensive. This is why the number of pins on controllers decreased as newer models were created. It was achieved by encoding or multiplexing the button controller states. This saved money, but slightly increased complexity. Let’s take a look at the Super Nintendo Controller (Figure 4) and its pinout (Figure 5).
![FIGURE 4
The Super Nintendo controller [4]. It consists of 12 digital inputs with a wired connection consisting of 7 pins.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/03/387_Corleto_F4.jpg?resize=445%2C226&ssl=1)
The Super Nintendo controller [4]. It consists of 12 digital inputs with a wired connection consisting of 7 pins.
![FIGURE 5
The Super Nintendo controller pinout [5]. It consists of 7 pins that can read 12 digital inputs.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/03/387_Corleto_F5.jpg?resize=437%2C215&ssl=1)
The Super Nintendo controller pinout [5]. It consists of 7 pins that can read 12 digital inputs.
As you can see, this is a bit different from the Atari controller shown in Figure 3. The Super Nintendo controller has more than twice as many buttons, but with two less pins on its connector. And two of the seven are not used, so technically speaking, there are only five pins. If this had been performed in the same manner as the Atari controller, it would have required a 13-pin connector. The schematic of the Super Nintendo controller, revealing its secrets, is shown in Figure 6.
The Super Nintendo controller is able to capture many button inputs with a few pins by using two 8-bit shift registers. This gives it the ability to read 16 button inputs, though only 12 are used. Each button input is pulled up to +5V, so that when a button is pressed, the input to the shift register is sensed. In other words, a button in the released position is read as a logic “1,” and a button in the pushed position is read as a logic “0.”
UNDERSTANDING THE PROTOCOL
The schematic shown in Figure 6 is the same scheme used for devices that use the synchronous peripheral interface (SPI), a type of serial communication protocol. Of course, the controller is much simpler, since it doesn’t have multiple registers for different types of data or configuration, and is a Simplex system (data sent in one direction only). Let’s walk through what needs to happen in the circuit, to read button inputs successfully.
When you want to grab the button states from a player’s controller, you first need to capture the button states using the latch line (pin 9). Changing its state from low to high, and then back to low, creating a brief pulse, causes the shift registers to shift-in the button states into its D flip flops. The button state for the first bit to be clocked-in is available on the data line (pin 3 on U1).
Imagine you are bit banging the SPI protocol, and do not have access to external shift register circuitry on your microcontroller circuit. You can first read the state of the data line. Once saved, the clock line can be pulsed to see what the state of the next button in line is at. Again, save the state then pulse the clock line. After doing this, sequence all the way through to retrieve all the states for all 16 button states. The timing diagram of this entire process looks like Figure 7.
Now that we have gone through the operation of this controller, we can appreciate how simple the Atari controller really was!
— ADVERTISMENT—
—Advertise Here—
OTHER RETRO CONTROLLERS
The Super Nintendo controller uses shift registers to gather multiple button states, whereas other controllers use different techniques. For example, the Sega Genesis controller uses a single quad, 2-input multiplexer for the 3-button model. The 6-button model uses something similar, but requires a custom circuit and a state machine, so that it is backwards compatible and is harder to visualize in a schematic. The Sega Saturn controller also uses multiplexers, whereas the Sega Dreamcast uses a custom circuit with a Sega-specific protocol.
The Nintendo 64 and Gamecube controllers use something similar to a one-wire interface with a Nintendo-specific protocol. Playstation 1 and 2 use SPI with a Sony-specific protocol. These controllers have their own microcontrollers inside, and can no longer be easily visualized with discrete digital components. Are you starting to see the trend of complexity going up as the video game scene starts to get more complicated with its hardware? Going further up, everything starts to go USB and wireless (usually Bluetooth classic or Bluetooth Low Energy).
READING THE SUPER NINTENDO CONTROLLER
Any kind of microcontroller can be used for this project, since all you need is a way to flip a pin state and to read a pin state. You can use SPI, but I decided that it would be clearer to see what is going on in the code if I rolled my own code. I elected to use the ESP32-S3-DevKitC. I have a ton of these lying around, since it has been my “go to” testing setup for little projects like these. As for the Super Nintendo controller, I didn’t have the heart to rip apart a first-party model, so I decided to use a no-name brand. Really, any Super Nintendo controller will work, because it has to comply with the protocol mentioned earlier. If you look around Amazon or eBay, you will find plenty from which to choose.
GETTING IT SET UP
It is apparent that the Super Nintendo Entertainment System (SNES) controller’s plug definitely will not work with a breadboard or any other standard plug, because it’s unique to the Super Nintendo. You could poke wires into the plug, or hack apart an old Super Nintendo port. Instead, I chopped off the plug and soldered a header pin to each wire. You will need to identify what color wire is which function, following Figure 5 and using a multimeter, but this takes only a few minutes.
The ESP32-S3-DevKitC is a tad too wide to fit on a regular breadboard. I wound up merging two breadboards together with one skinny breadboard, so that I can place the ESP32 board on it. After that, I plugged the Super Nintendo controller wires into wherever I chose for the GPIO. It is worth noting that even though +5V is how the Super Nintendo controller is typically powered, you can use +3.3V from the ESP32 board without any problems. This is all shown in Figure 8.

The ESP32-S3-DevKitC mounted on two small breadboards. The Super Nintendo Controller is directly connected to the breadboard via soldered header pins.
ROLLING OUR OWN CODE
As I mentioned earlier, nothing fancy is required from our microcontroller, just your typical GPIO pins. To start, I like to have two separate files, snes.h and snes.c. Within snes.h, I have an enumeration for each button. This makes it simpler to choose which button I use to check the state within our library (see Listing 1).
After this, I include standard integer types along with a standard way to define Boolean values. I also need to include the GPIO library, in order to easily get and set the GPIO pins. Finally, I finish off this file with a function to initialize the library, and another function to retrieve the state of a button in which we are interested (see Listing 2).
Moving on to the snes.c file, I would like to have an easy way to reference the built-in GPIO I have chosen to work with. The ESP32 IDF (the framework for this ESP32), has a way to do this, but I would rather have something more friendly to look at, shown in Listing 3.
If you are used to using a handler variable to configure GPIO settings in other microcontroller platforms, such as the STM32, you will find that it is a similar scheme within the ESP32 IDF. In Listing 4, I set up the GPIO directions and their default states. The default states can be observed within the timing diagram in Figure 7, where initially the clock pin must be high, and the latch pin must be low.
Finally, we get to the heart of the code in Listing 5, which is how we wind up talking to the Super Nintendo controller. As we have seen in Figure 7, the latch line is where it all begins. It must be pulsed high and then back to low, for the Super Nintendo controller to shift in all the button states. To query which button, remember we need to pulse the clock line a certain number of times. Note that I chose the enumeration values of SnesButton_t to be in a specific order. This is actually the order of the bits that come in every time the clock line is pulsed. We can use these values in a loop, so that when we decide to read the state of the data line, we can be sure it’s the button in which we are interested.
TRYING IT OUT
All we need to do now is create our main.c file as shown in Listing 6.
To test this, pick a button to query within the Snes_Get_Button function with the corresponding enumeration value. Then hold that button down on the controller, and press the reset button to run the code to see the state. The correct state will be printed out.
LISTING 1
Enumeration for all of the Super Nintendo controller buttons. This makes it easier to query the state within our library.
#ifndef _SNES_H
#define _SNES_H
typedef enum
{
BUTTON_B = 0,
BUTTON_Y = 1,
BUTTON_SL = 2,
BUTTON_ST = 3,
BUTTON_DU = 4,
BUTTON_DD = 5,
BUTTON_DL = 6,
BUTTON_DR = 7,
BUTTON_A = 8,
BUTTON_X = 9,
BUTTON_L = 10,
BUTTON_R = 11
} SnesButton_t;
LISTING 2
A function to initialize the library, and another function to retrieve the state of a button in which we are interested
#include <stdint.h>
#include <stdbool.h>
#include “driver/gpio.h”
void Snes_Init(void);
bool Snes_Get_Button(SnesButton_t);
#endif
LISTING 3
Using the preprocessor directive #define makes it easier to reference our GPIO.
#include “snes.h”
#define CLK_PIN GPIO_NUM_6
#define CLK_IDF GPIO_SEL_6
#define LAT_PIN GPIO_NUM_5
#define LAT_IDF GPIO_SEL_5
#define DAT_PIN GPIO_NUM_4
#define DAT_IDF GPIO_SEL_4
LISTING 4
Setting up the GPIO directions. Note that we need to end our initialization by having the GPIO in a certain initial state.
void Snes_Init()
{
// Setup io config handler
gpio_config_t config;
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
config.intr_type = GPIO_INTR_DISABLE;
// Configure all inputs
config.pin_bit_mask = DAT_IDF;
config.mode = GPIO_MODE_INPUT;
config.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&config);
// Configure all outputs
config.pin_bit_mask = CLK_IDF | LAT_IDF;
config.mode = GPIO_MODE_OUTPUT;
config.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&config);
// Default output states
gpio_set_level(CLK_PIN, 1);
gpio_set_level(LAT_PIN, 0);
}
LISTING 5
This function makes it possible to read the state of a particular button. The SnesButton_t enumeration value dictates the correct amount of clock pulses.
bool Snes_Get_Button(SnesButton_t button)
{
// Pulse latch pin
gpio_set_level(LAT_PIN, 1);
gpio_set_level(LAT_PIN, 0);
// Clock in the desired button
uint8_t count = 0;
while(count < (uint8_t)button)
{
gpio_set_level(CLK_PIN, 0);
gpio_set_level(CLK_PIN, 1);
count++;
}
// Return the button’s state
return gpio_get_level(DAT_PIN);
}
LISTING 6
Defining constants we will be using in the code
#include “snes.h”
#include “esp_log.h”
void app_main(void)
{
Snes_Init();
bool x = Snes_Get_Button(BUTTON_DU);
ESP_LOGI(“SNES”, “Button State: %s”, x ? “released” : “pressed”);
}
CONCLUSION
I hope this gives you a better idea of how to start interfacing with retro controllers, and how to choose the right one for your project. Remember that the more recent the controller, the harder it is likely to be to read its button states (and any other type of hardware it might have). Stick with a retro controller for simpler projects, and if using a USB controller, it’s often possible to find a library to do the heavy lifting for you.
RESOURCES
Atari | atari.com
Espressif | www.espressif.com
Nintendo | www.nintendo.com
Raspberry Pi | raspberrypi.com
ESP32-S3-DevKitC Development board is created by Espressif. The development kit was purchased at Digikey.
https://www.digikey.com/en/products/detail/espressif-systems/ESP32-S3-DEVKITC-1-N32R8V/15970965
REFERENCES
[1] https://commons.wikimedia.org/wiki/File:Atari-2600-Joystick.jpg (Figure 1)
[2] https://commons.wikimedia.org/wiki/File:Playstation_DualSense_Controller.png (Figure 2)
[3] https://www.atariarchives.org/creativeatari/Joytricks5.jpg (Figure 3)
[4] https://commons.wikimedia.org/wiki/File:SNES-Controller-Flat.jpg (Figure 4)
[5] https://commons.wikimedia.org/wiki/File:Nintendo-Super-NES-Controller-Plug.jpg (Figure 5)
[6] https://fpgalover.com/images/manuals/SNES/2.jpg (Figure 7)
— ADVERTISMENT—
—Advertise Here—
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • OCTOBER 2022 #387 – Get a PDF of the issue
Sponsor this ArticleJoseph Corleto holds a Master’s Degree in Electrical Engineering. Aside from working as a full-time Electrical Engineer, he has a small business (Bit Bang Gaming LLC), which creates video game electronics hardware, and is actively pursuing the creation of a project-based video course using the ESP32. He describes himself as an Electrical Engineering handyman since he has skills in firmware, R&D, PCB design, PCB assembly, and automated testing. You may reach him anytime via his email: Corleto.joseph@gmail.com.