OK, here it is. I guess this is a
spoiler, so if you don't want to know how it's done, look away now!
Here is some skeleton code (and a disc image) presented in BeebAsm format. Essentially, it's more a tutorial in setting up the hardware and handling the interrupts than anything else. You'll notice that the configuration of the video hardware is very important, in particular explicitly setting the screen line where VSync should be generated, and setting the interlace mode. You can't necessarily guarantee either of these when selecting Mode 2 (because it depends on the *TV setting). Anyway, this code shows you how to set a timer to trigger
just before the scanline of your choice starts to be rasterised.
Code:
vsynccount = 0 ; zero page address of the vsync counter
DEBUG_RASTERS = TRUE ; conditionally assemble debug code
screenLine = 32 ; screen line (in pixels) on which timer should interrupt
vsyncPosition = 34 ; screen character row at which VSync is generated
vsyncWidth = 2 ; pulse width in scanlines of VSync signal (default is 2)
; number of screen lines (in pixels) between Vsync and line 0
numLinesAfterVsync = (39 - vsyncPosition) * 8 - vsyncWidth
; time spent rendering the border prior to our chosen line, in usecs
borderTime = (128 - 80) / 2
; time in usecs between the irq occurring and the service routine being reached
irqServiceLatency = 51
; calculate the timer value required to interrupt at the right place
timerLength = 64 * (screenLine + numLinesAfterVsync) - irqServiceLatency - borderTime
ORG &1900
\\-----------------------------------------------------------------
\\ The entry point of the code
\\-----------------------------------------------------------------
.start
SEI
LDX #&FF:TXS ; reset stack
\\ Configure VIAs
STX &FE44:STX &FE45
LDA #&7F:STA &FE4E ; disable all System VIA interrupts
STA &FE6E ; disable all User VIA interrupts
STA &FE43 ; set keyboard data direction
LDA #&C2:STA &FE4E ; enable VSync and timer interrupt
LDA #&0F:STA &FE42 ; set addressable latch for writing
LDA #3:STA &FE40 ; keyboard write enable
LDA #0:STA &FE4B ; timer 1 one shot mode
\\ Configure CRTC
LDA #8:STA &FE00
LDA #0:STA &FE01 ; turn off interlace
LDA #10:STA &FE00
LDA #32:STA &FE01 ; turn off cursor
LDA #7:STA &FE00
LDA #vsyncPosition:STA &FE01 ; explicitly set vsync position
LDA #3:STA &FE00
LDA #vsyncWidth * 16 + 8
STA &FE01 ; explicitly set vsync pulse width
\\ Set interrupt handler
LDA #LO(irq):STA &204
LDA #HI(irq):STA &205 ; set interrupt handler
CLI
\\-----------------------------------------------------------------
\\ This is the main loop - totally empty at the moment!
\\-----------------------------------------------------------------
.mainloop
LDA #1:STA vsynccount
\\ Do main loop here
.waitforvsync
LDA vsynccount:BNE waitforvsync
JMP mainloop
\\-----------------------------------------------------------------
\\ The IRQ handler
\\-----------------------------------------------------------------
.irq
LDA &FE4D
AND #2 ; test if it's a Vsync interrupt
BNE irqvsync
\\ If we got here, it's definitely a timer interrupt
.irqtimer
IF DEBUG_RASTERS
LDA #4:STA &FE21 ; debug change background to blue
ENDIF
LDA #&40:STA &FE4D ; acknowledge timer interrupt
LDA vsynccount
BEQ skipdec
DEC vsynccount ; decrement vsync count if not already zero
.skipdec
LDA &FC ; restore A
RTI ; and exit
\\ If we got here, it's a vsync
.irqvsync
STA &FE4D ; acknowledge vsync interrupt
LDA #LO(timerLength):STA &FE44 ; set timer interrupt period
LDA #HI(timerLength):STA &FE45
IF DEBUG_RASTERS
LDA #7:STA &FE21 ; debug change background to black
ENDIF
LDA &FC ; restore A
RTI ; and exit
.end
SAVE "Code", start, end
For some reason, the irqServiceLatency value seems to be rather large, but it has to be this big in order that the timer interrupts as soon as the right border begins on the line previous to the target line (in order to maximise our time as much as possible). Tom, you got any ideas where I've gone wrong here?