NES to USB
Table of Contents
Introduction
Recently I got my hands on an old NES controller which I want to use as a windows controller. However, any store that sells the converter cables either does not ship to my location or does not have any in stock. So I decided to do it myself. Im using an Arduino Uno for this project.
The NES Controller
Since I do not have too much experience with microcontrollers I took some inspiration form this website, which is a good starting point for getting the controller to the computer. The wiring and code is from there although I changed most of the code to fit my own programming style and purpose of this project.
Basically, the NES controller is PISO shift register which stands for Parallel-In-Serial-Out, i.e. the button presses go in parallely and the controller outputs which buttons are pressed, the buttons states, in series. The only thing we need to do is tell the controller that we want to start reading, read, tell it we want to read the next button state and so on, or in pseudocode:
def loop():
buttonStates = {button:'not pressed' for button in buttons}
startReading()
for(button in buttons):
if readNesOutput() == 'pressed':
buttonStates[button] = 'pressed'
showNext()To do this we need to plug the NES controller into the Arduino and connect 5V and ground. Telling the controller to start reading is done by sending a signal to the latch and telling it to advance which output it sends is done through the clock lastly we need to read the controllers output. We plug the controller into the pins as shown:
The empty pins are not used. Pin 2 is the clock, 3 is the latch and 4 is the controller output. All thats left is programming the setup function and actually implementing the pseudocode. For the setup function we just set which pins are input and output.
// Input
int nesData = 4;
// Output
int nesClock = 2;
int nesLatch = 3;
void setup()
{
Serial.begin(9600);
pinMode(nesData, INPUT);
pinMode(nesClock, OUTPUT);
pinMode(nesLatch, OUTPUT);
// Set initial states
digitalWrite(nesClock, LOW);
digitalWrite(nesLatch, LOW);
}The functions beginReading and showNext are also just as described:
void beginReading()
{
digitalWrite(nesLatch, HIGH);
digitalWrite(nesLatch, LOW);
}
void showNext()
{
digitalWrite(nesClock, HIGH);
digitalWrite(nesClock, LOW);
}Now the last problem is to know which button is which. We can find out by just reading the output and writing it to Serial in the loop and pressing buttons.
byte readNesController()
{
int tempData = 255;
beginReading();
for(int i = 0; i < 8; i++)
{
if (digitalRead(nesData) == LOW)
bitClear(tempData, i);
if(i < RIGHT_BUTTON)
showNext();
}
return tempData;
}
byte buttonStates = 0;
void loop()
{
buttonStates = ~readNesController();
delay(10);
for(int i = 0; i < 8; i++)
Serial.print((buttonStates & (1 << i)) > 0);
Serial.println();
}I negate the output of readNesController() so that 1 stands for button is down, but this is arbitrary, then I loop through the bits of buttonStates and write them into one line in Serial, effectively we are converting buttonStates into base 2.
After you have found out which key is which change the loop function to:
void loop()
{
buttonStates = ~readNesController();
delay(10);
Serial.println(buttonStates);
}Simulating Keyboard Input
Since I want to actually use this controller I need to simulate keyboard input, which the Arduino I use does not support. If u have a different Arduino you can check if yours supports keyboard input.
Alternatively, we can read the Serial output of the Arduino in another programming language and then simulate the keystrokes there. I used C++ for this. We need to read the Serial stream line by line and then press the keys:
auto port = findArduinoPortName();
if (!port.has_value()) {
std::cout << "arduino port not found" << std::endl;
return 1;
}
std::wcout << port.value() << '\n';
std::ifstream serial(port.value());
std::string line;
while (std::getline(serial, line)) {
std::cout << line << '\n';
handleLine(line);
}We handle the line by, again, looping through the bits of the serial output and pressing keys accordingly:
const std::array<WORD, 8> KEYS = {
'A', 'B', 'K', 'L', VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT
};
void pressKey(WORD vk) {
INPUT input = {};
input.type = INPUT_KEYBOARD;
input.ki.wVk = vk;
SendInput(1, &input, sizeof(INPUT));
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
}
void handleLine(const std::string& line) {
if (line.size() == 0) return;
uint8_t bits = static_cast<uint8_t>(std::stoi(line));
for (int i = 0; i < 8; ++i) {
if (bits & (1 << i))
pressKey(KEYS[i]);
}
}Just the Code
The Arduino code should look like this:
const int A_BUTTON = 0;
const int B_BUTTON = 1;
const int SELECT_BUTTON = 2;
const int START_BUTTON = 3;
const int UP_BUTTON = 4;
const int DOWN_BUTTON = 5;
const int LEFT_BUTTON = 6;
const int RIGHT_BUTTON = 7;
//Inputs:
int nesData = 4;
//Outputs:
int nesClock = 2;
int nesLatch = 3;
void setup()
{
Serial.begin(9600);
pinMode(nesData, INPUT);
pinMode(nesClock, OUTPUT);
pinMode(nesLatch, OUTPUT);
digitalWrite(nesClock, LOW);
digitalWrite(nesLatch, LOW);
}
byte buttonStates = 0;
void loop()
{
buttonStates = ~readNesController();
delay(10);
Serial.println(buttonStates);
}
void beginReading()
{
digitalWrite(nesLatch, HIGH);
digitalWrite(nesLatch, LOW);
}
void showNext()
{
digitalWrite(nesClock, HIGH);
digitalWrite(nesClock, LOW);
}
byte readNesController()
{
int tempData = 255;
beginReading();
for(int i = A_BUTTON; i <= RIGHT_BUTTON; i++)
{
if (digitalRead(nesData) == LOW)
bitClear(tempData, i);
if(i < RIGHT_BUTTON)
showNext();
}
return tempData;
}And here is the full C++ code, including the findArduinoPortName function:
#include <windows.h>
#include <initguid.h>
#include <devguid.h>
#include <setupapi.h>
#include <tchar.h>
#include <iostream>
#include <optional>
#include <fstream>
#include <string>
#include <array>
#pragma comment(lib, "setupapi.lib")
const std::array<WORD, 8> KEYS = {
'A', 'B', 'K', 'L', VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT
};
void pressKey(WORD vk) {
INPUT input = {};
input.type = INPUT_KEYBOARD;
input.ki.wVk = vk;
SendInput(1, &input, sizeof(INPUT));
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
}
void handleLine(const std::string& line) {
if (line.size() == 0) return;
uint8_t bits = static_cast<uint8_t>(std::stoi(line));
for (int i = 0; i < 8; ++i) {
if (bits & (1 << i))
pressKey(KEYS[i]);
}
}
std::optional<std::wstring> findArduinoPortName() {
HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, nullptr, nullptr, DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE) return std::nullopt;
SP_DEVINFO_DATA devInfo = {};
devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo); ++i) {
TCHAR name[256], desc[256];
DWORD len = 0;
SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfo, SPDRP_FRIENDLYNAME, nullptr, (PBYTE)name, sizeof(name), &len);
SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfo, SPDRP_DEVICEDESC, nullptr, (PBYTE)desc, sizeof(desc), &len);
std::wstring wname(name);
if (wname.find(L"Arduino") == std::wstring::npos)
continue;
size_t start = wname.find(L"(COM");
size_t end = wname.find(L")", start);
std::wstring port = wname.substr(start + 1, end - start - 1);
SetupDiDestroyDeviceInfoList(hDevInfo);
return port;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return std::nullopt;
}
int main() {
auto port = findArduinoPortName();
if (!port.has_value()) {
std::cout << "arduino port not found" << std::endl;
return 1;
}
std::wcout << port.value() << '\n';
std::ifstream serial(port.value());
std::string line;
while (std::getline(serial, line)) {
std::cout << line << '\n';
handleLine(line);
}
}