PCB is a 2-layer design with HASL finish, fabricated by JLCPCB. Firmware was developed with MPLAB X, compiled with XC8 (C), and programmed/debugged with a PICkit 3.

The main board was designed with the controller interface in mind. A real NES controller uses a 4021 shift register with some pull-up resistors to relay its button presses to the 6502 CPU running inside the NES. The 6502 uses three of its data bus bits to interact with the 4021: a latch output that latches the parallel inputs in place, a clock output that clocks the shift register out, and a data input that reads out the data starting at bit 8. This board implements that interface, along with some additional circuitry.

Driving this interface is a PIC18LF2420 microcontroller. I use these a lot because I bought a few of them for my graduate school project and have been looking for uses for the spares. They're very capable and work well for this effort. I connected the 8-bit controller bus to the PIC18 to simulate the button presses inputs to the 4021. I placed a pair of ULN2003A load drivers in parallel with the data bus to drive a 10-LED display module for debugging purposes. As the ULN2003A IC has pull-down circuitry, I placed a 1K ohm resistor array in the circuit to pull up the bus so that it emulates the NES controller interface - the NES will invert all inputs on its side. Therefore, a '0' indicates a press and a '1' is idle behavior. The result is a ~4V voltage on the 8-bit bus, which is enough to register as high with a 4021 (3.5V is the threshold for high). This, however, is simply a fail-safe circuit as the PIC18 will be using its push-pull outputs to drive the 4021 at close to Vdd/Vss.

Bluetooth Low Energy is handled by an HM-10 module. I had one left over from my graduate school project and decided to put it to use here. This is a module based on the CC2540 MCU that allows designers to interface a lower-end microcontroller with a Bluetooth Low Energy interface - all that is required is a UART interface, which virtually any microcontroller in existence has. The HM-10 is intended to receive a single byte representing the 8 button states of an NES controller and relay it to the PIC18 through serial.

The PIC18 is clocked at 4 MHz with an external crystal and programmed to read the 19200 baud output of the HM-10 through its USART interface and uses an interrupt-driven 8-entry ring buffer (interrupt on serial receipt) to store data prior to pushing it out onto the 4021 in the main program loop. The PIC18 looks for a "high" state pin on the HM-10 to read data from it; if this pin is low, there is no BLE connection and the PIC18 sets the 4021 to an idle state. A system tick of 1.024 ms is used for timekeeping; if 100 ticks pass between receipt of a byte over BLE, the system resets the 4021 interface back to a high / idle state.

For debugging, a switch is present to allow the MCU to enter test states in which the data bus is set to different states on startup to test the functionality of the PIC18. The user must select a mode and press reset for this state to be set.

A custom cable was fabricated for this effort. I simply snipped the controller side off of a broken third-party NES controller and matched the colored wires up with the port pinout with an ohmmeter. I consulted the Internet for an NES controller pinout and soldered them up to a DE-9 connector. I used some heat shrink and a plastic hood for protection of the contacts and to provide strain relief on the cable and it works very well so far.

A Python script was created with pygame and Bleak to relay the inputs of a Logitech F310 gamepad to the main board over BLE. This script simply reads the button states with Pygame, places them into an array, collapses this array into a byte, and transmits it with BLE. This script runs on my M2 MacBook Air and seems to work pretty well.

I put a 1A fuse on the board to protect my top-loader NES from harm as this board draws power right from the NES.  I'll probably...

Read more »