Generating a VGA output from a microcontroller using a simple resistor DAC is one of the most impressive projects you can build with an Arduino. By wiring just a handful of resistors to your microcontroller’s digital pins, you can drive a standard VGA monitor to display graphics, text, and animations — all without a dedicated graphics chip. This tutorial covers the theory behind VGA timing signals, how to build a resistor DAC for colour output, and the complete Arduino code to bring it all to life.
Table of Contents
- VGA Signal Basics
- Building the Resistor DAC
- Wiring the Arduino
- Arduino VGA Code
- Colour Depth and Resolution
- Common Issues and Troubleshooting
- Recommended Products
- Frequently Asked Questions
VGA Signal Basics
VGA (Video Graphics Array) uses analogue signals for colour and digital TTL signals for synchronisation. The connector carries five key lines: Red (0–0.7V), Green (0–0.7V), Blue (0–0.7V), Horizontal Sync (HSYNC), and Vertical Sync (VSYNC). A standard 640×480 @ 60 Hz VGA signal requires precise timing: pixel clock 25.175 MHz, 800 pixels per horizontal total line (640 visible + blanking), 525 lines per frame (480 visible + blanking), HSYNC pulse 96 pixel clocks wide (active low), and VSYNC pulse 2 line times wide (active low).
The Arduino Uno runs at 16 MHz, which cannot achieve the exact 25.175 MHz pixel clock. However, by using clever timer configuration and tight assembly-level loops, you can generate a 16 MHz pixel clock that most modern monitors accept due to their wide sync tolerance.
Building the Resistor DAC
A VGA monitor expects analogue voltages between 0 V and 0.7 V on each colour channel, loaded by a 75-ohm termination resistor inside the monitor. An Arduino’s digital output swings between 0 V and 5 V, so we need a voltage divider network — a resistor DAC — to scale the signal correctly.
For a 2-bit DAC per colour channel (4 shades per colour, 64 colours total), use this configuration for each channel:
- MSB pin → 470 Ω to VGA colour pin
- LSB pin → 1 kΩ to VGA colour pin
- VGA colour pin has 75 Ω monitor termination (internal)
For a 3-bit DAC (512 colours), add a third resistor per channel: Bit 2 (MSB) → 270 Ω, Bit 1 → 560 Ω, Bit 0 (LSB) → 1.2 kΩ. Use 1% tolerance resistors for accurate colour reproduction.
Wiring the Arduino
Connect Arduino digital pins to the VGA DE-15 connector as follows. Keep all signal wires under 10 cm to minimise capacitance and ringing at high frequencies. All RGB ground returns (VGA pins 6, 7, 8) must connect to Arduino GND.
- D6 → Red MSB → VGA pin 1 via 470 Ω
- D7 → Red LSB → VGA pin 1 via 1 kΩ
- D4 → Green MSB → VGA pin 2 via 470 Ω
- D5 → Green LSB → VGA pin 2 via 1 kΩ
- D2 → Blue MSB → VGA pin 3 via 470 Ω
- D3 → Blue LSB → VGA pin 3 via 1 kΩ
- D9 → HSYNC → VGA pin 13 via 68 Ω
- D10 → VSYNC → VGA pin 14 via 68 Ω
- GND → VGA pins 5, 6, 7, 8, 10
Arduino VGA Code
The following sketch generates a 160×120 resolution colour test pattern using Timer1 for precise HSYNC generation. Pixel data is output through PORTD for maximum speed.
// VGA Output using Resistor DAC
// Arduino Uno - 2-bit RGB (64 colours)
// HSYNC on D9, VSYNC on D10, RGB on D2-D7
#include <avr/interrupt.h>
#include <avr/io.h>
#define V_PIXELS 480
#define V_FRONT 11
#define V_SYNC 2
#define V_TOTAL 525
volatile uint16_t currentLine = 0;
volatile bool drawPixels = false;
// Frame buffer: 80 columns x 60 rows
// Each byte = 0bRRGGBB00
uint8_t frameBuffer[60][80];
void generateTestPattern() {
for (int y = 0; y < 60; y++) {
for (int x = 0; x < 80; x++) {
uint8_t r = (x / 20) & 0x03;
uint8_t g = (y / 20) & 0x03;
uint8_t b = ((x + y) / 20) & 0x03;
frameBuffer[y][x] = (r << 6) | (g << 4) | (b << 2);
}
}
}
void setup() {
DDRD |= 0b11111100; // D2-D7 as output (RGB)
DDRB |= 0b00000110; // D9, D10 as output (HSYNC, VSYNC)
generateTestPattern();
// Timer1 CTC mode for HSYNC timing
TCCR1A = (1 << COM1A0);
TCCR1B = (1 << WGM12) | (1 << CS10);
ICR1 = 400; // 16MHz / 400 = 40kHz line rate
TIMSK1 = (1 << OCIE1A);
sei();
}
ISR(TIMER1_COMPA_vect) {
currentLine++;
if (currentLine >= V_TOTAL) currentLine = 0;
// VSYNC pulse
if (currentLine >= (V_PIXELS + V_FRONT) &&
currentLine < (V_PIXELS + V_FRONT + V_SYNC)) {
PORTB &= ~(1 << 2);
} else {
PORTB |= (1 << 2);
}
drawPixels = (currentLine < V_PIXELS);
}
void loop() {
if (!drawPixels) return;
uint16_t ln = currentLine;
if (ln >= V_PIXELS) return;
uint8_t fbRow = ln / 8;
uint8_t* row = frameBuffer[fbRow];
for (uint8_t x = 0; x < 80; x++) {
PORTD = row[x];
asm volatile("nopnnopnnopnnopnnopnnopnnopnnop");
}
PORTD = 0;
}
Colour Depth and Resolution
The number of pins available on your microcontroller directly determines achievable colour depth and resolution. With 1-bit RGB (3 pins + 2 sync = 5 pins total) you get 8 colours and roughly 120×60 resolution. With 2-bit RGB (6 pins + 2 sync = 8 pins) you achieve 64 colours at 160×120. A 3-bit RGB DAC uses 11 pins for 512 colours but the Arduino Uno runs out of processing headroom to write pixel data fast enough while maintaining sync.
For higher resolutions and true colour, the ESP32-S3 at 240 MHz with its integrated LCD peripheral is a far better platform — it can drive TFT panels natively at 320×240 full colour without any external DAC circuitry.
Common Issues and Troubleshooting
No signal: Check HSYNC frequency (must be 30–60 kHz for most monitors). Verify all GND connections on the VGA cable — a floating ground is the most common cause.
Rolling image: VSYNC timing is incorrect. Ensure your line counter wraps at exactly V_TOTAL (525). Off-by-one errors cause slow vertical roll.
Wrong colours: Wrong resistor values cause incorrect voltage levels. Measure the VGA colour pin voltage with a multimeter — full-on should read ~0.7 V at pin 1/2/3 with 75-ohm termination.
Flickering pixels: Interrupt jitter is disrupting pixel timing. Output pixels inside the ISR itself, or disable interrupts during the active pixel region using cli()/sei().
Recommended Products
1.8 Inch SPI TFT LCD Display Module for Arduino
A compact 128×160 colour TFT with SPI interface, ideal for beginners learning colour graphics before tackling VGA output. Uses the ST7735 driver with full Adafruit GFX library support — a great comparison to a DAC-driven VGA approach.
2.4 Inch Touch Screen TFT Display Shield for Arduino UNO/MEGA
This shield plugs directly onto an Arduino Uno or Mega and provides a 320×240 colour display with resistive touch — showing what a dedicated display controller can achieve compared to a resistor DAC VGA hack, with no external monitor required.
3.5 Inch TFT Touch Screen Display for Arduino
For larger display requirements, this 480×320 TFT with resistive touch is an excellent alternative to the resistor DAC VGA approach. Plug-and-play with the MCUFRIEND_kbv library, offering 16-bit colour and touch input in a convenient shield form factor.
Waveshare 2.8 Inch Touch LCD Shield for Arduino
Waveshare’s 240×320 TFT with ILI9341 controller is ideal for projects needing a reliable, well-documented display interface. Works as both a standalone module and Arduino shield, with full resistive touch support and stylus included.
Frequently Asked Questions
Can I use a 3.3 V microcontroller like ESP32 for VGA output?
Yes. With a 3.3 V MCU, recalculate resistor values so the output at the VGA pin reaches 0.7 V. Use the formula R_series = (V_high / 0.7 − 1) × 75 Ω. For 3.3 V, R_series ≈ 280 Ω for the MSB.
Will every VGA monitor work with Arduino VGA output?
Most modern TFT monitors with VGA inputs sync to non-standard timing generated by an Arduino. CRT monitors are more tolerant. Try multiple refresh rate targets (60 Hz, 72 Hz, 75 Hz) if your monitor doesn’t lock on.
What resolution can an Arduino Uno realistically achieve?
Practical limits are around 160×120 for 2-bit colour. The VGAx library achieves 120×60 at 8 colours. For 640×480 with 8+ bit colour, use an STM32F4 (168 MHz), ESP32-S3, or FPGA.
Is there a library that makes VGA output easier on Arduino?
Yes — the VGAx library by smaffer generates true 8-colour VGA on Arduino Uno. For STM32, the arduino-vga library supports up to 800×600. TVout is another option for composite video output.
Can I display text and fonts over VGA on Arduino?
Yes. The VGAx library includes basic 6×8 pixel font rendering. Define a character lookup table in PROGMEM and write character pixels row by row during the active video region of each horizontal line.
Add comment