タイマー

GBAのタイマーは4つ用意されており、カウントアップにはCPUのクロック数が基準になっています。人間の体感では秒より下数桁は知覚できない世界です。少し頭を捻って覚える必要があるでしょう。GBAのCPUクロック数は16.78Mhzです。Hzの単位にすると16,780,000になります。この数を1秒で割ると1サイクル時間がわかるという寸法です。 結果は0.00000005959475566となりました。これらを人間に理解できる単位に直してみます。実際、ExcelやGoogleスプレッドシートで自分の目で確かめて見てください。

s0.00000005959475566
ミリ秒ms0.00005959475566
マイクロ秒us0.05959475566
ナノ秒ns59.59475566

秒を1000倍したのがミリ秒、ミリ秒を1000倍したのがマイクロ秒、というように変換して最終的にナノ秒という単位に行きつきました。約59nsがCPUの1クロック数というわけです。さて、タイマーは1, 64, 256, 1024クロックごとにインクリメント(カウントアップ)する設定が可能です。この関係を表にすると以下のようになります。

CPU(クロック)1サイクル時間(ns)1サイクル時間(us)
159.594755660.05959475566
643814.0643623.814064362
25615256.2574515.25625745
102461025.029861.0250298

そろそろ頭痛くなってきました。書いている中の人もこの数値に意味を持たせることはできるのだろうかと思ってしまいます。もう少しだけ付き合ってください。このタイマーカウンターは16bitである為、0x0から0xffffの範囲を入れられます。つまり0x10000(10進数は65536)を掛け算すると次のようになります。

cpunsusmss
1 * 0x100003905601.9073905.6019073.9056019070.003905601907
64 * 0x10000249958522.1249958.5221249.95852210.2499585221
256 * 0x10000999834088.2999834.0882999.83408820.9998340882
1024 * 0x1000039993363533999336.3533999.3363533.999336353

ナノ秒の世界からようやく秒の世界に戻ってきました。上記の表でいうと256クロックでカウンタを0から開始すると0.99秒後、ここではわかりやく1秒後ということにしますが、カウンタの桁があふれる(0x10000になる)ということがわかります。また0.5秒単位であふれさせたいなら0x10000/2を初期値にすれば良いということもわかります。

タイマーのアドレス

#define REG_TM0CNT_L	*(vu16*)(REG_BASE + 0x100)
#define REG_TM0CNT_H	*(vu16*)(REG_BASE + 0x102)

#define REG_TM1CNT_L	*(vu16*)(REG_BASE + 0x104)
#define REG_TM1CNT_H	*(vu16*)(REG_BASE + 0x106)

#define REG_TM2CNT_L	*(vu16*)(REG_BASE + 0x108)
#define REG_TM2CNT_H	*(vu16*)(REG_BASE + 0x10a)

#define REG_TM3CNT_L	*(vu16*)(REG_BASE + 0x10c)
#define REG_TM3CNT_H	*(vu16*)(REG_BASE + 0x10e)

CNT_Lがタイマーの値、CNT_Hが設定値と分かれています。大きさは16bit単位です。

TMxCNT_L

タイマーの値はu16(16bit)なので65535まで増加すると次は0になります。
このことをオーバーフローといいます。

オーバーフロー時に割り込みを発生させることやカスケード機能(後述)を利用することができます。
また、0以外の初期値を入れておくとオーバーフロー時、その初期値からスタートします。

TMxCNT_H

//タイマーが進む周期 何クロックにインクリメントするか
#define TIMER_FREQ_PER_1	0
#define TIMER_FREQ_PER_64	1
#define TIMER_FREQ_PER_256	2
#define TIMER_FREQ_PER_1024	3
//カスケード
#define TIMER_CASCADE		(1<<2)

カスケード機能は下位のタイマー(タイマー1なら0、タイマー2なら1)が
オーバーフローしたときに、上位のタイマーの値がインクリメントされるという機能です。

カスケード機能を使うと上位のタイマー自身は周期によって
インクリメントされないようになっています。

ちなみにタイマー0は下位のタイマーがないのでカスケード機能は使えません。

#define TIMER_IRQ		BIT(6)

タイマーがオーバーフローしたときに割り込みを発生させるかの設定です。

#define TIMER_START		BIT(7)

TM_ENABLEをセットした瞬間にタイマーが開始されます。

#ref(): File not found: "clip_2.png" at page "tutorial.9"

タイマーの動作例

#include "lib/gba.h"
#include "res.h"

//---------------------------------------------------------------------------
void WaitForVsync(void)
{
	while(*(vu16*)0x4000006 >= 160) {};
	while(*(vu16*)0x4000006 <  160) {};
}
//---------------------------------------------------------------------------
void SpriteSetPalNo(u32 num, u32 palNo)
{
	OBJATTR* sp = (OBJATTR*)OAM + num;

	sp->attr2 &= 0x0fff;
	sp->attr2 |= (palNo << 12);
}
//---------------------------------------------------------------------------
void SpriteMove(u32 num, s32 x, s32 y)
{
	OBJATTR* sp = (OBJATTR*)OAM + num;

	sp->attr1 &= 0xfe00;
	sp->attr0 &= 0xff00;
	sp->attr1 |= (x & 0x01ff);
	sp->attr0 |= (y & 0x00ff);
}
//---------------------------------------------------------------------------
void SpriteSetSize(u32 num, u32 size, u32 form, u32 col)
{
	OBJATTR* sp = (OBJATTR*)OAM + num;

	sp->attr0 &= 0x1fff;
	sp->attr1 &= 0x3fff;
	sp->attr0 |= col  | form | (160);
	sp->attr1 |= size | (240);
}
//---------------------------------------------------------------------------
void SpriteSetChr(u32 num, u32 ch)
{
	OBJATTR* sp = (OBJATTR*)OAM + num;

	sp->attr2 &= 0xfc00;
	sp->attr2 |= ch;
}
//---------------------------------------------------------------------------
void SpriteInit(void)
{
	u32 i;
	for(i=0; i<128; i++)
	{
		SpriteMove(i, 240, 160);
	}
}
//---------------------------------------------------------------------------
void SpriteShowNumber(u32 base, s32 num)
{
	s32 i;

	for(i=5-1; i>=0; i--)
	{
		SpriteSetChr(base + i, num % 10);
		num /= 10;
	}
}
//---------------------------------------------------------------------------
void SpriteSetDatChr(u16* dat, u32 size)
{
	u16* p = OBJ_BASE_ADR;
	u32 i;

	for(i=0; i<size/2; i++)
	{
		p[i] = dat[i];
	}
}
//---------------------------------------------------------------------------
void SpriteSetDatPal(u16* pal)
{
	u16* p = OBJ_COLORS;
	u32 i;

	for(i=0; i<16; i++)
	{
		p[i] = pal[i];
	}
}
//---------------------------------------------------------------------------
int main(void)
{
	// モード設定
	SetMode(MODE_0 | OBJ_ENABLE | OBJ_1D_MAP);

	SpriteInit();
	SpriteSetDatChr((u16*)&sprTiles, sprTilesLen);
	SpriteSetDatPal((u16*)&sprPal);

	u32 i;

	// タイマー0  5個のスプライトを使用
	for(i=0; i<5; i++)
	{
		SpriteSetSize (i, OBJ_SIZE(Sprite_8x8), OBJ_SQUARE, OBJ_16_COLOR);
		SpriteSetChr  (i, 0);
		SpriteMove    (i, 20+i*8, 20);
		SpriteSetPalNo(i, 0);
	}

	// タイマー1  5個のスプライトを使用
	for(i=5; i<10; i++)
	{
		SpriteSetSize (i, OBJ_SIZE(Sprite_8x8), OBJ_SQUARE, OBJ_16_COLOR);
		SpriteSetChr  (i, 0);
		SpriteMove    (i, 40+i*8, 20);
		SpriteSetPalNo(i, 0);
	}

	// タイマー設定
	REG_TM0CNT_L = 0;
	REG_TM1CNT_L = 0;
	REG_TM0CNT_H = TIMER_FREQ_PER_256 | TIMER_START;
	REG_TM1CNT_H = TIMER_CASCADE      | TIMER_START;

	for(;;)
	{
		WaitForVsync();

		SpriteShowNumber(0, REG_TM0CNT_L);
		SpriteShowNumber(5, REG_TM1CNT_L);
	}
}

動作画面

#ref(): File not found: "clip_1.png" at page "tutorial.9"

履歴


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