gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Proper way to use Vblank Interrupt

#11841 - Tinyn - Tue Oct 21, 2003 7:27 am

So, I don't want to just do while(VCOUNT != 160), cause thats lame. But I can't seem to figure out how Im supposed to use the interrupt.

Do I...
Code:


int main(void){
// Stuff

  SWI_VBlankInterWait();
}

InterruptHandler() {
  if(IF & VB_INTER)
   // Do real stuff
}

And evrything important is done by the Interrupt Handler (or functions it calls)

Or...
Code:

void main(void) {
// stuff
  while(1) {
    SWI_VBlankInterWait();
    // Do real stuff
  }
}

void InterruptHandler() {
 // Do not much, cause just noticing that the interrupt happened and returning
}


The first puts junk in the InterruptHandler that feels wrong, but the second doesn't like its going to work that way, it won't return back to immediatly after the SWI function. I really don't see how this thing works.


PS: Why, why is my compiled C/C++ including a "b #07000160" in its ASM? 07000160 is a very very silly place to jump to...

#11843 - Gopher - Tue Oct 21, 2003 9:57 am

I have a flexable module I use for handling interrupts that lets me assign seperate functions to each interrupt. Here's what I use...

ggbaInterrupt.h
Code:

#ifndef  GGBAInterrupt_H
#define GGBAInterrupt_H

#include "GGBA.h"

#define gbaEnableInterrupt(wInterrupt) REG_IE |= (wInterrupt);

#define gbaDisableInterrupt(wInterrupt) REG_IE &=~(wInterrupt);

#define gbaEnableMasterInterrupt REG_IME = 1;
#define gbaDisableMasterInterrupt REG_IME = 0;

inline void gbaEnableInterruptHandler();


inline void gbaSetInterruptHandler(
      hword wInterrupt,
      gbaInterruptFunc* pFunc);




#endif


ggbaInterrupt.c
Code:

#include "GGBAInterrupt.h"
#include "GGBAGfx.h"

static gbaInterruptFunc *sg_InterruptTable[14]={0,};

inline hword IntFlagToIndex(hword wFlag)
{
  hword index=0;

  if (0==wFlag)
    return 0xffff;

  while (0==(wFlag&1))
  {
    index++;
    wFlag>>=1;
  }
  return index;
}


void InterruptHandler(void)
{
  hword index=0;
 
  while (REG_IF)
  {
    index IntFlagToIndex(REG_IF);
 
    if (index!=0xffff && sg_InterruptTable[index])
        sg_InterruptTable[index](); 

    REG_IF|=wflag;
  }

}   


inline void gbaEnableInterruptHandler()
{
  *GBA_INT_FUNC_PTR=&InterruptHandler;
}



inline void gbaSetInterruptHandler(
      hword wInterrupt,
      gbaInterruptFunc* pFunc)
{
 

  hword index=IntFlagToIndex(wInterrupt);
  if (index!=0xffff)
    sg_InterruptTable[index]=pFunc;

}



Not the most efficient approach, but very flexable and works well for rapid prototyping. If the speed becomes an issue, I can always optimize by implementing app-specific handlers once if it becomes an issue.

I'm not sure exactly how the interrupt handler sends interrupt events, so to be on the safe side at present I have it handling the case of multiple interrupt flags being set at once. Anyone know if this can actually happen, or does it always send the flags one at a time on seperate calls to the interrupt handler? And while I'm asking questions, will interrupts interrupt eachother, or do they queue up internally and come out one-at-a-time?
[/url]
_________________
"Only two things are infinite: the universe, and human stupidity. The first is debatable." -Albert Einstein

#12192 - dushan42 - Mon Nov 03, 2003 2:49 pm

I'm new to GBA development, so I'd also like to know what's the usual VBLANK practice.

Tinyn, I'd avoid the first option - keep your interrupt handlers short.
In my project, I've gone for the following solution:

Code:
int gLastFrameCount=1;
int gFrameCount=0;
bool gProcessingFrame = true;

void main()
{
   while(1)
   {
      processFrame(); // (update gamestate, draw into offscreen buffers)
      gProcessingFrame = false;
      while(!gProcessingFrame); // Wait for the next frame
   }
}

void VBlankHandler()
{
   gFrameCount++;
   if (gProcessingFrame)
      return; // prcessFrame() hasn't finished yet - we've missed this frame...
   
   processVBlank(); // launch DMA transfers from offscreen buffers into VRAM
   gLastFrameCount = gFrameCount; // useful for framerate independence / FPS display
   gFrameCount = 0;
   gProcessingFrame = true; // kick off processing of the next frame
}


To prevent nasty tearing effects, I only write to VRAM and/or graphics control registers in the processVBlank() function. Make sure that it doesn't overrun the VBLANK period.
I use mode 0 and simply DMA precalculated EWRAM buffer into VRAM - not the most efficient approach, but it keeps things very simple.

I'd be interested in seeing alternative solutions.


dushan42

#12202 - sajiimori - Mon Nov 03, 2003 7:57 pm

Code:

bool gProcessingFrame = true;
...
      gProcessingFrame = false;
      while(!gProcessingFrame); // Wait for the next frame

Careful, that variable needs to be declared volatile or you could get lockups.

#12203 - dushan42 - Mon Nov 03, 2003 8:46 pm

Good point!

I was at work so I was typing that of the top of my head - treat it as pseudocode! :)

#12221 - Drago - Tue Nov 04, 2003 9:36 am

This method lets you save batteries while waiting:

Code:

while(true)
{
    // Read keys, chk collision, move entities...
    updateGame();

    // Switch the CPU into low-power mode until VBlank occurs
    VBlankIntrWait();

    // Dump to VRAM
    drawFrame();
}


Note that you must acknowledge the wait by OR'ing the bit 0 (VBLANK) to [3007FF8h]. A good place to do that is inside the VBLANK handler.

#12223 - dushan42 - Tue Nov 04, 2003 1:48 pm

Thanks for the tip - I wasn't quite sure before what was the advantage of using SWI to sync with VBLANK..

I also forgot to mention that I have to wait for my DMA transfer to finish before I start drawing into the off-screen buffer again.. I guess I should replace that loop with IntrWait (SWI 4).

edit: ehm, it appears that DMA transfers halt the CPU (?!??) so there's no need to explicitly wait for them to finish.. that makes things a bit easier :)