Jump to content
IGNORED

What a RIOT! (Timer weirdness?)


Sohl

Recommended Posts

I'm working on a new game.  I used @SpiceWare's "Collect" sample code as a starting point for my OverScan subroutine.  However, during running of my game, sometimes the RIOT timer count-down is not working with the divide-by-64 as expected, but rather seems like divide-by-1, so naturally the timer is wrapping around before I can catch it and I get a way-to-long overscan period, messing up vsync. So I get a partial 87-line frame between each 262-ish frame (still working to get that exactly correct).  During this behavior, in Stella the screen flashes and the RIOT reg subwindow shows TIMINT as $C0 and INTIM Clks always as 1.  This weird behavior can last a few seconds to several seconds, then can go back to normal for awhile.  Real hardware seems to be affected the same way... I get occasional frame break up. On my LCD TV, it goes grayscale or weird color and vertical stripy during these periods.

So RIOT is acting like I am not storing to TIM64T but TIM1T, but my code is still definitely storing into TIM64T, which I can see occur in Stella IO window.  Earlier in my kernel, for the main play area, I'm using the timer two other times with TIM8T when I'm updating player sprite positions and other sprite drawing preparations.  I'm not sure if using those could leave my timer in a weird state.

Now here is the funny part... I have come up with two hacky "fixes" for this weird behavior that don't make much sense to me. See code excerpt below.


1) FIRST OPTION - Delete Darrell's initial STA WSYNC instruction, changing the timing of the later STA TIM64T to ending on cycle 21 rather than 11. 

2) SECOND OPTION - Immediately read INTIM after storing to TIM64T

 

Either of these works individually or together, both in Stella sim and on real hardware.   

 

If these work consistently, I suppose I can leave it as-is, but they seem hacky, especially the 2nd option.  Note in the code below I commented-out the later OSwait store to WSYNC to make sure I was not barely missing the countdown to 0.

OverScan:
;        sta WSYNC  ; FIRST OPTION HACKY(?) FIX <<<< COMMENTED OUT! ; Wait for SYNC (halts CPU until end of scanline)
        lda #2      ; LoaD Accumulator with 2 so D1=1
        sta VBLANK  ; STore Accumulator to VBLANK, D1=1 turns image output off
        lda #32     ; set timer for 27 scanlines, 32 = ((27 * 76) / 64)  
        sta TIM64T  ; set timer to go off in 27 scanlines
        lda INTIM   ; SECOND OPTION HACKY FIX <<<<< EXTRA READ HERE!!! SEEMS TO HELP PROPER DIV-BY-64 WORK.
; my game logic...
OSwait:
;        sta WSYNC   ; Wait for SYNC (halts CPU until end of scanline)
        lda INTIM   ; Check the timer
        bne OSwait  ; Branch if its Not Equal to 0
        rts         ; ReTurn from Subroutine

If there is something subtle about using the timer I'm not taking into account, or good practices to cleanly reset the timer function, I would appreciate hearing about them and learning. Maybe others would learn too! Thanks.

--

Mike

Link to comment
Share on other sites

I have run into issues before if the timer ran out too close to the end of a scanline is. Given that LDA INTIM followed by BNE Oswait is 7 cycles you would want the timer to run out at least 7 cycles before the scanline ends. Truthfully there is a wide range that is okay, but I generally try and get it to run out somewhere in the middle of a scanline.

 

I do remember a thread a while back where we found some timer weirdness with either the timer being set or running out on the last cycle of the scanline (don't remember) and that caused problems.

 

Here are some other things you can do to solve timing issues (besides adding more time to the timer):

 

1) Use TIMINT instead of INTIM. TIMINT is set after the timer runs out. It helps to minimize the amount of scanlines it takes to recover when the code takes longer than it should.

2) For all of the code development try putting a couple of WSYNC's after the loop waiting for the timer to run out. When the game is pretty much done and stable remove the WSYNC's and add the extra time to the timer. This should ensure you have ample time for all tasks.

3) If you simply don't have enough time then try and schedule tasks over a few frames. Not every frame needs everything done on it after all.

Link to comment
Share on other sites

Thanks @Omegamatrix.  I don't have all that much game logic in the overscan period at this point (around 300 cycles at this point), but maybe I will later.  When is TIMINT flag cleared? Very soon after writing to a TIMxT interval register?

Edited by MLockmoore
Added some detail.
Link to comment
Share on other sites

I've gone to using TIMINT very early in my efforts to get code running. Since it's long ago and I'm using my old code ever since unchanged, there's how I remember it:

- just write to TIMxTI instead of TIMxT (it might be that the ...TI is not defined in vcs.h, then go for (...T | 0x8)

- instead of waiting for the timer to zero out, I'm waiting using a loop like this: "@loop: bit TIMINT : bpl @loop"

And that was all. If you like, you can take a look at the code of all of my releases. They are available at: https://xayax.net/ . A rather simple demo is this: http://xayax.net/2k_is_no_limit/ . I've got three functions (in main.s) there "waitvblank", "waitscreen" are called using "jsr" and just wait until the beam has passed these areas. "waitoverscan" is called using jump and does more than just waiting, as it contains logic to decide on what to run in the next frame and generates the sync signal.

  • Like 1
Link to comment
Share on other sites

This happens when you write the timer exactly at the "wrap around" cycle, that is when the timer value is decremented from $00 to $ff. In that case the interrupt flag isn't cleared and the timer counts at "1T" interval.


In fact, when the interrupt flag (TIMINT bit 7) is set, the timer always counts at 1T, while when it's clear, it counts at the programmed interval (1T,8T,64T,1024T).
The flag is set whenever the timer wraps around, while it's cleared on every read (INTIM) or write (TIM1T,TIM64T,etc.) access, except when the read/write happens at the wraparound cycle.

 

Stella has two pseudoregisters to easily debug these occurrences:


_timwrapread:    true if you read the timer at wraparound cycle
_timwrapwrite:    true if you write the timer at wraparound cycle

 

So you can set a conditional break in the debugger prompt

breakif _timwrapwrite

to detect such cases.

 

If you can't ensure to avoid that condition, a solution is to just write to the timer twice:

    sta TIM64T
    sta TIM64T


The second write will always be ok, unless you're storing a value of "3". In that case, either use any other value for the first write, or delay the second write by executing other instructions in between.

 

  • Like 2
Link to comment
Share on other sites

  • 3 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...