Video memory for our girl.
I considered subtitling this article, “adventures in breadboard noise”, since that’s what I spent most of my time dealing with. In any case, let’s recap where we were. Veronica’s video generator was generating a stable VGA signal. In addition, a test pattern was being displayed by altering pixel colors on the fly during the scanning process (so called “racing the beam”).
That’s pretty flashy, but ultimately not very useful. At the ATmega’s I/O bandwidth limit of 10Mhz, and a horizontal scan line of 512 pixel clocks, you’re not going to get much horizontal resolution this way. Every clock cycle spent calculating the color or fetching it from somewhere is a lost pixel. My goal is 256 pixels across, which is a nice usable resolution on par with 1980s computers. It also makes for very nice addressing possibilities for an 8-bit computer. However, that means I have only two clock cycles to get each pixel out. Not much time!
A simple solution to this is to have the video DAC pull its color data directly out of RAM. The VGA output will be basically wired directly to an SRAM (through the analog conversion resistor ladder). That way, all the CPU has to do is change the address lines on the SRAM at a rate of one address every two clocks. The response time on the SRAM is much much faster than the pixel clock, so if we change the address lines in real time, it will be seamless to the video (and the SRAM will get a real workout!). Even just changing the address lines is tight at 2 clocks per pixel, but we can do it.
Let’s take a look at the schematic (Eagle file is here).
Before I get into the details, let me mention some tweaks to the old parts of the circuit. On the right side is the resistor ladder DAC. You’ll note that I added three 180Ω resistors to the outputs. The VGA standard specifies that the analog color outputs are supposed to be in the range of 0 to 0.7V above ground. I was previously outputting a full 5V range. Honestly, the LCD monitor I’m using didn’t seem to care, so I never gave it a second thought. However, other monitors might, so it seems like a good idea to fix this. The extra resistors create an additional voltage divider on the outputs to compress the 5V range to 0.7V. I didn’t notice any difference at all in my display, but here we are. Interestingly, if you play with the values of these new resistors, you can apply a red, green or blue tint to the output with no change in brightness or quality. The monitor seems to care more about the ratio of the three voltages than the actual levels.
Next, there’s one very critical little component I should mention- there’s now a small capacitor to ground on the V-Sync signal. I found that my monitor would occasionally have trouble locking to my VGA signal, even though all the timing and everything was correct. I attribute this to noise, and a cap to ground has made it very robust. It never fails to lock for me now.
Okay, let’s dig into the meat here. For my 256 x 240 x 8 bit display, I need a little under 64k of VRAM. I’m using two 32k SRAMs, because that’s the largest size you can get in a DIP package, and it was a good learning opportunity for doing bank switching. I have a “video data bus” that connects the data lines on both SRAMs to Port C on the ATmega. The video data bus is also connected to a 74HC541 buffer.
As you can see, the DAC pulls digital pixel data directly from SRAM, through the buffer. The purpose of the buffer is to allow us to disconnect the DAC from the video data bus when we need to. This allows us to blank the pixels during syncing periods, which you may recall is needed to prevent confusing monitors (thanks to LucidScience for this trick!). It also allows the ATmega to write to VRAM without messing up the display.
Addressing the SRAMs is done via ports D and A on the microcontroller. Port D is the high 8 bits of the 16 bit addresses. Since our SRAMs are only 32k, they only have 15 address lines. The high bit of Port D thus goes to the Chip Enable lines on the SRAMs, to control which one is active. The Chip Enable lines on the SRAMs are tied together with an inverter. Thus, when we request an address in the upper or lower 32k of our memory space, one SRAM is automatically disabled, and the other enabled. SRAMs are designed to be used precisely this way, so it’s very easy to get this style of bank switching working with the available control lines. It’s also worth noting that, with this method, you can build a simpler 32k version for testing just by omitting one SRAM and tying the Chip Enable low (active). That will simply replicate the contents of the top “half” of VRAM on to both halves of the screen. I started out by doing this in order to get the basics working.
A quick note on the images below- many of them are photographs of my cheap eBay LCD monitor. It has a really crappy backlight that makes uneven brightness top to bottom. The camera exaggerates this effect quite a bit, giving everything a sort of dark-to-light top-to-bottom gradient look. You’ll have to bear with me on that. The screen shots are also frequently grainy, since I shot them with the lights off to make the screen pop better.
So with some SRAM hooked up, it’s time to write some code to use it! This code is actually simpler than the pattern generator, but longer, because it has to crank out an entire line of pixels at two clocks each. There’s no room there to manage loop counters or anything. There’s barely time to push an address value and increment it. So in place of the loop counters and manual color calculations of the old test pattern code, this new code just pushes addresses out of Port D/A as fast as possible. If everything worked, we should get a stable signal that is showing random junk that happened to be in the SRAM at startup.
So with that minor victory in hand, it’s time try rendering a known rectangle of something. To do this, I updated my code to erase all the memory at startup, then to copy a rectangle of bytes from a random place in the ATmega’s program space to VRAM.
I’ve said this a lot, but every project seems to have a little “gotcha”. That one thing that stumps me for hours or sometimes days on end. In this case, it was little stray pixels. After much experimenting, testing, and researching, I determined that my problem was noise. Running a breadboard at 20Mhz is a LOT to ask. This is quite a bit above what is generally recommended. Breadboards are noisy as hell, and when signal frequencies get above a couple MHz, this starts to really matter. In order to get good results, I had to take a number of steps such as:
- Use short wires that are fresh and clean
- Make sure the holes in the breadboard are clean (and not, say, gummed up from the paper tape that your resistors came on)
- Avoid extra connectors and headers where possible
- Keep the circuit as compact as possible
- Extra decoupling capacitors (more than the usual one-per-IC) sometimes help
- If in doubt, wiggle stuff a bit
- Make sure ICs are really well seated. Sometimes the pins don’t grab well in the sockets.
- Make sure all unused inputs are pulled high. This is easy to forget on TTL logic chips, where you might not be using all the gates. Those unused gates still need to be tied off.
With all those measures being applied where needed, I was able to get most of the noise out. I still had some issues with the SRAM getting noise, though. Eventually, I ended up putting a 100Ω resistor on the ground line of the SRAM to insulate it from some of the noise, and this worked really well. In the final circuit, this should not be needed, since it’s just breadboard noise we’re dealing with here. We weren’t quite out of the woods yet, but with half our SRAM in place, we were noise free.
So with that working, let’s have a little fun! How about displaying a real bitmap, like… say… Veronica’s sexy 80’s computer logo? To do that, I would need a data set for it that would be in the format of Veronica’s VRAM (which is currently one byte per pixel, in an xxRRGGBB layout for a total of 64 colors). I started by scaling the logo to a size that would fit on my screen. Then, I made a palette in GIMP that matched Veronica’s complete range of colors. The trick is that the order of each color in the palette matches the bit pattern of that color on Veronica. So, entry 14 in the palette (which is 00001110 in binary) is bright teal ( xx 00 11 10 in Veronica’s format, which is full green and 2/3rds blue). This little trick makes converting artwork to run on Veronica trivial. I can load up any image, convert it to Indexed Color using my Veronica Palette, then save it as Raw image data. This data is now in Veronica’s VRAM format!
Next, I have to get that data into VRAM somehow. The easiest way to do that right now is to hardcode it as constant data in the program space of the ATmega, then have it copy the data into VRAM at startup. To do that, I use the following Un*x command line:
hexdump -ve ‘1/0 “.byte ” 200/1 “0x%.2x,” 1/0 “\n”‘ VeronicaLogo.raw > VeronicaLogo.S
The output file (VeronicaLogo.S in this case) is a series of .byte statements that can be pasted into the assembly source code.
The great thing about this resolution of 256×240 is that addressing a position in VRAM becomes a simple matter of the high address byte being the Y position, and the low address byte being X. That makes blitting code really easy to write. Case in point:
The next step is to get my full 64k VRAM setup working.
Once I had it wired up, I needed to test the extents of the VRAM and make sure my pixels are where they should be.
If you look really closely in that photo, you can see the different colored pixels in each corner. These are written to VRAM at startup into the addresses that should be the screen extents at 256 x 240 (namely $0000, $00ff, $efff, and $ef00). Using these extents, I tuned the cycle counts a bit in my code to make sure these pixels land correctly at the extremes of the physical screen. If the extents are right, then every pixel on the screen will be where it should be.
So it seems like my dual-chip VRAM is working, right? Time for that groovy logo again!
So looking at that, I have more noise problems, right? On further inspection, not really. The defining characteristic of noise is its randomness. After each power up, or after wiggling a wire or two, the pattern will change. This effect was not changing. Furthermore, it has a distinct regularity both horizontally and vertically. It’s perfectly repeating previous groups of pixels. This led me to believe that a particular pin on my address bus wasn’t working, and it was repeating old groups of addresses instead of iterating through them. This theory turned out to be correct, and the cause was yet another hard lesson with breadboards. That is, they wear out. I had an entire row of holes that simply was not connecting anymore. The holes felt strange when pushing a wire into them, which was a good clue. A test with the ohmmeter confirmed they were dead. I marked this row with nail polish so I know to avoid it, and moved my second SRAM clear of the trouble area.
Here’s the code for all this fun. The sync signal generation is the same as before, but I’ve added code to initialize the VRAM, handle the control signals for writing to VRAM, and of course write out addresses as fast as possible.
The next step is interfacing with Veronica’s main bus, so the main CPU can have some say in what is being displayed! That portion of the video board is in the works as we speak, so stay tuned.
Exercise for the reader- there is a way to iterate through the addresses at twice the speed I’m doing here (one clock per address). Do you know what it is? Hint: the demo scene on the Apple //gs used the same trick to blit at high speed.
Doubling the horizontal resolution wouldn’t help me here, since I don’t have the memory or horsepower to drive that many pixels. Still, it’s neat to know it can be done. Discuss.