ホームチュートリアルプログラム
1-セットアップ 4-サウンド 7-FIFO インベーダー
2-フレームバッファ 5-SRAM 8-割り込み DSEmu
3-キー入力 6-ファイルシステム 9-マイク  
10-拡張回転バックグラウンド

Homebrew任天堂DS開発 Part 1

最近私は、Nintendo DS を持っているので、それにおける何らかの開発を行い、腕を試そうと決めました。 このシリーズの記事は Nintendo DS 開発に関する私の理解について説明して、様々な開発ツールを使用するときの簡易なチュートリアルになることを意図しています。

装備とソフトウェア

DS上で自己開発コードを実行するには、デバイスに実行可能コードを転送する必要があります。 これをするいくつかの方法がありますが、私が使用しているのは'WifiMe'と呼ばれる方法です。

これは、PCMCIA互換 または PCI 802.11g ネットワークカード を使用し、PC上で、'DS ダウンロードプレイ'とDSで表示されるサーバプログラムを動かします。

DSで、'DS ダウンロードプレイ'オプションによって、PCから送信された利用可能なプログラムを選択することができます。 そして、このプログラムは、GBA フラッシュカートリッジ上に保存したDSで実行するためのプログラムを実行します。

したがって、このチュートリアルの目的に、私は以下を使用しています:

あなたはその他の互換のワイヤレス・カード、フラッシュカートリッジ、および開発環境も使用することができますが、以下の例は私のセットアップでのものです。

セットアップ

ここでの手順はWindowsベースのシステムのためのものです。 DevKitPRO はクロスプラットホーム開発環境であり、Linux、MacOS X、および Windows の上で動作しますので、他のオペレーティングシステムでのインストールは可能です。

  1. WifiMe チュートリアルに従ってPCI ネットワークカード、WifiMeソフトウェア、およびRT2560ドライバーをインストールします。PCIネットワークカード添付のドライバーはインストールしません。DS用にカスタム開発された RT2560 ドライバーをインストールしなければなりません。
  2. 'wmb.exe'プログラムをディレクトリにインストールします。 それは'data'サブディレクトリを持っています。 'data'サブディレクトリには'WifiMe'ファイルを含む'wifime'サブディレクトリがあります。
  3. DevKitPRO 開発環境を セットアップページに従って、インストールします。解説されているようにパスと適切な環境変数を必ず設定します。

私のシステムの上で行った環境変数とPATH変更は以下の通りでした。

DEVKITARM=/d/devkitPRO/devkitARM
DEVKITPRO=/d/devkitPRO
PATH=d:\devkitpro\msys\bin;d:\devkitpro\devkitarm\bin;

DevKitPROのための 'libnds' ライブラリを必ずダウンロードしてください。 'tar' を使用することで正しいディレクトリにこれらを解凍することができます:

d:
cd \devkitpro
mkdir libnds
cd libnds
tar -jxvf libnds-20050521.tar.bz2
tar -jxvf libnds-src-20050521.tar.bz2

DevKitPRO は 'libnds' を使用しますが、他の開発システムでは 'ndslib' について言及されています。 これらは実際には同じライブラリです。 'ndslib' はオリジナルのライブラリで、'libnds' は DevKitPRO 環境で管理するわずかに変更されたバージョンです。 それは 'ndslib' と同時性が保たれています。 これからのチュートリアルでは、私は両方の名前をしばしば互換性を持って使用するつもりです。

テスト セットアップ

ファイル demo1.zip をダウンロードしてください。これはこのチュートリアルで使用するサンプルアプリケーションが含まれています。 任意のディレクトリにこのファイルを解凍してください、そして、作成された 'demo1' サブディレクトリに現行ディレクトリを変更してください、そして、'make' コマンドを実行してください。

D:\examples\demo1>make
arm-elf-g++ -g -Wall -O2 -mcpu=arm7tdmi ... -oarm7_main.o
arm-elf-g++ -g -mthumb-interwork ... -oarm7.elf
arm-elf-objcopy -O binary arm7.elf arm7.bin
arm-elf-g++ -g -Wall -O2 -mcpu=arm9tdmi ... -oarm9_main.o
arm-elf-g++ -g -mthumb-interwork ... -o arm9.elf
arm-elf-objcopy -O binary arm9.elf arm9.bin
ndstool -c demo1.nds -9 arm9.bin -7 arm7.bin
Nintendo DS rom tool 1.21 - Jul 12 2005 by Rafael Vuijk (aka DarkFader)
dsbuild demo1.nds -o demo1.nds.gba
dsbuild 1.21 - Jul 12 2005
using default loader

'demo1' ディレクトリに、2個の新しいファイルが作成されます:

あなたが'wmb.exe'を解凍したディレクトリで、以下のコマンドを実行してください:

f:\wmb>wmb -data wifime

Nintendo DS - Wireless Multiboot Application - Version 1.3 BETA 4

(c) 2005 Tim Schuerewegen

Device Description = Ralink RT2560 Device
Device Hardware ID = PCI\VEN_1814&DEV_0201&SUBSYS_700A1799&REV_01
Device Location    = PCI bus 1, device 9, function 0
Driver Version     = 1.0.0.7

Loading "wifime"... ok

Press [x] to abort

Sending multiboot beacons & waiting for authentication

>GBA フラッシュカードに添付されたソフトウェアを使用して、それに'demo1.nds.gba'ファイルをコピーしてください。 それはカードの上の唯一のファイルでなければなりません。ローダーは使用しませんので、オプションから外してください。

カードをDSに挿入し、電源を入れます。 そして、'DSダウンロードプレー'を選択してください。DSは、'WifiMe'がダウンロード可能なのを見つけます。 これを選択し、確認します。 ソフトウェアがダウンロードされます、そして、次に、GBA Flashカード上の実行可能プログラムが稼働します。

開発概要

以下は任天堂DSでどのように開発を行うかに関する私の理解です。 私は今までGBAおよびDSの開発はしていません。誤りの訂正は歓迎します。(私のEメールアドレスは、この記事のフッターを見てください)。

任天堂DSは、ARM7とARM9の2CPUがあり、両方が同時に、コードを実行することができます。A DS program is 通常、DSプログラムはARM7の実行可能コード、ARM9の実行可能コード、様々なアイコン、ビットマップ、リソースなどで構成されます。 これらは、'ndstool'と呼ばれるツールを使用することで単一の'実行可能コード'に結合されます。 その結果、このファイルは自己含有のファイルシステムに似ています。 これはデバイスで動かすことができる'someprog.nds'ファイルを作り出します。

GBAフラッシュカートリッジから起動するテクニックを使用するために、それは最初にDSモードに切り替って実行する'ローダー'が必要です。 'dsbuild'と呼ばれるツールを使用することでこれを.ndsファイルに追加します。 これは'someprog.nds.gba'(または使用する開発システムによっては'someprog.ds.gba')ファイルを作成します。

DevKitPROシステムには、開発を簡素化するテンプレートとメイクファイルがあります。 以下の例では、様々なツールでそれらがどう使用されているかを示すのに、私が makefile を使用する代わりにいろいろなコマンドを手作業で使用します。うまくいけば、makefile が '魔法'のように見せられるでしょう。

サンプル デモ プログラム

ARM7 コード

私たちが必要とする最初のものはARM7コードです。 私の理解ではARM7がタッチスクリーンを制御するのに使用することができる唯一のCPUであるということです。 'libnds' ライブラリの例は'垂直ブランク割り込み'のために割り込みハンドラをセットアップする'定型'コードを使用します。 これはスクリーン描画を処理するために定期的に発生する割り込みです。

割り込みハンドラの中では、'定型'コードは、タッチスクリーンパラメタの値を得て、検索するARM9コードのためにそれらを保存します。 それを読む必要がある場合は、ARM7だけがタッチスクリーンにアクセスすることができるのを覚えていてください。 しかし、ARM9がアクセス可能な内部の構造にそれを保存することによって、ARM9がデータを使用するのを可能にします。

割り込みコードは以下の通りです。

void InterruptHandler(void) {
  static int heartbeat = 0;
 
  if (IF & IRQ_VBLANK) {
    uint16 but=0, x=0, y=0, xpx=0, ypx=0, z1=0, z2=0, batt=0, aux=0;
    int t1=0, t2=0;
    uint32 temp=0;
    uint8 ct[sizeof(IPC->curtime)];

    
    // heartbeatの更新
    heartbeat++;
 
    // X/Y ボタンおよび /PENIRQ ラインの読み込み
    but = XKEYS;
    if (!(but & 0x40)) {
      // タッチスクリーンの読み込み
      x = touchRead(TSC_MEASURE_X);
      y = touchRead(TSC_MEASURE_Y);
      xpx = ( ((SCREEN_WIDTH -60) * x) / TOUCH_WIDTH  ) - TOUCH_OFFSET_X;
      ypx = ( ((SCREEN_HEIGHT-60) * y) / TOUCH_HEIGHT ) - TOUCH_OFFSET_Y;
      z1 = touchRead(TSC_MEASURE_Z1);
      z2 = touchRead(TSC_MEASURE_Z2);
    }

    batt = touchRead(TSC_MEASURE_BATTERY);
    aux  = touchRead(TSC_MEASURE_AUX);

    // 時刻の読み込み
    rtcGetTime((uint8 *)ct);
    BCDToInteger((uint8 *)&(ct[1]), 7);
 
    // 温度の読み込み
    temp = touchReadTemperature(&t1, &t2);
 
    // IPC 構造の更新
    IPC->heartbeat = heartbeat;
    IPC->buttons   = but;
    IPC->touchX    = x;
    IPC->touchY    = y;
    IPC->touchXpx  = xpx;
    IPC->touchYpx  = ypx;
    IPC->touchZ1   = z1;
    IPC->touchZ2   = z2;
    IPC->battery   = batt;
    IPC->aux       = aux;

    for(u32 i=0; i<sizeof(ct); i++) {
      IPC->curtime[i] = ct[i];
    }

    IPC->temperature = temp;
    IPC->tdiode1 = t1;
    IPC->tdiode2 = t2;


    // サウンドコード :)
    TransferSound *snd = IPC->soundData;
    IPC->soundData = 0;
    if (snd) {
      for (int i=0; i<snd->count; i++) {
        s8 chan = getFreeSoundChannel();
        if (chan >= 0) {
          startSound(snd->data[i].rate, 
                     snd->data[i].data,  
                     snd->data[i].len, chan, 
                     snd->data[i].vol, 
                     snd->data[i].pan, 
                     snd->data[i].format);
        }
      }
    }
  }
 
  // 割り込みのアクノレッジ
  IF = IF;
}

ARM7コードには、通常のCプログラムのような'main'関数があります。 それは、割り込みハンドラをセットアップし、上記の関数が垂直ブランク割り込みから呼ばれるのを待つ無限ループに入るだけですので非常に簡単です。

int main(int argc, char ** argv) {
  // 必要なら、時計をリセット
  rtcReset();

  // サウンドのイネーブル
  SOUND_CR = SCHANNEL_ENABLE | SOUND_VOL(0x7F);
  IPC->soundData = 0;
 
  // 割り込みハンドラのセットアップ
  IME = 0;
  IRQ_HANDLER = &InterruptHandler;
  IE = IRQ_VBLANK;
  IF = ~0;
  DISP_SR = DISP_VBLANK_IRQ;
  IME = 1;

  // Keep the ARM7 out of main RAM
  while (1) swiWaitForVBlank();
  return 0;
}

私は 'arm7_main.cpp' ファイルにこのコードを入れました。 それは'libnds'のサンプル内のARM7コードからコピーしました。 ARM7用のg++コンパイラは、ARM7コンパイルコードを含む 'arm7.o' ファイルを作り出します。 ARM7のためのコードを生成するためのオプション、インクルードパス、出力ファイルによって必要とされるg++のオプションを選びます。

arm-elf-g++ -g -Wall -O2 -mcpu=arm7tdmi -mtune=arm7tdmi \
   -fomit-frame-pointer -ffast-math -mthumb-interwork   \
   -Id:\devkitpro\ndslib\include -DARM7 -c arm7_main.cpp -oarm7_main.o

生成された 'arm7_main.o' ファイルは 'libnds' によって提供されるDS ARM7の標準のライブラリにリンクされる必要があります。 これは通常のプログラムをCの標準のライブラリにリンクするのと同じです。 このステップは次に 'objcopy' を使用することでDS ARM7バイナリーに変換された'ELF'形式ファイルを作り出します:

arm-elf-g++ -g -mthumb-interwork -mno-fpu  \
  -specs=ds_arm7.specs arm7_main.o -Ld:\devkitpro\ndslib\lib -lnds7 -oarm7.elf

arm-elf-objcopy -O binary arm7.elf arm7.bin

ARM9 コード

ARM9コードはこの例における主な処理を行います。 それはタッチスクリーンから入手した X・Y座標を簡単なテキストメッセージで表示します。 ここからアクセス可能なデータ構造に、ARM7コードがこの情報を得て、それを保存したのを覚えていてください。

このコードは、'nsdlib'のhello_worldのサンプルに、非常に小さいな変更を加えています。 私はここに自分が理解していることを記録しているだけです。私がこれに到着できたのは、libnds、ndsdev、wifime、および他のユーティリティの開発者のおかげで、大いに感謝しています。

ARM9コードは'main'関数だけです。 まず最初に、任天堂DSのスクリーンの1つを使用するためにビデオモードをセットアップします。 この場合は'main'スクリーンです。 また、私たちはテキストを表示するために、フォント色などをセットアップします:

  powerON(POWER_ALL);
  
  // メインスクリーンを表示のために使用
  videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG);
  BG0_CR = BG_MAP_BASE(31);

  // フォントカラーを白にセット
  BG_PALETTE[255] = RGB15(31,31,31);

  // メインスクリーンを下部スクリーンにするためにスクリーンをスワップ
  lcdSwap();

  consoleInitDefault((u16*)SCREEN_BASE_BLOCK(31), (u16*)CHAR_BASE_BLOCK(0), 16);

タッチスクリーンのデータを表示してループします。

  consolePrintf("\n\n\tHello World!\n");
  while(1) {
    consolePrintSet(0,10);
    consolePrintf("Touch x = %d   \n", IPC->touchXpx);
    consolePrintf("Touch y = %d   \n", IPC->touchYpx);			
  }

あなたはIPC-> touchXとIPC-> touchYがARM7がセットしたタッチスクリーン値の構造メンバーであることを認識するべきです。 このコード 'arm9_main.cpp' はARM7コードの時と同様の方法で(ただし、ARM9コードを生成するように変更して)コンパイルします。

arm-elf-g++ -g -Wall -O2 -mcpu=arm9tdmi -mtune=arm9tdmi \
   -fomit-frame-pointer -ffast-math -mthumb-interwork   \
   -Id:\devkitpro\ndslib\include -DARM9 -c arm9_main.cpp -oarm9_main.o

arm-elf-g++ -g -mthumb-interwork -mno-fpu  \
  -specs=ds_arm9.specs arm9_main.o -Ld:\devkitpro\ndslib\lib -lnds9 -o arm9.elf

arm-elf-objcopy -O binary arm9.elf arm9.bin

DS 実行可能コードの生成

.bin形式のARM7とARM9コードができたので、次に、これらのファイルとビットマップ・アイコン・リソースなどを含くむ'mini-filesystem'を結合して、実行可能コードを作成する必要があります。 これをするツールが'ndstool'です:

ndstool -c demo1.nds -9 arm9.bin -7 arm7.bin 

上記の 'ndstool' コマンドは、実際にハードウェアで動作する'demo1.nds'ファイルを生成します。それは私たちが先ほどコンパイルしたARM9 CPUの上で動作するための'arm9.bin'と、ARM7 CPUの上で動作するための'arm7.bin'を含みます。

私たちがコードを実際のデバイスで実行するのには、ARM9コードを実行するためにDSをGBAモードへからARM9に切り換える'ローダー'がそれに追加されるのを必要とします。 これをするツールは'dsbuild'です:

dsbuild demo1.nds -o demo1.nds.gba 

これは'demo1.nds.gba'ファイルを生成します。 GBAフラッシュカートリッジにこのファイルをコピーしてください、そして、WifiMeを使用して、同様に、 Test Setupで説明されるようにそれを実行してください。

また、あなたはどんなDSエミュレータでも'demo1.nds'ファイルを動かすことができます。 エミュレータで操作するアプリケーションの動画がここにあります。完全なソースコードは demo1.zip で利用可能です。そして、あなたはエミュレータとハードウェアの上でそれぞれテストを行ったコンパイル後の demo1.ndsdemo1.nds.gba ファイルをダウンロードすることができます。

むしろ、Makefileを使用する方が、個別にプログラムを生成するためにコマンドを実行するより簡単です。 Makefileはそれぞれのファイルの依存関係をリストし。要求された実行可能ファイルを生成するために必要なコマンドを実行します

DevKitPROは、最小の作業でかなり精巧なプログラムを組立てるための Makefile テンプレートを含んでいます。 マルチターゲット(例えば、カスタムのARM7とARM9 バイナリ)を生成するためにこれらのメイクファイルをどのように作成するかまだ完全に理解はしていないので、私は今まで解説したコマンドを実行する、非常に簡単なMakefileに書いています。'demo1'サブディレクトリ内に必要なファイルを生成する完全な Makefile はここにあります。

結論

任天堂DSはhomebrew開発のすばらしいシステムです。 それは1つはタッチスクリーンである2つのスクリーン、マイクロホン、3Dハードウェア、およびWifiを持っています。 うまくいけば、私は後の記事でそれ以上の大規模な例を探るでしょう。 いろいろなコメントや提案は歓迎します。 以下の私の連絡先を見てください。