情報システム実験 K-13 組み込みオペレーティングシステム No.2
GBAがどのようなハードウェアから構成されているかは, 本の6ページの図1,8 ページの表1を見るとわかります. メモリは,プロセッサチップ上にある内部RAM32KB, オフチップの外部RAM256KB, 240x160ドットの解像度を持つ液晶ディスプレイのVRAM(フレームバッファ)として96KBなどが用意されています.
皆さんの作るプログラムは外部RAMにロードされ,実行されます. VRAMに何かを書き込むと画面に何かが表示されます. 内部RAMを使うには工夫が必要ですので, その使い方は実験資料ではふれません.
これらのRAMは, そのアドレスを指定することでアクセス(読み書き)することができます. また,例えばキーパッドでどのキーが押されているのかを知るためには, 入出力のためのI/Oレジスタから値を読み込む必要がありますが, そのI/OレジスタもRAMのようなメモリと同じようにアクセスできます.
このようにI/Oレジスタをメモリと同じようにアクセス可能にする方式をメモリマップドI/O (Memory-Mapped I/O) と呼びます(本の11ページ「メモリと同じように扱えるI/O」参照). 別の方式として,メモリ空間とは別にI/O空間を用意し, I/O空間にアクセスするためには専用命令を使う方式もあります.
メモリ空間上にRAMやI/Oレジスタなどが配置されていますが, どのように配置されているかの情報をメモリマップやアドレスマップと呼びます. GBAのメモリマップは本の11ページ表2に書いてあります. これを図示すると,下図のようになります.
実際に存在するメモリ領域のうち最大のものが外部RAMの256KBですが, 外部RAMが始まる0x0200:0000から次の内部RAMの領域が始まる0x0300:0000まで16MBあいており, そのうちわずか256KBだけを使っていることになります.
図形描画はVRAMに直接値を書き込むことで行います. 簡単のために各点が16ビット(2バイト)に対応するモード3を使用します. モード3を使用しますためには,ディスプレイ制御レジスタで, モード3を使用することをGBAに伝える必要があります. モード3の詳しい説明は本の34ページにあります.
モード3では1つの点(ドット)の色を表すために, 赤緑青(RGB)の各色ごとに5ビット, 合計15ビットを使用します. 15ビット全てが1 (0x7FF) の場合そのドットは白色になり, 全てが0の場合黒色になります. 1つのドットが15ビットでは半端なので, 1ビットは未使用にして, 16ビットにしています(34ページ図4).
VRAM は 0x0600:0000 から始まっています. GBAの液晶ディスプレイの解像度は240x160なので, (0, 0) の点のアドレスは 0x0600:0000 になり, (0 ,1) のアドレスは 0x0600:0002 になります. 横1列(240ドット)の点で480バイト使いますから, (1, 0) の点のアドレスは 0x0600:01E0 となります. 本の34ページ図5にドットとVRAMアドレスの関係が図示されています.
ある座標にドットを表示するには, 対応するVRAMアドレスに表示したい色の値を書き込むだけです. 結果はすぐに液晶ディスプレイの表示に反映されます.
(10, 10) から (10, 19) まで白いドットを打つプログラムは以下のようになります.
1 #include "gba.h"
2
3 main()
4 {
5 hword *fb = (hword*)VRAM;
6 int x, y;
7
8 /* Initialize LCD Control Register to use Mode 3. */
9 gba_register(LCD_CTRL) = LCD_BG2EN | LCD_MODE3;
10
11 /* Put 10 dots from (10, 10) to (10, 19). */
12 for (x = 10, y = 10; x < 20; x++) {
13 *(fb + (LCD_WIDTH * y) + x) = BGR(0x1F, 0x1F, 0x1F);
14 }
15
16 /* spin forever here */
17 for (;;) {}
18 }
1行目の gba.h は ~kumikomios/CDROM/chapter-2 にありますので,cp してきてください (gba.h についてはこのページの最後に書いてある注意事項を参考のこと). gba.h の中身は本の33ページのリスト8にあります. VRAMのアドレスである 0x0600:0000 や, ディスプレイ制御レジスタのアドレス 0x0400:0000 がマクロとして定義されています. よく使用する定数はマクロにしておくと便利ですし, 間違いを減らす効果もありますので, 真似をすると良いところです.
9行目ではVRAMをモード3で使用するように,ディスプレイ制御レジスタを設定しています. モード3を使用するためには,この行は必ず必要になります.
前後しますが,5行目では VRAM のアドレスを hword のポインタ型変数 fb に代入しています. ポインタ型を使うことで,*fb に値を代入すると VRAM に値を書き込めることになります.
hword は unsigned short が typedef されたものです. unsigned short は2バイトですので, そのポインタに 1 を足すとポインタとしては次の要素を指しますので, アドレスとしては2バイト増えることに注意してください. 例えば
hword *p = (hword*)0x100;
とした時,(p + 1) の値は 0x102 です.
つまり hword のポインタ型変数 fb を使って VRAM に値を書き込む場合は, アドレス計算は C 言語にまかせることができます. ですから, (x, y) のドットに値を書き込む場合は *(fb + (LCD_WIDTH * y) + x) に値を代入すればよいわけです(13行目). LCD_WIDTH は gba.h で定義されているマクロで 240 に展開されます.
13行目で代入している値 BGR(0x1F, 0x1F, 0x1F) は gba.h で以下のように定義されているマクロです.
#define BGR(r, g, b) (((b) << 10) + ((g) << 5) + (r))
RGBの各色の値を引数として指定できるようにしています. BGR(0x1F, 0x1F, 0x1F) の場合,0x1F は2進数では 11111 ですから,全てのビットが 1 になり,白色が表示されます.
このマクロの良くないところは, 引数の r, g, b に5ビット以上の値を与えても, そのまま計算してしまうところです. 正確には,以下のように5ビット (0x1F) でマスクするべきです.
#define BGR(r, g, b) ((((b) & 0x1F) << 10) + (((g) & 0x1F) << 5) + ((r) & 0x1F))
17行目では main から戻らないように無限ループを作っています. OS も何もありませんから,main 関数の実行が終わっても戻るところがありませんので,無限ループするようにします. 本来は crt.S で同じことをすべきかもしれません.
文字描画も原理的には図形の描画と同じです. 文字も結局はドットの集まりですので, どこにドットを打てばよいかを現すフォントデータがあれば, その通りにVRAMに書き込んでいけばよいわけです.
以下のプログラムの2行目で #include している 8x8.til がフォントのビットマップデータです. ファイル名のとおり縦横8ドットの大きさのフォントです. ~kumikomios/CDROM/chapter-2 にありますので,cp してきてください. 内容は44ページのリスト17にあります. 各フォントがどのようにビットマップとして表されているかは, 43ページのリスト15を見るとわかると思います.
以下のプログラムは8x8.til に入っているフォントを使用して A を表示しています. 1つのフォントを表示するための関数 draw_char を定義し, それを main 関数から呼んでいます.
1 #include "gba.h"
2 #include "8x8.til"
3
4 #define COLOR_WHITE BGR(31, 31, 31)
5 #define COLOR_BLACK 0
6 #define FONT_SIZE 8
7
8 /*
9 * Draw a font of code with color.
10 * ptr specifies the font's top left corner.
11 */
12 void
13 draw_char(hword *ptr, hword color, int code)
14 {
15 hword *p;
16 int i, j;
17 unsigned char *font = char8x8[code];
18
19 for (i = 0; i < FONT_SIZE; i++) {
20 p = ptr + LCD_WIDTH * i;
21 for (j = FONT_SIZE - 1; j >= 0; j--, p++) {
22 if (font[i] & (1 << j))
23 *p = color;
24 }
25 }
26 }
27
28 main()
29 {
30 hword *fb = (hword*)VRAM;
31 int i;
32
33 /* Initialize LCD Control Register to use Mode 3. */
34 gba_register(LCD_CTRL) = LCD_BG2EN | LCD_MODE3;
35
36 /* Call draw_char to display 'A'. */
37 draw_char(fb + (LCD_WIDTH * 10) + 10, COLOR_WHITE, 'A');
38
39 /* spin forever here */
40 for (;;) {}
41 }
1 #include "gba.h"
2 #include "8x8.til"
3
4 #define COLOR_WHITE BGR(31, 31, 31)
5 #define COLOR_BLACK 0
6 #define FONT_SIZE 8
7
8 /*
9 * Draw a font of code with color.
10 * ptr specifies the font's top left corner.
11 */
12 void
13 draw_char(hword *ptr, hword color, int code)
14 {
15 hword *p;
16 int i, j;
17 unsigned char *font = char8x8[code];
18
19 for (i = 0; i < FONT_SIZE; i++) {
20 p = ptr + LCD_WIDTH * i;
21 for (j = FONT_SIZE - 1; j >= 0; j--, p++) {
22 if (font[i] & (1 << j))
23 *p = color;
24 }
25 }
26 }
27
28 main()
29 {
30 hword *fb = (hword*)VRAM;
31 int key;
32
33 /* Initialize LCD Control Register to use Mode 3. */
34 gba_register(LCD_CTRL) = LCD_BG2EN | LCD_MODE3;
35
36 /*
37 * Loop forever reading a key status and displaying the
38 * corresponding character.
39 */
40 for (;;) {
41 /* Read a key status from Key Status Register. */
42 key = gba_register(KEY_STATUS);
43
44 if (! (key & KEY_L))
45 draw_char(fb + 10, COLOR_WHITE, 'L' );
46
47 if (! (key & KEY_R))
48 draw_char(fb + 20, COLOR_WHITE, 'R' );
49
50 if (! (key & KEY_DOWN))
51 draw_char(fb + 30, COLOR_WHITE, 25) ;
52
53 if (! (key & KEY_UP))
54 draw_char(fb + 40, COLOR_WHITE, 24) ;
55
56 if (! (key & KEY_LEFT))
57 draw_char(fb + 50, COLOR_WHITE, 27) ;
58
59 if (! (key & KEY_RIGHT))
60 draw_char(fb + 60, COLOR_WHITE, 26) ;
61
62 if (! (key & KEY_START))
63 draw_char(fb + 70, COLOR_WHITE, 'S' );
64
65 if (! (key & KEY_SELECT))
66 draw_char(fb + 80, COLOR_WHITE, 's' );
67
68 if (! (key & KEY_B))
69 draw_char(fb + 90, COLOR_WHITE, 'B' );
70
71 if (! (key & KEY_A))
72 draw_char(fb + 100, COLOR_WHITE, 'A');
73 }
74 }
しかし,上記のプログラムでは一度キーを押してそのキーに対応する文字が表示されると, キーを離してもずっと表示されっぱなしです. キーを離すとその文字が消えるようにするには, 一度表示した文字を消してあげる必要があります. 背景色(今は黒)で塗りつぶせば,表示した文字は消えます.
消すのに最も簡単なのは,キーが押されていなかったら,いつも文字を消すようにする方法です (同じような処理の繰り返しですので一部だけ抜書きします).
44 if (! (key & KEY_L)) 45 draw_char(fb + 10, COLOR_WHITE, 'L'); 46 else 47 draw_char(fb + 10, COLOR_BLACK, 'L');
文字を消すのもそれなりに手間がかかりますので, 書いてあったら消すようにした方が処理量は減ります. 前回のキーの状態を oldkey に保存しておき,その値を調べることで, 書いてあるのかどうかわかります. この方法をプログラムにすると以下のようになります.
44 if (! (key & KEY_L)) 45 draw_char(fb + 10, COLOR_WHITE, 'L'); 46 else if (! (oldkey & KEY_L)) 47 draw_char(fb + 10, COLOR_BLACK, 'L');
※ヒント: 点の動きが速すぎる場合は, 同じ処理(例えば点を書く処理)を何度か繰り返したりして, 時間をつぶさせるようにして調整しましょう. 何度かといっても,点を書くのは一瞬ですので, 相当回数繰り返さないとゆっくりにはできません.
gba.h は以下のように各章の内容にあわせて,拡張されています. 3章または4章に書かれているプログラムを参考にする場合は, それぞれの章に対応した gba.h を cp してきて,使用するようにしてください.
% ls -l ~kumikomios/CDROM/chapter-*/gba.h -r--r--r-- 1 shui lab 3599 May 10 2003 /home1/lecture/kumikomios/CDROM/chapter-2/gba.h -r--r--r-- 1 shui lab 5910 Jun 26 2003 /home1/lecture/kumikomios/CDROM/chapter-3/gba.h -r--r--r-- 1 shui lab 10568 Jun 30 2003 /home1/lecture/kumikomios/CDROM/chapter-4/gba.h