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 > Mysterious interrupt issue

#51563 - jarobi - Sun Aug 21, 2005 9:22 pm

I have just started getting into interrupts and for some reason, they are not working for me. I have looked at numerous threads regarding the issue, but I can't seem to find any info that helps me out. Since I do not have the time to learn ARM assembly, I have used the IrqHandler routine from The PERN Project website (day 4 in tutorials). I have also modified the interrupt.h from that same tutorial. I have checked the assembler instructions at 0x3007ffc and the IrqHandler routine is there. However, when I load my ISR into the IntrTable and enable interrupts, absolutely nothing happens. Everything seems to be in the right place, which makes things very confusing for me now. The code (partial) below should move a 32x32 px window one pixel down and one pixel right during vblank:

Code:
//
// interrupt.h
//
#define INT_VBLANK      0x0001
#define INT_HBLANK      0x0002
#define INT_VCOUNT      0x0004
#define INT_TIMER0      0x0008
#define INT_TIMER1      0x0010
#define INT_TIMER2      0x0020
#define INT_TIMER3      0x0040
#define INT_SERIALCOM   0x0080 //serial communication interupt
#define INT_DMA0      0x0100
#define INT_DMA1      0x0200
#define INT_DMA2      0x0400
#define INT_DMA3      0x0800
#define INT_KEYPAD      0x1000
#define INT_CART      0x2000

void interruptEnable(); //sets REG_IME to 1
void interruptDisable(); //sets REG_IME to 0
void irqEnable(int, fp); //enables an interrupt request for a given interrupt
void irqDisable(int); //disables an interrupt request

//
// interrupt.c
//
extern void IrqHandler();

fp IntrTable[14];

void interruptEnable()
{
   REG_IME = 1;
}

void interruptDisable()
{
   REG_IME = 0;
}

void irqEnable(int irqId, fp irq_funct)
{
   int i, j, prevIMEval;

   for (i = 0, j = 1; i < 14; i++, j<<=1)
   {
      if ((j & irqId) != 0)
      {
         IntrTable[i] = irq_funct;
      }
   }
   
   REG_IRQHAND = IrqHandler;
   prevIMEval = REG_IME;
   REG_IME = 0;
   REG_IE |= irqId;
   REG_IME = prevIMEval;
}

void irqDisable(int irqId)
{
   int prevIMEval;
   prevIMEval = REG_IME;
   REG_IME = 0;
   REG_IE ^= irqId;
   REG_IME = prevIMEval;
}

//
// irqh.s
//
@ Interrupt Request Handler taken from "The Pern Project"
@ http://www.thepernproject.com/tutorials/GBA/day_4.html
       .SECTION    .iwram,"ax",%progbits
       .EXTERN     IntrTable
       .GLOBAL     IrqHandler
       .ALIGN
         .ARM

IrqHandler:
                                         @ Multiple interrupts support
        mov     r2, #0x4000000           @ REG_BASE
        ldr     r3, [r2,#0x200]!         @ r2 = IE : r3 = IF|IE
        ldrh    r1, [r2, #0x8]           @ r1 = IME
        mrs     r0, spsr
        stmfd   sp!, {r0-r2,lr}          @ {spsr, IME, REG_IE, lr}
        mov     r0, #1                   @ IME = 1
        strh    r0, [r2, #0x8]
        and     r1, r3, r3, lsr #16      @ r1 = IE & IF

        @BIOS uses 0x300:7FF8 to hold IRQ flags for certain functions
        @such as IntrWait
       
        ldrh    r3, [r2, #-8]             @ r3 = [0x300:7FF8]
        orr     r3, r3, r1                @ r3 |= IF & IE                   
        strh    r3, [r2, #-8]             @ [0x300:7FF8] = r3

        @Set r12 to point to our IRQ table
        ldr     r12, =IntrTable

        @cycle through IF checking to see if the bits are set.
        @if set we jump to the interrupt table
        ands    r0, r1, #1               @ V-blank interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #2               @ H-blank interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #4               @ V-counter interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #8               @ Timer 0 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x10            @ Timer 1 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x20            @ Timer 2 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x40            @ Timer 3 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x80            @ Serial Comm Interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x100           @ DMA 0 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x200           @ DMA 1 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x400           @ DMA 2 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x800           @ DMA 3 interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x1000          @ Key interrupt
        bne     jump_intr
        add     r12,r12, #4
        ands    r0, r1, #0x2000          @ Cart interrupt
        strneb  r0, [r3, #0x84 - 0x200]  @ Stop sound if cart removed (REG_SOUNDCNT_X)

jump_intr:
        strh    r0, [r2, #2]             @ Clear IF
        mrs     r3, cpsr
        bic     r3, r3, #0xdf            @ Stack stuff
        orr     r3, r3, #0x1f            @
        msr     cpsr, r3
        ldr     r0, [r12]
        stmfd   sp!, {lr}
        adr     lr, IntrRet
        bx      r0

IntrRet:
        ldmfd   sp!, {lr}
        mrs     r3, cpsr
        bic     r3, r3, #0xdf            @
        orr     r3, r3, #0x92            @
        msr     cpsr, r3
        ldmfd   sp!, {r0-r2,lr}          @ {spsr, IME, REG_IE, lr}
        strh    r1,  [r2, #0x8]          @ restore REG_IME
        msr     spsr, r0                 @ restore spsr
        bx      lr

    .ALIGN
    .POOL

//
// main.c
//
#define WIN_SIZE 32

int i = 0, j = 0;

void doSomething()
{
   setWin0Bounds(i++, i+32, j++, j+32);
}

int main()
{
   Background demon_bk;
   
   irqEnable(INT_VBLANK, doSomething);
   interruptEnable();
   
   demon_bk.bgNumber = 2;
   demon_bk.attributes = BG_COLOR_256
                  |ROTBG_SIZE_1
                  |BG_PRIORITY(3)
                  |CHAR_BASE_BLOCK(0)
                  |SCREEN_BASE_BLOCK(24)
                  |WRAP_AROUND;
   
   loadBGPalette(demonPal);
   loadBGTiles((u16*)demonData, 0, 256, 0);
   loadBGMap(demon_map, 24, 16, 16);
   
   REG_BG2CNT = demon_bk.attributes;
   
   setWin0Content(W0_BG2);
   
   setMode(MODE_2|BG2_ENABLE|WIN0_ENABLE);
   while (1)
   {
      waitForVBlank();
   }
   
   return 0;
}

//
// window.h
//
//Window 0 content flags (inside window)
#define W0_BG0 0x1
#define W0_BG1 0x2
#define W0_BG2 0x4
#define W0_BG3 0x8
#define W0_SPRITE 0x10
#define W0_BLEND 0x20

//Window 1 content flags (inside window)
#define W1_BG0 0x100
#define W1_BG1 0x200
#define W1_BG2 0x400
#define W1_BG3 0x800
#define W1_SPRITE 0x1000
#define W1_BLEND 0x2000

//Window Out content flags (outside window)
#define WOUT_BG0 0x1
#define WOUT_BG1 0x2
#define WOUT_BG2 0x4
#define WOUT_BG3 0x8
#define WOUT_SPRITE 0x10
#define WOUT_BLEND 0x20

//Object Window content flags (in object window)
#define WOBJ_BG0 0x100
#define WOBJ_BG1 0x200
#define WOBJ_BG2 0x400
#define WOBJ_BG3 0x800
#define WOBJ_SPRITE 0x1000
#define WOBJ_BLEND 0x2000

#define setWin0Content(n) (REG_WININ=(REG_WININ&=0x3f00)|(n&0x3f))
#define setWin1Content(n) (REG_WININ=(REG_WININ&=0x3f)|(n&0x3f00))
#define setWinOutContent(n) (REG_WINOUT=(REG_WINOUT&=0x3f00)|(n&0x3f))
#define setWinObjContent(n) (REG_WINOUT=(REG_WINOUT&=0x3f)|(n&0x3f00))

#define setWin0Bounds(x1, x2, y1, y2) REG_WIN0H=((x1)<<8)|(x2);REG_WIN0V=((y1)<<8)|(y2);
#define setWin1Bounds(x1, x2, y1, y2) REG_WIN1H=((x1)<<8)|(x2);REG_WIN1V=((y1)<<8)|(y2);

_________________
Nihongo o hanasemasen!

#51575 - DekuTree64 - Mon Aug 22, 2005 1:01 am

You also need to set the VBlank interrupt bit in REG_DISPSTAT.

The logic behind it is that each piece of hardware that can generate an interrupt has a bit to enable it (in DISPSTAT, the timer regs, DMA regs, etc), and then REG_IE is like a bunch of AND gates to block those signals if you want to.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#51579 - jarobi - Mon Aug 22, 2005 2:58 am

Oh, geez. How could I have missed that? Thank you very much, DekuTree64, your advice has helped my code work. On another note, one thing I have noticed is that when my isr runs via an interrupt, it is much slower than when it is called directly in my main loop with a waitForVBlank routine. Is there severe overhead in using interrupts?
_________________
Nihongo o hanasemasen!