How to create or tweak a video mode: Difference between revisions
No edit summary |
No edit summary |
||
Line 13: | Line 13: | ||
==Checking the Current Timing== | ==Checking the Current Timing== | ||
Let's see the current timing. Start AVRStudio and insure a simulator is defined by going into menu Debug->Select Platform and Device... and choose AVR Simulator (you can choose AVR Simulator 2, but it is much slower because it models more closely the chip). Open project Tutorial, and import source file /kernel/videoMode1/videoMode1.s in the workspace. Perform a build and set a breakpoint on the line defined below. Then hit Run/Start. | Let's see the current timing. Start AVRStudio and insure a simulator is defined by going into menu Debug->Select Platform and Device... and choose AVR Simulator (you can choose AVR Simulator 2, but it is much slower because it models more closely the chip). Open project Tutorial, and import source file /kernel/videoMode1/videoMode1.s in the workspace. Perform a build and set a breakpoint on the line defined below. Then hit Run/Start and the F5. | ||
[[File:Videomode1 trace1.png]] | [[File:Videomode1 trace1.png]] | ||
The programs run for a while, then stops at the breakpoint. Hit F11 to step into the function call. In the top-Right window, select TIMER_COUNTER_1 from the list. In the window just underneath select TCNT1, this is the TIMER 1 current count value. | |||
[[File:Videomode1 trace2.png]] | |||
Look at the function's comment: TCNT1 should be equal to 0x68 on the cbi, 0xf0 on the sbi. Now check the current TCNT1 value on the right panel: 0x68...bingo. When the cursor is on the hsync_pulse function first instruction (the CBI), TCNT1 must always be 0x68, whatever the video mode. hsync_pulse is a shared function called by all video modes. It will generate the HSYNC pulse and also mix and/or update the sound port. You don't have to care about the SBI, since this will be handled by the sync code later. | |||
So that's it! Hit F5 again to continue execution. This will run one iteration of our line render loop, the "render_tile_line" function and then come back to the breakpoint. Insure TCNT1 is fine. Run at least 8 time to be sure a full tile height is covered and validate timing is still ok. Then remove the breakpoint, locate label "text_frame_end:" and put a breakpoint on the "rcall hsync_pulse" following. Hit run and validate again timing is ok. If all is fine, you're done, timing will be rock steady on your TV. | |||
==Implementing the Change== | ==Implementing the Change== |
Revision as of 01:38, 17 October 2012
Introduction
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:
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 simulator will come handy and help us insure our timing is spot on.
Checking the Current Timing
Let's see the current timing. Start AVRStudio and insure a simulator is defined by going into menu Debug->Select Platform and Device... and choose AVR Simulator (you can choose AVR Simulator 2, but it is much slower because it models more closely the chip). Open project Tutorial, and import source file /kernel/videoMode1/videoMode1.s in the workspace. Perform a build and set a breakpoint on the line defined below. Then hit Run/Start and the F5.
The programs run for a while, then stops at the breakpoint. Hit F11 to step into the function call. In the top-Right window, select TIMER_COUNTER_1 from the list. In the window just underneath select TCNT1, this is the TIMER 1 current count value. Look at the function's comment: TCNT1 should be equal to 0x68 on the cbi, 0xf0 on the sbi. Now check the current TCNT1 value on the right panel: 0x68...bingo. When the cursor is on the hsync_pulse function first instruction (the CBI), TCNT1 must always be 0x68, whatever the video mode. hsync_pulse is a shared function called by all video modes. It will generate the HSYNC pulse and also mix and/or update the sound port. You don't have to care about the SBI, since this will be handled by the sync code later.
So that's it! Hit F5 again to continue execution. This will run one iteration of our line render loop, the "render_tile_line" function and then come back to the breakpoint. Insure TCNT1 is fine. Run at least 8 time to be sure a full tile height is covered and validate timing is still ok. Then remove the breakpoint, locate label "text_frame_end:" and put a breakpoint on the "rcall hsync_pulse" following. Hit run and validate again timing is ok. If all is fine, you're done, timing will be rock steady on your TV.
Implementing the Change
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
First we will need to tweak the file constants definition file /kernel/videoMode1/videoMode1.def.h to support 6 or 8 pixels tile wide. From now on, we can use -DTILE_WIDTH=8 in our makefile to tell the video mode we want 8 pixels wide tiles.
...
#ifndef TILE_WIDTH
#define TILE_WIDTH 6
#endif
#if TILE_WIDTH == 6
#define VRAM_TILES_H 40
#define SCREEN_TILES_H 40
#elif TILE_WIDTH == 8
#define VRAM_TILES_H 30
#define SCREEN_TILES_H 30
#else
#error Invalid value defined in the makefile for TILE_WIDTH. Supported values are 6 or 8.
#endif
...