Cornell University ECE4760
VGA
640x480, 16-color, version 3
Pi Pico RP2040/2350

VGA 4-bit color with Double-buffering
Here start with Hunter Adam's VGA driver, specifically this example, then extensively modify it to support double-buffering on the rp2350.
This is a basic 2D graphics interface which draws text and geometric shapes.
We add double-buffering to eliminate the annoying flicker caused by single buffer systems.
Features:
-- This version works with both 2040 and 2350 because main sets the clock to a standard 150 MHz.
-- It can support double-buffering ONLY on rp2350
-- There are 16 color values with two green bits, one red bit and one blue bit.
-- Drawing shapes and text are much faster. Screen erase is also faster.
-- Drawing filled triangles is supported, also polylines.

-- All drawing routines are now re-entrant and safe to use on both cores.
-- This VGA version is compatable with LWIP for tcp/ip networking.

-- It uses Protothreads 1.4 with options for roundrobin or priority scheduler.

Internal structure of the driver

The VGA implementation uses:
- PIO state machines 0, 1, and 2 on PIO 0;
the three state machine need to be on one PIO instance, so they are unconditionally hard-claimed.
You must initialize VGA before other libraries claim state machines!
- Four DMA channels determined by the dma_claim_unused_channel routine
(this is to conform to the protocol ued by LWIP for DMA channels)
- 153.6 kBytes of RAM (for 640x480 pixel, 4-bit, color data) and twice that memory, if double buffering is turned on.
The two memory screen buffers (buffer0 and buffer1) alternate betwween being the display buffer and the draw buffer.

The four DMA channels send data to the screen, set drawing and display buffers, and signal the drawing thread.,
-- The three PIO machines control the first DMA channel to push the current display buffer to the VGA monitor.
-- After an entire frame is drawn, the DMA channel chanis to a second DMA channel which resets the display buffer pointer to the other screen memory.
-- The second channel chains to a third channel which resets the draw buffer pointer to the buffer not set by the second channel.
-- The third channel chains to a fourth channel which tells the application thread it is time to redraw in the new draw buffer.
-- The fourth channel chains back to the first channel to start the next frame.
The buffer swaps and signalling the thread takes place in less than a microsecond.
Your application thread must check the status supplied by the fourth channel before starting a new frame.

The PIOs supply the sync signals to the VGA as well as the graphics content. We chose to map the VGA drive pins to gpio 16 through 21.
The blue and red VGA lines are driven directly through 330 ohm resistors, as in Hunter's implementation.
The VGA-green connection requires two resistors connected to the VGA-green line
If you don't like my color balance for bits on the green channel, try varying the 470 ohm resistor.
- GPIO 16 ---> VGA Hsync
- GPIO 17 ---> VGA Vsync
- GPIO 18 ---> VGA Green lo-bit --> 470 ohm resistor --> VGA_Green
- GPIO 19 ---> VGA Green hi_bit --> 330 ohm resistor --> VGA_Green
- GPIO 20 ---> 330 ohm resistor ---> VGA-Blue
- GPIO 21 ---> 330 ohm resistor ---> VGA-Red
- RP2040 GND ---> VGA-GND

Using vga16_v3

There are a few required steps to set up the DMA to thread communication:

There are five demo programs that show off the version 3 graphics and which I used for testing.

ZIP of project includes driver, fonts, and demo code
vga16_graphics_v3.c -- vga_graphics_v3.h

Video showing test of the drawing primitives and double buffering:

List of Functions.
Note that older text functions still work, but are not listed here.

Initialization and DMA signals Description
void initVGA(void) Call once to set up 4 DMA channels and 3 PIO state machines and
init the double buffering.
int draw_start_signal(void);
Returns non-zero when the draw/display buffers are swapped.
The function also clears the start signal.
int get_buffer_type(void)
Returns 1 for 60Hz double-buffer, 2 for 30Hz double-buffer,
and 3 for no double-buffer.
Buffer copy
 
void copy_buffer0to1(void)

Copies the buffer0 to buffer1, Typically only used once to init static display.

void copy_buffer1to0(void) Copies the buffer1 to buffer0, Typically only used once to init static display.
void copy_buffer_to_other(void) Copies the draw buffer to display buffer, Typically only used once to init static display.
Erase  
void clearRect(short x1, short y1, short x2, short y2, short color) Clears a region of the draw buffer from (x1,y1) to (x2,y2) to a color .
For speed, there is NO error or bounds checking!
void clearLowFrame(short top, short color)
Clears draw buffer below a top y value. Fastest erase routine.
Again, NO bounds checking.
void clearRegion(short y1, short y2, short color) Clears draw buffer from y1 to y2. Again, NO bounds checking
Text NOTE that all of the text routines require x to be even.
An odd value of x is modified one pixel to the left to make it even.
int drawTextAscii(short x, short y, char * str, char color, char bgcolor);
Draw one line of text in a 5x7 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn. This was included because the character bit maps are easily hacked.
int drawTextGLCD(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 5x7 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
int drawTextTiny8(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 8x8 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn. This is an efficient, small font.
int drawTextVGA437(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 8x16 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn. This font is copied from the 1981 IBM ROM.
int drawTextArial24(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 16x24 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
int drawTextGrotesk32(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 16x32 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
Outline shapes  
void drawPixel(short x, short y, char color) ;
Draw one pixel of specified color at (x,y). Will not draw outside 640x480 region.
void drawVLine(short x, short y, short h, char color)
Draw a vertical line of specified color from (x, y) to (x, y+h)
void drawHLine(int x, int y, int w, char color) Draw a horizontal line of specified color from (x, y) to (x+h, y)
void drawLine(short x0, short y0, short x1, short y1, char color) Draw a line of specified color between two end points. Uses Bresenham alogrithm.
void drawRect(short x, short y, short w, short h, char color);
Draw a rectangle of specfied color from corner (x, y) to (x+w, y+h)
void drawCircle(short x0, short y0, short r, char color) ;
Draw a circle of specifed color with center (x0,y0) and radius r.
void drawRoundRect(short x, short y, short w, short h, short r, char color) ; Draw a rectangle, but with rounded corners of radius r. For sanity, w>2*r and h>2*r
void drawMultiLine(int num_points,  short point_list[][2], char color) ;
Draws (num_points-1) lines of specified color using an array of x,y points point_list[num_points][2].
void crosshair(short x, short y, short c)
Draws a little cursor-like cross of specifed color at x,y.
Filled shapes  
void fillRect(short x, short y, short w, short h, char color)
Fills a rectangle with specfied color from corner (x, y) to (x+w, y+h)
void fillCircle(short x0, short y0, short r, char color)
Fills a circle with specifed color with center (x0,y0) and radius r.
void fillRoundRect(short x, short y, short w, short h, short r, char color) ; Fills a rectangle with riounded corners of radius r. For sanity, w>2*r and h>2*r
void fillTri(float x0, float y0, float x1, float y1, float x2, float y2, char color)
Fills a triangle specifed by three vertices with the specified color

 


Copyright Cornell University December 31, 2025