トップ 差分 一覧 ソース 検索 ヘルプ RSS ログイン

h2e開発環境

about

ここでは実行ファイル h2e の開発環境の構築方法、ビルド方法、ソースの記述方法について記載します。

h2e はメモリーカードに配置する実行ファイルです。System24 は割と高機能なグラフィック機能、並列処理可能な68000、FM 音源(YM2151@4.00MHz)などを持ちます。
通常のゲーム基板だと ROM である役割のデータも全て RAM となっているので、メモリーカードリーダをお持ちであれば手軽に System24 の機能を利用したアプリケーションを作成することが出来ます。


開発環境の構築方法

gcc (m68k 向けクロスコンパイラ)を使用します。 Linux などでは m68k-xxx-gcc をビルドしていただくとして、 Windows 向けに cygwin を利用した導入方法を記載いたします。

 cygwin 環境の構築

  1. http://cygwin.com/ から setup.exe をダウンロード
  2. 下記のパッケージを選択し、それ以外はdefault
    1. Devel - binutils, gcc-core, gdb, make
    2. (必要であれば) Ruby - ruby
    3. (好みで) Shells - rxvt
  3. 依存関係のパッケージはすべてインストール
  4. http://download.rockbox.org/cygwin/release/ から m68k-elf-binutils, m68k-elf-gcc をダウンロード
  5. cygwin の shell を開き、下記コマンドでファイルを配置 (downloaded/path は任意)
  6. cygwin の shell の path に /opt/m68k/bin を追加 (~/.profile でやるのがよいと思われる)
$ cd /
$ tar jxvf /cygdrive/downloaded/path/m68k-elf-gcc-3.4.6-1.tar.bz2
$ tar jxvf /cygdrive/downloaded/path/m68k-elf-binutils-2.16-1.tar.bz2

補足

  • cygwin は広く使われている Unix 互換環境ですので足りない情報は都度検索して調べてください。
  • Rockbox は音楽プレイヤーのライブラリ(?)で、Coldfire を使った組み込み機器向けの開発用にバイナリを配布しているようです。
  • Ruby は後述する checksum の算出に必要なだけです。
    • Cygwin 向け Ruby にするか、 Windows ネイティブの Ruby にするかは状況に応じて選択してください。
    • ruby をいれたくないひとは h2esum.rb (単純なスクリプト)を他言語に利用してご利用下さい。
  • Cygwin の標準のターミナルは使いにくいので Rxvt をお勧めしますが、ちゃんと設定しないと見づらいです。

 h2e ライブラリの構築

下記をダウンロードして任意のパスへ展開してください。


以下はファイルの説明です。

  • tilemap.c tilemap.h: BIOS で使われている文字列を表示するのに使用します。必要でなければ組み込む必要はありません。
  • assert.h: デバッグ向けに使用します。必要でなければ組み込む必要はありませんが、ソースレベルデバッガは存在しないので使用することをお勧めします。
  • h2e.h: ヘッダについて記載してあります。必須。
  • h2e_api.s h2e_api.h: メモリーカードのファイルを扱う為に必要なライブラリで任意です。
  • inttype.h: 整数型についての定義で、 h2e.h に必要です。
  • h2e.mak: Makefile の使い回しの多い設定をまとめてありますので Makefile 内で include してご使用下さい。

ソースの記述方法


 sample.c

#include <inttype.h>
#include <h2e.h>
#include <tilemap.h>

void hello(struct _FATFS_ *f, uint8_t screen_rotate)
{
	{
		extern uint8_t rotate;
		rotate = screen_rotate;
	}
	str2_print(0, "HELLO System24", 0, tileoffset(10, 10));
}

__attribute__((section(".text.header")))
const struct h2e_header header = {
	.magic = H2E_MAGIC,
	.exec = hello,
	.checksum = 0,
	.label = "h2e sample",
	.interrupt = NULL
};

include について:

  • inttype.h, h2e.h はヘッダの記述に必要なため inlcude してください。
  • tilemap.h は str2_print に必要です。

hello() について:

  • hello の引数は h2e ヘッダの関数ポインタの型に合わせます。
    • f はファイルアクセスに必要なポインタでこのソースでは未使用です。
    • screen_rotate は画面の回転のパラメータで、tilemap.o に必要なパラメータです。
  • extern uint8_t rotate は tilemap.o の画面回転のパラメータで、各種出力、ポインタ演算に必要です。
  • str2_print は 16x16 のフォントで文字列を出力する関数です。
    • 第1引数の 0 は色です。
    • 第2引数は null 終端の表示文字列です。
    • 第3引数は表示文字数で 0 だと null までそれ以外は最大表示数です。
      • この値を超える文字列が渡された場合は最大表示数で打ち切ります。
      • この値未満の文字列は空白で埋めます。

header について:

  • ヘッダはファイルの先頭に配置するために attribute と const を必ず設定してください。
  • magic: H2E_MAGIC を設定してください。
  • exec: 最初に実行する関数を渡してください。
  • checksum: 別途書き換えますので 0 を渡してください。
  • label: h2e 選択メニューで表示される名称です。好きな名前を決めて下さい。
  • interrupt: vblank 割り込みを使用しないので NULL を渡して下さい。詳細は後述します。

  Makefile の記述方法


Makefile

all: sample.h2e
OBJ_O = sample.o tilemap.o
OBJ_OBJ = sample.obj

include $(H2E_LIB)/h2e.mak

  • all:: 必要な h2e のファイル名を記述します。
  • OBJ_O: コンパイル/アセンブルするオブジェクトファイル名を記述します(ソースではない)。拡張子は o を指定して下さい。
  • OBJ_OBJ: OBJ_O をリンクした実行可能 elf ファイル(実際には実行できない)を記述します。拡張子は obj を指定して下さい。
  • 出力する h2e と obj の拡張子前の名前は同じにする必要があります。

shell:

$ make H2E_LIB=../h2e_lib
m68k-elf-gcc -m68000 -O2 -Wall -Werror -D_M68K_  -I. -I../h2e_lib  -fomit-frame-pointer -ffreestanding -mno-align-int   -c -o sample.o sample.c
m68k-elf-gcc -m68000 -O2 -Wall -Werror -D_M68K_  -I. -I../h2e_lib  -fomit-frame-pointer -ffreestanding -mno-align-int   -c -o tilemap.o ../h2e_lib/tilemap.c
m68k-elf-gcc -nostartfiles -nodefaultlibs -Wl,-Map,h2e.map,--cref -T ../h2e_lib/h2e.x -o sample.obj sample.o tilemap.o
m68k-elf-objcopy --remove-section=.data.global sample.obj -O binary sample.h2t
ruby ../h2e_lib/h2esum.rb sample.h2t sample.h2e
rm sample.h2t

  • h2e ライブラリのパスをコマンドラインで指定して make します。
  • H2E_LIB は Makefile に埋め込んで、 make だけで済ましてもよいでしょう。

 割り込みについて

  • ソフトウェア割り込みについては API で使用します → h2e 仕様 - API
  • 外部割り込みについては vblank のみ h2e として管理します。

vblank 割り込み (INT4)

INT4 は BIOS でも使用しており、 h2e の切り換え時にベクタアドレスのジャンプ先を変更します。 h2e 終了時に BIOS はこのジャンプアドレスを元に戻すので h2e 開発者はこれを操作する必要はありません。

header.interrupt の関数ポインタは vblank 発生直後に呼び出されます。 戻り値 void, 引数 void で、アセンブラから記述する必要があります。下記はそのサンプルです。

interrupt:
	movem.l	d0-d7/A0-A6,-(SP)
	tst.b	0xa00005
	jbsr	interrupt_main
	movem.l	(SP)+,d0-d7/A0-A6
	rte

  • movem.l: 割り込み直後のため全レジスタをスタックに push します。
  • tst.b: 割り込み応答ポートを read します。
  • jbsr: C で記述した関数を実行します
  • movem.l: 割り込み終了のため全レジスタをスタックから pop します。
  • rte: 割り込み処理を終了します。

割り込み発生手順

h2e 呼び出し直後は割り込み禁止の状態で header.exec() を実行します。この中で割り込み許可レジスタを初期化して、 stop 命令などで割り込みを待ちます。

その他の割り込み

下記の割り込みは BIOS でも使用せず、管理しません。使用する場合は各自でベクタアドレスのジャンプ先を変更し、レジスタを初期化してください。

address #    発生元
-------------------
$080036 INT1 不明
$08003c INT2 YM2151
$080042 INT3 Timer
$08004e INT5 CN2 (使用不可)
$080054 INT6 Sprite Generator
$08005a INT7 不明

また h2e 終了時は各割り込みが発生しないようにして下さい。

C のソース記述での注意事項

 暗黙的に使用される関数

  • struct 全体の代入などで任意のデータをコピーする処理の場合は memcpy() が呼び出されます。
    • オーバーヘッドがかなりありますが、BIOS で使用している memcpy を記載いたします。
void memcpy(void *dest, const void *src, int length)
{
	uint8_t *dest8 = dest;
	const uint8_t *src8 = src;
	//when both pointer is even, use movem.l and move.l
	uint32_t s = (uint32_t) src;
	uint32_t d = (uint32_t) dest;
	s &= 1; d &= 1;
	if(s == 1 && d == 1){
		*dest8++ = *src8++;
		length--;
		s = 0; d = 0;
	}
	if(s == 0 && d == 0){
		while(length >= 0x80){
			asm volatile(
				"movem.l %0@+,%%d2-%%d7/%%a4-%%a5\n"
				"movem.l %%d2-%%d7/%%a4-%%a5,%1@\n"
				"movem.l %0@+,%%d2-%%d7/%%a4-%%a5\n"
				"movem.l %%d2-%%d7/%%a4-%%a5,%1@(0x20)\n"
				"movem.l %0@+,%%d2-%%d7/%%a4-%%a5\n"
				"movem.l %%d2-%%d7/%%a4-%%a5,%1@(0x40)\n"
				"movem.l %0@+,%%d2-%%d7/%%a4-%%a5\n"
				"movem.l %%d2-%%d7/%%a4-%%a5,%1@(0x60)\n"
				"lea %1@(0x80),%1"
				:"+a"(src8), "+a"(dest8) :
				: "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", 
				  "%a4", "%a5", "%cc"
			);
			length -= 0x80;
		}
		while(length >= sizeof(uint32_t)){
			asm volatile(
				"move.l %0@+,%1@+"
				: "+a"(src8), "+a"(dest8) :
				: "%cc"
			);
			length -= sizeof(uint32_t);
		}
	}
	while(length > 0){
		*dest8++ = *src8++;
		length--;
	}
}

  • 定数を用いない乗算、除算を使用すると、 mulsi3, udivsi3, __umodsi3 が呼び出されます。
    • m68k 向け uCLinux にアセンブラで記述した物がありますが、ライセンスが GPL なのでファイル配布を予定するなら参考に作り直した方が良いでしょう。

 グローバル変数と静的変数

変数の宣言時に初期値を代入しても実際には初期化されず、不定値です。

int hoge = 0;
void aaa(int bbb)
{
	static int fuga = 0x1234;
	(以下略)
}

この2種類の変数 (.bss に配置される)は、初期値を設定せず (.data に配置される)に通常の関数で値を初期化してください。結果として関数内部の静的変数は実際には使い物になりません。

auto 変数の宣言時の初期値設定は問題ありません。

void ccc(int ddd)
{
	int nyonnyon = ddd >> 1;
	(以下略)
}

const を付けた定数は .rodata に配置され初期化されます。ポインタの場合は *const を使用しないと .bss ということになりますので注意してください。

const int hoge = 0;
static const char *const strpointerarray[] = {
	"hello", "abc", "efg"
};

  int の幅と関数の呼び出し規約

m68k-elf-gcc では未指定時は -mint となり int は 32bit です。コンパイルオプションに -mshort を指定すると int は 16bit となります。 BIOS では int を 32bit として扱っており、 h2e アプリケーションでも 32bit を推奨します。

関数の引数のスタックの積み方が int の bit 幅で代わり下記となります。

引数の型   -mint -mshort
------------------------
8bit 整数  32bit 16bit
16bit 整数 32bit 16bit
32bit 整数 32bit 32bit
ポインタ   32bit 32bit

h2e_header.exec の引数に uint8_t の引数があるため、 -mshort を使用するとスタックの積み直しのコードをアセンブラで記述する必要があります。API の方は引数が全て32bit整数とポインタのため問題はありません。