プログラムを作るとき一番最初に考えるのがmain関数でのループ処理だと思います。このWikiではシンプルを重視して以下の書き方をしてきました。
// 昔(GBAプログラム研究所さんから)の書き方 void WaitForVsync(void) { while (*(volatile u16*)0x4000006 >= 160) {}; while (*(volatile u16*)0x4000006 < 160) {}; } int main(void) { // TODO for(;;) { WaitForVsync(); // TODO } }
ところがこの方法だとforループ中もCPUが動いているため、バッテリーを無駄に消費します。とてもエコではありません。そこで待っている間はCPUをお休み(HALT)させて、VBLANKになったら起こすことにするといいのではないでしょうか。devkitProのサンプルプログラムにその方法が書いてあったので流用させていただきました(^^;。
今までウェイトをするとき、VBLANKを待っている方法を採用していました。
#include "lib/gba.h" #include "intr_arm.h" //--------------------------------------------------------------------------- int main() { IntrInit(); IntrStart(); for(;;) { SystemCall(5); // BIOS Call 5 TRACEOUT("VCOUNT: %d\n", *(volatile u16*)0x4000006); } }
#ifndef __INTR_H__ #define __INTR_H__ #ifdef __cplusplus extern "C" { #endif #include "lib/gba.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- typedef struct { void* handler[MAX_INTS]; } ST_INTR_TABLE; //--------------------------------------------------------------------------- EWRAM_CODE void IntrInit(); IWRAM_CODE void IntrMain(); IWRAM_CODE void IntrDummy(); EWRAM_CODE void IntrStart(); EWRAM_CODE void IntrStop(); EWRAM_CODE void IntrEnable(irqMASK mask); EWRAM_CODE void IntrDisable(irqMASK mask); EWRAM_CODE void IntrSetHandler(irqMASK num, void* function); EWRAM_CODE void IntrUnSetHandler(irqMASK num); //---- IWRAM_CODE void IntrVcount(); IWRAM_CODE void IntrVblank(); #ifdef __cplusplus } #endif #endif
#include "intr_arm.h" //--------------------------------------------------------------------------------- ST_INTR_TABLE IntrTable; //--------------------------------------------------------------------------------- EWRAM_CODE void IntrInit() { REG_IME = 0; REG_IF = ~0; REG_IE = 0; u16 i; for(i=0; i<MAX_INTS; i++) { IntrTable.handler[i] = IntrDummy; } INT_VECTOR = (u32)IntrMain; // 割り込みハンドラの設定をします IntrSetHandler(IRQ_VBLANK, IntrVblank); IntrEnable(IRQ_VBLANK); } //--------------------------------------------------------------------------------- IWRAM_CODE void IntrMain() { asm volatile ( "ldr r3, =0x04000200" "\n" "ldrh r1, [r3, #0x08]" "\n" // r1に REG_IME をロード "mrs r0, spsr" "\n" "stmfd sp!, {r0,r1,lr}" "\n" // SPSR, REG_IME, lrを保存 "mov r0, #1" "\n" "strh r0, [r3, #0x08]" "\n" // REG_IME に#1をストア "ldr r1, [r3,#0x00]" "\n" // r1に REG_IE, REG_IF をロード "and r0, r1,r1,lsr #16" "\n" // r0 = REG_IE & REG_IF "ldr r2, =IntrTable" "\n" // r2 = &IntrTable[0] //------------------------------------------- "ands r1, r0, #1" "\n" // VBlank "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #2" "\n" // HBlank "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #4" "\n" // VCount "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #8" "\n" // Timer0 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #16" "\n" // Timer1 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #32" "\n" // Timer2 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #64" "\n" // Timer3 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #128" "\n" // Serial "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #256" "\n" // DMA0 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #512" "\n" // DMA1 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #1024" "\n" // DMA2 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #2048" "\n" // DMA3 "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #4096" "\n" // Joy "bne .Ljump_intr" "\n" "add r2, r2, #4" "\n" "ands r1, r0, #8192" "\n" // Cart #if HANDLE_CART_INTERRUPT "strneb r1, [r3, #0x84 - 0x200]" "\n" // Stop sound if cart removed (REG_SOUNDCNT_X) ".Lcart_loop:" "\n" "bne .Lcart_loop" "\n" // Infinite loop if cart removed #endif ".Ljump_intr:" "\n" "strh r1, [r3, #0x2]" "\n" // Acknowledge Interrupt(REG_IF Clear) "mrs r3, CPSR" "\n" #if 0 "bic r3, r3, #0xdf" "\n" // Switch to System Mode "orr r3, r3, #0x1f" "\n" // "mov r3, #0x12" "\n" // Switch to System Mode #else "orr r3, r3, #0xc0" "\n" // Switch to IRQ Mode #endif "msr CPSR, r3" "\n" "ldr r1, [r2]" "\n" "adr lr, .Lintr_return" "\n" "bx r1" "\n" ".Lintr_return:" "\n" "mrs r3, CPSR" "\n" #if 0 "bic r3, r3, #0xdf" "\n" "orr r3, r3, #0x92" "\n" // Switch to IRQ Mode, disable IRQ #else "orr r3, r3, #0x80" "\n" // Disable IRQ #endif "msr CPSR, r3" "\n" "ldmfd sp!,{r0,r1,lr}" "\n" "msr spsr, r0" "\n" ::); return; asm (".pool"); } //--------------------------------------------------------------------------------- IWRAM_CODE void IntrDummy() { // EMPTY } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrStart() { REG_IME = 1; } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrStop() { REG_IME = 0; } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrEnable(irqMASK mask) { REG_IME = 0; if(mask & IRQ_VBLANK) REG_DISPSTAT |= LCDC_VBL; if(mask & IRQ_HBLANK) REG_DISPSTAT |= LCDC_HBL; if(mask & IRQ_VCOUNT) REG_DISPSTAT |= LCDC_VCNT; REG_IE |= mask; } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrDisable(irqMASK mask) { REG_IME = 0; if(mask & IRQ_VBLANK) REG_DISPSTAT &= ~LCDC_VBL; if(mask & IRQ_HBLANK) REG_DISPSTAT &= ~LCDC_HBL; if(mask & IRQ_VCOUNT) REG_DISPSTAT &= ~LCDC_VCNT; REG_IE &= ~mask; } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrSetHandler(irqMASK num, void* function) { if(num >= MAX_INTS) { return; } IntrTable.handler[num>>1] = function; } //--------------------------------------------------------------------------------- EWRAM_CODE void IntrUnSetHandler(irqMASK num) { if(num >= MAX_INTS) { return; } IntrTable.handler[num>>1] = IntrDummy; } //--------------------------------------------------------------------------------- IWRAM_CODE void IntrVcount() { // EMPTY } //--------------------------------------------------------------------------------- IWRAM_CODE void IntrVblank() { REG_IRQ_WAITFLAGS |= IRQ_VBLANK; }
ポイントはVBLANKの割り込み許可して、VBLANKハンドラに「REG_IRQ_WAITFLAGS |= IRQ_VBLANK;」をさせることです。この処理を忘れるとmain関数の「SystemCall(5);」を呼んだ時、永久ループしてしまうので注意してください。ゲームを作るときは、この形を基本にするのがいいと思います。