How to create or tweak a video mode: Difference between revisions

From Uzebox Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 3: Line 3:
As a concrete example, we will use video mode 1 that we will tweak to support 8 pixels wide tiles (in addition to the current 6 pixels ones). The most important thing when making or tweaking a video mode is to be sure each scan line is always exactly 1820 cycles. Two approach are possible to count cycles, manual and with an emulator. Manual is tedious and error prone. Moreover, if you are off by any cycles amount, you won't see the result in uzem. The best approach is to use Atmel's own AVR simulator. The simulator displays a global cycles counter and also all I/Os and, most importantly, the current values of all timers.
As a concrete example, we will use video mode 1 that we will tweak to support 8 pixels wide tiles (in addition to the current 6 pixels ones). The most important thing when making or tweaking a video mode is to be sure each scan line is always exactly 1820 cycles. Two approach are possible to count cycles, manual and with an emulator. Manual is tedious and error prone. Moreover, if you are off by any cycles amount, you won't see the result in uzem. The best approach is to use Atmel's own AVR simulator. The simulator displays a global cycles counter and also all I/Os and, most importantly, the current values of all timers.


The video rendering process is initiated 60 times per second by the kernel's main video interrupt. The kernel then does some setup and then delegates to the video mode that will actually render a full frame in one shot. Video mode 1 works according to the following process:
A video frame is made of a series or scanlines. Those scanlines are then it turn made of a horizontal synchronization pulse (also called HSYNC) and image data. A full picture is made of 524 of those lines. The kernel uses TIMER1, a 16-bit counter set to count to 1820, rollover and then generate an interrupt. The video rendering process is initiated by that interrupt. The kernel then does some setup and then delegates to the video mode that will actually render a full frame in one shot. It's important to note that during frame rendering, interrupts are disabled but the TIMER1 counter continues to count.
 
Video mode 1 works according to the following process:


[[File:Videomode1 process.png]]
[[File:Videomode1 process.png]]
The one thing thing that will be critical is to insure that those "Generate H-Sync pulse" function calls are ''always'' executed at the same cycle in regard to TIMER1. That's where the simualator will come handy and help us insure our timing is spot on. To implement our 8 pixel wide option, we need to modified the "render_tile_line" function:
<source lang="asm">
;*************************************************
; Renders a line within the current tile row.
; Draws 40 tiles wide @ 6 clocks per pixel
;
; r22    = Y offset in tile row (0-7)
; r23   = tile width in bytes
; Y      = VRAM adress to draw from (must not be modified)
;
; cycles  = 1495
;*************************************************
render_tile_line:
movw XL,YL ;copy current VRAM pointer to X
;////////////////////////////////////////////
;Compute the adress of the first tile to draw
;////////////////////////////////////////////
ld r20,X+ ;load absolute tile adress from VRAM (LSB)
ld r21,X+ ;load absolute tile adress from VRAM (MSB)
mul r22,r23 ;compute Y offset in current tile row
movw r24,r0 ;store result in r24:r25 for use in inner loop
add r20,r24 ;add Y offset to tile address
adc r21,r25 ;add Y offset to tile address
movw ZL,r20 ;copy to Z, the only register that can read from flash
ldi r18,SCREEN_TILES_H ;load the number of horizontal tiles to draw
mode1_loop:
lpm r16,Z+ ;get pixel 0 from flash
out VIDEO_PORT,r16 ;and output it to the video DAC
ld r20,X+ ;load next tile adress from VRAM (LSB)
lpm r16,Z+ ;get pixel 1 from flash
out VIDEO_PORT,r16 ;and output it to the video DAC
ld r21,X+ ;load next tile adress from VRAM (MSB)
lpm r16,Z+ ;get pixel 2 from flash
out VIDEO_PORT,r16 ;and output it to the video DAC
rjmp . ;2 cycles delay
lpm r16,Z+ ;get pixel 3 from flash
out VIDEO_PORT,r16 ;and output it to the video DAC
add r20,r24 ;add Y offset to tile address
adc r21,r25 ;add Y offset to tile address
lpm r16,Z+ ;get pixel 4 from flash
out VIDEO_PORT,r16 ;and output it to the video DAC
lpm r16,Z+ ;get pixel 5 from flash
movw ZL,r20 ;load the next tile's adress in Z
dec r18 ;decrement horizontal tiles to draw
out VIDEO_PORT,r16 ;and output it to the video DAC
brne mode1_loop
rjmp . ;2 cycles delay
nop ;1 cycle delay
clr r16 ;set last pixel to zero (black)
out VIDEO_PORT,r16
ret
</source>

Revision as of 00:44, 17 October 2012

This tutorial describe the approach that was use to develop video modes and, more specifically, how to adjust delay loops so the video timing is correct.

As a concrete example, we will use video mode 1 that we will tweak to support 8 pixels wide tiles (in addition to the current 6 pixels ones). The most important thing when making or tweaking a video mode is to be sure each scan line is always exactly 1820 cycles. Two approach are possible to count cycles, manual and with an emulator. Manual is tedious and error prone. Moreover, if you are off by any cycles amount, you won't see the result in uzem. The best approach is to use Atmel's own AVR simulator. The simulator displays a global cycles counter and also all I/Os and, most importantly, the current values of all timers.

A video frame is made of a series or scanlines. Those scanlines are then it turn made of a horizontal synchronization pulse (also called HSYNC) and image data. A full picture is made of 524 of those lines. The kernel uses TIMER1, a 16-bit counter set to count to 1820, rollover and then generate an interrupt. The video rendering process is initiated by that interrupt. The kernel then does some setup and then delegates to the video mode that will actually render a full frame in one shot. It's important to note that during frame rendering, interrupts are disabled but the TIMER1 counter continues to count.

Video mode 1 works according to the following process:

Videomode1 process.png

The one thing thing that will be critical is to insure that those "Generate H-Sync pulse" function calls are always executed at the same cycle in regard to TIMER1. That's where the simualator will come handy and help us insure our timing is spot on. To implement our 8 pixel wide option, we need to modified the "render_tile_line" function:

;*************************************************
; Renders a line within the current tile row.
; Draws 40 tiles wide @ 6 clocks per pixel
;
; r22     = Y offset in tile row (0-7)
; r23 	  = tile width in bytes
; Y       = VRAM adress to draw from (must not be modified)
; 
; cycles  = 1495
;*************************************************
render_tile_line:

	movw XL,YL			;copy current VRAM pointer to X
	
	;////////////////////////////////////////////
	;Compute the adress of the first tile to draw
	;////////////////////////////////////////////
	ld	r20,X+			;load absolute tile adress from VRAM (LSB)
	ld	r21,X+			;load absolute tile adress from VRAM (MSB)
	mul r22,r23			;compute Y offset in current tile row
	movw r24,r0			;store result in r24:r25 for use in inner loop
	add r20,r24			;add Y offset to tile address
	adc r21,r25 		;add Y offset to tile address
	movw ZL,r20 		;copy to Z, the only register that can read from flash

	ldi r18,SCREEN_TILES_H ;load the number of horizontal tiles to draw

mode1_loop:	
	lpm r16,Z+			;get pixel 0 from flash
	out VIDEO_PORT,r16	;and output it to the video DAC
	
	ld	r20,X+			;load next tile adress from VRAM (LSB)

	lpm r16,Z+			;get pixel 1 from flash
	out VIDEO_PORT,r16	;and output it to the video DAC

	ld	r21,X+			;load next tile adress from VRAM (MSB)

	lpm r16,Z+			;get pixel 2 from flash
	out VIDEO_PORT,r16	;and output it to the video DAC

	rjmp .				;2 cycles delay

	lpm r16,Z+			;get pixel 3 from flash
	out VIDEO_PORT,r16	;and output it to the video DAC

	add r20,r24			;add Y offset to tile address
	adc r21,r25 		;add Y offset to tile address

	lpm r16,Z+			;get pixel 4 from flash
	out VIDEO_PORT,r16	;and output it to the video DAC

	lpm r16,Z+			;get pixel 5 from flash
	movw ZL,r20			;load the next tile's adress in Z
	dec r18				;decrement horizontal tiles to draw

	out VIDEO_PORT,r16	;and output it to the video DAC
	brne mode1_loop		
	
	rjmp .				;2 cycles delay
	nop					;1 cycle delay
	clr r16				;set last pixel to zero (black)
	out VIDEO_PORT,r16

	ret