・GBA programming tips 2002/03/02 hikky.pg [ This text was written in Japanese ] translation site http://babel.altavista.com/translate.dyn 目次: 1:const 2:makeのエラー 3:大きなデータの運用 4:IOレジスタへのアクセス 5:Sound1を鳴らす 6:V-Blank・H-Blankとは 1:const constを付けて変数を宣言した時、ROM領域にデータが置かれるようです。 これは、(240*160*2)バイトの、65536色・フルスクリーン用の画像を 使った時に分かったのですが、下のように宣言した場合、 unsigned short Pic[74801] = { .... }; RAM領域に置かれるらしく、他の作業で不都合が出たり、画面に描画するときに この変数を使うと、画面にゴミが乗ったりしました。 あと、エミュレータ上でしか実験していませんが、あまり大きな変数は、 後ろの方が切り取られるらしく、上の画像(Pic[])は、上半分くらいしか うまく表示されませんでした。 ※ちなみに、変数はグローバルで作りました。  ローカルについてはまだ検証していません。 上のものを、このように書き直した場合、 const unsigned short Pic[74801] = { .... }; うまく読み込むことができました。 エミュレータ上で、Pic[]のアドレスを表示したところ、 0x8000000台のアドレス(ROM領域)が出てきました。 2:makeのエラー GBA開発系サイトでサンプルをDownloadしてくると、makeファイルを 使ってるのが多いです。 で、makeを実行しても、 D:\GBA\sample\proto_devkit\proto_devkit>make gcc -g -O3 -mthumb-interwork -nostdlib -Wall -fverbose-asm -fpeephole -M main.c > Makedepend.txt make: /bin/sh.exe: Command not found make: *** [Makedepend.txt] Error 127 このようなメッセージが出て、コンパイルできませんでした。 どうやらsh.exeの場所が見つからないような感じのメッセージみたいです。 これを直すにはmakeファイルに、 SHELL=sh と、1行書き足してやればいいようです。 3:大きなデータの運用 画像データなどの大きなデータをヘッダファイルに直に置いて、 mainファイルにインクルードしていると、コンパイル時に、 毎回読み込まれて、非常に時間がかかります。 そこで、データをあらかじめコンパイルしておいて、 ヘッダにはデータを示す場所のみを記述するようにします。 これで、コンパイルの速度はだいぶ早くなります。 例えば、Pic[]という画像データがあるとして、 これをdata.cに置くとします。 ----------------------- data.c ----------------------- const unsigned short Pic[] = { .... }; ------------------------------------------------------ これをコンパイルして、data.oというファイルにします。 コマンドラインから、 gcc -c -o data.o data.c と入力します。 ※-cはコンパイルのみを表し、-oは出力されるファイル名を  表しています。 data.o内のPic[]を使うためには、Pic[]を使いますよーっていう 宣言をする必要があります。例えば、data.hにこれを書いてみます。 ----------------------- data.h ----------------------- extern const unsigned short Pic[]; ------------------------------------------------------ このファイルを、使いたいところでインクルードすると、 Pic[]が使えるようになります。 プログラムが完成して、コンパイルする時には、 gcc -o main.elf main.c data.o のように、メインのファイル名(この例の場合、main.c)のあとに、 あらかじめコンパイルしておいたファイルの名前を書いておきます。 これでコンパイルが早くなるので、大きなデータが 運用しやすくなると思います。 しかし、この例の場合は少し不都合がおこります。ソース中に、 sizeof(Pic) が出てきたときに、エラーを起こしてしまいます。 これを避けるには、data.hの中に明示的に変数のサイズを 書いておきます。 ----------------------- data.h ----------------------- extern const unsigned short Pic[76801]; ------------------------------------------------------ これでsizeofを使った、データサイズの取得も問題なく できるようになります。 data.cには、変数のサイズを明示的に書く必要はありません。 またdata.h内で、実際の値より小さい数を書いた場合、 sizeofで、そのサイズが返されます。 実際より大きい値を書いたら、それが返されるかも しれないので危険です。 4:IOレジスタへのアクセス IOレジスタは、RAM領域に比べてアクセス速度がだいぶ遅いような気がします。 例えばDMA0を使って、DirectSoundAを鳴らすときに、 毎フレーム、DMA0の転送元アドレスを確認するプログラムを書くとします。 i = *(u32*)0x40000B4; これは変数iに、DMA0の転送元アドレスの場所を入れている所ですが、 この1行があると、1フレーム内に処理が間に合わなくなり、 動作が、30FPS程度になってしまいます。 エミュレータ上では、IOレジスタも読み込みが早いので、 実機とは違う動作になってしまいます。(VisualBoyAdvance0.8で確認) この例の場合、エミュではちゃんと動作するようです。 あと、IOレジスタに直接読み書きする時に問題を起こす時が あるようです。 例えば、BG0の水平スクロール値を毎フレーム1ドットずつ 増やしていくプログラムを書くとします。 *(u16*)0x4000010 += 1; これはIOレジスタの、BG0の水平スクロール値を示す場所に 1を足すことを表しています。 エミュ上では動きますが、実機ではまったくスクロールしませんでした。 これを修正するには、 i++; *(u16*)0x4000010 = i; のように書くと良いようです。 また、iを1に固定して、 *(u16*)0x4000010 += i; と書いた場合はどうなるのかまだ分かりません…。 ※なんか、IOレジスタの場所によってアクセス速度が違うようです。  もしかしたら早いところもあるかも知れません。 5:Sound1を鳴らす Sound1を鳴らそうと思って、下のような感じでレジスタに値を セットしても鳴らない時があります。 --------------------- 鳴らない例 --------------------- REG_SOUNDCNT_L = 0x1177; //Sound1の左右をON、音量0x77 REG_SOUNDCNT_H = 2; //出力レートFULL REG_SOUNDCNT_X = 80; //サウンド全体の動作をON ------------------------------------------------------ このように、L→H→Xの順に設定しても、サウンドが 鳴りません(エミュでは鳴る)。Xをセットした時、 LとHの値が初期化されるっぽいです。 なので、上を修正して、 ------------------------ 修正 ------------------------ REG_SOUNDCNT_X = 80; //サウンド全体の動作をON REG_SOUNDCNT_L = 0x1177; //Sound1の左右をON、音量0x77 REG_SOUNDCNT_H = 2; //出力レートFULL ------------------------------------------------------ としたら、鳴るようになります。 6:V-Blank・H-Blankとは ゲーム機のプログラムをやっているのに、 V-Blank・H-Blankの意味がよく分かってませんでした…。 今まで読んだドキュメントを参考に、自分なりに まとめてみます。 どうやらディスプレイ(モニタ)は、一番左上の位置から始まって、 右下の位置まで、1個1個、順番に画素を描画していくようです。 1行を描画する時、左から始まって右にたどり着いた時にH-Blank、 画面全体を描画する時、一番上の行から一番下の行まで描き終ったら V-Blankというようです。 HというのはHorizontal(水平)、VはVertical(垂直)を それぞれ表しています。 GBAのスクリーンは、240*160ドット=38400画素です。 1画面描き終わるまでにH-Blankは160回、 V-Blankは1回発生します。 また1秒では、H-Blankは160*60回、 V-Blankは60回発生します。 そして、なぜ”Blank”が付くかというと、例えば1行の描画なら、 右端まで書き終わった後に、左端に描画位置が戻る時間が あります。この戻るまでの時間が”Blank”なのです。 (戻るというのをイメージするには、パソコンのモニタを  考えてみてください。電子ビームが右端まで移動したら、  次の行を描くには左に移動しておかないと描画できません。…たぶん。  ※実際にはどういう仕組みで動いているかよく知りませんが…) Blank中にビデオメモリを入れ替えておくと、次の描画時に、 チラつくことがなく、表示する事ができます。 また、Blank時間は非常に短いので、大きな処理を 書くと、処理に時間がかかりすぎて、チラついてしまうことがあります。 V-Blankはほとんど全てのゲームで使われていると思います。 毎秒60フレーム、キレイに表示するにはこれが必要です。 プレステ1などのゲーム(レースゲームなど)で、V-Blank1回の内に 処理が間に合わないので、30フレームで表示しているものも あります。(リッジレーサーR-4とか) H-Blankを使ったゲームは、ゲーセンのゲームに多いです。 特に有名なものでは、スト2の地面の描画、タイトー系シューティング の背景(ダライアス外伝の地上とか)があります。 一般的に、ラスタースクロールとか言われてるみたいです。 H-Blankを使うと1行ごとに、スクロール量を変えることができるので、 奥の方の地面はゆっくり、手前の地面は速くスクロールさせたり できます。(電車に乗って、外を眺めているような感じ) で、GBAの話に戻りますが、GBAでは HBlankは16.212μ秒(68ドット描画分)、VBlankは4.994m秒(68ライン分描画) らしいです。 CPUの1クロックが59.595nsらしいので、それを考慮して、 アセンブラのあの命令は何クロックで……なんとかかんとか… と考えると、Blank中に処理できる量が分かるみたいです。 C言語でやってると、全くイメージ湧きませんが…。