Sunday, March 04, 2018

Nestronic System Architecture (Part 2)

(For an introduction to this project, please read the previous blog post.)

System Design

Now that the design of the system is mostly complete, I'd like to present it in a little more of a top-down fashion. First, I'll present a block diagram of the complete system. Then I'll go in-depth on how I arrived at all the relevant elements of the system. Finally, I'll show the detailed circuit schematics this all represents.

The design of the Nestronic consists of two major subsystems. First, there's the NES CPU Section, which builds around the RP2A03 CPU and contains everything necessary to actually synthesize video game music. Second, there's the ESP32 Microcontroller Section, which provided the front-end and completes the project.

Block Diagram

NES CPU Section

Any functional computer generally needs 3 things: A place to store the program code, working memory, and some form of I/O. This part of the project was no different. I used the design of the actual NES as a guide, but didn't follow it to the letter. The actual NES has peripherals and support chips I had no use for, such as the Picture Processing Unit (PPU) and controller port interfaces. My goal was to just build what I actually needed, using components that were cheap and easy to buy in the modern era.

Memory Hardware

The 6502-derived RP2A03 has a 16-bit address bus and an 8-bit data bus, with the reset vector at address $FFFC. What this means is that on startup, the CPU will load whatever memory address sits at that memory location, jump to it, and begin program execution.  So for program memory, I'd need something that could sit at the end of the memory map and cover that address. I decided to opt for an 8KB EEPROM, because it was easily available and had enough capacity for the project. The actual NES cartridges usually had quite a bit more capacity, and often also used mapper chips to go beyond the 16-bit address limitations. But for my purposes, 8KB was plenty. After all, I'd just need a simple program that could read data from an external source and write it to the APU registers.

For the working memory, I picked an 8KB SRAM chip. This is actually more than the NES has on-board, but was the smallest easily available chip I could find. Its also way more than I'm likely to need.

I/O Interface Hardware

I/O presented a more interesting problem. Old school CPUs love their big parallel address/data buses, while modern microcontrollers prefer high speed serial interfaces. Hacking together a custom solution to make the two speak to each other didn't seem like fun, especially if I wanted something robust.

One common serial protocol with good support on modern microcontrollers is SPI. It can run at very high speeds, and is well understood. Unfortunately, there don't seem to be any good off-the-shelf solutions for making a parallel-bus CPU speak SPI. I could bit-bang it, but the RP2A03 isn't fast enough to bit-bang it with acceptable performance. The only obvious way of doing SPI would likely involve a complex mess of shift registers and glue logic, which would get out of hand if I actually wanted something bi-directional.

Another serial protocol with good support is I2C. Its a more complex and slower protocol than SPI, but has a number of advantages. It only needs two wires to connect to a whole chain of devices, whereas SPI needs up to four (plus a separate chip select for each additional device). Its protocol is more standardized, so each device doesn't require a different timing configuration. Its also message-oriented, making it easier to build a well-designed control protocol on top of it. Finally, they actually make chips that are designed to interface a parallel-bus CPU to an I2C bus.

The only big question was whether I2C could meet my performance requirements. Thankfully, the code I wrote during my initial tinkering made this fairly easy to test. I inserted delays in my scratch code to simulate the timing of a slower data source, and everything still worked fine. So, I was good to go!

The key to making I2C work was the PCA9564. Its a chip that is designed to connect to an 8-bit parallel data bus on one side, and speak I2C out the other. Its also a 5V tolerant chip that runs off a 3.3V supply, so its actually the perfect solution. Since the modern microcontroller I was planning to use ran off 3.3V, this meant I didn't have to do any level shifting.

Memory Map

Putting all these parts together, I arrived at a memory map for the system:
Address Range Size Device
$0000-$1FFF $2000 Internal RAM (8KB)
... ... ...
$4000-$4017 $0018 NES APU and I/O registers
$4018-$401F $0008 Reserved
... ... ...
$6000-$6003 $0004 PCA9564 Registers
... ... ...
$E000-$FFFF $2000 Program ROM (8KB)
Nestronic Memory Map

Audio Amplifier

Figuring out the audio amplifier took a fair bit of experimentation. In the beginning, I started by looking over how the NES did it:
NES Audio Path

Once you get past the resistors used to mix the channels and the coupling capacitor, the NES uses a rather weird preamplifier circuit. This circuit uses a 74HCU04 at its core, which is a special unbuffered version of the standard 74-series inverter IC. This circuit basically doubled the input waveform, and its output was fed to whatever circuitry connects to the actual line-out jack of the console. While I could make this work on my breadboard (using a 74HCU04 desoldered from an actual NES), I had difficulty getting it to work with modern single-gate surface-mount equivalent components.

The next thing I experimented with, was a power amplifier to drive the actual speaker. In the beginning, this involved tinkering with the simple LM386. It mostly worked, but didn't provide the cleanest output. Given what I had read, I kinda expected this, but it was still a good first step.

I then decided to try a modern, surface-mount, all-in-one amplifier chip that was likely designed for smartphones.  This chip was the LM4889, and it required a minimum of external components. During my tests, it worked so well that I decided to just hook it directly to the output of the coupling capacitor without any preamp stage at all.

Gain Control

The way you control the output gain, and thus volume, from these amplifier circuits is by changing the value of a feedback resistor. The simple way to do this would be to stick a potentiometer into the circuit and expose it out the side of the case. However, I discovered a niftier approach. Apparently, they make digitally controllable variable resistors with I2C interfaces. By using one of these, such as the MCP40D17, I could implement fully digital volume control driven by the modern microcontroller. Most of the time, this doesn't make much of a difference. However, it gives me the ability to adjust or override the user's volume setting when it makes sense.

ESP32 Microcontroller Section

At the core of this section is the ESP-WROOM-32 microcontroller module. Its the more basic of the ESP32 modules available, and has plenty of I/O capabilities for this project. It has built-in I2C, SPI, SD card support, capacitive touch, ADC, and plenty of I/O. It also has Wi-Fi and Bluetooth.

Display Module

Fairly early on in this project I decided that I wanted to use an OLED display, rather than a backlit LCD. This is because I wanted something that would look good in a dark room, just like old 7-segment LED modules. Unfortunately, this made it difficult to find something I liked. It seems like most OLED modules come in two varieties: postage-stamp sized with usable interfaces and 5.5" smartphone sized with 4-lane MIPI interfaces. Since monochrome was fine for this project, that gave me a few more options, but all still having limited size and pixel density.

Eventually I settled on a Newhaven 3.12" OLED module. It was the biggest thing I could find without going to a full 5" smartphone display, but it still wasn't quite as big as I wanted.

3.12" OLED Module

Ambient Light Sensor

Since I thought it would be a neat idea to make the display auto-dim in a dark room, I decided to add in an ambient light sensor. Selecting this one was really just a simple matter of narrowing things down via a parametric part search. I ended up going with the TSL251R, which is a side-looking thru-hole package sensor with analog output. I chose this one so that I could mount it on the edge of the PCB and have it peek out a hole in the front of the case.

Volume Control

Even though I was digitally controlling the amplifier gain, I still needed some sort of actual user input for volume control. While I could have used anything for this, I still kinda liked the idea of an analog thumbwheel on the side of the device. Unfortunately, it seems to be impossible to actually find an off-the-shelf thumbwheel that's more than ~10cm in diameter. I eventually decided to just buy a potentiometer with a flatted shaft, and broke out the 3D printer:
Potentiometer /w 3D Printed Thumbwheel

SD Card Slot

It seemed like a good idea to add a microSD card slot to the design, as a way of feeding in VGM data. This was fairly easy, since the ESP32 already has the necessary interface and SDK support for working with one. The only interesting part of this was my use of an EClamp2410P chip to combine the pull-up resistors and protection diodes into a single component.

Real-Time Clock

At the outset of this project, I pretty much assumed that the ESP32 would already have everything I needed to implement a battery-backed real-time clock (RTC). Unfortunately, this turned out to not be the case. The power pins for the RTC are not broken out separately on the completed modules, so I'd need to add both a 32kHz crystal and a battery sleep/switch-over mechanism myself. Since cheap off-the-shelf RTCs already exist, and have the battery switch-over mechanism built-in, I decided to just go with one of those. I'd need just as many external components to make it work, so it didn't really seem excessive. The RTC chip I decided to use was the MCP7940N.

Input Button Board Interface

This part is mostly going to be left to a future stage of the project. At this point, all I really need to do is nail down the signals that have to go on the connector for the button board. After doing some experiments with the I2C interfaced TCA8418 keypad controller, and with the ESP32's touch sensor interfaces and Proto-pasta's Conductive PLA, that was mostly figured out. I decided to route an I2C channel and two GPIOs (one for key interrupts and the other for the touch sensor). From this I could build any of my ideas for a button/switch panel.

Circuit Schematics

This was my first attempt at doing a multi-level schematic in KiCad. I took this approach because the RP2A03 and ESP32 sections have different design styles, and because there was only so much space. The top-level only shows the basic connections and power supply, while all the details are in the sub-schematics.

Top Level

NES CPU Section

ESP32 Microcontroller Section

From here, the next step is PCB layout and fabrication, followed by the design of the enclosure and the input button board.

No comments: