10

I've written a delay function that simply counts the times the screen raster line reaches a particular value.

// zero page addresses
.const delayFrames = $00fa        
.const delayCounter = $00fb

// ==============================================
// Delay n frames
// [A,delayFrames,delayCounter]
// ==============================================   
Delay:
lda #0
sta delayCounter
!:
// check if delay done
lda delayCounter
cmp delayFrames
beq !++ // -> done

// inc delayCounter
inc delayCounter

// loop until raster line reached (1 frame)
!:
lda #200    
cmp Screen.RasterLine
bne !-
jmp !--

// done
!:
rts

But it doesn't behave the way I expect. Could anyone help me understand the following behaviour...

  1. With delayFrames set to 50 (1 second) and checking for raster line 0 : I get about 200ms delay

  2. With delayFrames set to 50 (1 second) and checking for raster line 200 : I get about 500ms delay.

So, I don't understand why checking for a different raster line should change anything. Every raster line will be reached once in 1/50th second. Is there something wrong in my code, or is there an issue with reading the raster line?

The way I understand the raster line system variable (RasterLine = $d012) is that it contains the current y position of the electron gun as driven by the C64. And if I read it continuously, I would get something like this:

0,0,0,1,1,2,2,2,3,3,3,3,4,4,4,5,5,6,6,6

or...

0,1,2,3,4,5,6

or...

1,4,9,12,16,18

i.e. some readout of the current raster line.

Presumably it's going pretty fast, but I don't really know how fast, and I might get a few readings for each line. Or one reading, or just one reading every few lines. But I just don't know what the situation is.

How reliable is the raster line value?

Links: Get exact position of raster beam on C64/C128

2
  • From what I've been reading at that link I've provided and a comment elsewhere, I think we get multiple readings per line. So what does that mean for my delay function - it probably won't miss a raster line. But I think it can short circuit when it does hit the selected raster line, causing the function to rush through and return sooner than the selected delay. Commented May 14, 2023 at 17:25
  • Don't know the C64 but some systems have different numbers of CPU cycles available depending on the scanline number, which could mean that your code double-counts scanline 0 more times than it double-counts scanline 100, which would explain that part. I believe C64 has "bad lines" on lines divisible by 8 and the CPU gets only 1/3 as much time to run on these lines. Not sure why you see the opposite. Commented May 15, 2023 at 13:47

4 Answers 4

13

I would get something like this...

Yes, all of them. The code may be fast enough to report a line multiple times, get all lines and miss some, all at once. It all depends on the machine setup, other interrupts and so on. Way more information(*1) would be needed to give a definite answer.

Presumably it's going pretty fast, but I don't really know how fast, and I might get a few readings for each line.

Yes, as your inner loop is only some 9 clocks while the outer loop is ~25 clocks (hard to tell exact without knowing what that assembler is really generating with the input shown), but a raster line is ~63 clocks. So your routine may detect a line up to 2-3 times.

Of course that is only true if the routine is continuously working and not put aside by some interrupt - like timer or some VIC source.

How reliable is the raster line value

Very reliable, unless you're suspended and not able to read it or alike.

The most simple way to measure time on a C64 is by looking at the Jiffy Clock - or if there's a real need to synchronize with video, then set up a raster interrupt at the position needed and count within.


Jiffy Clock

The Jiffy Clock is a left over from PET times. Instead of adapting the C64 Kernal to use the 6526 TOD clocks (and provide BASIC with time) they just kept the timer based clock routines. Here one of the timers interrupts the CPU 60 times per second, which in turn increments a 24 bit counter at $A0..$A2 by one. Those locations are cleared at system start (Reset) and roll over to zero after 24 hours with a max value of 5183999 (=( 24h * 60min * 60sec ) -1).

Important here, that 60 times per second increment is independent of the C64 hardware used. It is always the same, no matter if we have an NTSC or PAL machine (well, within the variety of the machine clock that is).

So, if it's really just about waiting for some short amount of time, then looking how many Jiffys pass will do it. An active waiting may look like this:

JIFFLOW  EQU   $A2

; Subroutine to wait for 0 to 4.25 seconds
; called using JSR with
; time to wait in A in 1/60 seconds 
JIFFWAIT:
         CLC
         ADC   JIFFLOW      ; Add time to wait to 'now'
JIFFWTLP:
         CMP   JIFFLOW      ; Are we there yet
         BNE   JIFFWTLP     ; No -> Continue waiting
         RTS                ; Done waiting

The routine will ofc only work with interrupts enabled and having the Kernal still in the interrupt loop (which is default C64 behaviour). There's also a hickup if the routine is called in the last few seconds before it rolls over. How to circumvent this may depend on your application - if you care at all, as it will only wait once every 24 hour run time (who works that long with a C64 anyway) for up to 4.25 seconds more than intended.

Due its granularity of 1/60th second it will jitter by up to 1/60th second depending on when being called. So I wouldn't use it for anything below 1/10th of a second (value of 6).


*1 - It may already help if the code would contain all information. For example what assembler is used,what libraries or what does Screen.RasterLine mean (I would assume Vic register #18), or what the initial value of delayFrames is. Yes, some of this is spread out in text, still, having a single consistent readable source does help a lot to get good answers.

10
  • So it's going to get complicated to use this method? When I wrote the question, I thought I would be able to tweak it with help. But now I'm starting to think it's just not the best way to time things. In the program I'm working on I'm trying to scroll text and I need to slow it down. But maybe I should look at a more general approach to timing? Commented May 14, 2023 at 17:35
  • 1
    @GavinWilliams If this is not about some highly accurate graphics timing, then looking at the daytime clock might be the most simple solution. Let me add some lines to show how it may work for you.
    – Raffzahn
    Commented May 14, 2023 at 17:38
  • @GavinWilliams Hope this helps
    – Raffzahn
    Commented May 14, 2023 at 18:05
  • 1
    That little routine you provided is brilliant. It's actually simpler than mine and even makes more sense, because it's using a clock. Commented May 14, 2023 at 19:47
  • 2
    @tevemadar No, it does not assume anything beyond basic binary math. There is no need to reset or checks as any overflow and carry will cancel each other out. They don't matter. Note: Waiting for a duration, not absolute time. Need an example? Assume clock is at start at 12.34.56 and we're waiting for 2 s (120 ticks = $78) $56+$78 = $CE, so we wait until the low byte is $CE (no carry and no overflow). Waiting another 2 s gives $CE+$78 = $146 - so we wait for the low byte reaching $46, ignoring carry and overflow as they cancel each other out. Remember: Best code is code not needed at all.
    – Raffzahn
    Commented May 16, 2023 at 11:30
9

Going from recollection, I got pretty decent results by waiting for line 0, then line 100 (pick a random, but different line) counting one frame, then repeating the cycle.

I'd also have written the code a bit differently. Instead of counting up from 0 to N, I'd have counted down from N to 0 (and held that counter in X or Y), something along this line (but it's been a long time since I wrote any code for a 6502, so slip-ups are likely):

    ldx delayFrames
lineZero:
    lda #200
    bne lineZero    ; wait for line 0
Line100:
    lda #200
    cmp 100
    bne line100
    dec x           ; now we've seen line 0 and 100, count one frame
    bne lineZero    ; if we have more frames, keep counting

; Now `delayFrames` frames should have passed

I don't recall thinking of it at the time, but you could probably streamline it a little bit:

    ldx delayFrames
lineZero:
    lda #200
    bne lineZero    ; wait for line = 0
NotZero:
    lda #200
    beq NotZero     ; wait for line != 0
    dec x           ; two different lines, count one frame
    bne lineZero    ; if we have more frames, keep counting

Should save a couple of bytes, anyway.

...but Raffzahn is right: the jiffy clock is usually easier.

1
  • 5
    That's a good idea to avoid multiple hits! Commented May 14, 2023 at 18:27
5

I live in VIC-20-land rather than in the C64 world and know comparatively little about the inner workings and features of the 656x/856x VIC-II, but I do know that it has a hardware raster interrupt that can be programmed to fire at a specific line. Ironically, the technique you've been experimenting with is what we have to do in the VIC-20 arena because the 656x VIC doesn't have a hardware raster interrupt and so we have to poll the raster counter and synchronise that with the 6502 IRQ.

Notably, the VIC-II hardware raster interrupt fires at the start of the line, and only once per line, making it a reliable 'tick' for a timer. Obviously you'll need to account for the difference between PAL vs. NTSC clock frequencies to equate either 50 or 60 ticks with one wall-clock second.

If you set-up your timer on the raster interrupt and enable it, you've got a guaranteed once-per-frame tick. It could get a bit more complicated if you do multiple interrupts per frame (i.e. you'd want your timer code to determine which line interrupt has been fired and only 'tick' on one specific line). And there are arcane aspects of C64 screen operations of which I am only dimly aware, such as the need to disable screen refresh during tape operations, which might or might not also have an impact.

But hey, if it was easy it wouldn't be fun, would it?

Here's an authoritative link to a page describing the technical details and sample code for getting the VIC-II raster interrupt working.

C64 VIC-II Raster Interrupts

4

Also be aware that $D012 reports the lower 8 bits of the current raster line.

For NTSC, the raster line can range from 0 to 262; values of 256-262 will alias with 0-6. The MSB of the raster line is available elsewhere.

This may explain the different results taken from waiting for line 200 (no possibility of alias) and line 0 (alias with 256).

1
  • 1
    Good point explaining the roughly halved execution time.
    – Raffzahn
    Commented May 17, 2023 at 7:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .