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 > Need help in fixing bugged ISR

#45541 - DiscoStew - Sat Jun 11, 2005 9:27 pm

Right now I'm at a point in my GBA project that I'd like to start cleaning up my code for bugs and errors, but before I can do that, I have to fix whatever is the problem with the ISR that I'm working with. It works fine, but when I activate the VBlank, I get almost 60 errors a second, and tons more when I activate the HBlank. I used the ISR example that Cearn used for his demos, which was taken from wintermute's "libgba". I've stripped everything down to just setting up the ISR. Here are the files with the code, both C\C++ and Assembly

"single_ints.s"
Code:
@
@ Interrupt switch block
@ Taken from wintermute's libgba (www.devkit.tk)
@
@---------------------------------------------------------------------------------
   .section   .iwram,"ax",%progbits
   .extern   IntrTable
   .code 32

   .global   IntrMain
@---------------------------------------------------------------------------------
IntrMain:
@---------------------------------------------------------------------------------
                           @ Single interrupts support
   mov      r3, #0x4000000         @ REG_BASE
   ldr      r2, [r3, #0x200]      @ Read REG_IE and REG_IF
   and      r1, r2, r2, lsr #16      @ r1 =   REG_IE & REG_IF

   ldrh   r2, [r3, #-8]         @\mix up with BIOS irq flags at 3007FF8h,
   orr      r2, r2, r1            @ aka mirrored at 3FFFFF8h, this is required
   strh   r2, [r3, #-8]         @/when using the (VBlank)IntrWait functions

   add      r3, r3, #0x200
   ldr      r2, =IntrTable

   ands   r0, r1, #1            @ V-Blank Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #2            @ H-Blank Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #4            @ V Counter Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #8            @ Timer 0 Interrupt
   bne      jump_intr
   add      r2,   r2,   #4

   ands   r0, r1, #0x10         @ Timer 1 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x20         @ Timer 2 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x40         @ Timer 3 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x80         @ Serial Communication   Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x100         @ DMA0 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x200         @ DMA1 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x400         @ DMA2 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x800         @ DMA3 Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x1000         @ Key Interrupt
   bne      jump_intr
   add      r2, r2, #4

   ands   r0, r1, #0x2000         @ Cart Interrupt

   strneb   r0, [r3, #0x84 - 0x200]   @ Stop   sound if cart removed (REG_SOUNDCNT_X)
loop:
   bne      loop               @ Infinite   loop if   cart removed

@---------------------------------------------------------------------------------
jump_intr:
@---------------------------------------------------------------------------------
   strh   r0, [r3, #2]         @ Acknowlegde int (will clear REG_IF)
   ldr      r0, [r2]            @ Jump   to user   IRQ   process
   bx      r0
@ required for assembling on dkAdv (but not dkARM)
   .align
   .pool

"interrupt.h"
Code:
// interrupt.h:
// interrupt header file
//
// (Created: 2003-11, Modified: 2004-05-21, Cearn)

#ifndef GBA_INTERRUPT
#define GBA_INTERRUPT

#include "gba.h"

// === CONSTANTS ======================================================

// interrupt enums
enum eIntrIndex
{
   INT_VBLANK=0, INT_HBLANK, INT_VCOUNT, INT_TM0,
   INT_TM1,      INT_TM2,    INT_TM3,    INT_COM,
   INT_DMA0,     INT_DMA1,   INT_DMA2,   INT_DMA3,
   INT_KEYS,     INT_CART
};

// === GLOBALS ========================================================

extern fnptr IntrTable[];
// === PROTOTYPES =====================================================

// default interrupts

void IntrMain();   // see single_ints.s for definition

void int_init();   // Must be called for interrupts to work


// advanced interrupt en/disable
// NOTE: uses the II_xx enums, not the INT_xx flags
void int_enable(enum eIntrIndex eii);
void int_disable(enum eIntrIndex eii);

#endif // GBA_INTERRUPT

"interrupt.c"
Code:
// interrupt.c:
// interrupt implementation file
//
// (Created: 2003-11, Modified: 2004-05-21, Cearn)

#include "interrupt.h"


void VBlankInterruptHandler()
{
};

void HBlankInterruptHandler()
{
};

void UnusedInterruptHandler()
{
};


// === CLASSES ========================================================

// used for int_enable_ex and int_disable_ex
typedef struct tagINT_SENDER
{
   u16 reg_ofs, flag;
}   INT_SENDER;

// === GLOBALS ========================================================

fnptr IntrTable[] =
{
   VBlankInterruptHandler,      // INT_VBLANK
   HBlankInterruptHandler,      // INT_HBLANK
   UnusedInterruptHandler,      // INT_VCOUNT
   UnusedInterruptHandler,      // INT_TM0
   UnusedInterruptHandler,      // INT_TM1
   UnusedInterruptHandler,      // INT_TM2
   UnusedInterruptHandler,      // INT_TM3
   UnusedInterruptHandler,      // INT_COM
   UnusedInterruptHandler,      // INT_DMA0
   UnusedInterruptHandler,      // INT_DMA1
   UnusedInterruptHandler,      // INT_DMA2
   UnusedInterruptHandler,      // INT_DMA3
   UnusedInterruptHandler,      // INT_KEYS
   UnusedInterruptHandler,      // INT_CART
};

// yeah, yeah, I really should use the registers and defines
// I have else where.
// NOTE: haven't really tested this very much; if inaccurate,
// plz tell me
static const INT_SENDER _int_senders[] =
{
   { 0x0004, 0x0008 },      // REG_DISPSTAT, VID_INT_VB
   { 0x0004, 0x0010 },      // REG_DISPSTAT, VID_INT_VH
   { 0x0004, 0x0020 },      // REG_DISPSTAT, VID_INT_VC
   { 0x0102, 0x0040 },      // REG_TM0CNT, TM_INT
   { 0x0106, 0x0040 },      // REG_TM1CNT, TM_INT
   { 0x0108, 0x0040 },      // REG_TM2CNT, TM_INT
   { 0x0102, 0x0040 },      // REG_TM3CNT, TM_INT
   { 0x0128, 0x4000 },      // REG_SCCNT_L{14}         // unsure
   { 0x00ba, 0x4000 },      // REG_DMA0CNT_H, DMA_INT_ON>>16
   { 0x00c6, 0x4000 },      // REG_DMA1CNT_H, DMA_INT_ON>>16
   { 0x00d2, 0x4000 },      // REG_DMA2CNT_H, DMA_INT_ON>>16
   { 0x00de, 0x4000 },      // REG_DMA3CNT_H, DMA_INT_ON>>16
   { 0x0132, 0x4000 },      // REG_P1CNT, KEY_CNT_INT
   { 0x0000, 0x0000 },      // none
};
// === PROTOTYPES =====================================================

// === FUNCTIONS ======================================================

// IntrMain is an asm function in single_ints.s
void int_init()
{
   REG_INTMAIN = IntrMain;
}

void int_enable(enum eIntrIndex eii)
{
   REG_IME=0;

   const INT_SENDER *is= &_int_senders[eii];
   *(u16*)(0x04000000+is->reg_ofs) |= is->flag;

   REG_IE |= 1 << eii;
   REG_IME= 1;
}

void int_disable(enum eIntrIndex eii)
{
   u16 tmp= REG_IME;
   REG_IME= 0;

   const INT_SENDER *is= &_int_senders[eii];
   *(u16*)(0x04000000+is->reg_ofs) &= ~is->flag;

   REG_IE &= ~(1 << eii);
   REG_IME= tmp;
}

void int_setvcount(u8 Scanline)
{
   u16 tmp = REG_IME;
   REG_IME = 0;
   if(REG_DISPSTAT & 0x0020)
   {
      REG_DISPSTAT &= ~0xFF00;
      REG_DISPSTAT |= Scanline;
   }
   REG_IME = tmp;
}

"main.c"
Code:

#include "gba.h"
#include "interrupt.h"

int main()
{
   int_init();
   int_enable(INT_VBLANK);
   int_enable(INT_HBLANK);

   while(1);
   return (0);
}


The "gba.h" file is just the header that contains all the normal memory registers and stuff. I'm assuming it is a single line in the "single_ints.s" file that is bring up the error, since I get an average of 58 errors a second with just the VBlank interrupt enabled. I've been meaning to work with Assembly more, but things happen, so I haven't a clue abuot what the problem is.
Anyone have any thoughts?
_________________
DS - It's all about DiscoStew

#45605 - DiscoStew - Sun Jun 12, 2005 5:57 pm

Well, I've been tinkering with this problem for a bit, and I've got some progress on it. I went and commented out the line "int_enable(INT_HBLANK);" just so that I get only the VBlank interrupt with the approximate 58 errors per second, and I placed a "while(1);" line into the "void VBlankInterruptHandler() " function, just to see if the errors were coming before that function was called or after

My test shows that there are no errors before the "while(1);" line. So the errors are being brought up after it, but there is nothing after that line except for the function ending. So my guess from this is that although the code works as it should, there is a problem with the interrupts ending and resuming normal execution of code.

Any other thoughts?
_________________
DS - It's all about DiscoStew

#45876 - DiscoStew - Wed Jun 15, 2005 11:33 pm

I'm guessing that no one here at the moment know what to do with what I currently have set up to make it error-free. So...
Does anyone have an error-free Interrupt Handler that is set up similarly to what I have here?
_________________
DS - It's all about DiscoStew

#45878 - DekuTree64 - Wed Jun 15, 2005 11:38 pm

Yeah, it all looks fine to me. Try commenting things one bit at a time until you find the exact point that the error happens. Like, first try skipping the branch to the user handler at jump_intr (just return there without doing anything) to see if the problem is in the main ISR code, or in jumping to the user handler.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#45922 - DiscoStew - Thu Jun 16, 2005 7:04 pm

Hmmm, I tried what you advised me to do, but by doing that, I get 1 of either 2 results. First, I either get a butt-load of errors in the 100,000+ (even with skipping the branch), or, Second, the ROM crashing in NO$GBA.

This is just bugging the heck out of me, cause I was looking forward to my program having not 1 single error some time in the future, but I can't even get past this hurdle. I've thought about not worrying about it, but I'm also using the HBlank interrupt, and that brings up 10,000+ errors per second, so it's just really hard to debug errors when these others get in the way.

I had the sudden feeling that maybe my "interrupt.c" file should be in IWRAM (since I feel the error lies around the C function ending), but when I renamed it to "interrupt.iwram.c" (which with my makefile would place it as ARM code into IWRAM), my ROM crashes, so that didn't work.
I was also thinking that just MAYBE it has something to do with my Makefile, so I'll post that up too for everyone's viewing pleasure. It's obviously an edit from another person's makefile, but arranged to my use.
"Makefile"
Code:
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------

#---------------------------------------------------------------------------------
# TARGET is the name of the output, if this ends with _mb generates a multiboot image
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET      :=   BGTest
BUILD      :=   build
SOURCES      :=   src
INCLUDES   :=   header

#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------

CFLAGS   :=   -mthumb-interwork -Wall -mcpu=arm7tdmi -mtune=arm7tdmi \
         -ffast-math -mapcs-32 -fno-exceptions -std=c99 -fomit-frame-pointer
         
THUMB   :=   -mthumb -O3 -ffixed-r14 -funroll-loops
ARM      :=   -marm -O3 -mlong-calls
ASFLAGS   :=   -mthumb-interwork

LDFLAGS   =   -mthumb-interwork


LIBAAS      :=   ../LibAAS

#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS   :=   -lAAS

LIBDIRS   :=   $(LIBAAS)


#---------------------------------------------------------------------------------
# path to tools - this can be deleted if you set the path in windows
#---------------------------------------------------------------------------------
export PATH      :=   /c/GBA/DevKitARM/bin:/bin:/c/bin

#---------------------------------------------------------------------------------
# the prefix on the compiler executables
#---------------------------------------------------------------------------------
PREFIX         :=   arm-elf-

#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------

export OUTPUT   :=   $(CURDIR)/$(TARGET)

export VPATH   :=   $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))

export CC      :=   $(PREFIX)gcc
export CXX      :=   $(PREFIX)g++
export AR      :=   $(PREFIX)ar
export AS      :=  $(PREFIX)as
export OBJCOPY   :=   $(PREFIX)objcopy
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
#export LD      :=   $(CXX)
export LD      :=   $(CC)

BINFILES   :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.bin)))
CFILES      :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
SFILES      :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))


BINOBJS   := $(BINFILES:.bin=.o)
COBJS   := $(CFILES:.c=.o)
SOBJS   := $(SFILES:.s=.o)


export OFILES   :=   $(BINOBJS) $(COBJS) $(SOBJS)

export INCLUDE   :=   $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
               $(foreach dir,$(LIBDIRS),-I$(dir))   \
               -I$(CURDIR)/$(BUILD)

export LIBPATHS   :=   $(foreach dir,$(LIBDIRS),-L$(dir))

.PHONY: $(BUILD) clean

#---------------------------------------------------------------------------------
$(BUILD):
   @[ -d $@ ] || mkdir -p $@
   @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile


#---------------------------------------------------------------------------------
clean:
   @echo clean ...
   @rm -fr $(BUILD) *.elf


#---------------------------------------------------------------------------------
else

DEPENDS   :=   $(OFILES:.o=.d)

#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).gba   :   $(OUTPUT).elf

$(OUTPUT).elf   :   $(OFILES)


#---------------------------------------------------------------------------------
%.gba: %.elf
   @echo built ... $(notdir $@)
   @$(OBJCOPY) -O binary $< $@
   @gbafix $@

#---------------------------------------------------------------------------------
%_mb.elf:
   @echo linking multiboot
   @$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@

#---------------------------------------------------------------------------------
%.elf:
   @echo linking cartridge
   @$(LD)  -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@



#---------------------------------------------------------------------------------
# Compile Targets for C/C++
#---------------------------------------------------------------------------------


#---------------------------------------------------------------------------------
%.iwram.o : %.iwram.c
   @echo $(notdir $<)
   @$(CC) $(ARM) $(CFLAGS) $(INCLUDE) -c $< -o $@

#---------------------------------------------------------------------------------
%.o : %.c
   @echo $(notdir $<)
   @$(CC) $(THUMB) $(CFLAGS) $(INCLUDE) -c $< -o $@

#---------------------------------------------------------------------------------
%.o : %.s
   @echo $(notdir $<)
   @$(AS) $(ASFLAGS) $< -o $@

#---------------------------------------------------------------------------------

define bin2o
   cp $(<) $(*).tmp
   $(OBJCOPY) -I binary -O elf32-littlearm -B arm \
   --rename-section .data=.rodata,readonly,data,contents \
   --redefine-sym _binary_$*_tmp_start=$*\
   --redefine-sym _binary_$*_tmp_end=$*_end\
   --redefine-sym _binary_$*_tmp_size=$*_size\
   $(*).tmp $(@)
   echo "extern const u8" $(*)"[];" > $(*).h
   echo "extern const u32" $(*)_size[]";" >> $(*).h
   rm $(*).tmp
endef

#---------------------------------------------------------------------------------
%.o : %.bin
#---------------------------------------------------------------------------------
   @echo $(notdir $<)
   @$(bin2o)

-include $(DEPENDS)

#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------


_________________
DS - It's all about DiscoStew

#45924 - Quirky - Thu Jun 16, 2005 7:45 pm

If it's on no$gba, then guessing where the errors are is a bit hit and miss, I'd suggest running the SDL version of VBA with the -v flag, like this

/path/to/VisualBoyAdvance -v 767 mygame.gba

This will produce a file trace.log that contains any errors. It'll hopefully give you a big hint. If, for example you are reading or writing to misaligned addresses, this is the most likely problems I've seen, then the address off the misreading instruction will be shown together with where you misread from/write to.

If you link with the flag (when building the elf file) -Wl,-Map,mygame.map or use objdump then you can more or less see where abouts the address is, in which function I mean. From there you can debug (compile your asm files with -g) or take a more educated guess at the problem.


EDIT: Hmm, forget that. I've just compiled and tried it and no errors are produced in VBA.
EDIT2: And neither are they produced in no$gba.... what exactly are the 58 errors per second and how are you measuring them? I'm a bit lost!

#45927 - DiscoStew - Thu Jun 16, 2005 8:24 pm

I'm assuming that if through this process with the SDL version of VBA, if there is not a trace.log file made, then my program had no errors?

Because I did it, and no log was made, I should be happy, right?

But if VBA-SDL shows no errors, then why does No$GBA? One of these days I may go purchase the Debug version of No$GBA so that I can clearly see what the dang problem is.

EDIT:
The ~58 errors per second that I'm getting is when I only call the "int_init()" function, and enable just the VBlank using the "int_enable(INT_VBLANK)" function. I'm assuming there is ~1 error per interrupt call because of the VBlank being enabled, and occuring ~60 time a second. My explanation of the "while(1);" debugging is in the previous posts.

Did you use my code/makefile to compile your own test program, and you came out with no errors in No$GBA? How is that possible if I'm using the exact same code/makefile, and I get errors? What compiler you using? I'm currently using DevKitARM r11.

EDIT #2:
I think I may have found the culprit. It's a file called "NO$GBA.ROM" (the dumped GBA BIOS I made) for use with No$GBA. After having removed that file from the folder, and rerunning No$GBA, my program now has NO errors. But still, since this BIOS file is exactly (I hope) what's in the GBA, would it be a good idea to keep it unused? Maybe I should try and re-dump the BIOS again.
_________________
DS - It's all about DiscoStew

#45931 - strager - Thu Jun 16, 2005 9:06 pm

DiscoStew wrote:
I'm assuming that if through this process with the SDL version of VBA, if there is not a trace.log file made, then my program had no errors?

Because I did it, and no log was made, I should be happy, right?


Did you download the developer's version? It will show errors in the log that the "Gamer's" version doesn't. Be sure to enable logging, or you will not get any output either way.

#45992 - Quirky - Fri Jun 17, 2005 6:57 pm

DiscoStew wrote:

Did you use my code/makefile to compile your own test program, and you came out with no errors in No$GBA? How is that possible if I'm using the exact same code/makefile, and I get errors? What compiler you using? I'm currently using DevKitARM r11.


You seem to have found the 'problem' now but fwiw I copy pasted your code from here, built it using a generic Makefile and my own opy of gba.h which I guessed would match, and it did except for the REG_INTMAIN define, which I altered.


Quote:
Did you download the developer's version? It will show errors in the log that the "Gamer's" version doesn't. Be sure to enable logging, or you will not get any output either way.


How do you know if it's the developers verison or not? I think the SDL versions are all pre-compiled with the relevent DEV flags defined.

#45993 - DiscoStew - Fri Jun 17, 2005 7:26 pm

Yeah, it was the BIOS file that was the problem. Although everything was working as it should have been, I'm glad to at least know that what I had coded wasn't actually giving any errors (as far as I know =P).
Thanks everyone for all your help with this dang problem.
_________________
DS - It's all about DiscoStew