#177677 - chickendude - Thu Nov 29, 2012 8:56 am
I've just started learning GBA assembly and not surprisingly the most complicated part for me so far has been learning what the hardware can do and how to do it. I'm having trouble getting the Y scrolling to work correctly -- the screen scrolls fine but it looks like it first jumps randomly before it scrolls to the new coordinate. The weird thing is if i don't update REG_BG2Y, X scrolling works smoothly. If i do update REG_BG2Y, even if it's with the same Y value as before, i get the weird jumps. Here's a screenshot of what i'm talking about, the first part is updating REG_BG2Y, the second part (horizontal scrolling) with REG_BG2Y commented out.
EDIT: Seems i can't insert pictures, here's a link to the picture: http://www.mirari.fr/SNGs
Here's the bit of code for scrolling:
Code: |
ldr r0,[r0] @ Load X
ldr r1,[r1] @ Load Y
mov r0,r0,LSL #8 @ First 8 bits of REG_BG2X/Y = fraction
mov r1,r1,LSL #8
ldr r3,=REG_BG2X
str r0,[r3],#4 @ Update X and shift to REG_BG2Y
str r1,[r3] @ Update Y (REG_BG2Y) |
I don't know if it's got something to do with vSync or if there's something else i'm over looking, i just know that if i comment out the second str instruction it scrolls smoothly, just without vertical scrolling. Ah, and this is in Mode 1, 256-color palette, 256x256 background size.
Any help would be appreciated, as i'm completely stumped.
#177679 - elhobbs - Thu Nov 29, 2012 12:56 pm
I am not an asm expert, but shouldn't reg_bg2y be +4 instead of left shift 4 from reg_bg2x?
#177680 - chickendude - Thu Nov 29, 2012 1:21 pm
Maybe the syntax is different with other assemblers, but what that instruction does is post-increment r3 (pointer to REG_BG2X) by 4, so it loads r0 into [r3], then adds 4 to r3. I don't think the str instruction supports shifts... The Y coordinate IS updated and seems to update correctly, the screen just gets distorted slightly before it updates. Anyway, thanks for the reply. :)
#177681 - elhobbs - Thu Nov 29, 2012 2:31 pm
I looked at the nds samples and they appear to wait for vblank before updating scroll.
#177682 - chickendude - Thu Nov 29, 2012 4:26 pm
Awesome, it works perfectly now :D I tried implementing a little loop waiting for VBLANK before but i guess i didn't code it correctly. Here's the code and data i used, in case anyone else is interested. I don't think my waitVBLANK routine is very efficient, i think it'd be much better to put it into an interrupt. But at least it works now :)
scrollingMode1.7z
Btw, what NDS samples are you talking about?
#177685 - elhobbs - Thu Nov 29, 2012 6:41 pm
the ones that are in the devkitpro/examples directory. The windows installer installs them by default I think. see here for some install details.
#177686 - Dwedit - Thu Nov 29, 2012 6:44 pm
Look in C:\devkitpro\examples if you installed devkitarm. (elhobbs replied first)
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#177689 - chickendude - Thu Nov 29, 2012 7:16 pm
Thanks both of you, i found them :) It turns out i didn't download the NDS examples (just the GBA ones).
EDIT: I set up a VBLANK interrupt to update the screenshifts during VBLANK, but it doesn't quite seem to be doing what i want. Again, the screen will shift, but only the first row is drawn and it gets repeated all down the screen. Did i misunderstand how VBLANK interrupts work? Here's the source to the interrupt:
Code: |
interrupt:
ldr r0, =x
ldr r1, =y
ldr r0,[r0] @ Load X
ldr r1,[r1] @ Load Y
mov r0,r0,LSL #8 @ First 8 bits of REG_BG2X = fraction
mov r1,r1,LSL #8
ldr r3,=REG_BG2X
str r0,[r3], #4 @ X
str r1,[r3] @ Y REG_BG2Y
ldr r1, =REG_IF @ Acknowledge having run interrupt
mov r2, #INT_VBLANK
str r2,[r1]
bx lr |
#177691 - Dwedit - Thu Nov 29, 2012 9:39 pm
How are you doing interrupts? Are you making a function for Libgba's interrupt handler, or are you putting the address into the end of RAM so the BIOS jumps there?
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#177692 - chickendude - Fri Nov 30, 2012 6:16 am
I'm not using libgba's handler, i'm just enabling VBLANK interrupts and loading the address of my interrupt (simply the last function in my program) into REG_INTADDR (0x3007FFC). The debugger shows that it does run my interrupt, the problem seems to be that it's running far too often. I assumed a VBLANK wouldn't occur until after having completely finished updating the screen, is that incorrect? I could just do the same check i did before (checking VCOUNT for gt 160), but i think i'm not quite understanding something.
EDIT: Or is it possible that there's more than one interrupt being triggered? When i add in VBLANK detection it works great:
Code: |
interrupt:
ldr r0, =REG_DISPSTAT
ldr r0, [r0] @ Load the Display Status Register
@ to check if we're in VBLANK
ands r0, r0, #STAT_VBLANK
beq quitInterrupt @ If it's not during VBLANK, quit
ldr r0, =x
ldr r1, =y
ldr r0,[r0] @ X
ldr r1,[r1] @ Y
mov r0,r0,LSL #8 @ First 8 bits of REG_BG2X = fraction
mov r1,r1,LSL #8
ldr r3,=REG_BG2X
str r0,[r3], #4 @ X
str r1,[r3] @ Y REG_BG2Y
quitInterrupt:
ldr r1, =REG_IF @ Acknowledge having run the interrupt
mov r2, #INT_VBLANK @
str r2,[r1] @
bx lr |
#177693 - Dwedit - Fri Nov 30, 2012 9:28 am
Interrupts on the GBA:
The real interrupt vector is in the BIOS. The BIOS pushes 6 words onto the stack, switches the CPU into IRQ mode, then calls the address at 03007FFC. When the handler returns, it switches back to system mode, then pops 6 words off the stack, and returns to the previously executing code.
When it starts executing the interrupt handler, the initial value of r0 also happens to be 04000000 (REG_BASE), so fun times for code optimization junkies.
Your handler should check REG_IF to see what caused the interrupt, and AND that with REG_IE to see if that interrupt is actually enabled or not.
When it handles the interrupt, it needs to clear the bit in REG_IF.
Sources of interrupts on the GBA:
Let's copy-paste gbatek:
Quote: |
0 LCD V-Blank (0=Disable)
1 LCD H-Blank (etc.)
2 LCD V-Counter Match (etc.)
3 Timer 0 Overflow (etc.)
4 Timer 1 Overflow (etc.)
5 Timer 2 Overflow (etc.)
6 Timer 3 Overflow (etc.)
7 Serial Communication (etc.)
8 DMA 0 (etc.)
9 DMA 1 (etc.)
10 DMA 2 (etc.)
11 DMA 3 (etc.)
12 Keypad (etc.)
13 Game Pak (external IRQ source) (etc.)
14-15 Not used
|
The interrupts also require a separate flag to enable interrupts for those devices. Vblank, Hblank, and Vcount interrupt enable flags are in both REG_IE, and REG_DISPSTAT. Interrupt enables for timers are in the timer registers. Interrupt enables for DMA are in the DMA registers.
Gotcha:
The GBA BIOS keeps track of interrupt flags in 03007FF8 as well. So whenever you handle an interrupt, set that bit in 03007FF8 (16-bit word) as well. Easy way to do it:
mov r0,#REG_BASE
ldr r1,[r0,#REG_IE] @both REG_IE and REG_IF
ands r3,r1,r1,lsr#16 @AND the enabled bits and interrupt flag bits
ldrh r2,[r0,#-8] @read BIOS's flags (address 03FFFFF8 is a mirror of 03007FF8)
orr r2,r2,r3 @mix with interrupts that get handled
strh r2,[r0,#-8] @write back
Your code will still work fine even if you don't do this. But if you ever use any of the BIOS functions that halt until an interrupt occurs, you'll need to do this, otherwise it won't work correctly.
Repeatedly checking VCOUNT to see if you have reached vblank is a good way to waste battery power. Instead, use a system call to wait for a Vblank interrupt, that way you won't waste power. (Yes, I know even commercial games poll VCOUNT)
Look for SWI 0x05 (THUMB mode) or SWI 0x50000 (ARM mode) to wait for a vblank interrupt.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#177694 - headspin - Fri Nov 30, 2012 10:40 am
You will likely need the CowBite Virtual Hardware Specifications to find out how the registers work.
Also if you move to the NDS we have many C header files and routines converted to asm. We have a couple GBA examples but not much. You can visit our website here.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game
#177695 - chickendude - Fri Nov 30, 2012 4:08 pm
Dwedit wrote: |
Interrupts on the GBA:
The real interrupt vector is in the BIOS. The BIOS pushes 6 words onto the stack, switches the CPU into IRQ mode, then calls the address at 03007FFC. When the handler returns, it switches back to system mode, then pops 6 words off the stack, and returns to the previously executing code. |
This made me look up the bit on BIOS interrupt handling in gbatek, and now i'm wondering if there is any particular reason for pushing r12 (and not, say, r4)...
Quote: |
When it starts executing the interrupt handler, the initial value of r0 also happens to be 04000000 (REG_BASE), so fun times for code optimization junkies. |
That's also very interesting to know, thanks :)
Quote: |
Your handler should check REG_IF to see what caused the interrupt, and AND that with REG_IE to see if that interrupt is actually enabled or not.
When it handles the interrupt, it needs to clear the bit in REG_IF. |
Right now my interrupt just reads the display control register to check for a VBLANK which i assume does pretty close to the same thing, though i suppose multiple interrupts might occur during a VBLANK, so my interrupt might be running more often than need be. And when you say to clear the bit in REG_IF, do you mean to set it to 1? That's what everything else i've read seems to indicate, after the interrupt runs it should set the appropriate bit in REG_IF to 1. This part doesn't make much sense to me. You set it to 1 so that the bit can get reset? From gbatek:
"Interrupts must be manually acknowledged by writing a '1' to one of the IRQ bits, the IRQ bit will then be cleared."
Maybe the wording here is what's tripping me up.
Quote: |
The GBA BIOS keeps track of interrupt flags in 03007FF8 as well. So whenever you handle an interrupt, set that bit in 03007FF8 (16-bit word) as well. |
Again, thanks for this, it's good to know (but a lot to keep in mind!).
Quote: |
Repeatedly checking VCOUNT to see if you have reached vblank is a good way to waste battery power. Instead, use a system call to wait for a Vblank interrupt, that way you won't waste power. (Yes, I know even commercial games poll VCOUNT)
Look for SWI 0x05 (THUMB mode) or SWI 0x50000 (ARM mode) to wait for a vblank interrupt. |
That was how i originally did it, but now i tried setting up a VBLANK interrupt to handle it. The problem is it seems to be executing more than just a VBLANK interrupt (even though i've only activated VBLANK interrupts and it seems that only the VBLANK interrupt is set). Moreover, it works fine on VBA but no$gba doesn't recognize my keypresses. The interrupt code gets run, though. Using one of the SWI functions to wait for the next VBLANK interrupt isn't a bad idea, i just want to figure out what exactly i'm doing wrong with the interrupt. And you'd suggest reading REG_IF instead of REG_DISPSTAT?
@headspin: i already registered an account there yesterday, but it says it needs to be activated first. And without the CowBite site (and the Tonc tutorials) i don't know how i ever would've been able to figure any of this out!
I have to say, i'm really enjoying ARM assembly and especially learning about the GBA hardware. While the hardware is still a bit new to me, the actual code feels really tight and simple.
#177696 - chickendude - Sun Dec 02, 2012 8:02 pm
Should i be concerned if VBA doesn't seem to like my code? No$gba runs it just fine and it works lovely (and is even faster than before). VBA seems to never run the actual screen update loop:
Code: |
interrupt:
mov r0, #REG_DISPCNT
ldr r1, [r0,#REG_IE-REG_DISPCNT] @ r1 = REG_IE (2 octets) et REG_IF (2 octets)
and r0, r1,r1, LSR #16 @ ANDer REG_IE et REG_IF
tst r0, #INT_VBLANK
bxeq lr @ Si l'interruption n'est pas un VBLANK, quitter
ldr r0, =x
ldr r1, =y
ldr r0,[r0] @ Charger X
ldr r1,[r1] @ Charger Y
mov r0,r0,LSL #8 @ Premier 8 bits de REG_BG2X = fraction
mov r1,r1,LSL #8
ldr r3,=REG_BG2X
str r0,[r3], #4 @ X
str r1,[r3] @ Y REG_BG2Y
@ Annoncer que l'on a fini son travail avec l'interruption
mov r0, #REG_BASE @ 0x04000000 (REG_DISPCNT)
ldr r1, [r0, #REG_IF-REG_BASE]
mov r2, #INT_VBLANK @
orr r2, r1 @ Desarmer toutes les interruptions (il faut les mettre à 1 pour les désarmer)
str r2, [r0, #REG_IF-REG_BASE] @
ldr r1, [r0, #REG_IE-REG_BASE] @ r1 = REG_IE (2 octets) et REG_IF (2 octets)
and r3, r1,r1, LSR #16 @ ANDer REG_IE et REG_IF
ldrh r2, [r0, #-8] @ Les drapeaux d'interruptions du BIOS (0x3FFFFF8 est un miroir de 0x3007FF8)
orr r2, r2, r3 @ (REG_IE & REG_IF) | "BIOS_IF"
strh r2, [r0, #-8] @ Les écrire (pour que le hardware les efface, il n'a pas de sens, je sais)
bx lr |
The code now checks if it's a VBLANK interrupt (and if VBLANK interrupts are currently enabled, though i don't really think that part is really necessary). However, VBA doesn't seem to ever run the actual code inside the interrupt (the interrupt gets run, but the screen never updates). No$gba doesn't have any problems with the code, though, and i can scroll across a nice 512x512 (pixel) tilemap. I wish i could test it out on hardware to see whether or not it works, i thought using a VBLANK interrupt to update the screen was common practice, though i guess it's probably not with this exact method. Still, i wish i knew what was causing the problem(s) with VBA (since i don't have to run it through Wine and can assemble and load it up with a simple script). I imagine emulating the interrupts/display hardware can't be that simple, but it could be an issue with my program.
I looked through the source to Manic Miner real quickly to see how it was handled there, but it seems like it just waits for VCOUNT to reach the bottom of the screen. Or at least that's what the "waitforVblank" routine seems to do. I did see a really cool trick there, though, to get rid of the bx lr at the end of a routine when you've already gotta save some stuff to the stack :D I love those sorts of things!
#177697 - Dwedit - Sun Dec 02, 2012 11:44 pm
Did you provide a BIOS image for VBA and NO$GBA?
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#177698 - chickendude - Mon Dec 03, 2012 1:37 pm
I just tried with VBA, but i think it doesn't like my header (or lack thereof), after displaying the Gameboy logo and jumbled Nintendo logo it just freezes. For NO$GBA i haven't tried either but in the "Emulate BIOS functions" option it says "By real GBA.ROM", so i don't know if that means it's using a BIOS image or not. The BIOS is in the folder, though.
EDIT: I just added the GBA header and it still messes up in VBA and runs fine in NO$GBA.
EDIT2: I changed the interrupt acknowledgement bit to something based off the code you gave me and now it works fine on both emulators. Before i had one part to reset REG_IF and one part to reset the BIOS flags. I realized that really they should be setting the same bits so i just combined them into one:
Code: |
@ Annoncer que l'on a fini son travail avec l'interruption
@ r0 = REG_IE (début des registres d'interruption)
@ r1 = REG_BASE (REG_DISPCNT)
mov r0, #REG_BASE @ 0x0400:0000 (REG_DISPCNT)
add r0, #REG_INT_OFF @ 0x0400:0200 (début des registres d'interruption)
ldr r2, [r0] @ r2 = REG_IE (2 octets) et REG_IF (2 octets)
and r2, r2,r2, LSR #16 @ ANDer REG_IE et REG_IF
ldrh r3, [r1, #-8] @ Les drapeaux d'interruptions du BIOS (0x3FFFFF8 est un miroir de 0x3007FF8)
orr r2, r2, r3 @ (REG_IE & REG_IF) | "BIOS_IF"
mov r1, #REG_BASE
strh r2, [r1, #-8] @ Les écrire (pour que le hardware les efface, il n'a pas de sens, je sais)
strh r2, [r0, #REG_IF_OFF] @
bx lr |
Also, waiting for the next VBLANK interrupt with the BIOS function makes the screen scroll pretty slowly. Here's a screenshot. It's slightly faster than in the screenshot, but still pretty slow.
#177703 - sverx - Tue Dec 04, 2012 5:46 pm
I don't get why it should be slower, using the SWI function. You get one scroll each frame, no more, so how that could depend on how you manage the Vblank?
BTW the SWI is the way to go for that. It saves lots of power.
_________________
libXM7|NDS programming tutorial (Italiano)|Waimanu DS / GBA|A DS Homebrewer's Diary
#177705 - chickendude - Tue Dec 04, 2012 7:15 pm
I don't get it either, though i'm not exactly sure how the hardware scrolling works. I thought it would be more or less instantaneous, but it seems to take up quite a bit of time. I dunno if it's got something to do with my interrupt routine taking up too much time. Using the SWI function you don't even need an interrupt routine anymore, do you? I was using it to wait until the next VBLANK interrupt where my interrupt routine would handle it.
And if anyone's interested, this is my latest source and a screenshot:
Source directory
Screenshot
I've added another background layer (a sort of "sky") which scrolls more slowly than the "ground" layer.
#177706 - Dwedit - Tue Dec 04, 2012 7:22 pm
NO$GBA says:
* You're doing busy waiting to wait for vblank (100% CPU usage)
* You are doing some invalid memory reads (address FFFFFFFB, FFFFFFFF, 00000001) (it's inside the function "interrupt" by the way)
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#177707 - chickendude - Tue Dec 04, 2012 7:34 pm
Thanks. I think i've found where that's happening. How did you find that out so quickly? :D
EDIT: And i don't think i'm doing any actual waiting for VBLANK, i've just got a delay loop (which of course is just a huge waste of energy). I found HALTCNT in gbatek as well as a SWI halt function which'd probably be a better choice instead of what i'm doing now.
Annnd... now i think i've got the invalid reads fixed :)
#177723 - Miked0801 - Mon Dec 17, 2012 6:36 pm
For fun and profit, you can also update the scroll register during the HBlank period as well. Lots of fun possibilities when you start doing that ;)
#177726 - chickendude - Thu Dec 20, 2012 8:45 pm
Isn't that what i'm doing? Aren't REG_BG2X/Y the scroll registers? What sorts of things do you mean? :)
#177727 - elhobbs - Thu Dec 20, 2012 9:08 pm
Hblank - horizontal blank, rather than vblank - vertical blank. using hblank you could have a different scroll position for each scan line.
#177729 - sverx - Fri Dec 21, 2012 10:15 am
#177730 - Miked0801 - Sat Dec 22, 2012 6:47 pm
Check out The Legend of Spyro:Eternal Night's fire temple levels for some fun HBlank effects that we were able to accomplish. :)
#177732 - sverx - Thu Dec 27, 2012 10:25 am
You mean the background as seen in this video? Were you using HBlank and palette effets? Very cool! :D
_________________
libXM7|NDS programming tutorial (Italiano)|Waimanu DS / GBA|A DS Homebrewer's Diary
#177733 - Miked0801 - Thu Dec 27, 2012 6:07 pm
Yep - the waves were BG scaling and the colors were palette cycling. And then getting those big sprites to animate at full speed and such :)
That was a fun time.
#177734 - headspin - Thu Dec 27, 2012 9:46 pm
Fun times indeed. Here are some examples of these two effects I wrote for Warhawk (which has a whole bunch more there too).
fxColorCycle.s
fxSine.s
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game