GBAのタイマーは4つ用意されており、カウントアップにはCPUのクロック数が基準になっています。人間の体感では秒より下数桁は知覚できない世界です。少し頭を捻って覚える必要があるでしょう。GBAのCPUクロック数は16.78Mhzです。Hzの単位にすると16,780,000になります。この数を1秒で割ると1サイクル時間がわかるという寸法です。
結果は0.00000005959475566となりました。これらを人間に理解できる単位に直してみます。実際、ExcelやGoogleスプレッドシートで自分の目で確かめて見てください。
秒 | s | 0.00000005959475566 |
ミリ秒 | ms | 0.00005959475566 |
マイクロ秒 | us | 0.05959475566 |
ナノ秒 | ns | 59.59475566 |
秒を1000倍したのがミリ秒、ミリ秒を1000倍したのがマイクロ秒、というように変換して最終的にナノ秒という単位に行きつきました。約59nsがCPUの1クロック数というわけです。さて、タイマーは1, 64, 256, 1024クロックごとにインクリメント(カウントアップ)する設定が可能です。この関係を表にすると以下のようになります。
CPU(クロック) | 1サイクル時間(ns) | 1サイクル時間(us) |
1 | 59.59475566 | 0.05959475566 |
64 | 3814.064362 | 3.814064362 |
256 | 15256.25745 | 15.25625745 |
1024 | 61025.0298 | 61.0250298 |
そろそろ頭痛くなってきました。書いている中の人もこの数値に意味を持たせることはできるのだろうかと思ってしまいます。もう少しだけ付き合ってください。このタイマーカウンターは16bitである為、0x0から0xffffの範囲を入れられます。つまり0x10000(10進数は65536)を掛け算すると次のようになります。
cpu | 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 | 0.9998340882 |
1024 * 0x10000 | 3999336353 | 3999336.353 | 3999.336353 | 3.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単位です。
タイマーの値はu16(16bit)なので65535まで増加すると次は0になります。
このことをオーバーフローといいます。
オーバーフロー時に割り込みを発生させることやカスケード機能(後述)を利用することができます。
また、0以外の初期値を入れておくとオーバーフロー時、その初期値からスタートします。
//タイマーが進む周期 何クロックにインクリメントするか #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"