VGA Signal Configuration

This is a rough guide to VGA configuration and limitations for those wanting to do "normal" resolutions, double buffered graphics, and anyone else counting kbs for their memory use. At the bottom are scripts and notes for VGA timing calculation and GPU settings.

Getting Started

First thing's first: what resolution do you want to display at and how many individual colors do you want to support? Use of the Color Look Up Table (CLUT) makes it so that your palette can have any 16 bit colors, but your choice of resolution and true bits per pixel (BPP) will affect how big your palette can be and how much memory you need to use for your graphics frame buffer(s). The basic rule of thumb is that more resolution means less colors.

Common Resolutions BPP vs. Memory Usage

Res: Bits per pixel (BPP), Memory Usage (96k max on chip), (D=Double buffering possible):

640x350 @ 70Hz: 1bpp 28.0k (D), 2bpp 56.0k
640x400 @ 70Hz: 1bpp 32.0k (D), 2bpp 64.0k
640x480 @ 60Hz: 1bpp 38.4k (D), 2bpp 76.8k
320x480 @ 60Hz: 1bpp 19.2k (D), 2bpp 38.4k (D), 4bpp 76.8k
160x480 @ 60Hz: 1bpp 09.6k (D), 2bpp 19.2k (D), 4bpp 38.4k (D), 8bpp 76.8k
080x480 @ 60Hz: 1bpp 04.8k (D), 2bpp 09.6k (D), 4bpp 19.2k (D), 8bpp 38.4k (D), 16bpp 76.8k

Note: You have a maximum of 96k RAM. If you want to full double buffered framebuffers you need twice the memory value in the table above. There's ways to cheat other colors onto the screen in low BPP modes, but they come with their own limitations and implementation difficulties that I leave you to discover!
Note: It's also technically possible to store the framebuffer in program memory where you have 256K of space. Let us know if you have a working implementation of this!

VGA Modelines and CLOCKDIV settings

Now that you've chosen your resolution it's time to figure out all the VGA settings to make it display properly. The easiest way to do so for non-standard resolutions is to generate a modeline string which has the timings. Modeline strings can be generated with

Common resolutions:
"640x350@70" 20.10 640 672 744 776 350 357 361 368 (CLOCKDIV 15)
"640x400@70" 23.36 640 672 760 792 400 408 413 421 (CLOCKDIV 12)
"640x480@60" 24.11 640 672 760 792 480 490 495 505 (CLOCKDIV 11)
"320x480@60" 13.15 320 352 400 432 480 490 495 505 (CLOCKDIV 25)
"160x480@61" 7.67 160 192 216 248 480 490 495 505 (CLOCKDIV 46)
"80x480@61" 4.93 80 112 128 160 480 490 495 505 (CLOCKDIV 69)

Here's an example of how to interpret a modeline with the 640x480@60 string:

24.11 is your desired pixel clock. The equivalent CLOCKDIV setting is 11 (see below for how to generate these).

The differences between: 640 672 760 792
are H-Front Porch, H-Pulse Width, H-Back Porch

The differences between: 480 490 495 505
are V-Front Porch, V-Pulse Width, V-Back Porch

Example 640x480 @ 60HZ 1 BPP GPU configuration:

 // This configuration uses "industry standard" timings which don't 100% match up with the modeline timings for 640x480@60Hz.
// For most other resolutions, the modeline data will be sufficient because there aren't any industry standards to go by.

#define HOR_RES 640UL
#define VER_RES 480UL
#define HOR_FRONT_PORCH 16
#define HOR_PULSE_WIDTH  96
#define HOR_BACK_PORCH 48
#define VER_FRONT_PORCH 10
#define VER_BACK_PORCH 33
#define CLOCKDIV 11
#define BPP 1

__eds__ uint8_t GFXDisplayBuffer[GFX_BUFFER_SIZE] __attribute__((eds));

void config_graphics(void) {
        _G1CLKSEL = 1;

    G1DPADRL = (unsigned long)(GFXDisplayBuffer) & 0xFFFF;
    G1DPADRH = 0;
    G1W1ADRL = (unsigned long)(GFXDisplayBuffer) & 0xFFFF;
    G1W1ADRH = 0;
    G1W2ADRL = (unsigned long)(GFXDisplayBuffer) & 0xFFFF;
    G1W2ADRH = 0;

    G1PUW = HOR_RES;
    G1PUH = VER_RES;

    _GDBEN = 0xFFFF;

    // Using PIC24F manual section 43 page 37-38
    _DPMODE = 1;      /* TFT */
    _GDBEN = 0xFFFF;
    _DPW = _PUW = HOR_RES; // Work area and FB size so GPU works
    _DPH = _PUH = VER_RES;
// _DPHT may need to be adjusted for vertical centering
    _DPCLKPOL = 0;
    _DPENOE = 0;
    _DPENPOL = 0;
    _DPVSOE = 1;      /* use VSYNC */
    _DPHSOE = 1;      /* use HSYNC */
    _DPVSPOL = 0;     /* VSYNC negative polarity */
    _DPHSPOL = 0;     /* HSYNC negative polarity */
// _ACTLINE may need to be adjusted for vertical centering
    _DPPWROE = 0;
    _DPPINOE = 1;
    _DPPOWER = 1;
// Needs to be adjusted for BPP > 1
    _DPBPP = _PUBPP = 0;
    _G1EN = 1;

Additional notes:

How do I calculate CLOCKDIV?
CLOCKDIV is the microcontroller setting for CPU speed. The modeline generators spit out the literal CPU speed, but you need to set this value to an integer value that corresponds to the speed you want. You can generate clockdiv values with:

perl -e '$d = 1; $x = 0; while($x < 128) { printf "%s %.2fHz\n",$x,96/$d;if($x < 64) { $d += 0.25; } elsif($x < 96) { $d += 0.50 } else { $d += 1 }; $x++}'

How big should my framebuffer be?
Framebuffer memory usage is calculated for <=8bpp with (HOR_RES*VER_RES)/(8/BPP) - equation works for 16bpp but you need to make sure BPP is a float or you'll get a zero division

Note that the literal chip setting for _(DP|PU)BPP is the log(BPP,2) (aka power of 2) of your BPP; bit shifting method:

    int logc=0;
    while (BPP>>logc > 1) { logc++; }
    _DPBPP = _PUBPP = logc;

OR, with floating point / log() support:

    _DPBPP = _PUBPP = log(BPP,2); // BPP must be a power of 2

Can I do other resolutions than what is listed above?
YES. But you'll need to figure out all the settings and spend some time debugging in the event your monitor doesn't display anything / displays something incorrectly.

Why do I need double buffering?
Double buffering is a popular graphics programming technique for simplifying the act of drawing to the screen. If you use a single buffer, you'll probably get flickering caused by wiping the buffer to draw a new frame. Double buffering is the easiest solution for this, but it does make your memory usage skyrocket as a result. It's possible to write your code in such a way that you don't need double buffering (by using an algorithm that draws only changes and cleans up the previous frame without fully clearing it, or by chasing the VGA syncs and always drawing behind the active pixel), but if you were comfortable doing either of those you wouldn't be reading this page : )

Why can't I draw to the last on-screen column?
Drawing a non-black color to the last column of your screen causes some confusion between TFT and VGA specs. In VGA, when you're done drawing each horizontal line you enter horizontal back porch and all VGA RGB pins should be set to 0x00 per spec. TFT doesn't have this restriction, so it does weird stuff when you draw a color to the last column/during back porch time. Typically it looks like that color takes over the screen, flickers, and causes you to enter The Matrix. There's likely some minor timing issue at the root of this, but the easy way to avoid it is to draw a black column for HOR_RES-1 each frame.

Why is my screen covered with white/black dashes and lines?
This happens when you draw OUTSIDE your framebuffer. The GPU is not smart about the raster operations on the framebuffer and can/will overwrite other parts of memory, cause memory access violations, and other fun stuff. Typically it looks like a row of white dashes/lines on a black background.

Can I get past BPP restrictions with the CLUT?
The Color Look Up Table (CLUT) can be used to assign any 16 bit color value to particular pixels by switching the GPUs interpretation of the framebuffer values from literal colors to palette indexes. This can be used to get more color out of lower BPP configurations, but it is primarily limited to VERTICAL color changes. During the act of drawing each horizontal scanline, attempts to change the CLUT will cause color stretching and blank areas as a result of the CLUT being inaccessible to your program while it is being modified. During the act of moving from one hortizontal scanline to the next (SYNC/Porch time), you can toggle the CLUT values without any on-screen distortions. Please note that you need to draw black to the rightmost column of your framebuffer to fix an issue with monitor ambiguity between TFT and VGA signal specifications. We have not yet found a way to control the color of all individual pixels in a low BPP framebuffer. If you find a way to do this please let us know!

Last edited by datagram (Jan 27 2015 10:36 am)