Mar 28, 2013

Experimenting with AVR micros and VGA signal

Ever since I saw this (Craft by lft) MCU demo that won the Breakpoint 2008 Real Wild demo compo, I've been wanting to be able to do something similar. Well..it's still quite a long way to that kind of masterpiece but in this first part I'll clarify a little bit about the principles of generating the VGA-signal and also some "as simple as possible" code examples.

Basically the VGA signal consists of red, green and blue analog signals and H-sync & V-sync digital signals (5V). As you may guess, the voltage (0.0 - 0.7V) of each R/G/B wire tells the intensity of its colour. H-sync pulse is carried out after the end of each horizontal line and V-sync after each drawn screen.



























This is an illustrative view of one frame. Of course the real one would have 480 horizontal lines as this have only 11 for clarity. The easiest way to understand the picture is to think about old CRT (cathode ray tube) monitor. When the ray "hits" reactive material of display's inner surface, the area glows for a while. When this happens often enough (60Hz) we'll see steady image.

The beam draws the image quite much same way as you could write a letter (maybe slightly faster :) ). It starts the screen from upper left corner, writes the first line by going right, then H-sync signal tells that it's time to start the new line so the beam moves to the beginning of the next line and then start writing again. After reaching the end of the first frame, V-sync signal tells that it's time to start the new frame and all this starts again from the upper left corner of the screen. As the monitor draws 60 frames per second, there's 60 V-sync pulses and 31440 (60 frames * 524 lines) H-sync pulses per second. Because it takes some time to relocate the beam at the end of line or screen, there's "blanking areas" (bluish areas in the pic) where the beam is still going but it's actually outside of the visible area of the monitor. In these areas (front and back porch) R/G/B outputs have to be at 0 volts. It's common technique from old game consoles to use these blanking areas to generate audio and run other calculations when all the cpu time isn't going to generating of video signal.

After that you may realize that generating vga signal isn't so complicated as itself but the timing is everything! While the ray travels about 10 kilometers/second (6.2 miles/sec) there's no space for timing errors as we can see later.

The following figure shows all 5 signals relative to each other. Note that V-sync graph isn't in same scale as others.


24-bit image, 16.8M colours
6-bit image, 64 colours
Exact timing values can be found in here. The ideal clock speed for microcontroller would be 25.175MHz because with that clock speed every needed delay/timing would match exactly with N*clock cycle. But no worries. Monitors can stand fairly well small differences in timing values and frequencies so also other clock speeds can be used by calculating the closest possible delay times you can archieve with your particular clock frequency. The most important thing is that H-sync pulse and active video signals doesn't have any timing differences at all. Jitter in these causes wobbly and shaky image.
As you may deduce, the vertical resoluiton is always that same 480px in this display mode but the amount of horizontal pixels doesn't matter at all. For example if every R/G/B line is kept in same state whole active video sequence (25.4us) you'll get one pixel that is very wide. Or if rgb output values are changed every 0.254us, you'll get 100px wide image. Because of limitations of ATmegas processing speed we can't reach the full 640x480 resolution. The next reasonable step down is to draw every pixel twice as wide and every line also twice. Now we have 320x240 resolution. ATmegas should be fine with that.

These common ATmegas are 8-bit MCUs so it means that you are able to set only 8 output pins at once / simultaneously. This limits the max reasonable bit depth to 8-bit (256 colours). Because 3x 2-bit DACs would make only 64 colours and 3x 3-bit wouldn't fit in 8-bit limit, we could build 3x 2-bit DACs and then use the extra 2 pins to control the brightness of the colour so we get 256 colours. I used only 6-bits in my first experiment so there's 64 colours.


Click to enlarge

Assembly would be ideal language to write the vga code because you don't have to guess what C compiler is doing behind the scenes and you'd be able to optomize your code to run as fast as possible in every situation. I decided to save the ASM codes for a possible part 2 and made everything with C. I started by trying different techniques and ended up to use timer interrupt service that draws one line and H-sync every time when called. It also controls vertical amount of the lines and V-sync pulses.

My first working vga-code printed some "binary stripes" to screen. It simply counted from 0 (0b00000000) to 63 (0b00111111) and put the results in PORTC (3x 2-bit DACs connected to pins 0-5). Here it is presented as pseudocode (not any real programmin language or even fully working program, just easy to understand the idea):

// Timer interrupt service presented as pseudocode

ISR(timer1 interrupt routine){  // Runs every 31.8us
    line = line + 1;
    wait;  // Wait for 0.84us "front porch" after previous line
    turn HSYNC on; // 0 volts
    wait;  // Wait 3.8us (keeping the HSYNC active)
    turn HSYNC off; // 5 volts
    wait;  // Wait for 1.9us "back porch" before starting active video sequence

    if (run if line is 480 or less){  // If line is in visible area, run the code that produces the image.
        for(run 64 times by counting from 0 to 63){  // Counts from 0 to 63 in PORTC
            PORTC = value of for-loop variable;  // Set output
        }
        PORTC = 0; // Print black screen after the stripes (right side of screen)
    }
    else if (run if line is 492){
        turn VSYNC on; // 0 volts
    }
    else if (run if line is 494){
        turn VSYNC off; // 5 volts
    }
    else if(run if line is 524){ // One frame is complete, start another in next round.
        line = 0;
    }
}

Source files for ATMega32 @ 16MHz:

binary_stripes.c
vga_moving_palette.c

Fuse bits for ATMega 32:
-> High: 0xD9
-> Low: 0xFF

Fuse bit calculator: http://www.engbedded.com/fusecalc/


The code above should print stripes like that (slightly different colours but same principle). I had only 16MHz crystal so the horizontal resolution was quite poor. That was maybe the simplest vga program you can make. The next step could be adding some objects and specific colours. Simple square-shaped colour pattern might be quite easy to implement after understanding inner working of that first program.

In the second code example (colour pattern table) I used 8x8 array where all the colour values are stored (0bxxRRGGBB). For example:
0b00110000 = pure red 
0b00110011 = violet 
0b00000001 = dark blue
0b00111100 = yellow
And so on..

The square is made just by limiting upper and lower borders by testing that "if line is less or more than..." and same thing for left and right borders but at this time we'll modify the same for loop that made "binary stripes" in the first example to get the values from the array instead of just counting from 0 to 63.

At this point we got the first timing issue. When we are testing during active video sequence if it's time to change the row of the array, every last line of the specific pattern keeps going longer than others because processing takes some clock cycles more than normally (click the image to enlarge -> ). Of course it's possible to balance the delays with different tricks but it will be endless amount of work with C if the program grows bigger.

Since the whole 320x240 sized "display memory" won't fit in ATmegas RAM, we should manage to get the algorithm that uses blanking areas to prepare the incoming line beforehand and store only that to RAM at once. 




 
Ok. That was it for now. Let's see if I'll find some time to make part 2 with real-time calculated graphics, better optimized ASM-code and audio.

6 comments:

  1. The most interesting thing I've found is information about timing and creating VGA signal in a tiny AVR with memory.

    ReplyDelete
  2. not working with my samsung 17" LCD. I am using ATmega16 with 16MHz crystal oscillator, resistances are 1k and 2k instead of 820 ohm and 1800 ohm respectively

    ReplyDelete
    Replies
    1. Umm, can you figure out if it outputs any signal at all? I've tried them with 19" ViewSonic, 42" LG TV and 14" CRT monitor. If it doesn't output anything, I'd recommend to check AVR Studio "optimization" settings. For some reason it didn't work with "-O0". I'm using "-O1". Maybe I could upload .hex files and try to figure out the problem if you can't get it to work.

      Delete
  3. Hi. I think resistor values must be oposite. Now R1=820ohm and R2=1800ohm.
    That makes following voltages:
    PC0,PC1 -> Voltage
    -----------------------------
    0,0 -> 0V
    0,1 -> 0.403V
    1,0 -> 0.183V
    1,1 -> 0.587V

    I think you have to swap values of R1 and R2.
    Then you will have

    0,0 -> 0V
    0,1 -> 0.183V
    1,0 -> 0.403V
    1,1 -> 0.587V

    ReplyDelete
    Replies
    1. That's true! Thanks for the correction. :) It causes some colour distortion if not noticed. I will modify the schematic picture.

      Delete
  4. Great correction by a Vladimir :)
    Here is Demo of Haptic compass band

    ReplyDelete