投稿日 : 2021/07/14 2:38:36
PC-G850でスプライト&SPI版PSG音源にさらにSDカードにも対応してみた
本家で告知したのにまったく音沙汰が無かった(笑)ポケコン用の割とガチなアクションゲーム、「りりぃ★とらいある」で使用している3色対応の高速ソフトウェアスプライトエンジンや、BGMと同時にSEを1音鳴らすことが可能なアセンブラ版MMLプレイヤーと、今まではシフトレジスタ(74HC595)を使用してPSG音源(YMZ294)を鳴らしていましたが、今回は新たにSPI(MCP23S17)接続による音源への対応と、さらに同時にSDカードも使えるようにした汎用型のSPI音源ボードについてまとめてみました。


音源とSDカードを同時に処理しているため全体的に遅れがち(SDのロードで割り込みが頻繁にブロックされる)ですが、特にフリーズも無く両方とも問題無く動作しています。
※こちらのサンプルプログラムは今回のビルドツールに含まれています(音と動画は同期していません)

ちなみにボード自体は2枚に分かれていて、音源の付いたボードがメインボード、SDカードスロットのボードをサブボードとして、これらを合体させることで音源とSDカードの両方に対応しています。

※メインボードの左下にあるコネクタは今後ジョイスティック等に使用する予定(今回はまだ未使用)



今回は音源をSPI化することで同時にSDカードも利用できるようになったため、超大容量のゲームを作ることが可能となりましたが、欠点としてシフトレジスタ版と比べて音源へのアクセスがちょっぴり遅いため、シフトレジスタ版をベースにスペックギリギリのゲームを作ってしまうと、SPI版では処理落ちが顕著になってしまいます。このため、最初はSPI版をベースに開発するようにすることで、結果的にどちらも処理落ちしないゲームにすることが可能です。


ちなみに「りりぃ★とらいある」とはこんな感じのアクションゲームで、スプライトライブラリを使ったゲームの第1弾です。

※こちらは本家サイトのポケコンページで公開していますが、あとからSPIに対応させたところ処理落ちしたため、現在はシフトレジスタ版のみDLが可能です

全方向の高速スクロールや白と黒を高速に点滅させることで中間色が出せるようになったため、見た目のクオリティが大幅にアップしたのと、BGMと同時にジャンプ時のSEなどを1音鳴らせるようになったため、当時のレトロゲーム機に負けないくらいのゲームになったのではないかと思います。
※ポケコン本体の仕様により点滅が多少見えるのがちょっと残念ですが(こちらの実機動画を参照)


ちなみにSDカードはFAT32(通常はSDHCの32GBまで)に対応しているので、一生遊べるレベルのゲームも作ることが出来るかもしれませんが、そもそもそんなゲームを作るのに一生かかってしまうかもしれないので、あまりお勧めは出来ません(笑)。

いずれにせよ利用可能なRAMは常に32KBしかなく、この中にプログラムや変数などを入れる必要があるため、実際には1枚絵やコースデータのような1回ごとに捨てても良いデータくらいにしか使い道は無いかもしれません。
※プログラムのバイナリコードをSDカードに入れ、それを特定の番地にロードして直接コールするといった使用方法ならば、ROMバンク切替のようなことも可能なので実質プログラム自体もほぼ無制限と考えることは出来ますが、そもそもそういった制御を作ること自体がかなり大変です

ということで、今回はスプライトやSPI通信に対応したビルドツールと、SDカード対応のSPIボードの作り方について説明します。


なお、今回作成したSPIボードはPSG音源とSDカードが扱えるようになっていますが、SDカードの部分はドッキング前提のサブボードとして作成するため、SDカードの代わりに自前のSPIデバイスを接続することも可能です。特に、Arduino用としてSPI接続のセンサーや液晶モジュールなどが市販されているので、これらをポケコンで制御することが可能となります。


ちなみに今回作成したSPIライブラリは、音源を制御するための専用のライブラリというわけではなく汎用のライブラリとして作成しているため、音源もSDも使用しないのであれば、自前のSPIデバイスを2台まで同時に扱うということも可能です。

それとポケコンは5V動作ですが、SDカードは3.3Vで動作するハードのため、本来は直接接続することは出来ないのですが、今回使用したSDカードのキットはもともと5Vに対応しているため、基本的にはそのまま直結することが出来ます。しかし、もし独自で3.3V用のデバイスを使用する場合は、別途3.3V用のレギュレータなどが必要となるので、使用するデバイスの電圧を確認しておく必要があります。
※電圧が3.3VのSPIデバイスの場合は出力も3.3Vとなるため、ポケコン側はそのままでは正しく認識出来ない恐れがありますが、今回作成したSDカードのサブボードはFETを使って3.3Vの出力を5Vに変換しているので、もし3.3Vのデバイスを使用する時は参考にしてください

スプライトについて

まずスプライトというのは何なのかという話ですが、簡単に言うと例えば8x8pxのような決められたサイズで作られた画像データをまとめて番号順にセットしておき、その画像番号と画面上の位置を指示することでその画像を表示させたり(キャラクタースプライト)、ある大きな2次元配列にこれらの画像番号を羅列し、これを参照して一気に表示することで背景画像として表示させたり(背景スプライト)する方法で、最終的に背景とキャラクターが合成された画面を生成する仕組みのことですが、これらの処理は通常は専用のICによって行われるため、当時の低スペックなCPUでもそこそこ滑らかに動くゲームを作ることが出来ました。
※実際には専用のスプライトICと言えども、キャラクタースプライト数が多すぎると描画が間に合わずチラつきが発生します

逆に現在の画面の表示方法はフレームバッファ方式と言い、まず画面サイズと同等のメモリを2枚分用意しておき、現在表示中の方をフロントバッファ、もう片方をバックバッファとしますが、描画は全てバックバッファに行っておき最後にフロントバッファと切り替えることで一気に次の画面を表示するという方法ですが、これは当時と比べてメモリが大容量化しつつも価格が下がったため、贅沢に使えるようになったことで生まれた方式とも言えます。
※例えばPCエンジンのメモリベース128は、たった128KBの保存領域しかないのに当時は6000円もしました

ちなみに、このフレームバッファ方式はポケコンビルドツールv2以下の描画ライブラリが使用しています。ただし、当然ポケコンには専用のハードウェアは実装されていないため描画処理は全てCPUによる計算が必要で、一応このライブラリは任意のサイズの画像を任意の位置に表示することが出来ますが、画像が描画先からはみ出さないかを毎回チェックする必要があったりして、速度があまり出ないといった問題がありました。

描画用のハードウェアを持たないポケコンでもやはりゲームなら速度が重要だろうということで、もっと高速に動作するような処理が作れないかと試行錯誤したところ、結果的にレトロゲーム機のようなスプライト方式にして画像は固定サイズ、また同時に表示可能なキャラクター数にも制限を入れることで、かなり高速に動作する描画エンジンを作ることが出来ました。
※ライブラリ内部では正確には表示用と描画用のバッファがあるので、実質フレームバッファ方式も利用しています

しかも思った以上に処理が速かったので、試しに2枚分の画像バッファを作りLCDの割り込みタイミングで安定的に高速切り替えを行ってみたところ、2枚とも白の部分は白、黒の部分は黒、そして片方は白でもう片方を黒にした部分は灰色として表現することが出来ました。

そして、これらをまとめたのが今回のスプライトエンジンで、これを使ったゲームの第1弾が上記の「りりとら」ということになりますが、ゲームプログラム側の最適化も含め、C言語なのにここまで快適に動作するゲームは今まで無かったのではないかと思います。

SPIについて

SPIとはSerial Peripheral Interfaceの略で、極短距離にある複数のデバイスと接続するためのインターフェース仕様で、通信は全てシリアルで行われます。

各デバイスを制御するための1台をマスター、それ以外の通信を受けるデバイスをスレーブと言い、共通の信号線としてマスターから各デバイスへ送られるクロック端子(SCK)、マスターからスレーブへ送るコマンド端子(MOSI)、スレーブからマスターへ返すデータ端子(MISO)の3つを各デバイスが使用します。

さらに、それ以外に送りたいデバイスを選択するためのスレーブセレクト(SS)またはチップセレクト(CS)端子が各デバイスに用意されており、マスターからスレーブにコマンドを送る際には、該当するデバイスを選択してから実際にコマンドを送ります。

コマンドはデバイスごとに仕様として決まっており、正しいコマンドを送らないと結果が正しく返ってこなかったり、正しく通信出来ないということがあります。
※通常はデータシートにコマンド情報が載っています

SPI I/Oエクスパンダ(MCP23S17)

シフトレジスタを使用した音源でも説明しましたが、YMZ294音源を制御するにはデータ設定用の8bitバスの他に、チップセレクトやアドレス指定用の端子が必要なため、ポケコンの11pin側の端子では足りません。

このため今まではシフトレジスタを使って少ないピン数でこれらを制御していましたが、今回はSPIで似たようなことが可能なI/OエクスパンダというICを使用することにしました。

このI/Oエクスパンダというのは、コマンドにより任意のI/Oピンを入力用や出力用に設定したり、また出力したい値を送信することで、出力に設定した各ピンの状態をその値に合わせて変えることが出来ます。つまり、シフトレジスタと同じように少ないピンで音源を制御することが可能であり、さらにSPIはもともと複数のデバイスを制御出来る規格なので、汎用化が可能といった利点があります。

なお、今回使用した「MCP23S17」は8bit×2の計16bit分のピンを入出力出来るようになっているため、片方を音源用、もう片方を汎用のI/Oピンとして使えるようにし、あとでコントローラーなどを繋げられるように設計しました。

※日本語版のデータシートはこちら(最新版ではない可能性あり)

実は8bit版の「MCP23S08」というのもあり、最初は両方に対応しようと思っていましたが、価格が20円しか安くなくボードも2種類設計しないとならず、面倒だったので今回は16bit版のみにすることにしましたw
 
 ※こちらには汎用I/Oピンとして使えるピンが無いので単純に音源制御専用になります

SDカードについて

今回は何故SPI接続の音源にしたかというと、実はSDカードはもともとSPIモードによる通信がサポートされており、さらにSPIは1つのバス上に複数接続することが出来る規格なので、これを使えば音楽も流せてかつ大容量のデータも同時に扱えるのではと思ったわけです。

ちょうど秋月にポケコンに直結可能な5V対応のmicroSDのキットがあったので、これを使うことでかなりコンパクトに仕上げられたのではと思います。
microSDカードスロット レベルシフタ付きブレークアウト基板キット

※SDカードは3.3V仕様のため5Vで使うには追加パーツが必要となりますが、これにはそれらが実装済みとなっています

なお、今回のライブラリはFAT16かFAT32しか対応していないため、対応出来るSDカードはSDHCの32GBまでとしています。また、保存に関してはあらかじめ存在するファイルへの上書きのみが可能という制限があります。

SPIボードの回路図

今回作成したSPIボードの回路図です。(クリックで拡大)

※電源LEDは含まれていません

上部がPSG音源対応のメインボードに相当する部分で、下部がSDカードキットを使用したサブボード側となります。

実は音源となるYMZ294やアンプIC(HT80V739)、分周に対応したオシレータ(EXO3 12MHz)は以前のシフトレジスタ版とまったく同じ回路となっており、YMZ294のデータバス側に今回のI/OエクスパンダのMCP23S17を接続しただけです。

そして、このI/Oエクスパンダが使用するSPIバスにSDカードのSPIバスを乗せることで、同一の通信ラインを使って2つのデバイスを送受信することが出来るわけですが、よく見るとこのバスの上に2N7000というFETがあるのに気づくと思います。これは何なのかというと、SDカード側の出力が3.3Vでありこれをポケコンにそのまま入れると認識出来ない可能性があるため、これを5Vに変換するための簡易型のレベル変換回路となっています。どういう原理で動くのかは詳しくは分かりませんが、このような方法でレベル変換が出来る事が分かったので、それをそのまま利用しました。

それと補足として、この回路のSIと5Vの間に10KΩのプルアップ抵抗を入れていますが、これが無いとPC-G850無印にて何故かコマンドが正しく送られないことが判明したためで、実は最初に作った基板には抵抗が付いておらずあとから空き部分に追加するようにしたため、今回の基板は変な場所に抵抗が付いていますw

SPIボードの製作

今回作成するSPIボードは音源が搭載されたメインボードとSDカードを扱うためのサブボードの2枚ですが、ひとまず音源だけ使用出来れば良いのであれば、メインボードのみを作成するだけで済みます。また、メインボードにはサブボードが使用する電源やSPI用の端子を出しているので、SDカード以外に独自でSPIデバイスを作って接続することが可能です。

なお、今回設計したSPIボードはチップセレクト端子が音源用(CS1)とサブ用(CS2)の2つしか用意していないため、接続可能なデバイスは最大で2台までとなります。
※CS端子はNOT仕様のため0Vの時に有効であり、もし逆の5Vで有効となるようなデバイスを使用する場合はハード側で反転する必要があります(これとか)

以下はメインボードとサブボードを作成するのに使用した部品リストです。
番号 部品名 型番 使用数 価格 補足
1 片面ガラス・ユニバーサル基板 Cタイプ(72×47.5mm) めっき仕上げ 1 60 メインボード用
2 両面ユニバーサル基板(スルーホール) 45×45mm 1 70 サブボード用(表と裏の両方ではんだ付け出来るもの)
3 microSDカードスロット レベルシフタ付きブレークアウト基板キット 1 480 短いピンヘッダ付き
4 ヤマハ音源IC YMZ294 1 300 以前は4MHzのオシレータが付いていたが現在はICのみ
5 多出力クリスタルオシレータ EXO3 12MHz 1 200 12MHzだが分周して6MHzとして使用
6 16bit SPI I/Oエキスパンダー MCP23S17 1 120
7 低電圧1.2WオーディオアンプIC HT82V739 1 50 以前は2個セットで100円だったが現在は1個売り
8 ステレオミニジャック 3.5mmステレオミニジャックDIP化キット 1 150
9 半固定ボリューム 10kΩ [103] 1 50 同じ10KΩだがこちらはピンの配置が異なるためNG
10 MOSFET 2N7000 1 20 店舗で買ったときは10個入りパックになっていました(¥200)
※NチャネルのMOSFETならピン配置が同じならなんでもOK
※そもそも秋月に耐電圧が5Vを超えないものなんて売って無い?
11 電解コンデンサ 1μF50V85℃ (ルビコンPK) 2 10 アンプ用
12 電解コンデンサ 470μF16V105℃(ルビコンWXA) 1 10 アンプの直流遮断用(容量を下げると低音域が減ってきます)
13 電解コンデンサ(OS-CON) 100μF16V105℃ 1 40 電源安定用
※これ以上の容量だと突入電流によりポケコンの電圧が下がり、
 リセットの動作が発生してしまうことがあります
14 超高輝度5mmピンク色LED OSK54K5111A(10個入) 1 250 10個入りだが1個だけ使用/自分の好きな色のLEDでOK
15 カーボン抵抗(1KΩ) 1/6W 1kΩ (100本入) 1 100 100個入りですが1個しか使用しません。
※LED用のため好みで100~10Kを使用(小さいほどまぶしい)
16 カーボン抵抗(10KΩ) 1/6W 10kΩ (100本入) 3 100 100個入りですが3個しか使用しません。
17 ピンヘッダ(垂直) 1×40(40P) 1 35 切って使用
18 ピンヘッダ(L型) オスL型 1×40(40P) 1 50 切って使用
19 ピンソケット(垂直) 1×42(42P) 1 80 切って使用
20 ピンソケット(L型) 1×10(10P) 1 40 GPIO-A用(コントローラーなど)
21 ICソケット(8ピン) 8P(10個入) 2 100 10個入りですが2個しか使用しません。
※単品売りのこちらでもOK
22 ICソケット(18ピン) 18P(10個入) 1 100 10個入りですが1個しか使用しません。
※単品売りのこちらでもOK
23 ICソケット(28ピン) スリム300milタイプ (10個入) 1 200 10個入りですが1個しか使用しません。
※単品売りのこちらでもOK
600milタイプではありません
99 スピーカー ダイソー 耳もとキューブスピーカー(参考) 1 110 アンプ無しの安物でOK
これ以外にスズメッキ線やジャンパ用の銅線が必要です。

上記のパーツを集めるとだいたい以下のような感じになります。(クリックで拡大)

※一部抜けているパーツもあるので正確な数は上記の表を参考にして下さい
※右下の45x45mmの基板は両面にはんだ面があるものですので間違えないでください


メインボードの製作

ここでは音源となるメインボードを作成します。

※クリックで拡大

まずは表面の部品図です。

※クリックで拡大

次が裏面の配線図です。

※クリックで拡大

全てはんだ付けすると以下のような感じになります。

※クリックで拡大

はんだ付けは背の低いパーツから行うのがコツですが、ジャンパー線を裏面の配線と一緒に行うことで効率化出来たりするので、以下の流れはあくまでも参考として見てください。
  1. パーツの加工
    まずはピンヘッダとピンソケットを必要な数に折っておきます。
    ・L字型のピンヘッダを11pinに折る(ポケコン接続用)
    ・垂直型のピンヘッダとピンソケットをそれぞれ8pinと6pinに折る(ピンヘッダはサブボード側で使用)
     ※これらは上のパーツの写真を参考にしてください

    次にステレオミニジャックキットのピンをはんだ付けしておきます。
    ※ピンは付属のものを使います
  2. L型ピンヘッダ(ポケコン用11pin)とL型ピンソケット(GPIO用10pin/加工せずそのまま使用)をはんだ付けする
    最初に1pinだけはんだ付けし、そのあとそのピンに熱を加えてはんだを溶かしつつ水平になるように調整すれば、
    斜めにならずに済みます。また、このとき最初から全てのピンをはんだ付けせず両サイドだけはんだ付けしておき、あとでスズメッキ線で配線する際に一緒にはんだ付けを行うことで、配線時にはんだが邪魔をしてうまくピンに合わせられないといった問題を回避できます。
  3. ICソケットをはんだ付けする
    こちらも最初に全てのピンをはんだ付けせず、対角線上の2ピンだけをはんだ付けしておくことで、配線時の作業が楽になりキレイに仕上がります。
  4. ステレオジャック、可変抵抗(ボリューム)、コンデンサ、抵抗、LEDをはんだ付けする
    ユニバーサル基板の配線に慣れているなら、電源確認用の抵抗やLEDの足はそのまま裏側の配線に使用出来るので、上の裏面の配線図を見ながら一緒に配線してしまいましょう。難しいようなら普通にパーツのはんだ付けを行ったあと長い足は切ってしまい、あとで裏面の配線を行うようにしてください。
  5. ジャンパー線をはんだ付けする
    こちらも慣れているなら裏面の配線を表側に通して、同時にジャンパー線として使うことも出来ますが、難しいようなら普通のパーツと同じく表面と裏面で分けて考えましょう。
  6. 裏面の配線を行う
    最後にスズメッキ線で裏面の配線を行います。
  7. 動作テスト
    完成したらテスターでVCCとGNDがショートしていないか確認し、ひとまずICなどを付けない状態でポケコンに刺してみて、LEDが光るか確認してください。LEDは配線上で一番遠い場所に配置しているので、配線が間違っていたら光りません。
  8. 完成
    向きを間違えないよう注意しながらICをICソケットに刺し、ポケコンにビルドツールのテストプログラムを入れて実際に音が鳴るか確認してみましょう。またアンプは小さいながらパワーがあるので、ボリュームは最小状態から少し回すだけで十分大きな音が出ます。
    ※大きくしすぎると音が割れたり、ポケコン本体が電力不足に陥りリセット画面が表示されたりします
SDカードを使用しない場合はこれで終了です。

SDカードサブボードの製作

次にSDカードキットを搭載したサブボードを作成します。

注意点として、こちらのサブボードはメインボードと接続するためのピンヘッダが基板の裏側に存在するため、両面にはんだ面のあるユニバーサル基板を使用します。このためピンヘッダは表側ではんだ付けする形となり、そこから裏面に配線を持って行くという作り方になります。

さらに、このサブボードにはユニバーサル基板とSDカードキットの間にジャンパー線が必要なため、先にSDカードキットを取り付けてしまうとあとからジャンパー線を付けることが出来なくなるので、制作は以下の手順を参考に間違えないようにしてください。
  1. 表面のパーツのはんだ付け(クリックで拡大)

    最初に表面のFET、抵抗、ジャンパー線をはんだ付けします。
    なお、ジャンパー線はなるべく被膜のある銅線を使用してください。
    スズメッキ線を使用することも可能ですが、この場合ジャンパ間にある基板の穴に触れてしまうと裏側の配線とショートしてしまうことになるため、ビニールテープなどで絶縁する必要があります。
    また、この上にSDカードキットが乗るので、浮かせずぴったり基板に這わすようにしてください。
    ※参考
     
  2. 裏面のピンヘッダのはんだ付け(クリックで拡大)

    8pinと6pinにカットしたピンヘッダを裏側から差し込み、表側ではんだ付けを行います。
    この時ブレッドボードや作成したメインボードにピンヘッダを刺し、固定してから基板を乗せてはんだ付けすることで、ピンが斜めになってしまうのを防ぐことが出来ます。
  3. 表面にSDカードキットをはんだ付け(クリックで拡大)

    先にSDカードキットを組み立てておき、これを表側に差し込んではんだ付けします。
    はんだ付け後は裏側のピンが長いので切ってしまいます。
    ※参考
     
  4. 配線(クリックで拡大)
     
    SPIバス側(8pin)の配線は表面に出るため、裏面と照らし合わせながらはんだ付けしてください。
完成すると以下のようになります。

※クリックで拡大

このサブボードをメインボードと合体させることで、SDカード対応の音源ボードとして使用出来るようになります。

ポケコンビルドツール v3

ここではソフトウェアスプライトやBGM&SE対応の新型MMLライブラリ、またSPI音源や旧シフトレジスタ版音源(テンプレートでは595音源と表現)に対応した開発ツールをダウンロード出来ます。
ポケコンビルドツール v3.00 ダウンロード

このビルドツールにはデバッガー対応の改変済みエミュレータが含まれており、これとSDCCがあれば実機が無くてもゲームを開発することが出来ます。
・SDCCのインストール方法についてはこちらを参考にしてください
・デバッガーの使い方についてはこちらこちらを参考にしてください
・改変済みエミュレータのv1.03が同梱されています(変更点やソースコードは以下参照)

なお、同梱のテンプレートには今回のスプライトエンジン以外に、旧ビルドツール(v2.xx)で使用していた描画ライブラリも入っているため、以前作っていたゲームをSEに対応させることが可能です。また、最新のMMLプレイヤーライブラリはシフトレジスタ版の音源にも対応しているため、単に音を出すだけなら今回のSPI版音源を作り直す必要はありません。

テンプレート

ビルドツールには以下のテンプレートフォルダが用意されています。
この中から作成したいゲームの内容にもっとも近いものをコピーし、そのゲームの名前などに変更します。

  • 開発用テンプレート(旧描画エンジン&新MMLプレイヤー[595音源])
    ビルドツールv2.xxのPSG音源対応のテンプレートを新MMLプレイヤーライブラリに置き換えたもので、旧描画処理のままBGM&SEの同時再生に対応したテンプレートです。
    ※旧ライブラリの関数の詳細はこちらを参照してください
  • 開発用テンプレート(旧描画エンジン(改)&新MMLプレイヤー[SPI音源/SDカード])
    最新のg800ライブラリ上で旧描画ライブラリが使用出来るよう、描画処理だけをhとcに分離したもので、旧描画処理に対応しつつSDカードにも対応したテンプレートです。
    このサンプルではSDカードから曲データと動画データをロードするようになっていますが、動画データは旧画像フォーマットがただ羅列されたバイナリなので、これをロードしてそのまま旧描画ライブラリを使って描画しているだけです。
  • 開発用テンプレート(新スプライト&新MMLプレイヤー[595音源])
    最新のg800ライブラリにソフトウェアスプライトと新MMLプレイヤーを組み合わせたテンプレートです。
    旧描画ライブラリは使用出来ないため、任意のサイズの画像を任意の位置に自由に表示させることは出来ませんが、3色表示による広大なマップをスクロールさせるようなゲームが作成出来ます。
    サンプルとして横スクロールしながら空中にあるアイテムを取っていくゲームが実装されています。
    ※こちらはシフトレジスタ版の音源に対応しています
  • 開発用テンプレート(新スプライト&新MMLプレイヤー[SPI音源])
    上記の595版音源と同じサンプルですが、音源の処理がSPI版に置き換わっています。
    通常はこちらをベースにゲームを作成しあとで595版にソースをコピーすることで、両対応のゲームが作成出来ます。
    ※595版よりSPI版の方が重いため、先にSPI版で作ることで595版に移植した際に処理落ちしなくてすむ
    ※このテンプレートでSDカードに対応したい場合は、SDカード対応のテンプレートからSDライブラリをコピーする必要があります
各テンプレート直下にある「!!!run.bat」を実行すれば、ビルド済みのサンプルをエミュレータで実行することが出来ます。また、各テンプレートに含まれるエミュレータは音源もエミュレートする設定になっているため、PC上でそのまま音を再生することが出来ます。さらにSDカードに対応したテンプレートでは、音源とSDカードの両方をエミュレートしているので、SPIボード(SDカードサブボード含む)が無くても開発は可能です。(詳細は後述)

使用していないライブラリをプロジェクトフォルダに入れたままだと、実行ファイルが大きくなりポケコンのメモリをその分だけ使用してしまうので、使わないものはなるべく削除するようにしてください。
※音を扱わない場合はMMLPlayerライブラリを削除するなど

SDカードのエミュレートについて

SDカードに対応したテンプレートは音源と同時にSDカード自体もエミュレートしていますが、これはエミュレータの直下にあるSDカードのディスクイメージ(g800win32/SDCard.vhd)に直接アクセスすることで行っています。

なお、vhdファイルとはWindows標準の仮想ディスクのイメージであり、OS上から新規で作成したり既存のvhdファイルをマウントすることで、エクスプローラーなどから通常のドライブとして認識されます。ただし、注意点としてマウント中は書き込みなどの操作はリアルタイムにvhdに反映されず、OS側でキャッシュされていることがあるため、その状態でエミュレータから読み込み操作を行うと、ファイルが見つからずにエラーとなってしまうことがあります。このため、エミュレータを実行する前には必ずマウントを解除し、確実に保存された状態にしておく必要があります。

以下の方法でOSからvhdを扱うことが出来ます。
  1. デスクトップのPCアイコンを右クリックして「管理」を選択する(Win10の場合)
  2. コンピュータの管理」が開いたらその中の「ディスクの管理」を選ぶ

    ※右側にディスクの情報が表示されます
  3. 今度はこの「ディスクの管理」を右クリックし「VHDの接続」を選ぶ

    ※ディスクの管理以外が選ばれていると、このメニューは表示されないので注意してください
    ※ここで「VHDの作成」を選ぶと任意のディスクサイズのイメージを作ることが可能ですが、
     ライブラリがFAT12には対応していないので最低でも20MB以上、かつ最大はFAT32の32GBまでにしてください
     (一般的なHDDと同じく初期化やフォーマットが必要です)
  4. 同梱のvhdファイルを選択して開くボタンを押す
  5. マウントが成功するとエクスプローラーにドライブが現れるので、あとは自由にファイルを読み書きする

    ※同梱のvhdは容量が256MB、FAT32でフォーマット済みでsampleフォルダがあらかじめ入っています
     (このテンプレートの_SDCardDataフォルダにも同じデータが入っているので、実機でのテストの際はこれをmicroSDカードにコピーして使用してください)
  6. 「ディスクの管理」の下のディスクのリストからSDカードのディスクを探し、右クリックして「VHDの切断」をする

    ※これでエミュレータからファイルのアクセスが可能となります

ツールについて

ここでは各テンプレート内のtoolsフォルダに含まれる各ソフトについて説明しています。

このフォルダにはビルドや実機への転送に必要なソフトが含まれています。
なお、この中にはそのテンプレートに関連するソフトだけが含まれており、テンプレートによっては入っていないものがあります。

以下は各ツールの詳細となります。
ソフト名 内容 対象
PCGTool ポケコン転送ツール。
転送の前に必ず一度起動し、転送用の設定(COMポートやボーレート)などを設定する必要があります。
※最新版は本家サイトのSOFTWARE→ポケコンにありますが、一応このビルドツールにも入っています(ヘルプはありません)
all
AsciiConv2 旧描画ライブラリの文字表示関数(DrawString、PPrintf)で表示されるフォント画像のヘッダファイル(ascii.h)を作成します。
同梱の「ascii.bmp」を編集することで自前のフォントを作成することが出来ます。
v1.xxでは固定サイズ(5x6)で、強制的に全てのASCII文字が登録されてしまっていましたが、
v2.xxからは文字サイズをパラメータで指定出来たり、ゲーム中に使わない文字タイプを除外することで、
メモリの節約が出来るようになっています。
※詳しい使い方は同梱のAsciiConv2.htmlを確認してください
※裏ワザとして使わないフォントを♥♠♦などの記号にして、ゲーム中にそのコードを指定することで
 特殊な文字として表示させることが可能です
2.xx
ConvMidi MIDIファイルからg800ライブラリのSライブラリで使用する曲データのヘッダファイルを出力します。
これは指定した1トラック内にある全ノートを出力するため、和音も表現することが出来ます。
※SライブラリはPSG音源ではなく一般的なスピーカー(3pinと7pinに繋げたもの)を使うため、
 簡易的なBGMを再生するのに向いています
 (Sライブラリを使うには割り込み制御が必要です>ポケコンの割込みついてを参考)
※仕様上ドラムトラック(tr10)は変換出来ません
2.xx
IHXConv IHXファイルとバイナリファイルを相互に変換します。
変換の際にオフセットを指定することで全体をズラすことも出来ます。
ビルド時にバッチ内で使用されます。
※コマンドプロンプト上で実行するとヘルプが見れます
all
ImgConv BMPやTGAを変換して旧描画関数で直接使用可能なヘッダファイルを生成します。
BMPの場合はマスク無し(アルファチャンネルは無視)、
TGAの場合はマスクあり(32bitアルファチャンネルを使用)で変換します。
もし画像がカラーだった場合は一般的なモノクロ変換を行ってから灰色中心で2値化します。
ディザ合成は行われないため、必要ならばあらかじめ編集段階で白黒化(2値化)しておいてください。

透明画像(TGA)
TGAファイルでは透明部分を表現することが出来ます。
通常の合成とはORで各ビットを合成するため、背景に何らかの画像がすでに存在すると、
そのドットはそのまま残ってしまいます。

そこでここではマスクという元の画像と同じサイズの専用画像を用意します。
マスクとは消したくない個所を黒、消したい箇所を白で塗った画像で、
最初に背景とANDすることで、今回描画したい画像の部分のみを消しておきます。

そしてそのあとにこの位置に通常の画像をORすることで、
最終的に白で抜かれた透明部分のある画像を合成することが出来るわけです。
元画像


原理
AND OR
背景 マスク AND後 画像 OR後

画像データフォーマット
画像データは先頭にIMGDATA構造体があり、その直後に画像データが続きます。
マスクが存在する場合は、画像データ(OR用)のあとにマスクデータ(AND用)が続きます。
なお、マスクの存在する画像では通常の画像とマスク画像の2つ分のデータが存在することになるため、
メモリ使用量が単純に2倍になります。
このため基本的にはマスク無しの画像を使用し、必要な個所だけマスクありで作成することをお勧めします。
2.xx
MargeRam ポケコンから吸い出したRAM領域(0000h-7FFFh)のバイナリイメージに、
SDCCから出力されたコードを結合して最終的なRAMイメージを生成します。
ビルド時にバッチ内で使用されます。
all
MMLPlayer PSG音源ライブラリ用のMMLを作成し、これをヘッダファイルとして出力出来ます。
また、入力したMMLを即座に再生出来るためMMLのデバッグにも使用出来ます。
さらにMIDIからのインポートに対応しているため、既存のMIDIからMMLを手軽に作ることもできます。

詳しい使い方やMMLの書式は同梱のMMLPlayer.htmlを確認してください。
基本的にはTSSに準拠しており、ここから余計なコマンドを削除したり音色定義がポケコン専用になっているだけなので、
音色定義をTSSに合わせることでTSS側で再生も可能です。

※このソフトは音声出力にWASAPI排他モードを使用しているためVistaSP1以上が必要です
all
SpriteConv スプライトライブラリ用に画像から画像データを変換したヘッダファイルを生成します。
1チップを8x8とし、白、灰、黒の3色を使って描かれた画像を横に128個分並べたものを変換します。
背景用は透明色無し、キャラクターは透明色あり(アルファ値が0=透明、255=不透明)として変換します。
画像はBMP(24bit)とTGA(32bit)に対応しており、コマンドラインからファイル名を指定して実行することで、
同名のファイル名にhを付けたヘッダファイルを生成します。
※「SpriteConv <file> /BG」と最後に/BGを付けることで背景用(透過無し)として変換します
※スプライト対応のテンプレートの「data/resource」にスプライトのサンプルが入っています
3.xx
SpriteMap 背景用スプライト画像と背景画像からマッピングテーブルのヘッダファイルと、
スプライト番号を列挙したCSVファイルを生成します。
もし背景画像に背景スプライトと一致する画像が存在しなければエラーを出し、さらに存在しなかった部分を
可視化したエラー画像(***_err.bmp)を出力するので、あとでその部分を確認することが可能です。
※コマンドラインから「SpriteMap <背景画像> <背景スプライト画像>」を実行します
※スプライト対応のテンプレートの「data/resource」に背景画像のサンプルが入っています
3.xx

スプライトライブラリの仕様

今回作成したスプライトライブラリは、以下のような仕様となっています。
  • 1チップあたりの画像サイズは8x8pxで作成する(画像の最小単位)
  • 画像は白(#FFFFFF)、灰(#808080)、黒(#000000)の3色で作成する
  • スプライト画像は128チップを横に並べたものとする(1024x8px)
  • スプライト画像の各チップを左から0~127と番号を付け、画像を指定する場合はこのスプライトIDを使用する
  • スプライト画像には背景用とキャラクター用の2種類がある
  • 背景用スプライトには透過色は無いがキャラクタースプライトには透過色があり、背景と合成した時にその背景が抜けて見える
  • 画面上での背景スプライト位置とキャラクタースプライトの位置は連動しておらず、キャラクターの表示位置は常に画面上での位置で指定する
  • 背景用のスプライトIDを列挙した2次元配列(マッピングテーブル)と、それの横と縦のサイズを指定することで背景が一括表示される
  • 背景用のマッピングテーブルは、背景オフセットを考慮して画面表示エリア+1個分多めに用意する(最低19x7[152x56px]の配列が必要)
  • 背景は常に画面の左上を原点として表示され、その原点の開始位置を変更することで背景を配列単位でスクロールすることが出来る
  • さらに背景オフセット位置を0~7で指定することで、現在の開始配列を起点に1px単位でスクロールが可能
  • 開始配列とオフセットの両方を使用することで、広大な背景を1px単位でスクロール可能
  • 同時に表示可能な最大キャラクター数はデフォルトで24個(ソースを修正することで変更は可能)
  • 背景スクロールと組み合わせると半分の12個でも処理落ちするため、なるべく画面内のスプライト数が少なくするよう最適化すること

レイヤーについて

スプライトライブラリが管理するレイヤーは、以下のように透過色の無い背景の上に透過色のあるキャラクターが重なるといった、2重構造となっています。


背景には透過という概念が無いため背景を抜くということは出来ず、このため背景の下にさらに別な背景を多重スクロールするといったことは出来ません。

また、今回のスプライトライブラリは全てCPUによる計算であるため、同時に表示されるキャラクター数が増えれば処理落ちによりフレームレートが低下します。
※フレームレートが低下したとしても画面上での3色表示は維持されます

ライブラリ上は24個まで同時に表示することは出来ますが、その場合はフレームレートが本来の1/4以下まで低下してしまうため、基本的に同時に表示される数は多くても10個程度くらいになるようゲームの仕様を考えるようにしてください。例えば上記の参考画像ではキャラクター用に4つのスプライトバッファを使用していますが、このように大きな画像を使おうとするとすぐにスプライト数がオーバーするので、気を付けるようにしてください。

ちなみに、この参考画像はビルドツールに含まれるテンプレートの1つですが、右下をよく見るとアイテムは背景として表示されています。

スプライトライブラリには指定の背景座標からマッピングテーブル上のアドレスを取得するための関数が用意されており、これを使うことで即座にその位置のスプライトIDを取得することが出来るので、その位置がアイテムかどうかを判定し、もしアイテムだったなら取得処理と同時に何も書かれていない(判定のない)スプライトIDに差し替えることで、一瞬でアイテムを背景から消すことが出来ます。

スプライト画像について

スプライト画像は以下のように8x8のチップ画像を横に並べただけの画像です。
 
 ※使わないスプライトIDも含めて必ず横128個分(1024x8)を定義しなければなりません(白で良い)

各チップは左から順番に番号が付きますが、ここではこれをスプライトIDと言い、チップ画像を参照する場合は必ずこのスプライトIDで管理することになります。

なお、作成したスプライト画像はtools内のSpriteConv.exeで変換することで、unsgined charの配列としてヘッダファイルに出力されるので、これをインクルードしてスプライトライブラリにそのポインタをセットすることで使用することが出来ます。

キャラクター画像の変換イメージ(8x8px、透過ありの場合)
 
 ※背景画像の場合は透過色は作成されません

背景スプライト

背景スプライトの表示に必要な要素として、以下の4つが必要となります。
  • 透過色の無いスプライト画像
    例)
     
  • そのスプライト画像のIDを羅列した2次元配列(マッピングテーブル)のポインタ
  • マッピングメモリ上での左上の開始配列番号
    ※初期値は(0,0)
  • 画面上でのスクロールオフセット
    ※初期値は(0,0)
これらの情報をスプライトライブラリにセットすることで、背景を一括表示することが出来ます。

例えば上記の画像を背景スプライト画像として指定し、以下のようなマッピングテーブル(unsigned char型の2次元配列)を作成してスプライトライブラリに設定すると、ポケコンには赤枠内が画像として描画されます。
 

試しに左上の開始配列を右下に1個分変更(1,1)してみると、表示は以下のようになります。(黄色いセルが開始位置)
 

配列の開始位置を変更するだけだと8px単位でのスクロールしか出来ませんが、今回のスプライトエンジンでは開始配列の他にスクロールオフセットを0~7で指定することが出来るようになっており、例えば上記の開始配列の状態でさらにこのオフセットを右下に4pxずらした場合、以下のように表示されます。
 
 ※このとき右端と下端がさらに次の配列を参照することになるため、マッピングテーブルの最小値は18x6(144x48)ではなく19x7(152x56)となります

この例では8pxと4pxで実質12pxスクロールしたことになり、これにより広大な背景画像を1px単位でスクロール出来るようになるわけですが、実は全体のスクロール量から逆にこの2つの値を求めるのは簡単に出来ます。

まずは縦と横それぞれのスクロール量を1px単位で保持しておくためにshort型変数を2つ(iScrX、iScrYなど)用意したとします。
そしてこれらの変数に対して以下の計算を行うことで、簡単に配列番号とオフセットが算出出来ます。
配列番号   = (scroll) / 8;
オフセット = (scroll) % 8;

例として上記の画像の場合、XとYどちらも12px分スクロールしたと見なせますが、これを上記の計算式に当てはめると以下のようになります。
配列番号   = 12 / 8 = 1
オフセット = 12 % 8 = 4
※上記画像はXもYも12pxなので結果はどちらも同じ値となりますが、XとYを別々に計算すればそれぞれで配列番号とオフセットが求められます

なお、Z80は乗算やあまりを求めるための命令が存在しないためこの計算は少し重くなってしまうのですが、以下のようにビット演算に変えることで高速化することが出来ます。
配列番号   = (scroll) >> 3;
オフセット = (scroll) & 0x0007;
※1フレームに1回計算する程度なら特に気にしなくても良い

ちなみに1px単位のスクロールに関して内部的な処理について簡単に説明すると、Xオフセットは単にメモリ配列の開始位置を指定バイト数だけ進めるだけなのでさほど処理は食わないのと、Yオフセットに関してはLCD自体の機能を使用してハードウェアレベルで上方向にスクロールさせているため、CPU側での描画負荷はまったくありません。これは以前の記事でLCDの仕様について説明しましたが、LCDの仕様が分かればCPUに負荷をかけずいろいろなことが出来るようになります。

キャラクタースプライト

キャラクタースプライトの画像も背景スプライトと同様、横に128チップを並べた画像を使用します。ただし、背景スプライトと異なりキャラクターには透過色が必要なので、アルファチャンネルにて透過色を設定する必要があります。

※アルファチャンネルについてはこちらに記事があります

キャラクターの表示位置は常にLCDの左上を原点(0,0)としたスクリーン座標で指定します。

※背景スプライトの開始配列やオフセットは一切関係無いため、背景だけをスクロールさせてもキャラクターは常に同じ位置に表示されます

同時に表示可能なスプライトはデフォルトで24個ですが、スプライトライブラリの内部ではこれを管理するためのスプライトバッファが定義されています。そして、スプライトバッファ1個につきスプライト番号(-1の場合は非表示)とスクリーン上でのXY座標を持っており、初期化後やリセット時はスプライト番号は-1に設定されます。
※スプライト番号はあくまでも参照なので、複数のスプライトバッファに同一のスプライト番号を指定することで、同じ画像をたくさん表示させることが可能です

プログラム上ではこのスプライトバッファに対してスプライト番号とスクリーン座標を指定することで、その位置に指定したスプライト画像が表示されるようになります。ただし、スプライトの表示順は後方ほど手前に表示されるため、例えば常に画面に表示されるスコアなどは一番後方のスプライトバッファを指定し、自機や敵はその手前とすることでスコアが隠れてしまうといったことは起こらなくなります。

スプライトバッファは一度設定すると変更がない限りその状態を維持し続けます。これを利用して、例えば常に特定の位置に表示し続けるようなスプライトを指定したい場合は、初期化時にあらかじめその情報をセットするだけで済んだり、また数値画像のように位置は固定だが値は変化するような場合は、初期化時に位置だけを設定しておきメインループでキャラクター番号だけを変更するようにしたり、逆に自機キャラクターのように位置は変化するが画像自体は変化しない場合は、初期化時にキャラクター番号だけを設定しておいて、メインループ中は位置だけを変えるようにすることで、無駄な処理をなるべく抑えるといった作り方が出来ます。

スプライトライブラリの使い方

スプライトライブラリはsprite.hとsprite.asの2つを使用します。これらがプロジェクトフォルダに入っていれば「!!!build.bat」にて自動的にビルドされリンクされます。
※型定義用に共通ヘッダのplatform.hも必要です

また、実際の画面への描画は割り込み関数内で行います。これによりメインが処理落ちしても常に一定のタイミングで描画が行われるため、3色表示が乱れてしまうといった問題は発生しません。

内部的には画面表示用のフロントバッファと描画用のバックバッファの2つを持っており、バックバッファへの描画が完了したと同時にフロントバッファと差し替えることで次の画面が表示される仕組みですが、もしバックバッファへの描画が間に合わない場合はフロントバッファはそのまま維持されるため、結果的にフロントバッファの表示時間が長くなることで処理落ちしたように見えます。

スプライトライブラリは以下のような流れで実装します。
  1. game.cなどでsprite.hをインクルードする。
  2. 割り込み関数を作り、その中でSPRDraw()を呼び出すようにしておく。
  3. ゲーム開始時に1回だけSPRInit()を呼び出し、スプライトライブラリを初期化する。
    ※これ以降スプライトライブラリの各関数やマクロが使用可能となります
  4. SetHookInterrupt()を呼び出して割り込み処理を開始させる。
    ※これ以降割り込みでSPRDraw()が呼ばれるようになります
  5. SPRSetBgAddr()で背景用スプライト画像(1枚目、2枚目)のポインタをセットする。
  6. SPRSetBgTable()で背景用マッピングテーブルのポインタと、そのマッピングテーブルの横と高さをセットする。
  7. SPRSetBgTableX()、SPRSetBgTableY()で開始配列番号を、SPRSetOffset()で1px単位でのスクロール量をセットする。
  8. SPRSetCharaAddr()でキャラクター用スプライト画像(1枚目、2枚目、透過色)のポインタをセットする。
  9. SPRSetCharaIndex()で指定のスプライトバッファにキャラクタースプライトのIDをセットし、SPRSetCharaX()とSPRSetCharaY()でスクリーン座標をセットする。
    ※SPRSetCharaIndex()でスプライトIDに-1を指定することで、そのスプライトバッファを非表示にすることが出来ます
  10. SPRGetBgTableAddr()にて指定の位置の背景マッピングテーブルの配列のアドレスが取得できるので、その中の値を見れば現在のスプライトIDが分かるのと、その値を書き換えることでリアルタイムに背景を書き換えることが出来る
  11. EnterFrame()にて任意に5~10の処理を行い、1フレーム分の画面情報をセットしたら最後にSPRUpdate()を呼び出す。
    ※SPRUpdate()の内部では、バックバッファへの描画のあとにタイミングに合わせてフロントバッファとバックバッファの入れ替えを行います
  12. ゲーム終了時にUnsetHookInterrupt()を呼び出して割り込み処理を終了する。

関数一覧

関数 内容
void SPRInit(); スプライトライブラリを初期化します。
void SPRReset(); キャラクタースプライトを全て非表示(-1)にします。
スプライト画像のポインタや位置などはリセットされません。
void SPRUpdate(); 現在のスプライト設定に基づきバックバッファ(2枚分)を描画してから、しかるべきタイミングでフロントバッファと入れ替えます。
割り込みにて2枚目の描画が終わった直後でない場合、描画が完了するまでブロックされます。
void SPRDraw(); この関数を呼び出すごとにフロントバッファの1枚目と2枚目を交互に画面に表示します。
割り込み関数内で呼び出すことでバックバッファへの描画が遅れた場合でも、関係無く常に一定の速度で描画が行われます。
BYTE* SPRGetBgTableAddr(
 SHORT x,
 SHORT y
);
背景の左上を(0,0)原点として1ドット単位の画像として考えた場合に、指定した座標に対するマッピングテーブル配列のアドレスを返します。
裏技としてそのアドレスに+1することで、X座標的に8px加算した時の配列として参照することが出来ます。

マクロ一覧

※マクロはスプライトライブラリ内のグローバル変数を変更するためのラッパーであり、速度を求めるならマクロを使わず直接変数に値をセットしてください
マクロ 内容 実際のコード(型は参考用)
SPRSetBgAddr(
 addr1,
 addr2
)
背景スプライト画像の1枚目と2枚目のポインタを同時に指定します。 BYTE* _SPRBgAddr1 = (BYTE*)addr1;
BYTE* _SPRBgAddr2 = (BYTE*)addr2;
SPRSetBgTable(
 addr,
 w,
 h
)
背景マッピングテーブルの開始ポインタと幅と高さを指定します。
マッピングテーブルは「幅×高さ」分の連続したメモリとなっている必要があります。
指定できる値の範囲はwが19~255、hが7~255です。
addrにNULLを指定した場合は背景は表示されなくなります。
※それぞれの最小値は「画面表示サイズ+1」となります
BYTE* _SPRBgTableAddr = (BYTE*)addr;
BYTE _SPRBgTableWidth = w;
BYTE _SPRBgTableHeight = h;
SPRSetBgTableX(x) 背景マッピングテーブルの開始配列のX値を指定します。
指定出来る値は「0~(w-19)」までです。
BYTE _SPRBgTableX = x;
SPRSetBgTableY(y) 背景マッピングテーブルの開始配列のY値を指定します。
指定出来る値は「0~(h-7)」までです。
BYTE _SPRBgTableY = y;
SPRSetOffset(x, y) LCDのX軸スクロールオフセットとY軸スクロールを指定します。
指定できる値は「0~7」までです。
X軸またはY軸のみスクロールしたい場合は、直接変数に代入した方が高速です。
BYTE _SPRLcdOffsetX = x;
BYTE _SPRLcdOffsetY = y;
SPRSetCharaAddr(
 addr1,
 addr2
 mask
)
キャラクタースプライト画像の1枚目と2枚目、
さらに透過色のポインタを同時に指定します。
BYTE* _SPRCharaAddr1 = (BYTE*)addr1;
BYTE* _SPRCharaAddr2 = (BYTE*)addr2;
BYTE* _SPRCharaMaskAddr = (BYTE*)mask;
SPRSetCharaIndex(id, spr) 指定のスプライトバッファにスプライトIDを指定することで、
その画像を画面に表示します。
idにはスプライトバッファのIDを0~23で指定し、
sprにはキャラクタースプライトのIDを0~127、もしくは-1を指定します。
-1を指定した場合はそのスプライトバッファは非表示となります。
CHAR _SPRCharaData[id].cIndex = spr;
SPRSetCharaX(id,x) 指定のスプライトバッファのX座標をスクリーン座標で指定します。
そのスプライトバッファのスプライトIDが-1の場合は何も表示されません。
SHORT _SPRCharaData[id].sX = x;
SPRSetCharaY(id,y) 指定のスプライトバッファのY座標をスクリーン座標で指定します。
そのスプライトバッファのスプライトIDが-1の場合は何も表示されません。
SHORT _SPRCharaData[id].sY = y;

新MMLプレイヤーの使い方

新MMLプレイヤーライブラリはMMLPlayer.hとMMLPlayer.asの他に、対応した音源に合わせた制御ライブラリ(SPI版はspi_psg.hとspi_psg.as、シフトレジスタ版は74HC595.hと74HC595.as)の計4つを使用します。これらがプロジェクトフォルダに入っていれば「!!!build.bat」にて自動的にビルドされリンクされます。
※型定義用に共通ヘッダのplatform.hも必要です

このライブラリは割り込み関数内でMCRunTick()を呼び出すことで、常に一定のタイミングで曲やSEを再生することが出来ます。
※MML上にはテンポ設定はありますが、MMLプレイヤーは常に割り込みのタイミングでチックが進むのでテンポ設定は無視されます

BGMとSEのMMLには特に違いは無く、BGMとして再生するかSEとして再生するかを指定するための関数が異なるだけです。

例えばBGM再生時にSEを再生した場合、内部的にはそのSEが使用するチャンネルを優先的にSE用に使用し、その間はBGM側のそのチャンネルを出力しなくなります。なお、BGM側は音の出力のみを止めるだけでMMLの解析は継続されるため、SEの再生が終わるとそのチャンネルが復活し、通常のBGM再生に戻ります。

YMZ294音源は以下のような仕様になっています。
  • 最大で同時3チャンネルの発音
  • これらのチャンネルに対してトーン(矩形波)のみやトーンとノイズの合成、もしくはノイズのみを発音出来るが、ノイズパラメータは1つしか無いので全てのチャンネルで共有される
  • どれか1チャンネルに対してエンベロープ(音量波形)を指定可能
この仕様により、例えばBGMのチャンネル3でドラム音としてノイズを使い、SEのチャンネル1でもノイズを使用したとすると、2つのチャンネルで同一のノイズパラメータを使用することになり、最後に設定されたノイズがBGMとSEの両方に適用されてしまいます。

また、エンベロープは常に1チャンネルにしか設定出来ない(正確にはトーンの再生と一緒にエンベロープを再スタートする必要がある)ため、BGMのチャンネル1でエンベロープを使用し、SEのチャンネル2でもエンベロープを使用してしまうと、エンベロープの再スタート時に両方の音が最初から再生されてしまうことになり、意図しない音が鳴ってしまうことになります。

これを避けるためには、上記の音源の仕様に合わせてあらかじめチャンネルごとに鳴らし方を決めておき、BGMとSE双方のチャンネルを合わせるようにMMLを作成します。

なお、MMLには直接チャンネル番号を指定するコマンドは用意されていませんが、MMLの仕様でマクロ(#で始まるMML構文)以外でセミコロンが見つかると、そのチャンネルはそこで終了して次のチャンネルの定義に切り替わるので、例えばSEのMMLを強制的にチャンネル3で鳴らしたい場合は、これを利用して先頭にセミコロンを2つ入れることで、チャンネル1と2を使用しないという定義が可能です。
※MMLの解析時にチャンネルに音符が1つも無い場合は、そのチャンネルは即座に終了した扱いとなり未使用チャンネル扱いとなります

参考として、ビルドツールに含まれる新スプライト対応のテンプレートでは、BGMを再生しながらアイテムを取った時にSEを鳴らしていますが、この時の各チャンネルの使用方法は以下のように決めました。
チャンネル BGM SE
1 メイントーンで使用
2 ベーストーンで使用
3 ドラムで使用 SEで使用

人的にはSE音の方が耳に残りやすく、その間にドラム音が消えてしまってもさほど気にならないのと、BGMのメイントーンとベーストーンは鳴り続けた状態なので、結果的にBGMとSEが同時に鳴っているように感じます。

以下はサンプルをエミュレータで実行した時の動画で、アイテム取得時にドラム音が消えていますがほとんど分からないと思います。


新MMLプレイヤーライブラリは以下のような流れで実装します。
  1. game.cなどでMMLPlayer.hと、使用する音源に合わせてSPI版はspi_psg.h、またはシフトレジスタ版は74HC595.hの2つをインクルードする。
  2. グローバル変数にBGMやSE用のMMLSOUND構造体を定義する。
    ※通常は使用する曲データ分だけ用意しますが、この構造体はサイズが大きいので最小限の数だけ用意して使いまわすことも出来ます
  3. 割り込み関数を作り、その中でMCRunTick()を呼び出すようにしておく。
    ※スプライトライブラリと同時に使用する場合は、先にSPRDraw()を呼ぶようにします
     (スプライトは点滅により中間色を出しますが、MCRunTick()の負荷によって点滅のタイミングが変わると中間色にムラが出るため)
  4. ゲーム開始時に1回だけSPI版はSPIInit()とSPISetSpeed()を、シフトレジスタ版はYMZInit()を呼び出して11pin端子を8bit入出力モードに設定し、音源チップをリセットさせる。
  5. そのあと同じく1回だけMCInit()を呼び出し、MMLプレイヤーライブラリを初期化する。
  6. SetHookInterrupt()を呼び出して割り込み処理を開始させる。
    ※これ以降割り込みでMCRunTick()が呼ばれるようになります
  7. MML文字列を指定してMCPrepare()を呼び出し、MMLSOUND構造体を初期化する。
    ※あらかじめMMLを解析しておくことで、再生時に瞬時に音が出せるよう準備します
    ※MMLSOUND構造体を使いまわす場合は、必ず先に停止を行ってからMCPrepare()を呼び出すようにしてください
    ※指定したMML文字列は再生中に参照されるため削除しないでください(停止してもう使用しなくなったら削除してもOK)
  8. BGMとして再生するには鳴らしたいMMLSOUND構造体をMCPlayBGM()に渡し、再生中のBGMを明示的に停止するにはMCStopBGM()を呼ぶ。
    ※Playを呼ぶごとに最初から再生されます
  9. SEとして再生するには鳴らしたいMMLSOUND構造体をMCPlaySE()に渡し、再生中のSEを明示的に停止するにはMCStopSE()を呼ぶ。
    ※Playを呼ぶごとに最初から再生されます
  10. BGMが再生中かはMCIsPlayBGM()、SEが再生中かはMCIsPlaySE()がTRUEかをチェックする。
  11. EnterFrame()にて任意に7~10の処理を行う。
  12. ゲーム終了時にUnsetHookInterrupt()を呼び出して割り込み処理を終了させ、最後にSPI版はSPIExit()を、シフトレジスタ版はYMZExit()を呼び出して11pin端子を元のモードに戻す。

関数一覧

関数 内容
void MCInit( void ); MMLプレイヤーライブラリを初期化します。
void MCPrepare(
 LPMMLSOUND snd,
 CHAR *mml
);
MML文字列を指定してMMLSOUND構造体を準備します。
void MCPlayBGM( LPMMLSOUND snd ); 指定のMMLSOUNDをBGMとして再生します。
呼び出すごとに最初から再生されます。
void MCStopBGM( void ); BGMを停止します。
BOOL MCIsPlayBGM( void ); BGM再生中かを返します。
void MCPlaySE( LPMMLSOUND snd ); 指定のMMLSOUNDをSEとして再生します。
呼び出すごとに最初から再生されます。
void MCStopSE( void ); SEを停止します。
BOOL MCIsPlaySE( void ); SE再生中かを返します。
void MCRunTick( void ); MMLの解析を1チック進めます。
割り込み関数内で呼び出すことで、常に一定のタイミングでBGMやSEを再生することが出来ます。

SDカードライブラリの使い方

SDカードライブラリはsd.hとsd.c、またfat.hとfat.cの計4つを使用します。これらがプロジェクトフォルダに入っていれば「!!!build.bat」にて自動的にビルドされリンクされます。
※型定義用に共通ヘッダのplatform.hも必要、さらにSDカードライブラリは内部でSPIライブラリを使用するので、spi_psg.hとspi_psg.asも必要となります

SDカードライブラリは、SDカード自体へのアクセス(I/O)とディスクフォーマット(FAT)の解析を行う、2つのライブラリで構成されています。

なお、SDカードを途中で抜き差ししたりといったことには対応していないため、基本的にSDカードはゲーム起動前に刺しておき、ゲームが終了するまでは抜かないようにしてください。
※SDカードライブラリの初期化時にディスク情報を取得し、それ以降はそのディスク情報をグローバル変数として保持しているため、途中でディスクを変えたりした場合はもう一度ライブラリの初期化から始める必要があり、かなり面倒なシーケンスを組む必要があります(SDカードの挿入検知にも未対応)

SDカードには本来の高速アクセスモードとSPIによるアクセスモードの2種類があります。このうち今回は後者のSPIを使用しますが、困ったことにSDカードの種類や容量によりアクセス可能状態に持っていくまでの初期化シーケンスがぐちゃぐちゃで、ここでは説明しきれないくらい複雑な手順が必要なことが分かりました。
※SDカードのSPIモードについて知りたい場合は他のサイトで調べてください

なお、この中で1点だけ引っかかった部分があり、これが今回のSPIライブラリの仕様にも関わってくるのですが、SDカードをSPIモードに移行させる時の初期化シーケンスにて、与えるクロックの速度が速すぎるとSPIモードに変更出来ないことが判明しました。

SPIによる送受信では、ホスト(ここではポケコン)からデバイスに送るためのクロック信号も必要となりますが、このクロックのON/OFF処理をアセンブラで書いたところ、最初は何故かまったく無反応で参考にしたサイトの説明を疑ったりしましたが、試しにArduinoを使ってピンのON/OFFを1個ずつトレースするプログラムを組んでみたところ、まったく同じシーケンスで今度は何度やっても必ず成功したため、つまり考えられるのは通信速度ではないかと当たりをつけました。

ArduinoではPCからシリアル通信にて1ピンをON/OFFするためのコマンドで制御しており、そのため1クロックを生成するのにも時間がかかっていましたが、アセンブラの場合はメガHzくらいのクロックが生成出来てしまうため、SDカードがそのクロックに対応出来ずにSPIモードへの移行シーケンスがスタートできなかったのだと思われます。

結果的に1クロックごとにウェイトを入れるようにしてみたところ、ようやくポケコンでも初期化シーケンスに成功したわけですが、これにより今回のSPIライブラリには低速モードと高速モード(ウェイト無し)の2種類を用意することになりました。
※参考にしたサイトではSPIが使えるチップを使って通信していましたが、その通信速度は100KHzくらいなので問題無く動作していたのだと思われます

低速モードは実質SDカードの初期化に対応するために用意したもので、SPIを初期化するとまずは低速モードで動作し、あとからSPISetSpeed()を呼び出すことで高速モードに変更出来るようになっています。
※SPI音源のみ使用する場合はSPIの初期化後に即座に高速モードにしていますが、これは今回使用したI/Oエクスパンダがもともと高速で動作するためです

ちなみにSDカードをSPIモードに変更したあとは高速モードでも動作します。あくまでも低速でなければならないのはSPIモードへの移行までであり、それ以降はメガHzレベルの速度でも読み書きに問題はありません。
※ポケコンの場合はCPUクロックが8MHzで、しかもSPIのクロックは自前で生成するので実質1MHzも出ていないと思われます

手持ちのmicroSDカードで試した結果、最終的に256MB、1GB、2GB、32GBのmicroSDに関しては全て読み書きに成功しましたが、もしかしたら相性によりアクセス出来ないものがあるかもしれないので、その場合は別なもので試してみてください。

ちなみにこの中の256MBですが、SPIモード移行後にSPI音源へのアクセスが発生すると、SPIモードが切れてしまうという問題が発生しました。時代的に古めなので、SPIモードでも1対1でしか使えない仕様なのかもしれませんが、このためこの256MBのSDカードは音源と同時には使用出来ないということになりました。

なお、それ以外の1GB以上のmicroSDでは全て問題無かったのと、そもそも1GB未満のmicroSDはもうどこにも売っていないことを考えると、基本的に今回のライブラリは1GB以上のmicroSDを使うのが前提で良いと思います。

SDカードライブラリは以下のような流れで実装します。
  1. game.cなどでSPIアクセス用のspi_psg.hと、SDカードライブラリのsd.hをインクルードする。
    ※さらにsd.h内でfat.hがインクルードされており、これでファイル関数が使用出来るようになります
  2. ゲーム開始時に1回だけSPIInit()を呼び出す。
  3. 次にInitSDCard()を呼び出しSDカードのアクセスに成功すると、自動的に高速モードに移行する。
    ※エラー時はInitSDCard()がFALSEを返すので、その場合はエラーを表示してゲームを終了させます
  4. FFILE構造体をグローバル変数などに用意し、これを1ファイル分の管理用に使用する。
    ※複数のファイルを同時に管理するためFFILEを同時に複数用意することは可能だが、FFILE構造体の容量がかなり大きいためメモリ不足に注意
    ※FFILE構造体をローカル変数にすると、スタックエリアを超えてポケコンのシステム領域を壊してしまうので注意
  5. FOpen()、FRead()、FWrite()、FSeek()、FClose()でファイルにアクセスする。
    ※全ての関数はFFILE構造体のポインタを指定することで、複数のファイルを別々に読み書きすることが可能です
  6. FOpen()後はFFILE構造体のdwSizeにそのファイルのサイズ、dwPointerに現在のファイルポインタが入っているので、これを使ってEOF(End Of File)をチェック出来る。
  7. ゲーム終了時にSPIExit()を呼び出して11pin端子を元のモードに戻す。

関数一覧

関数 内容
BOOL InitSDCard( BYTE use_cs ); SDカードをSPIモードに変更し、成功したらSPIを高速モードに変更します。
成功したらTRUE、失敗ならFALSEを返すします。
use_csには使用するCS端子の定数を指定します。
 SDCARD_USECS1 : CS1に接続
 SDCARD_USECS2 : CS2に接続
 ※SDカードサブボードの場合はSDCARD_USECS2を使用
BOOL FOpen(
 const char *path,
 LPFFILE file
);
指定したパスのファイルを読み書きモードでオープンします。
パスはSDカードの"/"(ルート)から絶対パスで指定します。
成功するとfileにファイル情報を返します。
※例としてSDカードテンプレートでMMLテキストを開く際は
 "/sample/orusu.txt"と指定しています
LONG FRead(
 LPFFILE file,
 LPBYTE buf,
 LONG size
);
オープンしたファイルの現在のファイルポインタから、指定したサイズ分のデータをバッファにロードします。
戻り値は以下の通り。
-1 : マウントされていない
0 : EOF
1以上 : 実際に読み取ったサイズ
BOOL FWrite(
 LPFFILE file,
 LPBYTE buf,
 LONG size
);
オープンしたファイルの現在のファイルポインタに、指定したサイズ分のデータを書き込みます。
成功時はTRUE、失敗時はFALSEが返りますが、成功しても最後にFClose()にてファイルを閉じないと、正しく保存されません。
これは既存のファイルに対しての上書き専用関数であり、あらかじめ使用する最大サイズでファイルを用意しておく必要があります。
もし書き込み時に元のサイズをオーバーする場合は、それ以降のデータは破棄されTRUEが返ります。
BOOL FSeek(
 LPFFILE file,
 LONG offset,
 BYTE seek_mode
);
オープンしたファイルの現在のファイルポインタを移動します。
seek_modeはどの位置から移動を行うかを指示します。
seek_modeは以下の通り。
FSEEK_SET : ファイルの先頭から後方へoffset分だけ移動
FSEEK_CUR : 現在のファイル位置からoffset分だけ移動(マイナス可)
FSEEK_END : ファイルの終端から前方へoffset分だけ移動
BOOL FClose( LPFFILE file ); ファイルを閉じます。
読み込み時は実質何もしないので呼ばなくても問題ありませんが、書き込み時はこの関数で最後に変更したセクタをSDカードに書き込みます。
※閉じた後はFFILE構造体を他のファイル用に使用出来ます

g800エミュレータ(改)のソースコード

上記のビルドツールにはコンパイル済みの実行ファイルが入っているので、ポケコンの開発用途だけであればこちらは特に必要ありません。

g800(改) VC++ Build v1.03のソース ダウンロード
※SDL2.0.7同梱
※VC2010用のプロジェクトとなります

主な修正内容は以下の通りです。
  • グローバル変数をリアルタイムに確認するウィンドウの追加
  • アセンブラ上でのブレークポイントの指定に対応
  • 各デバッグ用ウィンドウの位置とサイズを保存
  • メモリタブを開いた際に再描画するように修正
  • グローバル変数のリストを名前順にした
  • 最後に選択したグローバル変数を記憶するようにした
g800エミュレータのライセンス表記
---------------------------------------------------------------------------------------
Copyright (c) 2005 ~ 2014 maruhiro
All rights reserved. 

Redistribution and use in source and binary forms, 
with or without modification, are permitted provided that 
the following conditions are met: 

 1. Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 

 2. Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution. 

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------------------

SPI版音源ボード&SDカードエミュレートプラグインのソースコード

同じく上記のビルドツールにはコンパイル済みのプラグインが入っているので、ポケコンの開発用途だけなら特に必要ありませんが、もし独自のSPIデバイスを使用する場合は、このソースコードを改変することでそのデバイスをエミュレートすることが可能です。

プラグインのソース ダウンロード
※VC2010用のプロジェクトとなります

PSG音源をWindows上で再現するため以下のライブラリを使用しています。
-------------------------------------------------
  FM Sound Generator with OPN/OPM interface
  Copyright (C) by cisc 1998, 2003.
  http://retropc.net/cisc/m88/
-------------------------------------------------
コメントはまだ登録されていません。
コメントする
名前
コメント
※タグは使用出来ません
この記事に関連するタグ
ポケコン(PC-G850)