In order to make a VGA driver for a PIC 24F microcontroller, I am generating an interrupt creating a VSYNC pulse, followed by a loop that puts data to the ports. HSYNC pulses are calculated after this video loop. All timings are correct, and my CRT monitor accepts the generated signal. However, I can only write literal data for the moment, and would like to be able to write arrays filled with data to my screen. This code is currently working:
MOV #80,W0 VID_LOOP: BSET LATB,#4 BCLR LATB,#4 SUB #1,W0 BRA NZ,VID_LOOP
The B4 port is the R part of my signal, so this loop writes vertical lines on the screen. my W0 reg is loaded with #80, this means 80 lines are written over the screen. This loop is executed 60 times and a front and back porch are added, so I get a resolution of 80*60 PX. So far so good, but this means I only have 2 instructions to load a variable from a screenbuffer array to the port. Does anyone know how I could go about doing this? Maybe my loop could be made shorter?
3 Answers 3
I've solved it with a macro:
#define showpx asm(" MOV [W8], W9"); \ asm(" BTSC W9,#3"); \ asm(" BSET PORTC, #3"); \ asm(" MOV W9, PORTB"); \ asm(" INC2 W8, W8");
and repeated it like this:
#define show8px showpx; showpx; showpx; showpx; showpx; showpx; showpx; showpx; show8px show8px show8px show8px show8px show8px show8px show8px show8px show8px
This removes the loop overhead and works perfectly!
The short answer is, a PIC24F just isn't fast enough to be a useful display driver on its own. You would have more chance with a PIC24E due to its faster clock and DMA, but even then I doubt it would be practical because (as you have already discovered) the PIC would be so busy writing the video signal that it would never have time to receive or load new data.
For a home-brew VGA driver you can't go past an FPGA; it's surprisingly easy to get VGA working in a Spartan-3. If you use the PIC24F to control what the FPGA displays then you will have a workable solution.
-
\$\begingroup\$ In the meanwhile I've fixed this problem and my CRT monitor is happily displaying a moving pong now :) the directives were to use a pic, otherwise I prolly would have done it in a different way yes... still thanks for the answer! \$\endgroup\$AEIO– AEIO2013年11月26日 12:05:05 +00:00Commented Nov 26, 2013 at 12:05
-
\$\begingroup\$ I'm impressed that you've gotten it to work at all, although the resolution is obviously a bit blocky at 80x60. \$\endgroup\$markt– markt2013年11月27日 05:24:58 +00:00Commented Nov 27, 2013 at 5:24
-
\$\begingroup\$ Yeah, the resolution is blocky and the timings are pretty hard to fine tune, needs a 1 instruction precision to not mess up everything... \$\endgroup\$AEIO– AEIO2013年11月27日 15:33:32 +00:00Commented Nov 27, 2013 at 15:33
The trick is to recognise that you don't have time during the VGA 640 pixel visible line to 'count' or 'loop' at all .. and for sure not enough time to 'bit bang' an i/o pin ...
However add an external shift register (74HC166) and set that up to 'load' 1 byte at a time from 8 PIC i/o pins and it becomes possible to display 80 FONT mapped (8x8) ascii characters in each of 48 lines.
You clock the shift reg at 24MHz (which is within most VGA display limits) and run the PIC with a CPU clk (OSC/2) of 15 MHz (so OSC is 30MHz) and that gets you 'exactly' 5 CPU cycles to update the byte i/o pins.
5 CPU cycles means no counting, calling, returning or any other useless time wasting. Instead we 'build' the 8x8 character font table out of 'sets' of 5 instructions that output a byte to the i/o pins and then 'jump' to the next font location that needs to output.
So each entry in the font table contains 8 sets 5 instructuions that 'output a byte' and then 'find the next byte' ... specifically :-
Load litteral byte to W, Output W byte to PORT (2) Load W word from Index++, Add W word to PCL (3)
To 'control' this sequence you load 80 Index registers with the offsets that takes the execution from one font location to the next. The last Index register contains an offset that takes you to the Line Sync code, during which time you have to load the Index registers for the next line to be output i.e. mod the contents of the 80 Index registers with 80 new offsets.
Within one line of characters, 'mod the Index registers' means re-setting the FIRST so it 'points' at the next 'font line table start' (i.e. next scan 'line down in the font) - all the other jumps are 'relative' so stay the same.
If you are displaying 8x8 character fonts you will typically have a 2 raster scanline inter-character-line 'gap' (so 480 scan lines will show 48 lines of 80 character text) and 2 lines is plenty of time to work out the 'relative offsets' for the next set of 80 of characetrs.
Note this can't do bit-mapped graphics without the help of external RAM (8kb is not enough space to store all the bit-map data, you need at least 640*480/8 bytes)