Back to the front page


February 2011

Part 1: Intercepting the gamebody LCD

A peculiar aspect of the original Gameboy is how well it did given how absolutely dreadful the display hardware was. The screen is not backlit, the colour palette is a meager 4 shades of gray and the resolution is only 160x144. Before you object that that's just how the technology was back then, note that it launched at about the same time as the Lynx and the Game Gear, both of which had beautiful backlit 4096 colour screens.

However the upshot of such a dismal screen is that the battery life is phenomenal, and this was instrumental in the Gameboy being such a success. And when talented artists get involved, you can still get graphics that are pretty nice even by today's standards. Turns out 2 bits are enough:

Twenty years later the hobby electronics scene is thriving and there is a wide variety of powerful, cheap hardware. You now have an army of people like me who are not embedded engineers, but have 20+MHz microcontrollers and a passionate desire to build stupid, pointless crap with them. And those people may be interested to learn that because the screen is so low-spec it's quite straightforward to hijack the gameboy display for whatever nefarious purpose might please you.

Note that - at least for now - I'm not interested in re-using the screen. As we just discussed, the screen is terrible. What I would like is to access the video data from the Gameboy so I can display it on something more interesting. Let's discuss how to go about this. A good source of information about the gameboy hardware is, and also this report by John Ernberg. For now let's focus on this schematic by Jeff Frohwein:

Direct your attention to the "LCD Display" block on the left. The signals we are interested in are VERTSYN,HORSYNC, DATAOUT0, DATAOUT1 and CLOCK. These are the control lines for the LCD coming from the central processor (which is not shown in this schematic). I'll explain how these control lines work in a moment. First we'll discuss how to tap into them, which should enable us to reconstruct the images being sent to the LCD.

Just opening a Gameboy turns out to be the most difficult step, thanks to Nintendo's predilection for tri-wing screws. You can in principle get by with just the right size flathead screwdriver, but I gave up and bought a tri-wing driver from ebay for a few bucks.

Once you do have it open, it's not hard to find the signals we're after with an oscilloscope or logic analyzer. These are the six signals of interest for the screen data:

They're 5V logic levels, but watch out for the -30V DC contrast signal! (I forget exactly which one it is, but it's somewhere in the lower 10 pins. The pitch is not particularly tight, so I simply soldered some ribbon cable to the LCD connector. It's possible (but tight) to then feed this through the cart slot and still have access to the pins with the Gameboy closed up again.

With the gameboy on, this is an example of the kind of data you'll see:

Logic capture of one horizontal display line. This is too zoomed in to see any VSYNC pulses.

Beginning at the upper left of the screen, the controller scans through each LCD frame row by row, setting each pixel to whatever colour value is required. The pixel clock pulses once for each pixel. At the falling edge of the CLOCK signal, DATA1 and DATA0 give you the 2-bit colour value corresponding to that pixel. In the screenshot above you can see that there is a large section in the middle of this particular line where the colour value is 0b11.

Once all 160 pixels in the first horizontal line are done, we wait for a signal to move down and start the next line underneath. This is the role of the HSYNC line, and the signal is a rising edge. Once 144 lines have been drawn, we wait for a signal to go back and start all over again with a new frame. This is the role of the VSYNC line, and the signal is a rising edge.

From the logic capture you can see that when a line is finished, the controller does not immediately start the next one. In fact over half the time assigned to each line is not used; the pixel clock is not running. We get 68.9us of deadtime after all 160 clock pulses before the next HSYNC pulse happens. After all 144 lines, we get 1.1ms before the next VSYNC pulse. This is not really an awful lot of time, so to do anything practical with this data you'll probably be skipping some frames. Remember that 60Hz is the LCD refresh rate, but it's not necessarily the speed at which the screen data is updating.

As a proof of principle I exported a capture of the signal waveforms (with the excellent Saleae Logic) to my graphing software of choice (the excellent Igor Pro). After throwing together a very simple Igor script to process the signals, we can indeed reconstruct the image!

If you want to play around, here's a zipped text file with logic capture data from a single frame. Or if you prefer, here's an Igor experiment file which will run the reconstruction on the data. Igor has a free 30-day demo you can use, email me if you get stuck.

Part 2: Reading data in realtime

When discussing the video signals, I implied that they were slow enough to be trivial for a typical 20MHz 8-bit microcontroller. In actual fact a 4MHz datastream is fast! You get 250ns between rising clock edges, and in that time you need to:

At 20MHz, you've only got 5 clock cycles to do it.

I thought about this for a while, and decided what I needed was some kind of serial-to-parallel conversion. That way I could read in 8 bits at a time on each channel, and only have to do it every (250ns*8)=2us. Since I'm not yet one of the cool kids who plays with FPGAs, I started doing this with 74 logic - counting clock pulses, latching data from a serial-parallel shift register every 8 counts, so forth and so such. It took a while.

And then a month into setting up this complicated circuit, it struck me that I was an idiot; that most microcontrollers already have exactly this feature and it's called SPI. Duh. Also since SPI modules are implemented in hardware they run much faster than you could do in software, definitely fast enough to keep up with this video signal in any case.

Excluding that little hiccup, the rest went pretty quickly. I'm using an XMega128A1 controller on one of Justin Mattair's XMEGA development boards. This thing runs at 32MHz, and is a great option for this project because the XMega series have peripherals coming out of their asses. Seriously, there's 4 SPI modules, 2 DACs, something like 8000 timers and probably a couple million UART modules.

In this case having two SPI modules makes life very easy - one for each data channel. I gave each module one of the DATA channels and the CLOCK signal. The HSYNC and VSYNC signals are monitored on general IO pins to keep everything synchronized. The c source is here if you want to get into the details.

The only other thing to attend to is level conversion - the Gameboy is giving 5V signals but the XMega wants 3.3V. Since this is a read-only arrangement, it's sufficient to just use a voltage divider on every signal line.

The end result of this is that I have a couple of arrays of pixel data sitting in the XMega, being updated in real time. Let's find something cool to do with it.

Part 3: Outputting to an oscilloscope

Here's a shot of the hardware, which I'm now halfway through describing. The other half consists of the digital-to-analog converters responsible for driving the oscilloscope.

One of the neat things about old CRT style oscilloscopes is how easy it is to repurpose the display. Instead of the usual time-sweep mode of operation, you often have the ability to instead control the brightness and x-y deflection of the electron beam with analog input voltages. Which means you just have to stick some DACs on your microcontroller and you have a video display! There are several great projects along these lines; two noteworthy examples are Jan's clock and Matthew's terminal console.

The scope I have is a PM3065, a 100MHz dual channel CRT scope and apparently a collaboration between Fluke and Phillips. Importantly it has a BNC input at the rear for controlling the beam intensity ("Z input"), in this case going smoothly from full-on at about 0.8V to completely blanked at about 1.06V. The exact numbers depend on what you have the intensity knob set to, but 26mV is approximately the working range.

Let's discuss the X-Y control first. Switched to XY mode, the voltage on the first input controls the X-deflection of the spot and the second controls the Y-deflection. By applying sawtooth ramp signals to both inputs you can raster out a square:

To match the Gameboy resolution we need a resolution of 160 x 144 pixels (DAC steps), and to get a halfway decent framerate we're going to need to sweep them pretty fast. The analog voltage range is not particularly important because we can easily adjust the gain settings on the scope.

This is where the boatload of peripherals on the XMega comes in handy again - it has two 12-bit DACs that can run at 1MHz. They have some non-linearity problems at the top and bottom, but with 4096 (12bit) resolution there's plenty of room to just add an offset to the ramps.

We're nearly there - all that's left is to set the intensity at each point of the raster. Sadly we've used up the two inbuilt DACs to do the rastering, but since we only need 2-bit control a simple R2R ladder will suffice. There's a little bit of fine tuning required though - for the intensity modulation input we need something like 9mV steps with about 800mV offset. Here's what I'm using, built on breadboard with potentiometers:

By putting a 2-bit value on DB1 and DB2 using a couple of IO pins on the XMega, we get an analog voltage out with an adjustable step size and offset. A good check that this works is to put the scope on regular (time sweep) mode and watch the R2R analog output. You'll see the discrete values, and if at the same time you connect that output to the intensity modulation input you get something like this:

From there it's easy to play with the various potentiometers until you get 4 distinct spot intensities.

And that's pretty much it! The program logic is along the lines of:

Capture a frame:
	Listen in on the video data until a new frame starts (VSYNC pulse)
	Capture the pixel data for that frame with the SPI ports

Draw the frame:
	For each pixel in Y (0...144):
		Increment the Y-deflection DAC output
		For each pixel in X (0...160)
			Increment the X-deflection DAC output
			Set the R2R DAC value for this pixel


The XMega is capable of capturing data at the full 60Hz that the LCD is refreshing, but in the pseudocode above we stop capturing while we are drawing a frame to the screen. At the moment this takes 35ms, so it has to draw one then skip three for a final refresh rate of 15Hz. This is a slow enough frame rate to be noticable and kind of annoying. The XMega has the speed to push this up to at least 20Hz, however at that speed the image starts to degrade. I found that needed to give the DACs a delay of at least 1us to settle after each pixel. We can check this out with the scope by looking at DAC sweeps:

Horizontal raster DAC (left) and spot intensity DAC (right). Timebase is 2us/division.

Unsurprisingly the culprit is the intensity DAC which is implemented as an R2R ladder with potentiometers on a breadboard. There's plenty of capacitance in that setup, so it's taking about 1us to settle after small changes and nearly 2us for large changes. It would be very easy to improve on this by simply relocating the R2R ladder and amplifier to some stripboard or a proper PCB. Alternatively you could think about scrapping the R2R DAC for a decent IC version, or shopping around for a micro with 3 DACs.

And now I'll leave you with a video of it in action. Sound is coming from the Gameboy speaker, and the horizontal brightness bands are a filming artifact. Enjoy, and let me know if you try something similar!

Back to the front page