#author("2023-04-17T09:58:34+09:00","","")
* メインループの書き方 [#i7b526f0]
プログラムを作るとき一番最初に考えるのがmain関数でのループ処理だと思います。このWikiではシンプルを重視して以下の書き方をしてきました。
#author("2023-04-17T15:53:50+09:00","","")
* プログラムのテンプレート [#l0b1d472]
自作プログラムを作るにあたって絶対に必要なことが2点あります。それはWAITCNTとHALT設定です。GBAプログラミングは様々なハードウェア依存で制約も多く、忘れるとすぐ動作が怪しくなってしまいます。あー面倒だなあ、この作業はしなくていいなあ、なんて悪魔が囁くわけですけれど、このページで紹介する方法は知らないとまずいです。2010年代ごろの私なら適当やっていましたけれど、もう2020年代に突入してますし・・・。ここだけは口酸っぱく言わせてもらいます。(^^;

 // 昔(GBAプログラム研究所さんから)の書き方
* WAITCNT [#b7f2672c]
 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のとおり''0x4317''を設定してください。もう少し詳しく説明しますとカードリッジ領域は3つのミラーリングを持っています。それぞれ0x8000000h、0xA000000h、0C000000hです。WAITCNTとは各ミラーリング領域のアクセスタイミングや、SRAMやプレフェッチバッファーの有無の設定などを行います。しかし自作プログラミングの場合は0x8000000h領域しか使いませんし残り2つの領域は気にしなくてかまいません。次に、cyclesのベンチマークを以下に表します。この数値を見て変更しないのはまずいと思っていただければ幸いです。ただ私としては2/1の方が優れているのになぜGBATEKは3/1を薦めているのかがイマイチわかりませんでした。

- dwelch.com GBA Dhrystone tests([[web.archive.org:http://web.archive.org/web/20210126091131/http://www.dwelch.com/gba/dhry.htm]])

 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 [#s5e08356]
この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のサンプルプログラムにその方法が書いてあったので流用させていただきました(^^;。
ところがこの方法だとforループ中もCPUが動いているため、バッテリーを無駄に消費します。とてもエコではありません。そこでVBLANKになるまでCPUをお休み(HALT)させて、VBLANKになったら起こすことをします。この方法は正直日記さんやdevkitProのサンプルプログラムに書かれていたものを流用しています。

** HALTを使おう [#m8786445]
今までウェイトをするとき、VBLANKを待っている方法を採用していました。







- main.c
-main.c
 #include "lib/gba.h"
 #include "intr_arm.h"
 #include "main.h"
 #include "bg.h"
 #include "irq.arm.h"
 
 //---------------------------------------------------------------------------
 ST_MAIN Main;
 
 //---------------------------------------------------------------------------
 int main()
 int main(void)
 {
 	IntrInit();
 	IntrStart();
 	REG_WSCNT = 0x4317;
 
 	for(;;)
 	{
 		SystemCall(5);  // BIOS Call 5
 	BgInit();
 	IrqInit();
 
 		TRACEOUT("VCOUNT: %d\n", *(volatile u16*)0x4000006);
 	}
 }


- intr_arm.h
 #ifndef __INTR_H__
 #define __INTR_H__
 #ifdef __cplusplus
 extern "C" {
 #endif
 	_Memset(&Main,  0x00, sizeof(ST_MAIN));
 
 #include "lib/gba.h"
 
 //---------------------------------------------------------------------------
 	ST_MAIN* p = &Main;
 	char str[32];
 
 	for(;;)
 	{
 	    VBlankIntrWait();
 
 //---------------------------------------------------------------------------
 typedef struct {
 	void* handler[MAX_INTS];
 } ST_INTR_TABLE;
 		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;
 		}
 
 //---------------------------------------------------------------------------
 EWRAM_CODE void IntrInit();
 IWRAM_CODE void IntrMain();
 IWRAM_CODE void IntrDummy();
 		_Sprintf(str, "FREAME:%d", p->frameCount);
 		BgAsciiDrawStr(0, 1, str);
 
 EWRAM_CODE void IntrStart();
 EWRAM_CODE void IntrStop();
 EWRAM_CODE void IntrEnable(irqMASK mask);
 EWRAM_CODE void IntrDisable(irqMASK mask);
 		_Sprintf(str, "DELAY :%d", p->delay);
 		BgAsciiDrawStr(0, 2, str);
 
 EWRAM_CODE void IntrSetHandler(irqMASK num, void* function);
 EWRAM_CODE void IntrUnSetHandler(irqMASK num);
 
 
 
 //----
 IWRAM_CODE void IntrVcount();
 IWRAM_CODE void IntrVblank();
 
 
 #ifdef __cplusplus
 		_Sprintf(str, "FPS   :%d", p->fps);
 		BgAsciiDrawStr(0, 3, str);
 	}
 }
 #endif
 #endif


- intr_arm.c
 #include "intr_arm.h"
- irq.arm.c
 #include "irq.arm.h"
 #include "main.h"
 
 //---------------------------------------------------------------------------
 extern ST_MAIN Main;
 
 //---------------------------------------------------------------------------------
 ST_INTR_TABLE IntrTable;
 
 
 //---------------------------------------------------------------------------------
 EWRAM_CODE void IntrInit()
 //---------------------------------------------------------------------------
 EWRAM_CODE void IrqInit(void)
 {
 	REG_IME =  0;
 	REG_IF  = ~0;
 	REG_IE  =  0;
 	REG_IME = 0;
 
 	u16 i;
 	INT_VECTOR   = (u32)IrqHandler;
 	REG_IE       = IRQ_VBLANK;
 	REG_DISPSTAT = LCDC_VBL;
 
 	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()
 //---------------------------------------------------------------------------
 IWRAM_CODE void IrqHandler(void)
 {
 	REG_IME = 0;
 }
 //---------------------------------------------------------------------------------
 EWRAM_CODE void IntrEnable(irqMASK mask)
 {
 	REG_IME = 0;
 	REG_IME  = 0;
 	u16 flag = REG_IF;
 
 	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)
 	if(flag & IRQ_VBLANK)
 	{
 		return;
 	}
 		ST_MAIN* p = &Main;
 
 	IntrTable.handler[num>>1] = function;
 }
 //---------------------------------------------------------------------------------
 EWRAM_CODE void IntrUnSetHandler(irqMASK num)
 {
 	if(num >= MAX_INTS)
 	{
 		return;
 	}
 		p->frameCount++;
 		p->fpsTimer++;
 
 	IntrTable.handler[num>>1] = IntrDummy;
 }
 		if(p->fpsTimer == 60)
 		{
 			p->fps = p->fpsCount;
 			p->fpsTimer = 0;
 			p->fpsCount = 0;
 		}
 
 		REG_IRQ_WAITFLAGS |= IRQ_VBLANK;
 	}
 
 //---------------------------------------------------------------------------------
 IWRAM_CODE void IntrVcount()
 {
 	// EMPTY
 	REG_IF  = flag;
 	REG_IME = 1;
 }
 //---------------------------------------------------------------------------------
 IWRAM_CODE void IntrVblank()
 {
 	REG_IRQ_WAITFLAGS |= IRQ_VBLANK;
 }

fpsの表示処理も加えてみたので若干読みにくくなって申し訳ありません。注目すべきポイントはVBLANKの割り込み許可して、VBLANKハンドラに「REG_IRQ_WAITFLAGS |= IRQ_VBLANK;」をさせることです。この処理を忘れるとmain関数の「VBlankIntrWait();」(この中身は「SystemCall(5);」)を呼んだ時、永久ループしてしまうので注意してください。ゲームを作るときは、この形を基本にするのがいいと思います。

ポイントはVBLANKの割り込み許可して、VBLANKハンドラに「REG_IRQ_WAITFLAGS |= IRQ_VBLANK;」をさせることです。この処理を忘れるとmain関数の「SystemCall(5);」を呼んだ時、永久ループしてしまうので注意してください。ゲームを作るときは、この形を基本にするのがいいと思います。
**動作画面 [#iced3ca8]
- [[github:https://github.com/akkera102/gbadev-ja/tree/main/doc16%20%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88]]
#ref(1.png,nolink)


** 状態遷移を使おう [#g2899146]


** 履歴 [#t48614b9]
- 2023/04/17
- 2008/07/04


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS