CPC 300Hz interrupt vblank check missed

Started by Synthpeter, 02:47, 02 June 21

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.


New here. Total Z80 noob. Done some PIC16 and 6510 assembly (and a little 68000 ages ago).
I'm using WinAPE. New to Amstrad.
Actually mostly learning to use WinAPE to learn Z80 since I want to make use of the debugger in WinAPE.

Classic "raster execution time measurement" attempt. I understand there are 6 interrupts per frame due to the 300Hz interrupts.
I've set up the 300Hz interrupt and want to change the colour on a specific raster interrupt and change it back when done.
I check for the vblank bit in the interrupt to see if it's a new frame. If it is, I refer to it as interrupt 0 as it's the first interrupt of a frame. I reset a counter, return, then increase counter each interrupt until counter is 2. Set the color. Waste some CPU cycles. Set color back to what it was and return.
Seems to work fine for reasonably short interrupt code.

However, if my interrupt code takes long enough (roughly 1+2/3 of a 300Hz interrupt interval, i.e. continues far longer than when the next 300Hz interrupt fires, but it has to be a lot more) it seems I'm not able to read the vblank bit every second frame, causing my interrupt code to flicker as it's not resetting the counter and thus only changes the color every 2nd frame.

Is this a WinAPE bug or am I doing something stupid?
Since the code behaves ok if the interrupt code runs well past interrupt 3, that's not the issue. The problem only arises as the execution time nears interrupt 4.

I know the code is garbage, but I'm curious about the flickering.
See my comment "882 makes it stop flickering"
And it's not because it takes so long it goes into the next frame. Starting the process on interrupt 3 or 1 yields the same issue with 883, and is flicker-free with 882.

If I in WinAPE in Registers enable "Int Highlight", I also see that when it flickers, the interrupt highlight lines are also flickering between two sets of positions after my color change.
When my code is made to not flicker, the interrupt highlights appear where they normally do.

See screenshots. When it flickers it alternates between these two frames.
Note how the interrupt highlight lines get "offset" after my color change code ends, as if all interrupts get offset. Weird?

When it doesn't flicker, it just changes the colors and the subsequent interrupt highlights are NOT offset.

Anybody know why? Are there other interrupts doing something weird that's causing it?

SCREENSIZE equ &4000
SCREENADR equ &c000
COL_BLUE equ &44

org &4000
   ld hl,SCREENADR   ; Clear screen
   ld de,SCREENADR+1
   ld bc,SCREENSIZE-1
   ld (hl),0
   di      ; Disable interrupts
   ld a,&C3   ; jp instruction
   ld (&0038),a   ; store it at int vector
   ld hl,interrupt
   ld (&0039),hl   ; Set interrupt handler to ours
   ei      ; Enable interrupts

   jp main

   exx      ; Backup registers
   ex af,af'
   ld b,&F5   ; ld bc,&F5xx
   in a,(c)   ; in a,(bc)
   rra      ; put vblank bit in carry
   jp nc,NotVBlank
   xor a      ; If vblank, reset int_count counter
   ld (int_count),a
   ld a,(int_count)
   jp nz,NotOurInt
   ld bc,&7f00   ; Background colour register
   out (c), c   ; out (bc),c
   out (c),a
   ; *** Waste some cycles ***
   ld hl,&9000
   ld de,&9000+1
   ld bc,883  ; <--------------- 882 makes it stop flickering
   ld (hl),0
   ld bc,&7f00   ; Background colour register
   ld a,COL_BLUE
   out (c),a
   ld a,(int_count)
   inc a
   ld (int_count),a
   ex af,af'   ; Restore registers

int_count:   db 0


Well, the root problem is that you're holding onto interrupt context for way too long. Interrupt handlers need to be as short as possible, even if that means just setting a flag for the non-interrupt code to detect and do the real work.
More specifically, the 300Hz interrupt is generated by the gate array which is counting hsyncs and watching vsync and interrupt state. The GA is set up to hold off triggering another interrupt until at least 32 lines after the previous one was acknowledged (by EI) which means staying in one interrupt too long delays all the subsequent interrupts by some amount. If that delay is just right (um, wrong) it puts the VBlank period right in-between two interrupts, and so you don't get an opportunity to see it.



Classic issue :)

You can dismiss an int (DI) as long as you want (must demos cut them entirely for most of the frame).
But there is a catch!

Each int is normally trigger by the VGA every 52 lines (relying on hsync signal to count).
That 32+20 (&20 + 20).
* If the interruption is re-enabled in the 0..31 range, then next int is triggered at expected line (that means your previous interruption could be 83 lines long instead of just 52).
* If the interruption is re-enabled in the 32..51 range, then next ints are triggered 32 lines after (the counting is now 20+32 rather than 32+20).
* At vsync + ~120 NOPs, the interruption counters are reset, meaning there **will** be an interruption 52 lines after anyway. BUT:
  * If it occurs in 32..51 range, no interruption is triggered. So there will be only at most 5 ints in this frame.

Fun facts:
  * Even the great Longshot didn't know about this and thought the behaviour was due to a CPC bug.
  * That why demos often aren't full-screen in Y: they start at the second int position, since this one is guaranteed to be present.
  * @Grim invented a cool technique to generate an reliable int at VBL time, no matter what.



Oooo.. Thanks a lot for your replies!

I'm Swedish btw so the English explanations are probably the best option (better than French to me at least), unless you're gonna go all svenska på mig ;)
So, out of curiosity, and I'm sure this has also been debated:

QuoteWhen the interrupt is acknowledged, this is sensed by the Gate-Array. The top bit (bit 5), of the counter is set to "0" and the interrupt request is cleared. This prevents the next interrupt from occuring closer than 32 HSYNCs time.

Why on earth would you want to (somewhat arbitrarily) alter the timing of what I assume most programmers/engineers expect to be a steady interrupt frequency? I'm not sure if I should interpret that as a feature or as an actual bug, which you said it isn't, but it may as well be, though some might call it "feature" :P
Maybe I WANT the interrupt to occur closer than 32 HSYNCs time? I'm being robbed of the option to :(

And I know, my interrupt is crappy and too long. This was mostly an experiment to see what it'll look like.
I'll need the interrupt to fire at exactly 50Hz, regardless of for how long it executes, since it'll be used for music playback and maybe some other stuff and I have zero "feeling" for how much work I can do in a frame (probably more than I think, but I want to find out), and it would be good to know that everything won't fall apart if I happen to take a bit too long at some point.
My target is no make a DIY Z80 system, so this isn't an actual issue to me.

I still think this is a really odd thing though. 300Hz is 300Hz, unless you suddenly delay it a little bit here and there.. and then it's not 300Hz anymore :) And this is the only "steady" interrupt to generate 50Hz I can get from the system, right?

I didn't expect I'd need to worry until I was reaching basically a full frame of raster time.
I'm sure a lot of CPC affecionados will get all upset now when I criticize the way it works, but this has to be a bit of a special quirk :P


The goal, I suspect, is to prevent an over-long interrupt acknowledge delay (because of a DI) from causing two very close interrupts, which might cause other issues, as it's aiming more for roughly even spacing rather than absolute consistency of timing.

If an IVR runs for too long or you disable interrupts for too long then nothing the hardware can do will prevent losing that consistent 300Hz timing. If you need long responses and good timing, it's better to have the IVR flag that specific vectors have occurred and the main code frequently poll those flags when it's free.

If you need tighter interrupts than 32 HSYNCs, your only choice is to use a Plus (since its PRI isn't affected by this) or third party add on like the PlayCity.


The problem is you read "300Hz interrupt" and think that's the true description of the signal, when it's not. It's first and foremost a signal for the firmware, used for various timers and housekeeping tasks, and therefore it's the requirements of the firmware which determine the exact behaviour of the interrupt timing.

That we can hijack the interrupt for our own ends is perfectly fine, but you have to accept that the system isn't a general purpose black box, it was designed with intent that may not align with yours and you have to work within those constraints. Heck, it's the quirks of a system that give it it's character.

Edit: oh, and even in embedded systems with general purpose interrupt peripherals, trying to do too much in a handler is just asking for trouble... :D


You can set a "frame flyback event" or a "ticker event", as these occur at 50Hz


The Events (Fast Ticker, Ticker, Frame Flyback etc) are just a Firmware concept built on top of the standard 300Hz interrupt timer. They aren't any more guaranteed to execute at 50Hz than any other interrupt mechanism of you allow them to overrun.

Powered by SMFPacks Menu Editor Mod