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 > Interrupts not working :(

#2109 - Wanderer - Wed Jan 29, 2003 4:21 pm

Hi Folks,

I'm just starting to play arround with interrupts and want to get them working from as low down and dirty as possible in C (to fully understand how they work)

I've knocked out the following code which should display a striped background on Bkg0 and then on the first HBlank turn off the display (not the most exciting use of interrupts ever but it should show if they're working for me).

Can someone please tell me what I'm doing wrong?

Code:

typedef unsigned char       u8;
typedef unsigned short       u16;
typedef unsigned long       u32;

u16 offset = 1;

void interruptCode() {
   offset = 0;
}


int main() {
   u16 pal, x, y;

   // Blank the screen while we set it up.
   *(u16*)0x04000000 = 0x0080;

   // Fill in the first 8 entries in the palette   
   for(pal=0; pal<8; pal++) {
      *(u16*)(0x05000000 + 2*pal) = pal * 0x0842;
   }

   // Create a single tile.
   for(y=0; y<8; y++) {
      for(x=0; x<4; x++) {
         *(u16*)(0x06000000 + x*2 + y*8) = (x*2) + (((x*2)+1)<<8);
      }
   }

   // Clear the tile map to all 0s
   for(y=0; y<32; y++) {
      for(x=0; x<32; x++) {
         *(u16*)(0x06000800 + x + y*32) = 0;
      }
   }

   // Set up Bkg 0 with a 256 colour palette, and the correct tile map offset.
   *(u16*)0x04000008 = 0x0180; 

   // Enable Bkg 0.
   *(u16*)0x04000000 = 0x0100;



   // Set the code to be run on an interrupt.
   *(u32*)0x03007FFC = (u32) &interruptCode;

   // Set interrupts for HBlank in DISPSTAT.
   *(u16*)0x04000004 = 0x0010;

   // Set the HBlank interrupt to be enabled in REG_IE.
   *(u16*)0x04000200 = 0x0010;

   // Enable Interrupts in REG_IME.
   *(u8*)0x4000208 = (u8) 1;

   while(offset);

   // Blank the screen once the interrupt has been hit.
   *(u16*)0x04000000 = 0x0080;   

   while(1);

   return 0;
}


cheers,


Wanderer.

#2110 - AnthC - Wed Jan 29, 2003 4:39 pm

Things you have to realise about interrupts
(1) The handler should be in ARM - not thumb.
(2) You have to acknowledge the interrupt when its triggered.
(3) If you have interworking enabled, you can call thumb functions also.

Here's a sample asm stub which calls c code :-

IRQVec is written to the interrupt address (like you do in your code)
HandleVBLIRQ is a c function, called by the interrupt -
void HandleVBLIRQ(void)
There's easier ways todo this if you have the right crt0.s, but I didn't do it that way. (If you are looking for a quick solution then the c table version is a better way to go imo)


.GLOBAL IRQVec
IRQVec:
ldr r0,=R_IME
ldr r1,=0
strh r1,[r0] /* disable interupts */

ldr r0,=R_IF
ldrh r1,[r0]

ands r2,r1,#1 /* vbl irq ? */
bne IRQVBL

/* fixme : handle others here */

int_ret:
ldr r0,=R_IME
ldr r1,=1
strh r1,[r0] /* enable interupts */
bx lr /* return to monitor */

IRQVBL:
strh r2,[r0] /* interrupt serviced */
stmfd sp!,{r4-r12,lr} /* preserve registers */
.extern HandleVBLIRQ
bl HandleVBLIRQ /* call the c interrupt function */
ldmfd sp!,{r4-r12,lr} /* restore registers */
b int_ret


Have fun
Anth

#2120 - headspin - Wed Jan 29, 2003 9:15 pm

Well obviously asm isn't going to help a C coder much.. here is something basic that may help... it sets up an interrupt for trapping VBlank and HBlank. Cowbite Virtual Hardware Specs are really handy for this sort of stuff... http://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm

Code:

#define REG_INTERUPT   *(u32*)0x3007FFC

#define REG_DISPSTAT   *(u16*)0x4000004
#define REG_IE         *(u16*)0x4000200
#define REG_IF         *(u16*)0x4000202
#define REG_IME        *(u16*)0x4000208

#define INT_VBLANK 0x0001
#define INT_HBLANK 0x0002

void hblank_vblank()
{
   REG_IME=0;
   REG_IE|=INT_HBLANK|INT_VBLANK;
   REG_DISPSTAT|=(1<<4) | (1<<3);
   REG_IME=1; //enable interrupt
}

void InterruptHandler(void)
{
   u16 temp_reg_if=REG_IF;
 
   REG_IME=0;

   *((u16*)0x03007ff8) = REG_IF;
 
   if(temp_reg_if & INT_HBLANK) { }

   if(temp_reg_if & INT_VBLANK) { }

   REG_IME=1;
}

int main(void)
{
   REG_INTERUPT=(u32)&InterruptHandler;

   hblank_vblank();
}

#2181 - Wanderer - Thu Jan 30, 2003 4:36 pm

Hi again,

thanks for the offerings, I managed to work out why things weren't working.. I was setting REG_IE incorrectly, so no interrupts were being fully enabled.

I do have a couple of quesions still though:

In the Cowbite Spec it suggests writting 1 into the relevant bit of REG_IF to show that the interrupt has been processed. However when an interrupt hasn't been processed yet it's flag seems to be at 1 already, so writting a 1 into it won't change it. Is this an error in the Spec or am I getting muddled up somewhere???

AnthC:
Are you sure it has to be ARM code?.. I managed to get it working ok in pure C which I believe is all Thumb code.

Headspin:
Thanks for the C, could you explain why you write REG_IF to the address 0x03007FF8? - I can't find any mention of this address in the Cowbite Specs?

thanks,


Wanderer.

#2356 - headspin - Sun Feb 02, 2003 11:58 am

Yes, that is strange that Cowbite don't mention that address. Well as far as I know 0x3007FF8 is usually defined as REG_IFCHECKBUFF which suggests it's a buffer used when checking REG_IF. I'm not sure if this step is entirely necessary since Cowbite say the basic model for setting up an interrupt is:

Quote:

1. Place the address for your interrupt code at 0x03007FFC.

2. Turn on the interrupts you wish to use:
- REG_DISPSTAT, REG_TMXCNT, REG_KEYCNT, or REG_DMAXCNT tell the hardware which interrupts to send
- 0x04000200 (REG_IE) masks which interrupts will actually be serviced (?)
- 0x04000208 (REG_IME) Turns all interrupts on or off.

3. When the interrupt is reached, the code at the address at 0x3007FFC gets loaded into the CPU. To prevent unwanted errors/behavior, the first thing this code should do is disable interrupts.

4. To determine what interrupt this is, check the flags in 0x04000202 (REG_IF). Unset the flag by writing a 1 to that bit.

5. Once finished with the service routine, reenable interrupts and execute a BX LR (*Not* a SUBS PC, LR #4, which is what the BIOS does). The BIOS will then take over and return your program to where execution left off.


They don't mention REG_IFCHECKBUFF at all, so it beats me the reason for it's use. Maybe someone can shed some light on the subject?

Quote:
In the Cowbite Spec it suggests writting 1 into the relevant bit of REG_IF to show that the interrupt has been processed. However when an interrupt hasn't been processed yet it's flag seems to be at 1 already, so writting a 1 into it won't change it. Is this an error in the Spec or am I getting muddled up somewhere???


I don't think writing a 1 to the interrupt's bit into REG_IF is necessary. I mean, you check that bit to see if the interrupt has occured in the first place, why would setting it again make any difference? I'm not sure what they mean by this at all. Perhaps setting REG_IME to turn off all interrupts means this step is not nessesary?

#2367 - tepples - Sun Feb 02, 2003 3:52 pm

headspin wrote:
Yes, that is strange that Cowbite don't mention that address.


Quote:
They don't mention REG_IFCHECKBUFF at all, so it beats me the reason for it's use. Maybe someone can shed some light on the subject?

As I see it, register 0x03007ff8 (called BIOS_INTACK in my header file) is used to tell IntrWait() and VblankIntrWait() which interrupts your handler acknowledged.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.