#author("2023-04-24T19:15:59+09:00","","")
* Tutorial.9 タイマー [#y7e25f12]
#freeze
#author("2023-05-26T19:35:54+09:00","","")
* タイマー [#l9a14972]
GBAのタイマーは4つ用意されており、カウントアップにはCPUのクロック数が基準になっています。人間の体感では秒より下数桁はなかなか知覚できない世界です。これから話すことは少し背中がむず痒くなるかもしれません。さて、GBAのCPUクロック数は16.78Mhz(メガヘルツ)です。Hz(ヘルツ)にすると16,780,000になります。クロック数を1秒で割り算すると1サイクル時間がわかるという寸法です。結果は0.00000005959475566となりました。桁がアレなので人間に理解できる単位に直してみましょう。

GBAのタイマーは4つ用意されており、カウントアップには~
CPUのクロック数が基準になっています。~
|RIGHT:|RIGHT:|RIGHT:|c
|秒| s |0.00000005959475566|
|ミリ秒|ms|0.00005959475566|
|マイクロ秒|us|0.05959475566|
|ナノ秒|ns|COLOR(red):59.59475566|

1秒1カウントしたい、といった方法には少し頭を捻ることが必要でしょう。~
秒を1000倍したのがミリ秒、ミリ秒を1000倍したのがマイクロ秒、マイクロ秒を1000倍したのがナノ秒という単位です。約59ns(ナノ秒)がCPUの1クロック数ということがわかりました。さて、タイマーは1, 64, 256, 1024クロックごとにインクリメント(カウントアップ)する設定が可能です。それぞれ掛け算をして表にすると以下のようになります。

** タイマーとCPUの関係 [#o37ad083]
GBAのCPUクロック数は16.78Mhzで、Hzの単位にすると16,780,000になります。~
この数を1秒で割ると1サイクル時間がわかるという寸法です。~
|RIGHT:|RIGHT:|c
| クロック|ns|
| 1| 59.59475566|
| 64| 3814.064362|
| 256| 15256.25745|
| 1024| 61025.0298|

タイマーは1, 64, 256, 1024クロックごとに~
インクリメント(カウントアップ)する設定が可能です。~
この関係を表にすると以下のようになります。~
そろそろ頭痛くなってきました。書いている中の人もこの数値に意味を持たせることはできるのだろうかと思ってしまいます。もう少しお付き合いください。タイマーカウンターは16bitである為、0x0から0xffffという範囲が決まっています。この入れ物を溢れさせる数0x10000(10進数は65536)を計算したものが次の表です。

| CPU(クロック) | 1サイクル時間  | 16bit カウント時間 |
| 1               | 60ns           | 3.9ms              |
| 64              | 3.8us          | 250ms              |
| 256             | 15.3us         | 1s                 |
| 1024            | 61us           | 4s                 |
|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|c
|クロック * 0x10000|ns|us|ms|s|
|1 * 0x10000|3905601.907|3905.601907|3.905601907|0.003905601907|
|64 * 0x10000|249958522.1|249958.5221|249.9585221|0.2499585221|
|256 * 0x10000|999834088.2|999834.0882|999.8340882|COLOR(red):0.9998340882|
|1024 * 0x10000|3999336353|3999336.353|3999.336353|3.999336353|

** タイマーのアドレス [#c804519b]
ナノ秒の世界からようやく秒の世界に戻ってきました。上記の表でいう256クロックでカウンタを0から開始すると、約1秒後(0.99834...)にカウンタが溢れるということがわかります。赤字の部分です。このことから0.5秒後に入れ物を溢れさせたいなら初期値を0x10000/2にすれば良いということもわかります。

 #define REG_TM0CNT_L	*(vu16*)(REG_BASE + 0x100)
 #define REG_TM0CNT_H	*(vu16*)(REG_BASE + 0x102)
** TMxCNT_L [#c804519b]
 The GBA includes four incrementing 16bit timers.
 Timer 0 and 1 can be used to supply the sample rate for DMA sound channel A and/or B.
 
 #define REG_TM1CNT_L	*(vu16*)(REG_BASE + 0x104)
 #define REG_TM1CNT_H	*(vu16*)(REG_BASE + 0x106)
 4000100h - TM0CNT_L - Timer 0 Counter/Reload (R/W)
 4000104h - TM1CNT_L - Timer 1 Counter/Reload (R/W)
 4000108h - TM2CNT_L - Timer 2 Counter/Reload (R/W)
 400010Ch - TM3CNT_L - Timer 3 Counter/Reload (R/W)

タイマーの値はu16(16bit)なので65536(0x10000)まで増加すると次は0になります。このことをオーバーフローといいます。オーバーフロー時、割り込みを発生させることやカスケード機能(後述)を利用することができます。なお0以外の初期値を入れておくとオーバーフロー時、その初期値に戻ります。

** TMxCNT_H [#c0da4f35]
 4000102h - TM0CNT_H - Timer 0 Control (R/W)
 4000106h - TM1CNT_H - Timer 1 Control (R/W)
 400010Ah - TM2CNT_H - Timer 2 Control (R/W)
 400010Eh - TM3CNT_H - Timer 3 Control (R/W)
 
 #define REG_TM2CNT_L	*(vu16*)(REG_BASE + 0x108)
 #define REG_TM2CNT_H	*(vu16*)(REG_BASE + 0x10a)
   Bit   Expl.
   0-1   Prescaler Selection (0=F/1, 1=F/64, 2=F/256, 3=F/1024)
   2     Count-up Timing   (0=Normal, 1=See below)  ;Not used in TM0CNT_H
   3-5   Not used
   6     Timer IRQ Enable  (0=Disable, 1=IRQ on Timer overflow)
   7     Timer Start/Stop  (0=Stop, 1=Operate)
   8-15  Not used
 
 #define REG_TM3CNT_L	*(vu16*)(REG_BASE + 0x10c)
 #define REG_TM3CNT_H	*(vu16*)(REG_BASE + 0x10e)
 
''CNT_Lがタイマーの値、CNT_Hが設定値''と分かれています。大きさは16bit単位です。~
 When Count-up Timing is enabled, the prescaler value is ignored, instead the time is incremented each time when the previous counter overflows.
 This function cannot be used for Timer 0 (as it is the first timer).
 F = System Clock (16.78MHz).

*** TMxCNT_L [#of668bfe]
タイマーの値はu16(16bit)なので65535まで増加すると次は0になります。~
このことを''オーバーフロー''といいます。~
下位0-1bitにて1, 64, 256, 1024クロックのどのタイミングでインクリメントするかを決めます。下位2bit目は、カスケード機能と呼ばれていて下位のタイマー(タイマー1なら0、タイマー2なら1)がオーバーフローしたときに、上位のタイマーの値がインクリメントされるという機能です。カスケード機能を使うと上位のタイマー自身は周期によってインクリメントされないようになります。ちなみにタイマー0は下位のタイマーがないのでカスケード機能は使えません。6bit目はタイマーがオーバーフローしたときに割り込みを発生させるかの設定です。7bit目はセットした瞬間にタイマーが開始されます。順番としてはTMxCNT_Lで値を入れてからTMxCNT_Hを実行してください。

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

*** TMxCNT_H [#i44a23a3]
 //タイマーが進む周期 何クロックにインクリメントするか
 #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(clip_2.png,nolink)

** タイマーの動作例 [#qaa9c659]
 #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];
 	}
 }
 //---------------------------------------------------------------------------
** 動作例 [#qaa9c659]
 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);
 	}
 }

スプライトを使って数字を表示しています。左側がタイマー0、右側がタイマー1です。

*** 動作画面 [#gc843ec0]
#ref(clip_1.png,nolink)
#ref(2.png,nolink)


** 履歴 [#za126cf7]
- 2023/04/26
- 2014/12/25


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