The base of the project is the cycle-by-cycle generation of VGA video signal borrowed from Tic Tac X, a previous project of mine.
A standard VGA frame (640x480@60Hz) has 525 lines of video. Each line takes 31.77us to happen.
When using a PIC running at 20MHz each instruction takes 200ns.
Divinding the numbers and rounding up to the next integer we have 159 cycles. Such amount of clock cycles shall be precisely counted in order to have a jitter free picture.
To form a frame you have to issue
- 2 lines front porch
- 2 lines vertical sync
- 25 lines back porch
- 8 lines top border
- 480 lines video
- 8 lines bottom border
The sum of it all gives exactly 525 lines or 83475 clock cycles
Considering that the back porch, top border and bottom border lines are all blank lines (1 hsync and black video after) we can rearrange the frame as:
- 2 lines vertical sync
- 33 lines back porch + top border
- 480 lines video
- 10 lines bottom border + front porch
The next trick is to split the 480 lines of visible video into 2 halves of 240 lines. Now that fits into an 8 bit counter:
- 2 lines vertical sync
- 33 lines back porch + top border
- 240 first half of lines video
- 240 second half of lines of video
- 10 lines bottom border + front porch
Next challenge was harder: Cascading loops! After finishing a loop (for example between the two halves of the visible we must load again a new value on the counter and it takes 2 instructions. The problem is that we have only one cycle time free that is caused by the skip of the 'goto' instruction that close the loop. I have spent some time trying to figure out a solution until it was given me by the mindset I got after TRIZ training: Use of 'preliminary action' as well as 'the other way out': I've inverted the order of the loading of a new value on the W register prior to test if the loop reached zero. Then if there is still more iterations this value is not used, otherwise I only spend 1 instruction time to save the pre-loaded value on the register used for counting.
The last problem is on the frame loop. I still have only one spare cycle and I need 2 for the 'goto' instruction that closes the loop.
The solution is philosophically similar: The last line was not called from a loop, so I do not have to test for the end of the loop (with decfsz) thus saving one instruction time that I need to jump again to the beginning of the loop.
(full explanation - in portuguese - here)
My final VGA frame is then:
- 2 lines vertical sync
- 33 lines back porch + top border
- 240 first half of lines video
- 240 second half of lines of video
- 9 lines bottom border + front porch
- 1 line bottom border + front porch
Talk is cheap, show me the code
Well, there it goes:
; ** Render a VGA Frame
DO_VGA:
movlw 2 ; Initialize amount of Vsync Lines
VGA_Frame:
movwf Conta
Vsync_loop:
call Vsync_line
movlw .33 ; amount of lines for next loop. Was placed here to
decfsz Conta,f ; equalize timing of all loops that make the frame
goto Vsync_loop ; each loop takes exactly 8 cycles + call time
; at 20Mhz we have 159 cycles per VGA line (31.8us)
VBackPorch:
movwf Conta ; 25lines backporch plus 8 lines top border
VBackPorch_Loop:
call Blank_line
movlw .240 ; first half of vilible lines
decfsz Conta,f
goto VBackPorch_Loop
First_half:
movwf Conta ; 240 lines
VFirst_Visible_Loop:
call Visible_line
movlw .240 ; second half of vilible lines
decfsz Conta,f
goto VFirst_Visible_Loop
Second_half:
movwf Conta ; 240 lines
VSecond_Visible_Loop:
call Visible_line
movlw .9 ; last 10 blank lines minus one
decfsz Conta,f
goto VSecond_Visible_Loop
VFront_Porch
movwf Conta ; 8 lines bottom plus 2 frontporch
VFrontPorch_Loop: ; minus one to equalize timing
call Blank_line
movlw .2 ; dummy
decfsz Conta,f
goto VFrontPorch_Loop
nop ; dummy for equalize timing
call Blank_line ; last is called outside a loop
movlw .2
goto VGA_Frame ;
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.