News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_SpDizzy

Better understanding of interrupts. Help needed

Started by SpDizzy, 11:37, 26 April 19

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

SpDizzy

Hi guys,
First of all, excuse me if this was asked before on the community, but could not find an exact answer for my question.
I have a working example with interrupts being called every 52 scanlines, 6 times per frame, for precise timings.
On each one of these interrupts, different code is performed to achieve needed tasks (scan keyboard, collisions, play sound,...) as well as drawing sprites to screen without the need of double buffer.

So far so good.
I'm facing the problem of drawing a bigger sprite for a boss, where more than 52 scanlines will be needed for that.Let's say drawing call comes on 3th interrupt and it will took more cycles than an interrupt, 1.5 ints for example, or 5000 microsecs (as each int is roughly 3300 microsecs).
Here my questions:

1/ May I expect then to lose 4th interrupt, or will be the whole interruption system being affected and corrupted since that delay?

2/ Had done some test but could not find and exact answer. The fact is that music, called on 6th interrupt, is still playing with no delay. It does confuse me.

3/ On the case of whole interruption system affected, how to deal with that problem not to lose the precise timing? Split the routine for drawing the sprite on 2 interrupts so each drawing take less than an interrupt? Disable interruptions after drawing, wait for Vsync, and re enable interruptions? In that case I lose 4th, 5th and 6th ints.

Thanks so much for your time and support,

pelrun

It's not a good idea to use the 300Hz interrupt to schedule background processing, for the reasons you've encountered. The interrupt is useful for synchronising things to specific areas of the screen (e.g. if you want to split the screen into multiple sections with different palette/mode/CRTC settings.) Music/control input/game logic just has to happen once a frame, and can all be done immediately after Vsync.


You then have the entirety of the time until the next Vsync to update the framebuffer. If you make sure that you don't draw on a scanline until after the beam has passed it, then you don't have to worry about tearing, either.

SpDizzy

#2
Thanks so much for your response and advice pelrun, really appreciate.The fact is that I'm on the need of a precise timing for various tasks, like mencioned palette changes, as well as the drawing routines for the characters on screen, so enemy1 updates on int1, enemy2 on int2,...hence the interrupt management (somewhat like "Operation Alexandra" from 4Mhz did).So this make me wonder about the risk of 'losing' just the next interrupt if the tasks for one interrupt take longer than 52 scanlines, or will the whole interruption system being affected and corrupted for that delay, going asynchronous all the way.

Longshot

#3
Hi,
When an interruption occurs, 52 hbl have occurred.
(3328 nop approximately according to the duration of the instruction in progress, and considering that there is 1 hbl every 64 nop)

For these 52 lines, you must consider that there are 2 counters.First 1 of 32 lines, then 1 of 20 lines. Each one count all the time.

When an interrupt is in progress, there can not be another interruption until you have acknowledged it (with the EI instruction).
If the code in your interrupt is longer than 3328 nop, then no new interrupts can occur.
The next interruption is pending and occurs immediatly after the first instruction after the EI.

Two different situations can occur then:
The pending interruption occurs while C32 counts. So everything goes well, C32 finishes counting, C20 counts, and the next interruption is located as habitual.
The pending interruption occurs while C20 counts. In this situation, the previous count information of the C32 is lost, and the next interrupt will be delayed by 32 hbl. (So it will happen 32 hbl after the normal one)

To summarize if the code of your sprite is > 3328 nop, the next interruption (pending) occurs immediatly.
If this interruption occurs before  3328+(32x64) = 5376 nop, the next one will not be delayed
If this interruption occurs after this delay, then the next interrupt will be delayed by 32 lines.

If you want to put your sprite code in the interrupt code, and you're not sure how long your routine lasts, you can do something simple by allowing reentrancy (just a little interrupt handler).
So you do not have to worry about these counters anymore.
When your interruption occurs, the code in your interrupt must begin by managing reentrancy.
Hope this can help  ;D

#38 JP MI_INTER

MI_INTER
    PUSH HL
    LD HL,BASURA_INTER
    EI                    ; acknowledge new interruption after the next instruction (LD (#39),HL)
    LD (#39),HL
    ... CODIGO POR EL SPRITE
    ...
    LD HL,MI_INTER
    DI                    ; prevent reentrancy on the main interrupt
    LD (#39),HL
    POP HL
    EI                    ; next interrupt can occur after the RET
    RET
   
BASURA_INTER ; this interruption ack C32/C20
    EI
    RET   

Another way to do the same...
#38 EI
       JP MI_INTER
       
MI_INTER
    PUSH AF
    LD A,#C9      ; RET
    LD (#39),A
     ...
     ...
     ...
     LD A,#C3
     DI
     LD (#39),A   ; JP
     POP AF
     EI
     RET
    
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

SpDizzy

Ouh, thanks so much Longshot for that precise response.
It really helps so much understanding the way interruptions work.
Really appreciate the support!!

GUNHED

Well, don't kow how your interrupt system looks like, but it's always a good thing to "reset" your counter with the frame fly back. So even if for one picture some delay happens the next screen will be displayed properly, because on every interrupt you check for frame flyback and if it happens you know it the first=last interrupt of the single picture displayed.
http://futureos.de --> Get the revolutionary FutureOS (Update: 2023.11.30)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> Get the RSX-ROM for LambdaSpeak :-) (Updated: 2021.12.26)

SpDizzy

Well, working under #Cpctelera, this is how my intHandler is declared:



static  int intCount = 0;



void main(void) {


init(); // sets mode, palette,...


   // disable ints
   __asm
   di
   __endasm;
   
   cpct_waitVSYNC();


   __asm
   ld b,#0x0f5
   wve::
   in a,(c)
   rra
   jp c,wve
   __endasm;
   
   // wait start of next
   cpct_waitVSYNC();


   intCount = 0;
   cpct_setInterruptHandler ( intHandler);


   __asm
   ei
   __endasm;
   
   while (1);
}



And this the interruption management




void intHandler(void){


   switch (intCount)
   {
      case 0:
      {


      }
      break;
      case 1:
      {


      }
      break;
      case 2:
      {
   
      }
      break;
     
      case 3:
      {
                drawEnemySprite();
      }
      break;
     
      case 4:
      {


      }
      break;
     
      case 5:
      {
      playmusic();
      scanKeyboard();
      }
      break;
   }
     
   intCount++;
   if (intCount==6)
   {
      intCount = 0;
   }
}




Longshot

#7
I've corrected my previous post...Sorry for the errors.
When the two counters reach 32 and 20, the interruption is pending and occurs immediatly after the first instruction after the EI.
I don't know "CpcTelera", but it seems the interrupt acknowledgment (ei) is managed internally by the function cpct_setInterruptHandler()
So you've no access to the interrupt ack (ei) and so you cannot use the method described above in your intHandler () function.
The exact position of an interruption cannot be guaranteed if the delay of you interruption varies a lot. 
There is a risk that the interruption that occurs after the one that was waiting is not placed on a line that is a multiple of 52
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

Arnaud

Quote from: Longshot on 10:36, 28 April 19
I don't know "CpcTelera", but it seems the interrupt acknowledgment (ei) is managed internally by the function cpct_setInterruptHandler()

Yes, CPCTelera managed internally the di/ei of the interruption :
https://github.com/lronaldo/cpctelera/blob/development/cpctelera/src/firmware/cpct_setInterruptHandler.s

Quote from: SpDizzy on 18:42, 27 April 19
Well, working under #Cpctelera, this is how my intHandler is declared:

If you want to synchronize your interruption with the display here a way to do :
http://www.cpcwiki.eu/forum/programming/how-correctly-synchronize-interruption-with-raster-(cpctelera)/msg117112/#msg117112

cpct_waitVSYNC();

__asm
halt
halt
__endasm;

cpct_waitVSYNC();

cpct_setInterruptHandler(interruptFunction);



SpDizzy

Thanks so much Longshot for the correction, and Arnaud for pointing in the right direction (I was going to link to  #Cpctelera cpct_setInterruptHandler.s as well)
I know about your post Arnaud, to correctly synchronize with raster. Really valuable.
Maybe as pelrun said, I will have to rethink the idea of drawing on each interrupt for the different enemies, or find a way to improve the drawing speed or split each enemy drawing on 2 interrupts to guarantee been able to fit on 52 lines for each one.




andycadley

One option is just to have your interrupt set a global count of which routine you're on and your main code check that. It works reasonably well.as long as you don't miss too many interrupts.

Longshot

#11
Quote
   ei          ;; [1] Reenable interrupt
   reti        ;; [3] Return to main program 
Arnaud, reti can be replaced by ret in the cpctelera interrupt handler.
reti is 4 nop, ret is 3 nop

SpDizzy :
What is the role of your main program?
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

TotO

RETI is mandatory is you are using a Z80 peripheral like the CTC (i.e. on PlayCity). Else, sure, RET does the job.
"You make one mistake in your life and the internet will never let you live it down" (Keith Goodyer)

GUNHED

Yes, using RET instead of RETI/RETN will also make problems with 6128 Plus when using IM2. If somebody wants to save time then saving 6 us per FRAME is not the thing to start with.
http://futureos.de --> Get the revolutionary FutureOS (Update: 2023.11.30)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> Get the RSX-ROM for LambdaSpeak :-) (Updated: 2021.12.26)

SpDizzy

Quote from: Longshot on 13:33, 29 April 19
SpDizzy :
What is the role of your main program?
It's just a game.
The main intent is to split the drawing routines for each sprite, as well as game logic, on diferent interrupts, for a fixed global game update of 25fps.
So let's say (take into account that's not necessarily the exact order, just for example purposes) this will be the approach for each frame:

FRAME 1

Draw enemy1 on int1, enemy2 on int2, player on int5, music and control input takes place on int6,...that's for one frame.

FRAME 2

Same approach for the other frame with other enemies, some logic in between, background captures,..

So if I understood the way interruptions work, whenever the routine on each interrupt takes less than 3328 + (32*64), that is 5376 nop, the exact position of each interruption will be guaranteed, with no delays.

Please correct me if I'm wrong.

Thanks so much all of you for the support,

Longshot

My question was not to know what kind of program you make. (a game is a very good option  :D )
If you manage all your code via interruption, what does the main code ?
Is it just waiting for each interrupt to occur ?
If so, you lost cpu time between each interruption.May be you have to dedicate the interrupt to some precise and short events.

About interruption, it's correct.
In fact, the pending interruption clear the interrupt flags
First C32 counter counts until 32 and then C32Flag=1
Then C20 counter counts until 20 and then C20Flag=1
When C20Flag=C32Flag=1 then an interruption is pending.
The 2 flags are cleared by the next EI (Acknowledge).
If the EI occurs after the C32Flag is 1, then it is cleared and then C32 needs to count another time.
So this behavior can occur if both flags are at 1 and you are doing an EI in one of the areas where C20 counts.

A little code below to see this phenomenon.
It changes the border color at each interrupt.
For one of the interrupt, the interrupt last 5372 nops, and then the next interrupt can occur.
But if you add one nop (5337 instead of 5336) then the next interruption is delayed.



    org #4000
    run $
    di
    ld bc,#7F10
    out (c),c
    ld hl,inter
    ld (#39),hl
    ld a,#c3
    ld (#38),a
    ld h,#59
    ld b,#f5
wvbl
    in a,(c)
    rra
     jr nc,wvbl
    ei
    jr $

inter
    ld b,#f5    ; 2
    in a,(c)    ; 4
    rra        ; 1
    jr nc,novbl    ; 3
    ld h,#59
    ld d,#ff
novbl
    inc d        ; 1
    inc h        ; 1
    ld b,#7F    ; 2
    out (c),h    ; 4
    ld a,d        ; 1
    cp 3        ; 2
    jr z,wait    ; 3
    ei
    ret
wait
    defs 5336,0    ; 5336
    ei        ; 1
    ret        ; 3
   

 
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

Longshot


    RETI is mandatory is you are using a Z80 peripheral like the CTC

    This is true. RETI can only acknowledge ZILOG devices.  ;)
    For a handler who handles GA interrupts, there is no reason to lose 1 nop.
   
   Yes, using RET instead of RETI/RETN will also make problems with 6128 Plus when using IM2. If somebody wants to save time then saving 6 us per FRAME is not the thing to start with.
   


If no device with a ZILOG device generating interrupts is connected to an Amstrad Plus, the RETI is no more mandatory than on the old CPC.
But if not, can you tell me in what context?
If you want to manage the interruption of a specific device, I imagine that the handler will have to be adapted to handle interruptions of this specific device.
If someone wants to save time, he can start by avoiding wasting time unnecessarily.
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

SpDizzy

Quote from: Longshot on 19:25, 01 May 19
My question was not to know what kind of program you make. (a game is a very good option  :D )
If you manage all your code via interruption, what does the main code ?
Is it just waiting for each interrupt to occur ?
If so, you lost cpu time between each interruption.May be you have to dedicate the interrupt to some precise and short events.
It looks like I minsunderstood your question, as well as the whole concept about interruptions. In fact, my main code does roughly nothing besides initialization (set mode, palette, disable firmware, initial Crtc changes, ..) so just waiting for every interruption to occur.
You point in the right direction. My main mistake, as pelrun already said, was manage all the stuff via interruptions. Really bad and wasted cycles idea.
Thanks so much once again for your precise explanation and the neat piece of code. I guess I finally understood!!  :)

GUNHED

Well, using RETI instead of RET saves 6 us every frame, this is about 0,03% of time. And there is a trade off for non-pure-stock CPCs. IMHO saving cycles should happen where it brings something like 20% to 30% of time, unroll loops etc. whatever you like.  :)  But yes, of course using RET saves 1 us per interrupt on stock CPCs. And it shouldn't be problematic for games and demos (or what ever one like).  :)  For more complex games supporting new hardware f.e. PlayCity, Amstrad SIO (and such games can be awesome!), still RETI would need to be used.  :)
http://futureos.de --> Get the revolutionary FutureOS (Update: 2023.11.30)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> Get the RSX-ROM for LambdaSpeak :-) (Updated: 2021.12.26)

andycadley

Quote from: GUNHED on 15:46, 02 May 19
Well, using RETI instead of RET saves 6 us every frame, this is about 0,03% of time. :)
How so? RETI takes 1 NOP longer than RET, doesn't it?

Longshot

GUNHED talks about 6 nop because there are 6 interrupts per frame ... (or 300 nop per second .... :P )
1 nop can also make the difference for synchronization issues.
IMHO saving cycles can occur everywhere.
French proverb: Small streams make big rivers. ;D
Rhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!

andycadley

I get the 6 part, but surely using RETI you're wasting 6 nops per frame as it's a longer instruction? That might help if you have specific timing that requires you to delay slightly, but it seems the using RET otherwise gives you an extra 6 nops per frame which is better.

GUNHED

Quote from: Longshot on 00:04, 03 May 19
1 nop can also make the difference for synchronization issues.


CPC - the world of perfection  :) :) :)
http://futureos.de --> Get the revolutionary FutureOS (Update: 2023.11.30)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> Get the RSX-ROM for LambdaSpeak :-) (Updated: 2021.12.26)

Powered by SMFPacks Menu Editor Mod