自作プログラムを作るにあたって絶対に必要なことが2点あります。それはWAITCNTとHALT設定です。GBAプログラミングは様々なハードウェア依存で制約も多く、忘れるとすぐ動作が怪しくなってしまいます。あー面倒だなあ、この作業はしなくていいなあ、なんて悪魔が囁くわけですけれど、このページで紹介する方法は知らないとまずいです。2010年代ごろの私なら適当やっていましたけれど、もう2020年代に突入してますし・・・ここだけは口酸っぱく言わせてもらいます。(^^;
4000204h - WAITCNT - Waitstate Control (R/W) This register is used to configure game pak access timings. The game pak ROM is mirrored to three address regions at 08000000h, 0A000000h, and 0C000000h, these areas are called Wait State 0-2. Different access timings may be assigned to each area (this might be useful in case that a game pak contai ns several ROM chips with different access times each). Bit Expl. 0-1 SRAM Wait Control (0..3 = 4,3,2,8 cycles) 2-3 Wait State 0 First Access (0..3 = 4,3,2,8 cycles) 4 Wait State 0 Second Access (0..1 = 2,1 cycles) 5-6 Wait State 1 First Access (0..3 = 4,3,2,8 cycles) 7 Wait State 1 Second Access (0..1 = 4,1 cycles; unlike above WS0) 8-9 Wait State 2 First Access (0..3 = 4,3,2,8 cycles) 10 Wait State 2 Second Access (0..1 = 8,1 cycles; unlike above WS0,WS1) 11-12 PHI Terminal Output (0..3 = Disable, 4.19MHz, 8.38MHz, 16.78MHz) 13 Not used 14 Game Pak Prefetch Buffer (Pipe) (0=Disable, 1=Enable) 15 Game Pak Type Flag (Read Only) (0=GBA, 1=CGB) (IN35 signal) 16-31 Not used At startup, the default setting is 0000h. Currently manufactured cartridges are using the following settings: WS0/ROM=3,1 clks; SRAM=8 clks; WS2/EEPROM: 8,8 clks; prefetch enabled; that is, WAITCNT=4317h, for more info see "GBA Cartridges" chapter.
GBATEKのとおりREG_WSCNT = 0x4317を設定してください。もう少し詳しく説明しますとカードリッジ領域は3つのミラーリングを持っています。それぞれ0x8000000h、0xA000000h、0C000000hです。WAITCNTとは各ミラーリング領域のアクセスタイミングや、SRAMやプレフェッチバッファーの有無の設定などを行います。しかし自作プログラミングの場合は0x8000000h領域しか使いませんし残り2つの領域は気にしなくてかまいません。次に、cyclesのベンチマークを以下に表します。この数値を見て変更しないのはまずいと思うでしょう。ただ私としては2/1の方が優れているのになぜGBATEKは3/1を薦めているのかがイマイチわかりませんでした。
Thumb C Compiler, ADS1.2 [Build 805] 4426 WAITCNT 4/2 no prefetch 3790 WAITCNT 4/2 prefetch 4106 WAITCNT 3/2 no prefetch 3639 WAITCNT 3/2 prefetch 3785 WAITCNT 2/2 no prefetch 3488 WAITCNT 2/2 prefetch 5707 WAITCNT 8/2 no prefetch 4391 WAITCNT 8/2 prefetch 3611 WAITCNT 4/1 no prefetch 2867 WAITCNT 4/1 prefetch 3292 WAITCNT 3/1 no prefetch 2715 WAITCNT 3/1 prefetch 6.87 Mips ← 注目! 2971 WAITCNT 2/1 no prefetch 2563 WAITCNT 2/1 prefetch 7.28 Mips ← 注目! 4892 WAITCNT 8/1 no prefetch 3477 WAITCNT 8/1 prefetch MetaWare High C Compiler R4.5a (THUMB) 3305 WAITCNT 3/1 no prefetch 2688 WAITCNT 3/1 prefetch 6.94 Mips ← 注目! 2985 WAITCNT 2/1 no prefetch 2547 WAITCNT 2/1 prefetch 7.32 Mips ← 注目!
HALTです。このWikiではシンプルさを重視して以下の書き方を慣例的にしてきました。
// 昔(GBAプログラム研究所さん)の書き方 void WaitForVsync(void) { while (*(volatile u16*)0x4000006 >= 160) {}; while (*(volatile u16*)0x4000006 < 160) {}; } int main(void) { // TODO for(;;) { WaitForVsync(); // TODO } }
ところがこの方法だとforループ中もCPUが動いているため、バッテリーを無駄に消費します。とてもエコではありません。そこでVBLANKになるまでCPUをお休み(HALT)させて、VBLANKになったら起きることをします。この方法は正直日記さんやdevkitProのサンプルプログラムに書かれていたものを流用しています。
#include "lib/gba.h" #include "main.h" #include "bg.h" #include "irq.arm.h" //--------------------------------------------------------------------------- ST_MAIN Main; //--------------------------------------------------------------------------- int main(void) { REG_WSCNT = 0x4317; BgInit(); IrqInit(); _Memset(&Main, 0x00, sizeof(ST_MAIN)); ST_MAIN* p = &Main; char str[32]; for(;;) { VBlankIntrWait(); p->fpsCount++; p->gameTime++; p->gameFrame++; if(p->gameFrame < p->frameCount) { p->delay = p->frameCount - p->gameFrame; p->gameFrame = p->frameCount; } else { p->delay = 0; } _Sprintf(str, "FREAME:%d", p->frameCount); BgAsciiDrawStr(0, 1, str); _Sprintf(str, "DELAY :%d", p->delay); BgAsciiDrawStr(0, 2, str); _Sprintf(str, "FPS :%d", p->fps); BgAsciiDrawStr(0, 3, str); } }
#include "irq.arm.h" #include "main.h" //--------------------------------------------------------------------------- extern ST_MAIN Main; //--------------------------------------------------------------------------- EWRAM_CODE void IrqInit(void) { REG_IME = 0; INT_VECTOR = (u32)IrqHandler; REG_IE = IRQ_VBLANK; REG_DISPSTAT = LCDC_VBL; REG_IME = 1; } //--------------------------------------------------------------------------- IWRAM_CODE void IrqHandler(void) { REG_IME = 0; u16 flag = REG_IF; if(flag & IRQ_VBLANK) { ST_MAIN* p = &Main; p->frameCount++; p->fpsTimer++; if(p->fpsTimer == 60) { p->fps = p->fpsCount; p->fpsTimer = 0; p->fpsCount = 0; } REG_IRQ_WAITFLAGS |= IRQ_VBLANK; } REG_IF = flag; REG_IME = 1; }
fpsの表示処理も加えてみたので若干読みにくくなってしまったかもしれません。注目すべきポイントはVBLANKの割り込み許可して、VBLANKハンドラに「REG_IRQ_WAITFLAGS |= IRQ_VBLANK;」を加えることです。この処理を忘れるとmain関数の「VBlankIntrWait();」(この中身は「SystemCall(5);」)を呼んだ時、HALTしたまま(休んだまま)になってしまうので注意してください。ゲームを作るときは、この形を基本にするのがいいと思います。