VGA Curiosity

Vga

The idea of driving a VGA display directly from the I/Os of a small 8-bit microcontroller might seem crazy at first, and perhaps because of this, it is certainly entertaining, and possibly useful to some of you.

It is actually easier than most would believe, especially with a modern PIC16F1 microcontroller thanks to a number of small but critical features:

  1. The oscillator circuit has a convenient 4x PLL that can can boost the internal oscillator frequency to 32MHz without requiring an external crystal.
  2. The EUSART module can operate in Synchronous mode up to 8MHz (and is dual buffered).
  3. The Enhanced Mid-Range core (PIC16F1) has dual indirect pointers with auto-increment/decrement
  4. MPLAB Code Configurator can help to create the project skeleton in minutes.

Granted the amount of RAM available is too small to do a proper graphical display (not to mention to make full use of color), it is though possible to achieve a text page (25×20), with a relatively small sacrifice while still leaving enough space for an actual application.

There are only three signals that need to be generated: luminance, horizontal sync and vertical sync, but contrary to composite video systems (such as those explored in my previous books) here the three are kept on separate pins/wires. On a PIC16F1619 (for example) the PPS feature allows us to remap our peripheral outputs to any (digital) pin, therefore we are free to allocate the three signals on any of the (Curiosity) board connectors. I chose RB6/5/4 , but you will be free to re-map as you see fit,

The luminance signal is the one that contains the actual stream of pixels. It can be connected in parallel to the three RGB inputs (pin 1,2, 3 of the VGA connector) to achieve a white output or just one of them to obtain a specific color. (Note: if all three are connected to independent I/Os, the PPS can be used to change the color output at least on a line by line basis). To obtain a contiguous sequence of characters it is important to use a peripheral that can serialise data while providing double buffering (one byte of data is transmitted while another one can be loaded before its completion). The EUSART module is the only one to satisfy this need if set in Master Synchronous mode.

The horizontal sync signal can be generated simply by a PWM module (PWM4 + Timer2). The period is dictated by the VGA specs (31.5us) as well as the duty cycle. Since the effective video window out of each period is only 25us large, the maximum number of pixels that can be generated on a line is dictated by the period divided by the maximum baud rate of the EUSART  in Synchronous mode, which is 8MHz (Fosc/4). This results in approximately 160 pixels or 20 horizontal characters (using an 8×8 font).

The vertical sync signal is generated on a much lower frequency (approx. 60Hz) and is based on a simple counter. There are 525 lines to be counted in each frame of which only 400 are actually used in the demo to produce 25 lines of text. To keep the aspect ratio of images close to a 1:1, those 400 lines are reduced 2:1 by ensuring that each odd line repeats the contents of the previous.

Synchronisation among all three signals can be maintained by a very simple state machine (based on four states) running off the interrupt service routine (ISR) of the only (8-bit) timer necessary (TMR2).

void interrupt VGA( void)
{
   static char line = 16; 
   switch ( VState) {
     case SV_LINE:   // 3 serialize the current line
       VPtr = LPtr;  // reset text pointer
       ...
       break;
     case SV_PREEQ:  // 0
       VPtr = VMap;  // prepare for the new frame
       break;
     case SV_SYNC:   // 1
       VS_SetLow();  // vertical sync pulse
       break;
     case SV_POSTEQ: // 2
       VS_SetHigh(); // horizontal sync pulse
       LPtr = VMap;  // reset text pointer to top of page
       break;
    } //switch
    // advance the state machine
    if ( --VCount == 0) {
        VCount = VC[ VState & 3];
        VState = VS[ VState & 3];
    }
    PIR1bits.TMR2IF = 0;
} // VGA state machine

Inside the SV_LINE state, is where we will insert instructions to serialise the contents of the text page. This requires a two step indirection:

  • First each character needs to be extracted sequentially from a screen buffer.
  • Then its ASCII code can be used as an index inside a table (font) line by line.

In C this would be expressed as follows:

TX1REG = pRFont[*VPtr++];
[ repeated 19 more times]

And it would be repeated 20 times throughout each line. The two indirection operations (*Vptr and pRFont[] ) can be performed on the PIC16F1 core by using two independent pointers (FSR0 and FSR1). Unfortunately no matter how smart the (MPLAB XC8) compiler is, it will fail to recognise the repetitive nature of the process and identify a common invariant (loading of the two FSRx) throughout the 20 repetitions. The code produced would be too slow resulting in large gaps between characters on screen.

So this is one of those cases where we can prove that a small bit of assembly language can go a long way. We can take matters in our hands and extract the pointers loading operation on top:

  // pre load FSR0 with _pRFont (pointer into the font) 
 asm("movlb 0");
 asm("movf _pRFont, w");
 asm("movwf 0x7F");
 asm("movf _pRFont+1, w");
 asm("movwf FSR0H");
 // pre load FSR1 with _VPtr (pointer into the screen matrix) 
 asm("movf _VPtr, w");
 asm("movwf FSR1L" );
 asm("movf _VPtr+1,w"); 
 asm("movwf FSR1H ");
 asm("movlb 3");

While a simpler/faster macro will take care of the 20 repetitions:

 #define mSendByte() asm("moviw FSR1++"); \
                     asm("addwf 0x7F,w"); \
                     asm("movwf FSR0L"); \
                     asm("movf INDF0,w"); \
                     asm("movwf TX1REG &0x7f"); \
                     NOP(); \
                     NOP();

Notice the last two NOPs included as the new macro makes the sequence too fast and risk over-running the EUSART buffer.

A simple implementation of the putch() function is sufficient to enable automatically the redirection of all stdio.h functions (printf, puts…) to the screen.

So if you have an old VGA display around, go ahead and give it a try! You will find the complete project added to the Rocket repository on GitHub together with the previous examples and demos developed for the Rocket Science book.

This entry was posted in Graphics, PIC16, Tips and Tricks and tagged , , , , . Bookmark the permalink.