DQYDJ Logo

How to Produce 640x480 Color VGA Video From an 8-Bit Arduino

Written by:
PK

Most 8-bit AVRs in the wild (including this project's Arduino Nano) run at a default maximum 16 MHz. That's 16,000,000 calculations per second... a respectable number for most embedded applications.

The VGA video standard, which is the default  "we-can-always-fallback-to-this" industry standard is 640 pixels wide by 480 pixels tall by 60 frames per second.

This standard requires pixels to be clocked out at 25.175 MHz:

25,175,000 > 16,000,000. 

Generating Full VGA Video from an Arduino

Clock speed was just one of the barriers to pulling off this (silly) project.

And with the clock doubling hack I documented, higher resolution is possible without overclocking the Arduino. You could reach roughly 800x600 in 4 bit color with 16MHz parts, and 1024x768 in 4 bit color for 20 MHz clocked parts.

(If you're willing to drop to 2 or 1 bit color and spend a ton on ICs that can handle even faster clocks, you can hit HD resolutions. You'll run into financial constraints before you max out on the technical side)

What Was the Previous Maximum VGA Resolution on an Arduino?

With tight coding you can achieve around 512 pixels wide on a 20 Mhz or ~400 pixels wide on a 16 MHz part. That math is simple: (20/25.175 * 640) or (16/25.175) * 640 at one pixel per clock cycle.

My goal was to produce 'full screen' Arduino VGA video. Color too, to spike the football.

A note on video technology: people have hacked color NTSC video out of Arduinos, but generally have only done so by changing the clock to 14.31818 MHz crystals.  Television compatibility is great, but I targeted VGA and its simple, discrete outputs for red, green, and blue all while keeping the stock 16 MHz clock.

And what were your goals?

My requirements for this project:

  • Full width video, (>= 640 pixels wide) from an Arduino
  • Color
  • Use only parts I have on hand
  • No reprogrammable logic (FPGA/CPLD/GAL/PAL/PLA/Whatever else is out there)
  • No overclocking!

Bill of Materials and Schematic for 640x480 VGA on an Arduino

I used 6 ICs, 10 resistors, one VGA port and a bunch of jumper wire in my original build. The first four parts are for the clock circuit I discussed last week. And yes, I mix logic families:

  • HD74LS04P – Not Gate.  Used as Delay
  • HD74LS08P – And Gate.  XOR half
  • HD74LS32P – Or Gate.  XOR other half
  • SN74F10N – Fast 3 Input Nand Gates.  Clean up the ugly clock from the hacked XOR, also used as Inverter
  • DM74LS244N - 3 State Buffer/Line Driver/Line Receiver.  Control which pixel of the two is on the output bus.
  • SN74LS373N - 8 D Type Latches. Our digital sampler/output to resistors and sync.
  • VGA Port - See Nick Gammon's site for how to wire this.
  • Resistors ~68 Ohm.  Sync signals.
  • Resistors ~470 Ohm. R/G/B signals.

Substitutions are fine, just keep the propagation delay of the parts under 32 ns so you can latch in time (if you do my clock hack, probably 20ns due to the weird duty cycle) or so.

Arduino 640x480 VGA Schematic
Click to Zoom
A Note/Disclaimer:  I make no warranty that this is absolutely correct, and that I even transposed it correctly from my working copy.  I may have transposed  incorrectly, don't even trust the picture 100%.  Before you turn this on, check my connections are accurate, your connections are correct and you understand what's happening.
DQYDJ won't be held responsible if you break any equipment using this circuit!

An Updated, Improved Bill of Materials

I bought some components and eliminated two pieces of silicon from the original build.  If you build this smaller version, the code itself doesn't change.

(Note: It's better to start with a 32 MHz oscillator and divide the clock by 2 to drive the Arduino than to use my clock doubling hack. That would cut your IC count down by 2-3!)

Be sure to add the 470 ohm resistors for Red/Green/Blue and the 68 ohm resistors for VSYNC/HSYNC - they aren't pictured in the schematic!

  • 470 ohm resistors for R/G/B
  • 68 ohm resistors for Hsync/Vsync
  • 74LS04P - delay circuit for making the 32 MHz clock
  • 74F257N - Multiplexer, replaces the previous latch/transceiver
  • 74F86N - XOR Gate, replaces the one I built above from And/Or/Not gates (Missed label - it's IC2)
Schematic for 640x480 Arduino Video
Click to Zoom

How Did You Achieve 640x480 with a 16 MHz Part?

Click to Zoom
Click to Zoom

First, I'd like to thank three folks in particular for inspiring this project. Nick Gammon is the first - his VGA library was the start point for this successful project, and this demo requires his Timing library. His timers greatly simplified the interrupt writing necessary for this project.  His wiring diagrams and initial code-base were invaluable for sanity checking my early prototypes.

Second, thanks is owed to Henning Karlsen, who had great 16x16 fonts available for easy use in Microcontroller projects. Please checkout his projects, especially his UTFT related projects with resources for serial displays. My message generator uses one of his fonts.

Third, to Linus Akesson (a.k.a. lft) who bitbanged VGA color video out of a similar part back in 2008. (His site, here). Inspirational!

How to achieve a pixel rate faster than a base clock

At the most basic level, I treated the 8-bit microcontroller as two interleaving 4 bit microcontrollers by dividing its output into two logical parts per cycle.

For this demo, I had two disparate sets of Reds, Greens and Blues, laid out like you see nearby. Here is the pin layout from my Arduino:

Pin D7: Red 1
Pin D6: Green 1
Pin D5: Blue 1
Pin D4: Red 2
Pin D3: HSync
Pin D2: Green 2
Pin D1 (Tx): N/C
Pin D0 (Rx): Blue 2

... that's all on the AVR's Port D.

Once I had the 32 MHz clock I discussed in my clock doubling hack post, the project came together quickly. The 32 MHz clock goes to the Latch Enable Pin (11) of the LS373. Enable is tied to ground (always on), in effect latching to the 373 outputs to whatever value happens to be presented every 32 ns.

The 244N uses the stock 16 MHz clock, plus the clock 180 degrees out of phase with itself connected to both enables.

Out of phase means when one clock is high, the other is low. Thus, only one pixel is allowed through to the 373N... and that's how you present ~810 samples to the monitor when you previously could only do half that.

I cleared 640 samples with plenty of breathing room.

Simplifying the Video Circuit

If you give up color fidelity (the latch), you can use the clock and an inverted clock into the enables on the DM74LS244N - 2 chips.

Some folks will be bothered by the mild amount of chip inflation on the board.  You could achieve a 32MHz clock with an external crystal, and divide it to a 16MHz clock for the Arduino (thus eliminating 3 ICs).

I've thought about it a bit, and I can't come up with a way to clock out more than one pixel per clock at 25 MHz without some kind of active component assistance. (But please contact me if you have a way!)

Can you eliminate the integrated circuits?

Yes, you could get rid of the latch and the buffer and go to a transistor only setup.

I tried to demo this with common NPNs and PNPs - 2222s and 3904s  and 3906s -  but they don't have the switching speed necessary. I don't have any higher speed transistors, but I'd love if someone tried to do this with something faster. I'm thinking:

2 transistors per bus for R/G/B, one active high and one active low, then the 470 ohm resistors into the VGA port.

If you pull that off you can kill the clock circuit completely since we'll be clocking at 32 MHz a second - low clock AND high clock assuming your transistors are fast enough.

Could you do it with only passive components?

Hmm, perhaps by introducing some well timed delays with some fancy math and carefully selected components?

I don't know but tend to doubt it; my guess is you'll attenuate the signal too much for it to be useful to the monitor/TV. I believe some silicon/silicon equivalent will need to be in there. My guess is the minimum circuit is 6-8 transistors (PNP/NPNs) and some resistors.

(Please someone try to prove that wrong, though!  It would make my day to see someone improve this design!)

Download and Run the Code

Sorry I buried this - I really wanted everyone to understand the challenges first.  Usage is simple:

  1. Burn the CKOUT fuse on your AVR microcontroller - you need to Google how to do this; I did it on a Mac by modifying "boards.txt" and choosing "Burn Bootloader" in the Arduino IDE.  This puts the 16 MHz clock on pin D8 for the Nano.
  2. Next, grab Nick Gammon's Timer libraries.
  3. Third, download our code which runs the demo for the below video from here: Full640x480Video (zip)
  4. Wire up the schematic - make sure to find a way to get a doubled clock.  We suggest a PLL or using a higher clock to begin with then dividing it for the Arduino to use.  You can always do something like my clock doubler circuit, but please, please do it a better way.

Want to edit the text to something meaningful to you?

We made a script to make it easy to change the message displayed.

Click here to try it out - you just need to copy/paste the output into the fontLines16.h and fontLines16_2.h file and overwrite whatever is there to see the message transition.

(On a side note, that's Javascript compiling C which you'll copy & paste into the Arduino IDE running on Java which compiles to AVR assembly. 🤯)

Oh, of course, you do this at your own risk.  You should work through this on your own before trusting a random guy on the internet.

Since you've been waiting patiently, let's show the demo. We show off all 8 colors - each in rows 10px high, a '14 color' video rainbow made by mixing pixels, then  some (ugly, 27% timing error) writing, then 10 lines of clocking out 640 pixels of red/white mixed followed by blue/white mixed.

Full VGA on an Arduino Demo!

//www.youtube.com/embed/nmdvhgbsglQ?rel=0

Here's a tight crop so you can see that I'm actually generating a rainbow pattern at 640+ pixels wide (thanks for the healthy skepticism!):

Full crop of color bars from an Arduino
Full crop of color bars from an Arduino (Click for full width)

If you still don't believe it's possible (you must be fun at parties!):

  1. Pop up the AVR 8-bit instruction listing.
  2. Search for the 'OUT' instruction.  Note that it is a one clock instruction (16 MHz).
  3. Note that we treat each 'OUT' as 2 pixels using our external hardware (2x16MHz = 32 MHz, which is more than 25.175)

If you look at the assembly generated by our code it looks like a whole bunch of this:

  •   // PORTD = B01010100; <--- 010 Green on one pixel, 110 Yellow on the one next to it
    4c2:    9b b9           out    0x0b, r25    ; 11

What Problems Did You Face with Arduino Full VGA?

Simple: samples, samples everywhere. Errors on top of errors!

First off, you've got the ugly clock and fluctuating duty cycle which is a consequence of the clock doubler hack. That's not even the main problem though.

The biggest issue is the fact that we're now clocking """32 MHz""" into a standard calling for 25.175 MHz,. That wasn't a problem when folks were producing fewer pixels than 640 (you were guaranteed to have at least one of your pixels sampled). At 32 MHz you actually lose data with a digital monitor.

It's also far enough off that you get weird artifacts... look at that video again.

The error is huge: 32 / 25.175 =~ 27% error rate! It's safe to say there's an issue every 3-4 pixels: the monitor will sample while the latch is changing, or catch it during its propagation (low to high or high to low isn't instantly 5v or 0v) and render a color darker than we intended. In short: there shouldn't be vertical lines in the solid color horizontal bars in my video.

The third factor is technological - the monitor samples our jittery 32MHz hacked clock at 25.175 MHz to try to get 640x480.

The monitor itself? It's designed to be 1920x1080.

For this particular monitor, that's not an issue because I can get it to display double pixels at 1280x960 and letterbox the sides. But still, of course, blowing up pixels means you're zooming in on problems while the monitor is applying its own smoothing algorithms.

So... errors on top of errors on top of errors.

How Could You Improve This Project?

First,...and simply: get a cleaner clock. Best would be to clock the Arduino at 12.5875 MHz.  Second best, is to target a higher clock rate. That could be either with VESA 'non-standard standards' or other accepted timings (which aren't quite 640x480@60 - that's the gold standard). 640x480@73 fps, for example, needs a 31.5 MHz clock - that's only a 1.59% error. (Quite the cleanup from 27%.) You can also use VESA timings targeted at 32 MHz - YMMV, and you'll have to read the specs on your target monitor.

Second, you can improve color with a fourth bit of color for each pixel (remember, we divided our 8 bit processor into 2 - we're wasting a bit!). It's a nonstandard palette, but RGBI is doable. You just need to move the one sync pin.

Third, use external memory. The only practical use of this code is a silly hack for a  web site (wait a second...). There isn't enough RAM to store discrete pixel information for 640x480, and self-modifying code on a Harvard Architecture uC which can only run from flash is... not smart.

Your Turn for Arduino VGA!

So, now, go have fun with the code and the schematic!  It's released with the same license as Nick Gammon's VGA code.

Go out and make something more interesting than a poorly sampled digital signage display which shows a rainbow, then come back and share!

Don't Quit Your Day Job...

DQYDJ may be compensated by our advertising and affiliate partners if you make purchases through links. See our disclosures page for more information.
Sign Up For Emails
© 2009-2020 dqydj.com
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram