投稿日 : 2019/04/30 2:04:11
「メモリベース128」をWindowsPCにバックアップしてみた
「メモリベース128」とはNECが発売したPCエンジン用の外部バックアップ機器のことです。
※同じ仕様でコーエーが販売した「セーブくん」というのもあります

メモリベース128は以下のような外観をしています。

これをPCエンジンのコントローラー端子に接続し、代わりにコントローラーはこのメモリベース128に繋げる形で使います。

メモリベース128に内蔵されているメモリは揮発性のため、電源の供給が無くなるとデータが消失してしまいます。このため普段は電池によりバックアップされていますが、アルカリ電池4本を使用したとしてもだいたい1年くらいしか持ちません。電池が無くなりかけるとバッテリーのLEDランプが光るので、それを目印に交換をすることになりますが、これを見逃してしまうとデータが消失してしまいます。

なお、PCエンジンDUOなどの本体にはあらかじめ2KBのバックアップ用メモリが搭載されていますが、実はこれも揮発性メモリであり、PCエンジンの電源を切ってから2週間ほど放置するとデータが消失してしまいます。また、本体のバックアップメモリだけではたくさんのゲームを保存することが出来ないため、これに対応するため内蔵のバックアップメモリをバックアップする、Huカードの形をした「天の声バンク」というものが登場しました。これにはバックアップ用のプログラムと8KBのメモリが搭載されており、本体のバックアップメモリを4つ分保存することが出来ます。天の声バンクはカードの手前側が膨らんでおり、実はそこにはリチウム電池が入っています。つまりこれも結局はいつかは消えてしまう運命であり、当時はFlashメモリというものが開発されていなかった時代のため、データを半永久的に残すということは出来ませんでした。

ちなみに初代PCエンジンにはバックアップメモリは搭載されておらず、そのため本体の後ろに合体させて使う「天の声2」というものがありますが、これも結局は電池で動作します。
※余談ですが、実はFlashメモリは元東芝の社員が発明したもので、青色LEDと同じく日本発祥です

メモリベース128は内蔵メモリの64倍となる128KBの容量を持っています。しかし、メモリベース128があればゲームデータをたくさん保存出来るようになったかと思いきや、困ったことにメモリベース128に保存出来るゲームというのは、メモリベース128に対応と書かれているゲームのみでした。つまり、Huカードのゲームは今まで通り本体のメモリにしか保存出来ないため、CD-ROM^2システムの無い素のPCエンジンでは何の役にも立ちませんでした。

これではせっかく高いお金を出して買ったのに、まったく意味のないものになってしまうと思われましたが、しばらくしてからとあるゲームに、天の声バンクのように本体のバックアップメモリをメモリベース128に保存したり、逆に本体側に書き戻したり出来るツールが実装されました。

なお、このツールと同じ機能を持ったソフトはいくつかあるようですが、ソフトによってメモリベース128の扱い方が異なり、あるソフトで保存したデータはあるソフトでは正しく読み取れないといった問題があります。ツール自体はあくまでもそのメーカーが独自で作ったもので、メモリベース128で本体側のバックアップメモリを保存するための仕様があったわけではありません。

その中で一番よく出来たツールが「エメラルドドラゴン(通称エメドラ)」に入っているものであり、メモリベース128のバックアップツールといえばエメドラが有名だと思います。
※エメドラでバックアップツールを使うには、起動画面で上を押しながらRUNボタンでスタートします(画面が切り替わるまでは上は押したまま)

このツールでは本体のバックアップメモリ全体(2KB)を1個のセーブデータとして扱っているようで、空き領域があれば詰めて保存出来るというわけではありません。必ず2KB単位で本体側のメモリをごっそり入れ替える形となるため、なるべく本体のメモリにたくさんセーブしてからメモリベース128にコピーしないと、空き部分が多くなってしまい勿体無いことになります。

なお、メモリベース128は本体の64倍の容量があるため、単純に計算すると本体のバックアップを64個分保存することが出来るはずですが、実は先頭の1KBがデータの管理領域(どの位置に何のデータが入っているのかが記録される)になっているようなので、実際には最大63個分が保存出来るのではないかと思っています。(試してないので分からない)

メモリベース128とエメドラがあれば、Huカードのゲームを遊ぶ分にはほとんど困らない状況にはなりましたが、本体側のバックアップをたくさん入れてしまうと今度は容量不足により、メモリベース128対応のゲームが保存できなくなります。Huカードのゲームとメモリベース128対応のゲームをたくさん保存したい場合、結局はメモリベース128を複数台持つ必要があり、そうなるとお金もかかるし、バッテリーを入れ替える手間も増えるし、そもそも機器自体が大きくてかさばるため、部屋の中にあるととても邪魔になります。

そしてゲームをやらなくなっていつの間にか1年経過し、ふと気づくとバッテリーが無くなっていてデータが消失しているというのはよくあることで、そうなると再びゲームをやりなおす気力も無くなるため、結局はもう遊ばなくなってしまったという人は結構いるのではないかと思います。

ということで前置きがかなり長くなりましたが、今回はメモリベース128の解析を行った結果、メモリベース128上のデータをPCにバックアップしたり、逆に書き戻したり(リストア)することが出来るようになりました。

つまり、もうメモリベース128のバッテリーを気にすることなく、ゲームをしたあとにPC上にデータをバックアップすることで、半永久的にデータを残すことが出来るわけです。そして例えば数年後、久しぶりにもう一度ゲームの続きをやろうと思った時に、あらためてメモリベース128に電池を入れ、PCからデータをリストアすることで続きから始めることが出来ます。

ということでここではその方法について詳しく説明しますが、もし現在でもメモリベース128を所持していて、忘れずにバッテリーを1年ごとに入れ替えている人がいるとしたら、これでもうこの煩わしさから解放されると思います。(いるの?w)

ちなみに今回の話とは全然関係ないけど、むかーし会社に入社したての頃に一緒に仕事をした人が、実はPCエンジン初期のBIOSの開発に関わってたそうなw

PCエンジンのコントローラー端子

PCエンジンのコントローラー端子は「ミニDIN8pinメス」と呼ばれる端子です。ただし、このミニDIN8pinには複数規格があるようで、その中でPCエンジンで使われる端子は以下の形状をしたものとなります。


なおWikipediaによるとPCエンジンのものは非標準のプラグ形状であり、標準のものと比べて真ん中の端子がさらに横に寄っているようで、標準タイプのコネクタでは微妙に入らないということになります。このため、入りにくいからと言って無理やり入れるようなことはしないでください。

ピン端子は以下のようにアサインされています。
ピン番号 用途 PCエンジン本体側から見た方向
1 +5V OUTPUT(電源供給)
2 D0 INPUT(コントローラー→PCエンジン)
3 D1 INPUT(コントローラー→PCエンジン)
4 D2 INPUT(コントローラー→PCエンジン)
5 D3 INPUT(コントローラー→PCエンジン)
6 SEL OUTPUT(PCエンジン→コントローラー)
7 CLR OUTPUT(PCエンジン→コントローラー)
8 GND -
9(外周) GND -

これを見るとボタンの数の割に入力数が少なかったり、電源以外の2つの出力ピンはいったい何に使うんだろうと思いますが、これは次の項目で説明します。

コントローラーのアクセス方法


2ボタン方式のPCエンジンのコントローラーには、「上」「右」「下」「左」「RUN」「SELECT」「I」「II」の合計8個のボタンがあります。しかし、上記のピンアサイン表を見ると入力ピンは4つしかありません。

これでどのように8個のボタンを入力しているかというと、ここで初めて出力ピンが役に立つわけです。

コントローラーの中には74HC157という、2入力1出力×4のマルチプレクサICが入っています。

マルチプレクサというのは簡単に言えば分配器であり、セレクト端子により入力側を電気的に切り替えられられます。そしてこのセレクト端子には上記のSEL出力が繋がっており、PCエンジン側からSEL出力を1と0に切り替えることで、各ボタンの状態がD0~D3を通してPCエンジン側に入力されるようになります。これにより8個のボタンを2回に分けて読み込むことが出来ますが、このため1フレーム中にすべてのボタンを認識するためには、1フレーム内に必ず2回のアクセスが必要となります。

ちなみにボタンの状態は押されていないときが1で押されると0になり、通常とは逆のイメージになります。
※内部的にはD0~D3はプルアップされており、ボタンが押されるとGNDに接続される感じだと思います


ところでマルチタップの場合はどうなっているのかと言うと、ここでもう1つのCLR出力が使われます。


マルチタップは最大5個のコントローラーを接続することが出来ますが、マルチタップ側ではSEL出力が0から1になった瞬間に入力対象のコントローラーを切り替えます。ただし、5個目のあとにSEL出力を0から1にしても自動的に1個目に戻ることはありません。

そこで1個目に戻るためにCLR出力が使われますが、仕様ではSELが1の時にCLRを0→1→0とすることで、1個目のパッドの入力に戻るようです。ちなみに逆に言えばCLRを出力しない限り6個以上のコントローラーを読み込むことも可能なので、理論上は何台でも繋げられるということになります。

なお、CLR直後というのはSELは1から始まることになるため、つまり1個分のコントローラーを読み取る場合は、1→0の順番で計8ボタンを読み取ることになり、0→1で次のコントローラーに切り替え、そして同じく1→0で次のコントローラーのボタンを読み取るといったことを繰り返します。

以下はSELとCLRの出力後に読み取れる情報の詳細です。
項番 SEL CLR 詳細
1 1 0 準備(もしあればコントローラー6の「↑」「→」「↓」「←」が読み込める?)
2 1 1 コントローラーを1番目にリセット
3 1 0 コントローラー1の「↑」「→」「↓」「←」を読み取る
4 0 0 コントローラー1の「RUN」「SELECT」「I」「II」を読み取る
5 1 0 コントローラー2の「↑」「→」「↓」「←」を読み取る
6 0 0 コントローラー2の「RUN」「SELECT」「I」「II」を読み取る
7 1 0 コントローラー3の「↑」「→」「↓」「←」を読み取る
8 0 0 コントローラー3の「RUN」「SELECT」「I」「II」を読み取る
9 1 0 コントローラー4の「↑」「→」「↓」「←」を読み取る
10 0 0 コントローラー4の「RUN」「SELECT」「I」「II」を読み取る
11 1 0 コントローラー5の「↑」「→」「↓」「←」を読み取る
12 0 0 コントローラー5の「RUN」「SELECT」「I」「II」を読み取る
※このあと1に戻るのを繰り返す

上記の表はエメドラの解析結果ですが、実はソフトがマルチタップに対応していなくても、常にコントローラー5個分を読み取っているようです。

例えばマルチタップが付いている場合、コントローラー1に繋ぐことで普通に入力は取れるのと、逆にマルチタップが付いておらず直接PCエンジンにコントローラーを刺していた場合は、コントローラー1~5の全てのタイミングでこのコントローラーの情報が入ってくることになりますが、ソフト側は常にコントローラー1だけを使うようにしておけば、結果的にどちらでもコントローラー1の入力として扱うことが出来ます。

このあたりの動作はよく考えられたものだと思いますが、逆に言えばもともとSELとCLRの2出力しか用意していなかったために、苦肉の策でこうせざるを得なかったとも言えます。

ちなみにコントローラーへの出力や入力処理は、一般的にI/Oへ直接アクセスするためCPU的には1命令あたり数クロックで終わります。
PCエンジンは最速7.16MHzのクロックで動作するため、上記の入出力を行ったとしても実時間はさほど要しません。
※74HC157やマルチタップ内のICの応答速度の関係で、入力が切り替わるまで若干待つ必要があるようですが、それでもこれがゲームの速度に影響することはほぼありません

もし自分でコントローラーを自作したい場合は、以下に回路図が公開されているので参考にしてください。
 ・2ボタン連射付きコントローラー
 ・6ボタンのアベニューパッド

この回路には連射機能が含まれていますが、連射についてはCLR出力をON/OFFのクロックと見たて、これをバイナリカウンタ(クロックを入れると2進数として出力bitが切り替わるもの)を通して分周することで連射クロックを生成し、これをボタンの入力に加えることでボタンを押している間だけその速度でON/OFFを繰り返すように見せかける回路になっているようです。

ちなみに桃太郎伝説で、仙人から技を教えてもらう時に岩を100回くらい切るといったイベントがあり、その際に連射機能を使うなと念押しをされるのを、構わず連射機能を使うと何故か仙人にバレてやり直しになるのですが、これはおそらくCLRのタイミングはソフト側で制御しているため、必ずそのCLRのタイミングでONとOFFが切り替わっていないかをチェックしていたのだと思われますw

メモリベース128のハードウェアについて

メモリベース128の中身ですが、主に制御用のカスタムIC、シリアルメモリ、そして大容量のコンデンサ(キャパシタ)が入っています。


制御用のICには65015と何やら型番らしきものが書かれていますが、どうやらこれはゲートアレイと呼ばれるNANDやNORといった汎用の論理回路を並べたものに、あとから配線を行うことで決められた動作をするようにカスタマイズされたICのようです。PCエンジンからの出力や入力は一度このICに通すことで、通常は下に繋げたコントローラーの状態をそのまま返しますが、ある一定の出力が来るとこのICが論理的に動作して、必要ならメモリにアクセスする回路に繋げることで、メモリの読み書きモードに切り替えているのではないかと思われます。

メモリは一般的なパラレル接続のものではなく、シリアル接続のメモリを使用しています。型番はOKIのMSM6389Cというもので、記録容量は128KB(1Mbit)で、まさにメモリベース128という名称そのままの容量となっています。
※このメモリの仕様書はこちら

緑色のコンデンサは電池を入れ替える際の一時的なバックアップ電池として使用されているのだと思われます。かなり大きな容量(0.47F)なので、10分程度であれば電池が無くても保持し続けられるのだと思います。
※メモリの仕様書を読むと4Vを下回ると保存データが消えてしまうようなので、実質コンデンサは1Vの電圧降下分までしか持たないことになります

メモリベース128の制御方法について(プロトコル)

上で説明した通り、PCエンジンのコントローラー端子は入力が4つ、出力が2つとなっていますが、メモリベース128への読み書きも当然この制約を受けます。

最初はメモリベース128が接続された時だけ4つの入力が出力にも使われるのかと思いましたが、当時は現在のようにピンの入出力をリアルタイムに切り替えられるような作りにはなっていないと思ったので、いったいどうやってこの端子だけでデータの読み書きをしているのだろうと不思議だったのですが、結論から言うと実はメモリベース128のデータは、常に1bit単位で入出力されていることが分かりました。

このためデータの読み書きにはかなり時間がかかるのと、仕様的にこの間はコントローラーの入力も出来なくなるため、大容量のデータを読み書きしているときはゲームが完全に停止してしまい、キャンセルも出来なくなります。PCエンジンDUOなどでは外部端子がことごとく省略されてしまったがために、他に外部機器を接続出来るとしたらコントローラー端子しかなく、PCエンジンの設計の限界がここで表れた感じです。


それではメモリベース128のアクセス方法ですが、これには以下のような手順で処理をしなければならないことが分かりました。
手順 内容
1 メモリベース128が接続されているかを確認する
2 読み込みモードか書き込みモードかを指定する
3 アドレスを指定する(セグメント単位)
4 読み書きするデータ数を指定する(bit数)
5 実際に読み込み、または書き込みを行う

メモリベース128へのアクセスはこの手順に沿ってSETとCLR出力を行い、応答が必要な場合はD0~D3の4bit値を、データの読み取りが必要な場合はD0の1bitを使用します。つまり、SETとCLR出力を駆使してメモリベース128を制御するわけです。

1.メモリベース128が接続されているかを確認する

初期状態として、まずはメモリベース128がコントローラーを読み取る状態になっている必要があります。

これは、メモリベース128へ読み書きを行っている最中に、コネクタを引っこ抜いたりしていなければ基本的にはこの状態になっていますが、もし読み書き中に引っこ抜いた場合、PCエンジンはメモリベース128の状態を把握することが出来ないため、メモリベース128を刺した瞬間に通常のコントローラーの読み取り制御が行われることで、最悪データが消されてしまうことがあります。
※詳細は次の項目(リセット)を参照してください

メモリベース128が存在しているかどうかを確認するには以下のようにSETとCLRを出力し、特定のタイミング時に応答データをチェックする必要があります。
項番 SEL出力 CLR出力 応答確認
(D3~D0)
内容
1 0 0 初期状態
2 0 1 SELを0にし、CLRを1→0を6回繰り返す
3 0
4 1
5 0
6 1
7 0
8 1
9 0
10 1
11 0
12 1
13 0
14 1 0 SEL=1でCLRを0→1→0、
SEL=0でCLRを0→1→0を2回繰り返す
15 1
16 0
17 0 0
18 1
19 0
20 1 0
21 1
22 0
23 0 0
24 1
25 0
26 1 0 SEL=1でCLRを0→1としたときにデータを読み込み、
D2のみ1でそれ以外が0になっているのを確認する
27 1 0100b (0x4)
28 0
29 0 0 SEL=0でCLRを0→1としたときにデータを読み込み、
全てのビットが0になっているのを確認する
30 1 0000b (0x0)
31 0
32 1 0 SEL=1でCLRを0→1としたときにデータを読み込み、
D2のみ1でそれ以外が0になっているのを確認する
33 1 0100b (0x4)
34 0

項番の27、30、33がそれぞれ特定の応答コードになっていない場合は、メモリベース128は接続されていないと判断して通常のコントローラーの入力状態に戻します。なお、ここでは必ずCLRが1の時にチェックしなければならず、0の状態でチェックすると正しい値にはなりません。

2.読み込みモードか書き込みモードかを指定する

1の処理が完了していた場合は、続いて読み込みか書き込みか、どちらを行うかを指定します。SELが1なら読み込み、0なら書き込みで指定し、その状態でCLRを0→1→0にすることでそのモードが確定します。
項番 SEL出力 CLR出力 内容
1 0=書き込み
1=読み込み
0 この操作で読み書きモードが登録される
2 1
3 0

3.アドレスを指定する(セグメント単位)

続いてアドレス(メモリのどこから読み書きするか)を指定します。

ただし、メモリベース128では読み書き開始可能なアドレスは、常に128バイト単位となっています。メモリベース128はこの128バイトを1単位として指定することになりますが、ここではこれをセグメントと定義しています。
※Intelの昔のCPUが似たようなアクセス方法で「セグメント:オフセット」という感じで、少ないビット数を使って大きな空間にアクセス出来るようにしていた

メモリベース128のアドレス(セグメント)は10bitで指定します。10bitで扱えるのは0~1023の範囲ですが、実際のアドレスはこれに128をかけることで求めることが出来ます。

例えばセグメント指定が1023だった場合は128をかけると130,944となり、アドレス130,944番地が実際の読み書きの開始位置となります。ちなみにこの番地から1セグメント分(128バイト)を読み取ったとすると、ちょうど131,072バイト(128KB)となるため、つまり10bitあればメモリベース128のすべてのメモリ領域にアクセス出来る計算になります。
※アクセスは特にセグメント単位(128バイト)である必要はなく、これはあくまでも読み書き時の先頭アドレスという意味であり、開始アドレスから128バイトを超えて一気に読み書きすることが可能です

メモリベース128へはセグメントの下位bitから順に出力していきます。
※ここでは例としてアドレス79,360番地の場合について解説しています。
 なお、アドレスからセグメントを求めるには単に128で割ればよいので、
 ここでのセグメント値は「620(1001101100b)」ということになります。
項番 SEL出力 CLR出力 送信されるビット部分
1 セグメントの0bit目=0 0 1001101100b
2 1
3 0
4 セグメントの1bit目=0 0 1001101100b
5 1
6 0
7 セグメントの2bit目=1 0 1001101100b
8 1
9 0
10 セグメントの3bit目=1 0 1001101100b
11 1
12 0
13 セグメントの4bit目=0 0 1001101100b
14 1
15 0
16 セグメントの5bit目=1 0 1001101100b
17 1
18 0
19 セグメントの6bit目=1 0 1001101100b
20 1
21 0
22 セグメントの7bit目=0 0 1001101100b
23 1
24 0
25 セグメントの8bit目=0 0 1001101100b
26 1
27 0
28 セグメントの9bit目=1 0 1001101100b
29 1
30 0

4.読み書きするデータ数を指定する(bit数)

次に読み書きするデータ数を指定しますが、データ数はここではバイト単位ではなくbit単位で指定します。つまり、1バイトを指定するには8を指定することになるため、ここでは処理したいバイト数に8をかけた値を指定することになります。
※当然1bitだけ取得することも出来ます

データ数は20bitで指定します。20bitでは0~1,048,575の範囲を指定することが出来ますが、これはちょうどメモリベース128の最大容量(128KBは1,048,576bit)と同じです。
※厳密には全データを指定するには+1しなければならないので、21bit必要になるのでは?と思ったり

実は実際にメモリ全体を指定して読み込み処理を行ってみると、何故か4096バイト目、たまに8192バイト目から正しく読み込めなくなるといった現象が発生します。通信速度が速すぎるのかと思い、入出力の速度を極端に落としたりしても変わらなかったため、メモリベース128に何らかの仕様があるのではないかと思っています。このため全データを一括で読み取ることは実質不可能なため、サイズ指定に20bit全てを使うことはほぼ無いと考えて大丈夫でしょう。

データ数はセグメント指定とほぼ同じで、10bitが20bit分になっただけで同じく下位bitから順に出力していきます。
※ここでは参考としてデータサイズ「1234バイト(8倍して9872bit/00000010011010010000b)」としています
項番 SEL出力 CLR出力 送信されるビット部分
1 データサイズの0bit目=0 0 00000010011010010000b
2 1
3 0
4 データサイズの1bit目=0 0 00000010011010010000b
5 1
6 0
7 データサイズの2bit目=0 0 00000010011010010000b
8 1
9 0
10 データサイズの3bit目=0 0 00000010011010010000b
11 1
12 0
13 データサイズの4bit目=1 0 00000010011010010000b
14 1
15 0
16 データサイズの5bit目=0 0 00000010011010010000b
17 1
18 0
19 データサイズの6bit目=0 0 00000010011010010000b
20 1
21 0
22 データサイズの7bit目=1 0 00000010011010010000b
23 1
24 0
25 データサイズの8bit目=0 0 00000010011010010000b
26 1
27 0
28 データサイズの9bit目=1 0 00000010011010010000b
29 1
30 0
31 データサイズの10bit目=1 0 00000010011010010000b
32 1
33 0
34 データサイズの11bit目=0 0 00000010011010010000b
35 1
36 0
37 データサイズの12bit目=0 0 00000010011010010000b
38 1
39 0
40 データサイズの13bit目=1 0 00000010011010010000b
41 1
42 0
43 データサイズの14bit目=0 0 00000010011010010000b
44 1
45 0
46 データサイズの15bit目=0 0 00000010011010010000b
47 1
48 0
49 データサイズの16bit目=0 0 00000010011010010000b
50 1
51 0
52 データサイズの17bit目=0 0 00000010011010010000b
53 1
54 0
55 データサイズの18bit目=0 0 00000010011010010000b
56 1
57 0
58 データサイズの19bit目=0 0 00000010011010010000b
59 1
60 0

5.実際に読み込み、または書き込みを行う

ここでは2で指定した読み書きのモードにより処理方法が変わります。

読み込みモードの場合

読み込みは3で指定した番地から始まりますが、データは常に1bitずつ読み取られるので、例えば1バイト分を読み出すには8回入力処理を行う必要があります。なお、この読み取る回数は4で指定した値となるため、単純にこの数だけループすれば欲しいデータの範囲を取得することが出来ます。

なお、データの読み取りは下位bitから始まり、入力処理ごとに1つ上のbitに移動します。そして8bit分の入力が終わると次のアドレスに移動して、再び下位bitから始まるようになっています。

データの取得は簡単で、まずSEL出力を0にしたままにしCLRを0→1にすることで、現在の番地にあるメモリのビット値がD0に入ってきます。次にCLRを1→0にして再び0→1するとbitまたは番地が1つ進み、そのビット値を取得することが出来ます。
指定した回数の最後のビット値の取得が完了すると、メモリベース128は通常のコントローラーを読み取る状態に戻ります。

例としてメモリアドレスの0番地から16bit分(2byte)のデータを取得する場合の、入出力のタイミングについて説明しています。
項番 SEL出力 CLR出力 D0 補足
1 0 0
2 1 入力 メモリ[0]のbit0のデータ
3 0
4 1 入力 メモリ[0]のbit1のデータ
5 0
6 1 入力 メモリ[0]のbit2のデータ
7 0
8 1 入力 メモリ[0]のbit3のデータ
9 0
10 1 入力 メモリ[0]のbit4のデータ
11 0
12 1 入力 メモリ[0]のbit5のデータ
13 0
14 1 入力 メモリ[0]のbit6のデータ
15 0
16 1 入力 メモリ[0]のbit7のデータ
17 0
18 1 入力 メモリ[1]のbit0のデータ
19 0
20 1 入力 メモリ[1]のbit1のデータ
21 0
22 1 入力 メモリ[1]のbit2のデータ
23 0
24 1 入力 メモリ[1]のbit3のデータ
25 0
26 1 入力 メモリ[1]のbit4のデータ
27 0
28 1 入力 メモリ[1]のbit5のデータ
29 0
30 1 入力 メモリ[1]のbit6のデータ
31 0
32 1 入力 メモリ[1]のbit7のデータ
33 0
※CLRが0の時にD0を読み取った場合は正しい値を取得することは出来ないため、必ずCLRが1の時に読み取りを行う必要があります

書き込みモードの場合

書き込みは3で指定した番地から始まりますが、読み込みと同じくデータは常に1bitずつ書き込まれるので、例えば1バイト分を書き出すには8回出力処理を行う必要があります。

書き込むデータも読み込み時と同様に、下位bitから順番に行います。そして8bit分の書き込みが終わったら次のデータに移動し、再び下位bitから書き込みます。

書き込み方法はSELに書き込みたいbit状態を設定したあと、CLRを0→1→0とします。これを指定回数繰り返し最後の書き込みが完了すると、メモリベース128は自動的にコントローラーを読み取る状態に戻ります。

例として書き込みたいデータが「data = [0x12,0x34]」の2バイトだった場合の流れを説明します。
※0x12は2進数で「00010010b」、0x34は「00110100b」です
項番 SEL出力 CLR出力 補足
1 data[0]のbit0=0 0 0x12(00010010b)
2 1
3 0
4 data[0]のbit1=1 0 0x12(00010010b)
5 1
6 0
7 data[0]のbit2=0 0 0x12(00010010b)
8 1
9 0
10 data[0]のbit3=0 0 0x12(00010010b)
11 1
12 0
13 data[0]のbit4=1 0 0x12(00010010b)
14 1
15 0
16 data[0]のbit5=0 0 0x12(00010010b)
17 1
18 0
19 data[0]のbit6=0 0 0x12(00010010b)
20 1
21 0
22 data[0]のbit7=0 0 0x12(00010010b)
23 1
24 0
25 data[1]のbit0=0 0 0x34(00110100b)
26 1
27 0
28 data[1]のbit1=0 0 0x34(00110100b)
29 1
30 0
31 data[1]のbit2=1 0 0x34(00110100b)
32 1
33 0
34 data[1]のbit3=0 0 0x34(00110100b)
35 1
36 0
37 data[1]のbit4=1 0 0x34(00110100b)
38 1
39 0
40 data[1]のbit5=1 0 0x34(00110100b)
41 1
42 0
43 data[1]のbit6=0 0 0x34(00110100b)
44 1
45 0
46 data[1]のbit7=0 0 0x34(00110100b)
47 1
48 0

メモリベース128をPCに繋げる方法

メモリベース128のコネクタからは上記の通り5Vを基準としたI/Oピンが出ています。つまりこのI/Oピンに対して、直接PCから入出力が出来れば、結果的にPCからメモリベース128を直接コントロールすることが出来るはずです。

PCからI/O制御を行う場合、昔は簡単に使えるI/Oというのは皆無であり、ものすごく高価な産業用のものしかありませんでした。例えばマザーボードに取りつけるPCボードで、入出力が16個ずつのContec製のI/Oボードは3万円を超える値段だったりして、遊びで買えるような値段ではありませんでした。
※Contecは仕事ではよく使いますが、会社レベルでもおいそれと買える値段ではありませんw

しかし、時代が変わって現在ではUSB経由で簡単にI/Oを制御するためのICが登場し、しかもIC自体は500円以下で買えるくらいまで価格も落ちたため、誰でも簡単にPCからI/O制御を行うことが出来るようになりました。

そして、その中で今一番流行っているものというのがArduinoというI/Oボードです。
※似たようなものでRaspberry Piというものもありますが、こちらはI/Oボードというよりは小型PCという位置付けなので、OSをインストールする必要があるなど、かなりハードルが高くなるためここでは使用しません

ちなみに仕事でもArduinoを使えばいいんじゃないかという話もありますが、Arduinoは電圧が低いためノイズなどの影響を受けやすかったり、耐久性がまだまだ未知数なので逆に産業用として使うのはちょっと躊躇します。

Arduinoとは

Arduino(アルドゥウィーノ)とは、簡単に言うとある規格に沿った形状の小型I/Oボード、またそれに使われているICチップ、またはそれらを動かすためのソフトウェアやハードウェア全般をひっくるめた総称で、言わばブランド名のようなものです。

ちなみにArduinoで純正と言われるものは、一般的にAtmel社が製造したAVRシリーズのICを使ったものを言います。このICにはCPU、RAM、ROM、I/O、クロックジェネレータ、タイマーなど、一般的なコンピューターに必要な要素が1チップに集約されており、これにプログラムを書き込んであとは電源を供給するだけで動作します。

なお、電源が必要なのはArduino単体で動作させる場合で、その場合はACアダプターを使用したり、電力を供給するピンがあるのでそこに電池などを繋ぐことで給電を行いますが、今回はPCとの接続が前提のため電源はUSB端子から供給されます。そのため別途電源を用意する必要はありませんが、USBの仕様により供給できる電流は最大500mAまでとなるため、それ以上の電流が必要な場合は別途電源が必要となります。
※USBとACアダプターを両方繋いだ場合は、設計上ACアダプターの方が優先されるようになっています

Arduinoのプログラムとは基本的にI/Oを制御するためのもので、最初に各I/Oピンを入力用か出力用かをセットして、そのあとは任意のタイミングで指定したピンの状態を取得したり、また指定のピンに対して出力を行うとそのピンに繋がっているLEDを光らせたりなどが行えます。また、このICにはRS-232C(TTLレベル)でのシリアル通信や、I2Cというシリアル通信より高速な通信機能が備わっているため、例えばI2C接続のセンサーなどのモジュールと通信して温度などの情報を取得し、これをシリアル通信でPCに送ることで、PC側で現在の温度をリアルタイムに表示したりといった、PCとの連動プログラムも作ることが出来ます。

プログラムを作るためのエディタとして「Arduino IDE」というソフトが用意されており、これを使うとソースコードの入力からコンパイル、そしてArduino本体への書き込みまでを一括で行うことが出来ます。さらにこれは公式サイトから無料でダウンロード出来るため、別途費用も掛かりません。

言語はArduino言語と言われるものですが、中身はほぼC++言語です。また、出力ピンをON/OFFしたり入力ピンの状態を取得したり、また文字列操作やシリアル通信、I2C通信を行うための関数があらかじめ用意されているため、特にC++を知らなくても決められた通りに記述するだけなので、簡単にプログラムを作ることが出来ます。

ただし、重要なこととしてArduinoにはピンの電圧が5V系か3.3V系かの2種類があり、使用する機器に合わせる必要があります。

例えば3.3Vの出力を5V系のArduinoに入力しても単に電圧が低いということで壊れることはありませんが、5V側はどのくらいの電圧をONと見るかは個体差もあるため、確実にONと判定出来ないかもしれません。
また逆に5Vの出力を3.3V系のArduinoに入力してしまうと、場合によってはArduinoが壊れてしまいます。
このため通常は3.3V系は3.3V系、5V系は5V系で組み合わせて使用します。

ちなみに、今回対象となるメモリベース128は5V仕様なので、Arduinoは5V系を使用します。ということで、ここでは定番の「Arduino Uno R3」を使います。

※ロットによって端子の隙間が埋められているものもあります(これは相当古いもので、元々は透明のフレームは付いていなかったのですが、会社でちょうど使わなくて余っていたものをがあったので貰ってきましたw)

今回はこのArduinoにメモリベース128の低レベルアクセスを行うプログラムを書き込んでおき、PCのソフトからArduinoにコマンドを送受信することで、メモリベース128のデータを間接的に読み書きします。

実はArduino側のソフトをもっと単純にし、SELとCLRとD0~D3の入出力だけを行う汎用プログラムにして、PC側からすべてのピンを制御するような作りも出来なくはないのですが、シリアル通信で1個1個入出力ピンの制御を行うとなると、シリアル通信の方が遅すぎてとてつもなく時間がかかってしまうため、ここではArduino側でなるべくまとめて処理することで高速化を図っています。

Arduinoへの接続方法

メモリベース128をArduinoへ接続するには、基本的にメモリベース128のコネクタの各ピンをArduinoのI/O端子に直結するだけです。

しかしメモリベース128をArduinoに接続する際は、メモリベース128のケーブルを切って直接取り付けるなんてことはしません。そもそも切ってしまうと今度はPCエンジンに接続できなくなってしまうため、ここではメモリベース128のコネクタをそのまま刺すことが出来る「ミニDIN8pinメス」のコネクタを用意します。

なお「ミニDIN8pin」というのはいくつか規格があるらしく、同じ名称でもピンの位置が違ったりするため、ここではきちんとメモリベース128で使えるコネクタを探さなければなりません。

そこで探すこと数分w

さすが開発元のNECということもあり、使われているコネクタが実はPC98用のキーボードと同じものであるのと、本来はコネクタの他に別途ケーブルも用意しなければならないということもあり、今回はケーブルが一体になっているPC98用のキーボード延長ケーブルを加工して使うことにしました。


ここではこの延長ケーブルを中央から切断し、メス側のコネクタのついたケーブルだけを使います。
これでメモリベース128に一切傷を付けることなく、Arduinoと接続が可能となります。

ちなみにオス側のコネクタは逆にPCエンジンに刺さるので、自作コントローラーを作ってみるなど何か別の用途に使ってくださいw
※実はArduinoを直接PCエンジンのコントローラー端子に繋いで、Arduino上でメモリベース128をエミュレーション出来るか調査してみたのですが、Arduinoの速度が遅すぎて使い物にはなりませんでした

メモリベース128のソフトリセットについて

メモリベース128へのアクセスを中断した際に、普通ならArduinoから取り外せば電源供給が絶たれるためメモリベース128がリセットされ、通常のコントローラーの入力状態に戻るだろうと思っていたのですが、その状態で再びArduinoに接続しても、なんとアクセス状態が残ったままになっていることが判明しました。

例えば、もしアクセス中にPCエンジンのコントローラー端子からメモリベース128が抜けてしまった場合、再度刺しなおしたあとに通常のコントローラーの入力処理が行われてしまうと、アクセス途中だったメモリベース128は読み書き処理が再開されてしまい、コントローラーの入力処理のための出力操作により、場合によってはメモリベース128内のデータを破壊してしまいます。

しかし、ふとArduinoでアクセス途中の状態にしたメモリベース128をPCエンジンに刺し、PCエンジンの電源を入れてみると普通にコントローラーの入力状態に戻っていることに気づきました。

最初は電源を入れた時に何か特殊なリセット用の出力をコントローラー端子に流しているのかと思い、SELとCLR出力を試しに3秒くらいONやOFFにしてみたりもしましたが、そもそもPCエンジンは電源を入れると即座に画面が出て、しかもすぐにコントローラーが効くので、つまり時間は関係無いということになり、かといってさすがに入力として使われるD0~D3が一瞬だけ出力に切り替わって、全部を一瞬出力すればリセットされるのかもとか考えたりしましたが、使われているICなどを考えると入出力の方向は決まっているはずなので、そんなことはまずあり得ないだろうという結論に至りました。

ではどうやってリセットしているのかですが、実はアクセス中の状態のメモリベース128をPCエンジンに刺す際、PCエンジンの電源は切られているという事実に気づきました。つまり電源を切った状態で接続するとメモリベース128がリセットされるというわけで、その時起こっている現象を考慮すると、その時は+5Vの端子が0Vに落ちているということになります。

これは盲点だったのですが、実は+5Vという端子はメモリベース128では電源供給の他にI/Oとしても使用されているわけです。

通常はメモリベース128を取り外すと+5Vはどこにも繋がっていない浮遊状態となっており、テスターで測ってみると最後に繋がっていた5Vの電位が残っていました。そこでこの+5Vの端子をGNDに一瞬だけショートさせ再びArduinoに刺しなおしたところ、見事にコントローラーの入力状態に戻っていました。

逆に試しに今度は電源が入ったままのPCエンジンに繋いだところ、Arduinoと同じくコントローラーが入力出来ない状態になったので、つまりPCエンジンの電源を切ることで+5VとGNDがショートした形となり、結果メモリベース128がリセットされていたということになります。

ということで、この+5V端子もI/Oで制御出来ればArduino上からリセットが可能となるわけですが、そこでまず一番簡単な方法としてはArduinoの出力ピンをそのまま+5Vに繋いでしまえば良いのではと考えました。しかし、Arduinoのピン端子から出力できる電流は最大で20mAとなっており、それ以上の電流が流れた場合Arduinoが壊れてしまいます。なお、読み書き中にメモリベース128の電流を計測してみたところ、およそ15mAくらいだったので直結しても大丈夫そうではありますが、もしメモリベース128の先にさらにコントローラーなどを繋げていた場合、その分の電流が加算され20mAを超えてしまう可能性があります。

そこで、今回はArduinoの出力ピンを直接+5Vに繋ぐのではなく、トランジスタを使ってスイッチングを行うことで、100mAくらいまで流せる回路を考えました。

ちなみにスイッチング回路を作らなくても、実は手動でArduinoのUSBケーブルを抜くことでもリセットさせることができます。これはUSBを抜くことでArduinoの+5Vの電圧が0Vとなるため、結果的にメモリベース128の+5VとGNDが繋がった状態になるためです。

ソフトリセットの機能が必要無ければ、トランジスタや抵抗は一切使わなくて済みますが、もしアクセス途中でソフトを中断した場合、USBの抜き差しを忘れるとメモリベース128のデータが破壊されてしまうことになるため、どうしても部品が手に入らないとか、作るのがめんどくさいという人以外は、安全性を考えてソフトリセット回路を実装することをお勧めします。

回路図

Arduinoとメモリベース128を接続するための基板の回路図は以下のようになります。


メモリベース128への電源供給は5Vを直結するのではなく、トランジスタ(2SA1015)を介して接続しています。

ここではA0端子のON/OFF出力によりメモリベース128への給電と切断を行います。なお、回路の仕様によりOFFの時に給電、ONの時に切断となります。このためArduinoからリセットを行う場合はON→OFFとしますが、コンデンサなどの放電を考えソフト上ではONの時間を2秒ほど取るようにしています。

ちなみに本来は余っているD8~D13のデジタルピンを使うべきですが、今回使用するユニバーサル基板ではピンを出すことが出来なかったため、仕方なくA0をデジタル出力として使用しています。
※Arduinoは元々アナログピンもデジタル入出力ピンとして使用できます

ところでいろいろ実験をしていて気づいたのが、メモリベース128をArduinoに繋いだままA0を操作し給電を切った状態でも、D0~D3への電位が残っていると、メモリベース128の5V端子が0Vに落ちないことが判明しました。これによりメモリベース128がリセットされず、読み書きが前回の続きから始まってしまうと、場合によってはデータが破壊されてしまいます。

そこで、今回の回路では給電が無い場合にD0~D3を確実に0Vにするため、1KΩのプルダウン抵抗を接続することにしました。これによりソフトリセットが確実に行えるようになりました。もしプルアップであればArduino内蔵の抵抗が使えたのですが、今回はプルダウンが必要だったため、Arduino内蔵の抵抗は無効にして自前で用意することにしました。
※これは自分の環境での検証結果であり、Arduinoやメモリベース128の製造時期などで変わるかもしれません

ハードウェアの制作準備

今回作成するハードは最終的に以下のような感じになります。


そしてこれをどう使うかというと、Arduino Uno R3の上からドッキングさせます。

Arduinoの端子はデジタルピン側(USBのコネクタがある方)の列が途中から半ピンずれているため、汎用のユニバーサル基板は使えなさそうに見えますが、ズレているピン以外はちょうどぴったり合うので、今回はズレたピンは使わないようにしてみました。

そして、最終的には以下のように接続して使います。




以下は今回使用した部品の全リストです。
番号 購入場所 部品名 型番 使用数 価格 補足
1 秋月 I/Oボード Arduino Uno R3 1 2,940 こちらはAmazonでも千石電商でもマルツパーツでも扱っているので、好きなところで購入してください。なお、基本的に純正のものは3000円位しますが、中華製のものだと500円もしなかったりするので、Arduinoに詳しければそういうものでも問題ありません。(5V系かは要確認)
2 千石電商 ユニバーサル基板 UP-203(25×15ピン/4隅ピン無し) 1 105 こちらは手持ちのものを使用しただけなので秋月には売ってはいませんが、秋月ならピン数が同じこちらが代替品として使えると思います。もしくはArduino専用のユニバーサル基板も売っているので、値段は少々高くなりますが見た目を重視するならこちらでも問題ありません。
※回路はすべて両端のピンの内側で完結するので、Arduinoの幅以上ある基板であればなんでもOKです
3 ヨドバシカメラ PC-9821シリーズ用キーボード延長ケーブル(1.5m) SANWA SUPPLY
KB-K98K
1 973 こちらはAmazonでも売っていますが、プライム会員でなければポイント10%還元のヨドバシの方が送料も無料なのでお得です。
4 秋月 ピンヘッダ ピンヘッダ 1×40 (40P 1 50 Arduinoの端子に刺すためのもので、ピンの数に合わせて切って使います。今回は6ピン×1と8ピン×2で、計22ピンあれば足ります。
5 秋月 トランジスタ 2SA1015Y 10個入 1 100 10個入りですが1個しか使用しません。
6 秋月 抵抗1KΩ 1/6W 1KΩ 100本入 4 100 100個入りですが4個しか使用しません。
7 秋月 抵抗4.7KΩ 1/6W 4.7KΩ 100本入 1 100 100個入りですが1個しか使用しません。
※写真のものは手持ちの少し大きな1/4W型を使用しました
8 秋月 抵抗10KΩ 1/6W 10KΩ 100本入 1 100 100個入りですが1個しか使用しません。
※写真のものは手持ちの少し大きな1/4W型を使用しました
9 秋月 熱収縮チューブ Φ3×0.2×1m 1 40 キーボード延長ケーブルで、ケーブルを剥いた後のシールド線の絶縁に使用します。ビニールテープなどでも代用出来ます。
10 - ジャンパ用銅線(6cmくらい) - 1 - GNDのジャンパに使用します。
※写真では基板表面の黒の銅線です
11 Amazon 結束バンド LD-T140BK30(参考) 1 227 キーボード延長ケーブルと基板をガッチリと固定するだけなので、実際には無くてもかまいません。もしくはねじりっこのように、電化製品を買ったときによくケーブルを束ねているのに使われているものを流用する方法もあります。(ただし結束バンドでないとガッチリとはいきません)

これ以外に工具としてニッパー、はんだごて、はんだ、スズメッキ線、ドリル、角型ヤスリ、それと念のためテスターが必要です。なお、角型ヤスリはバンド穴の整形用に使いますが、多少粗くても良ければカッターでもOKです。ドリルの刃は最大で5mmくらいが必要ですが、最初は小さな穴を開けておき、あとはヤスリで広げていくということも可能です。

ソフトリセット対応の基板の製作

ここでは基板の作り方を順番に説明しています。

1.ケーブルの加工

まずはメモリベース128を接続するためのケーブルの加工から始めます。

最初にPC98用のキーボード延長ケーブルの真ん中をひと思いにカットします。元は1.5mのケーブルなので、真ん中をカットするとだいたい75cmでちょうど良い長さになります。

次に、メス端子コネクタの付いた方(メモリベース128が刺さる方)の銅線の被膜を剥き、銅線をむき出しにします。

外側の被膜は5cmほど剥き、各銅線の被膜は5mmほど剥いて先端をねじっておきます。

最終的に以下のようになればOKです。


ちなみに各銅線は以下のようにアサインされていましたが、製造時期により色が違う可能性があるので、念のためテスターでこれと同じアサインかどうかを確認してください。
1
2
3
4
5
6
7
8
9 シールド

2.基板の穴あけ

次にユニバーサル基板に、このケーブルの銅線を通す穴とケーブルを固定するための穴を開けます。
穴の位置は以下を参考にしてください。


十字の中心にドリルで穴を開けたら、やすりやカッターなどを使って四角形にします。なお、ケーブルや結束バンドが通ればいいだけなので、特に厳密な穴を開ける必要はありません。時折ケーブルや結束バンドを通したりして確認しながら加工しましょう。

ケーブル用の穴は剥いた銅線だけが通ればOKです。

3.部品の取り付け

ケーブルが通ることが確認出来たら、先に部品をはんだ付けしておきます。

まずはピンソケットを6ピン×1、8ピン×2にカットしておきます。


次に、ジャンパ用ケーブルを6cm程にカットし、両サイドのビニールを剥いておきます。


最後にピンソケット、トランジスタ、抵抗、ジャンパ線を基板にはんだ付けします。
部品の位置は以下を参考にしてください。

4.配線

次に裏側の配線を行います。
配線はスズメッキ線や部品の足を使って行います。
※おそらく部品の足だけで事足ります

配線は以下のように色のついた部分を行ってください。

5.ケーブルの取り付け

最後にこの基板に1で加工したケーブルを取り付けます。

まずは、ケーブルの銅線部分を表から通し結束バンドで固定します。

この時、各色のケーブルがなるべく接続する先のピンに近い位置に向くようにすると、あとあとキレイに仕上がります。
※茶と白とシールドが4の裏(銅箔面)から見て左側、それ以外のカラーの銅線は右側の4つの抵抗が並んでいる方にする

※はじめは緩めに固定しておき、あとで締めても問題ありません
※写真ではケーブルを通す穴の位置が1個分ずれています(この時は適当だったためw)

次にこのケーブルを裏面から各ピンに配線します。
以下のようにケーブルの色を所定のピンにはんだ付けしてください。
※シールド線は絶縁のため、熱収縮チューブをかぶせてからはんだ付けしてください(特に熱風で収縮させる必要はありません)


全ての作業が完了したらArduinoにドッキングさせてみましょう。
なお、基板を刺すときは部品がArduino上の部品や端子などに触れていないかを確認しながら行ってください。

これで基板は完成したので、あとはソフトウェア側での作業となります。

Arduinoプログラム

Arduinoにメモリベース128を制御するためのプログラムを書き込みます。

ここではArduino IDEを使ってコンパイルと転送を行いますが、既にArduino IDEを使ったことがある場合はスキップしてください。

Arduino IDEのセットアップ方法

まずは以下のページにアクセスします。
https://www.arduino.cc/en/Main/Software

今回はWindows上での作業となるので、このページの「Download the Arduino IDE」の項目にある「WindowsInstaller, for Windows XP and up」をクリックします。


次にArduinoソフトへの寄付がどうのと出ますが、特に寄付しなくてもダウンロードは出来るので、下の「JUST DOWNLOAD」をクリックします。


するとダウンロード先を選ぶか既定のフォルダにダウンロードが始まるので、ダウンロードが終わるまで待ちます。

ダウンロードが終わったらインストーラーをダブルクリックして、Arduino IDEをインストールします。


インストーラーが起動したら画面の指示に従ってインストールを行います。基本的にはデフォルトの設定で次へをクリックしていけばインストールは完了します。

まずはライセンス確認画面ですが、ここでは「I Agree」をクリックします。


次にインストールするソフトやショートカットなどの設定を変更出来ます。「Next」を押すと次へ進みます。


インストール先を指定します。「Install」を押すと次へ進みます。


インストールが終わるまで待ちます。


インストールが完了したら、「Close」をクリックするとインストーラーが終了します。


デフォルト設定でインストールした場合は、デスクトップにArduino IDEのショートカットアイコンが出来ています。


これをダブルクリックしてArduino IDEを起動します。

※メニューはOSの言語に合わせて日本語で表示されます

Arduino IDEが起動したら、今回使用するArduino Uno R3を使用するための設定を行います。
まずは「ツール」→「ボード」→「Arduino/Genuino Uno」が選択されていなければ選択します。

※この設定により対象のボードのピン定義などが定まります(デジタルピンなどボードによってIDが異なるため)

次に「ツール」→「シリアルポート」→「COM*** (Arduino/Genuino Uno)」から接続先のポート番号を指定します。

※この設定により書き込み先のボードが決定されます

これでArduino Uno R3への転送設定が完了したので、あとはプログラムを入力して転送を行うことで、自動的にArduinoにプログラムが書き込まれて実行されます。

ちなみにArduinoのプログラムはsetup()とloop()関数があらかじめ定義されており、基本的にプログラムはこの中に入力します。setup()関数は起動時に1回だけ呼ばれるので、通常はピンの設定やタイマーの初期化などを行い、loop()は毎回呼び出されるのでこの中で常に処理したいプログラムを記述します。

なお、Arduinoには終了という概念は無く常にloop()が呼び出されるため、処理が完了しそれ以降何も処理をさせたくない場合は、while(1);などで永久ループするプログラムを書きます。



Arduino用のソフトのソースコードは以下からダウンロード出来ます。
メモリベース128ユーティリティ(Arduino source code) v1.00 ダウンロード

ソースコードを解凍しArduino IDEから「MB128Utility.ino」を開くか、直接inoファイルをダブルクリックすることで開くことが出来ます。


Arduino IDEでソースが開けたらArduino Uno R3をPCに接続し、上の→ボタンを押してArduino本体に書き込みます。
※書き込み時は基板をドッキングさせたままでも問題ありませんが、ピンへの余計な出力が発生することを考えて、メモリベース128本体はまだ接続しないください

書き込みが成功すると以下のように完了メッセージが表示されます。


これでArduino側の作業はすべて完了です。これ以降はArduino IDEは必要無いので、コントロールパネルの「プログラムと機能」からアンインストールしてしまってもかまいません。

Windows用ソフト「MB128Utility」の使い方

Arduinoへの書き込みが完了したらあとはWindows側からこのArduinoを制御して、メモリベース128のバックアップ&リストアを行います。

メモリベース128ユーティリティ for Arduino(Windows版) v1.00 ダウンロード

解凍すると「MB128Utility.exe」がありますので、これをダブルクリックすることで起動できます。


まずは設定でArduino Uno R3が接続されているCOMポートを選択します。なお、2回目以降はこの設定が保存されているので、間違いが無いかだけ確認してください。

※Arduinoを接続する前にソフトを起動すると、ポート番号にArduinoが表示されないので、その場合はいったんソフトを終了してから起動しなおしてください

以降のバックアップやリストアでは、メモリベース128をArduinoに接続してから作業を行ってください。

バックアップ


このボタンから、メモリベース128の全データをPCにファイルとしてバックアップ出来ます。

このボタンを押すとバックアップデータを保存するためのファイル名の入力ダイアログが表示されるので、ここで任意のファイル名を入力して保存ボタンを押すことでバックアップが始まります。

バックアップ中はゲージが表示されるので完了するまで待ちます。

リストア


このボタンから、バックアップしたデータをメモリベース128に書き込むことが出来ます。

このボタンを押すとリストアしたいファイルを選択するダイアログが表示されるので、ここで書き込みたいファイルを選択し開くボタンを押すことでリストアが始まります。

リストア中はゲージが表示されるので完了するまで待ちます。

これでメモリベース128のデータを半永久的に残すことが出来るようになったので、心置きなくゲームを堪能することが出来ると思います。
ちなみに読み書き中はArduinoボードの13ピンにあらかじめ実装されているLEDをビジーランプとして使用しているので、このLEDを確認することで読み書き中かを判断出来ます。

Windows⇔Arduino間コマンドプロトコル

ここからは各ソフトの詳しい動作について説明します。WindowsとArduinoがどのように通信を行い、メモリベース128のアクセスを制御しているのかの技術的な解説になるので、バックアップとリストアさえ出来れば良いのであれば、特にこの説明を読む必要はありません。

今回のソフトはWindows用のソフトとArduino側のソフトの2つが連動していますが、実はArduino側はメモリベース128への基本アクセスと、COMポートからの通信コマンドの送受信のみしか行っていません。このため特にWindows専用というわけではなく、プロトコルに準拠していれば他のCOMポートを扱える機器からアクセスすることが可能です。

Arduino側はCOM通信を以下の設定で行っています。
ボーレート 115200bps
データビット 8bit
ストップビット 1bit
パリティ 無し

この設定でCOMポートをオープンすれば、Arduino側と通信が可能となります。

なお、Arduinoは通常はCOMがオープンされると、自動的にリセットが行われArduinoのプログラムが最初から始まります。一般的なCOMポートを利用するプログラムではそのような挙動は無いため、その場合はCOMのコマンドとしてハードウェアのリセットコマンドを別途用意するなどが必要ですが、Arduinoでは必ず最初から始まるためリセットは考慮する必要がありません。逆に言えば必ずリセットが行われるため、前回から継続しての処理を行うことは出来ません。

今回のArduinoプログラムでは、リセットが行われると各ピンの入出力状態を初期化し、COMポートを有効にしてあとはCOMからのコマンド待ち状態となります。基本的にはPC側からコマンドを送ることで、それに対する応答を返すといった1対1の通信となっていますが、デバッグ用にいつでもArduino側からメッセージを送るためのメッセージ処理がイレギュラーで存在します。

コマンドは以下のものがあります。
コマンド定数 コード 内容 使用構造体
CMD_MESSAGE 0x01 デバッグ文字コマンド(Arduino→PC) CPMESSAGE
CMD_CHECK 0x02 Arduino接続確認(PC→Arduino) CPCHECK
CMD_READSTART 0x03 リード開始コマンド(PC→Arduino) CPREADWRITE
CMD_READNEXT 0x04 次のデータリードを指示(PC→Arduino) CPHEADER
CMD_READDATA 0x05 最大ARDUINO_BUFFERSIZEバイト分のデータのリードが完了した(Arduino→PC) CPDATA
CMD_WRITESTART 0x06 ライト開始コマンド(PC→Arduino/データはまだ送らない) CPREADWRITE
CMD_WRITEDATA 0x07 ライトデータを最大1024バイト分を送信する(PC→Arduino) CPDATA
CMD_SUCCESS 0x00 成功(Arduino→PC) CPHEADER
CMD_ERROR 0xFF 失敗(Arduino→PC/待機モードへ戻る/メッセージ付き) CPERROR
※ここではコマンドに対する応答もコマンドとして定義しています

各コマンドは専用のデータ構造で送信しなければならないものがありますが、すべてのコマンドには先頭に共通フォーマットを用意してあります。これにはコマンドの他にそのコマンドのデータのサイズが入っているため、不明なコマンドはそのサイズ分を無視することで、そのコマンドをスキップさせることが出来ます。なお、ここではこれを共通ヘッダ構造体として定義しています。

共通ヘッダ構造体は以下のように定義しています。
// 共通ヘッダ
typedef struct _CPHEADER {
    BYTE      cSync;                            // 同期ヘッダ(SYNC_CODE)
    BYTE      cCommand;                         // コマンド/応答コード
    WORD      wSize;                            // データ部のサイズ(データが無ければ0)
} CPHEADER,*LPCPHEADER;
データの必要無い応答だけのようなコマンドは、この構造体がそのまま使用されます。

ちなみに各変数の型はWindowsに準拠していますが、Arduino側でも同じ型を使えるようにするため「win_def.h」に型定義を行っています。

例えばWindowsではint型は32bitですが、Arduinoでは16bitとなります。なお、charはWindowsとArduinoで両方とも符合ありの8bitですが、以前の記事でポケコンをWindows上で開発するために使用したコンパイラ「SDCC」では、charは符号なしで定義されています。これらの違いを吸収するため、ハードウェアが異なるものを対象とする場合は、型の再定義を行うことで必ず同じ状態になるようにしなければなりません。

ここではBYTEはunsigned char(符号なし8bit)、WORDはunsigned short(符号なし16bit)として定義していますが、実は上記の構造体をそのままWindows上で定義すると、構造体のサイズが4byteではなく8byteとして定義されることがあります。これはCPUがより高速に動作するよう、最適化により間に空白が入ってしまうためです。これをアライメントと言いますが、もしこのことを知らずにこの構造体をそのままArduinoに送信してしまうと、当然Arduinoは4byteのデータとして受け取ろうとしてしまうため、本来の位置にあるべき値が違う場所にあるということになり、結果的に誤作動してしまいます。

これを避けるために、Windows側ではこの最適化が行われないように#pragma指定を行います。なお、アライメント数は任意のバイト数で指定することが出来ますが、ここでは一切空白を入れないようにするため1byte単位としています。

Windows上でアライメント指定するには以下のようにします。
#pragma pack( push,1 ) 

<ここに1バイト境界とする構造体を羅列>

#pragma pack(pop)

上記の通りpragmaで囲んだ中にある構造体は空白が入らないため、Arduinoと同じバイト数となります。

各コマンドに対する構造体は以下のようになります。
構造体 内容
// デバッグ
typedef struct _CPMESSAGE {
    CPHEADER  mHead;        // ヘッダ
    char      mMsg[32];     // メッセージ
} CPMESSAGE,*LPCPMESSAGE;
デバッグ文字コマンドを送信するための構造体です。
これはArduino側から任意のタイミングで送られるため、
いつでも受信出来る状態になっていなければなりません。
なお、あくまでもデバッグ用の文字なので、受信したら
即座に破棄してしまってかまいません。
// エラー
typedef struct _CPERROR {
    CPHEADER  mHead;        // ヘッダ
    char      mMsg[32];     // エラーメッセージ
} CPERROR,*LPCPERROR;
PC側からのコマンドに対する応答で、その処理が失敗したときに
送られる構造体です。失敗時はその内容を表示するなどして、
通常のコマンド受信状態に戻します。
// Arduino確認
typedef struct _CPCHECK {
    CPHEADER  mHead;        // ヘッダ
    WORD      wVersion;     // バージョン
} CPCHECK,*LPCPCHECK;
PC側からArduino側へ送られ、Arduino側のソフトが
PC側のソフトと連動可能かを確認します。
連動可能ならArduinoから成功が送られ、バージョンが異なる
場合は連動不可としてエラー応答を返します。
// 読書きコマンド
typedef struct _CPREADWRITE {
    CPHEADER  mHead;        // ヘッダ
    WORD      wSegment;     // 開始セグメント
    LONG      lSize;        // データサイズ
} CPREADWRITE,*LPCPREADWRITE;
PC側からArduinoへ読み込みまたは書き込みの開始を
宣言します。読み書きするメモリのアドレスとサイズを
指定すると、Arduino側でメモリベース128をオープンし、
指定のモードに設定します。成功すれば成功の応答が
返りますが、メモリベース128が接続されていないなど、
オープン出来なければエラーを返します。
// データ転送
typedef struct _CPDATA {
    CPHEADER  mHead;         // ヘッダ
    BYTE      cFlag;         // データフラグ
} CPDATA,*LPCPDATA;
読み書きのモード中にPCからArduino、
またはArduinoからPCへの実データの転送に
使用されます。この構造体のみ特殊で実データは
この構造体のあとに繋げて送られます。
このため、構造体のサイズには実データも含めた
サイズを指定する必要があります。
データはARDUINO_BUFFERSIZEバイトずつ送られますが、
最後のデータが送られるときにフラグを設定することで、
データが終わったことを判断します。
※これらの定義はすべて「MB128Def.h」にあるので、詳しくはそちらを確認してください

ここでは例として、Arduinoのバージョン確認コマンドを送信するための、構造体の定義方法について説明します。
上記の通りバージョン確認に使用するコマンド構造体は「CPCHECK」構造体ですが、これをどのように初期化するかは以下のようにします。
CPCHECK chk;
ZeroMemory( &chk,sizeof(chk) );
chk.mHead.cSync     = SYNC_CODE;
chk.mHead.cCommand  = CMD_CHECK;
chk.mHead.wSize     = sizeof(chk);
chk.wVersion        = ARDUINO_VERSION;
この構造体をPC側からそのままCOMポートに対して書き込むと、Arduino側はこの構造体のまま受信されます。

なお、COMポートやTCP/IPのようなストリーム通信を扱う場合、1回の受信で全てのデータ(ここでは構造体)を受信することが出来ないことがあります。これはOSやハードウェア側の仕様であり、基本的にデータは分割して送信されるため、受信のタイミングによっては送信途中のデータまでが受信されることもあります。一般的にこのようなストリームデータをリアルタイムで処理する必要がある場合、受信したデータを一度任意のメモリにキャッシュしておき、必要なデータサイズまで揃ったのを確認してから、あらためてコマンドの解析を行うようにします。

特に、COMやTCPでは送ったデータのサイズが自動的に相手に通知されるわけではないため、サイズのチェックはすべて自分で管理する必要があります。このため、今回は共通ヘッダ構造体にそのサイズを入れることで、受信側でそのコマンドのサイズが確実に分かるようにしています。

受信時の処理方法を詳しく説明すると、まず受信したデータを溜めるためのバッファを用意し、受信したデータをいったん全てそのバッファに追加していきます。今回はサイズ情報は必ず共通ヘッダの構造体に入っているわけなので、まずは最低限共通ヘッダ分のデータが受信されたかを確認します。共通ヘッダサイズ分のデータが受信出来ているならば、今回のコマンドサイズが確実にその構造体のwSizeに入っていることになるので、次はこのサイズになるまでバッファをそのまま溜めていきます。最終的にwSize以上のバッファが溜まった段階でこのコマンドの解析が可能となるので、ここでようやくこのコマンドを処理し、最後に今回使用したコマンドバッファ分を詰めることで処理済みのバッファを消去します。

ちなみに、通常のストリーミングではその後も次々とデータが送られてくるので、どんどんバッファに溜めながら同じく1コマンドずつ処理しなければなりません。しかし、もし処理が終わったら1コマンド分のバッファではなく、誤って受信バッファ全てを消去してしまった場合、既に受信済みの次のコマンドまでも消去してしまうことになるため、次のコマンドが壊れてしまうことになります。そうならないよう消去は必ず1コマンド分のサイズだけ行う必要があり、このため通信処理というのはかなり慎重に仕様を考える必要があります。

なお、場合によっては1回の受信で2個以上のコマンドが受信されることもあるため、受信したら1個分だけしか処理しないような作りになっていると、最終的にどんどんバッファが溜まっていってしまい、いつかはメモリオーバーとなってしまいます。これに対応するためには、1回だけ解析するのではなく、コマンドが解析出来なくなるまでループするようにします。
※この処理はArduino側のソースコードのGetCommand()関数で行っているので参考にしてください
 (今回の仕様ではArduino側に2個以上のコマンドが来ることはありませんが、PC側ではメッセージコマンドの受信が該当します)

ちなみに昔、仕事でクライアントの中国人が作ったプログラムで、最初は反応は良いがしばらくするとどんどん遅くなっていくので原因を調べて欲しいと言われ、ソースコードを貰って調べたところ、受信時に1回だけしかコマンド処理をしておらず、次のコマンド時に今まで溜まっていたコマンドが1個ずつ処理されてていたために、どんどん遅れてしまっていたということがあり、その対応として受信部分をwhile()で囲うように指示したところ、ようやく問題が解決出来たことがあります。


さて、Arduino側は以下の3つのモードのうち、常にどれか1つのモードで動作しています。なお、モードによりPC側から送ることが出来るコマンドには制限があり、例えばデータの読み書き中であれば読み書き用のコマンドしか受け付けません。
モード 内容
待機モード バージョン確認コマンドや、リードモード、ライトモードへの移行を待機している状態です。
これが通常状態のモードであり、リードやライト処理が終わった時もこの状態に戻ります。
リードモード メモリベース128のデータを読み出して、PCに転送している状態です。
高速化のためいったんArduino内のメモリに読み込まれ、溜まったら一気にPCに送ります。
指定したサイズ分の読み込みが完了したら、再び待機モードに戻ります。
ライトモード Arduinoからメモリベース128にデータを書き込んでいる状態です。
高速化のため書き込むデータはPCからまとめて送られ、そのあとで一気に書き込みが行われます。
指定したサイズ分の書き込みが完了したら、再び待機モードに戻ります。


以下は各コマンドの流れについて説明しています。

Arduino確認

読み書きコマンドが実行される前に、PC側からArduino側のソフトが正しいかをチェックしています。バージョンチェック自体はArduino側で行われ、それに対する応答を返すことでPC側は続行するか中断するかを判断します。

このコマンドは待機モードでのみ受信することが出来ます。また、確認処理のあとも引き続き待機モードのままとなります。

リードモード

これはメモリベース128からデータをPCに転送するために使用するモードで、リードコマンドを送ることで開始されます。

リードコマンドには読み込む開始位置(セグメント)と読み込むサイズ(bitではなくバイト単位)を指定することが出来ますが、上記のメモリベース128の制御方法に書いた通り、1回に読み込めるサイズには何らかの制限があるようなので、通常は4KB(4096バイト)を指定します。
※コマンド自体は4096バイト以上を入力することは可能ですが、これを超えるデータはおかしなデータになります

以下はリードコマンドの参考として開始アドレスを指定する方法です。
CPREADWRITE rw;
ZeroMemory( &rw,sizeof(rw) );
rw.mHead.cSync      = SYNC_CODE;
rw.mHead.cCommand   = CMD_READSTART;
rw.mHead.wSize      = sizeof(rw);
rw.wSegment         = (WORD)(iFileAddr / 128);       // アドレス(byte)をセグメントに変換
rw.lSize            = 4096;                        // 読み込むデータサイズ(byte)

Arduinoで使用できるSRAMは2KB(2048バイト)ですが、メモリベース128の高速化のためここでは半分の1KB(1024バイト)を読み書き用のキャッシュメモリとして使用します。例えば読み取りたいデータサイズが4096バイトだった場合は、1024バイト毎にPCへ送り、これを4回繰り返すことで目的の4096バイトを転送することが出来ます。

この時Arduino側から送られてくるデータはCPDATA構造体であり、コマンドはCMD_READDATAとなります。

まだすべてのデータが送られてきていない場合は、PCからCMD_READNEXTコマンドを送って次の1024バイトのデータを要求します。ちなみにCPDATA構造体のcFlagにDATAFLAG_LASTビットが立っていた場合は、最後のデータが完了したということでArduinoは待機モードに戻ります。


PC側からメモリベース128の全データを読み出すには、128KB(131,072バイト)を4096バイトごとに分けて行っています。このため、全データの読み出しには、上記のリードコマンド処理を合計32回行う必要があります。

ライトモード

これはPC上のデータをArduinoに転送し、メモリベース128へ書き込むモードで、ライトコマンドを送ることで開始されます。

ライトコマンドはリードコマンドと同じく最大で4096バイトを1回のコマンドで書き込みます。使用する構造体もリードコマンドと同じCPREADWRITE構造体を使用しますが、コマンドはCMD_WRITESTARTに変わります。

ライトモードではPCから1024バイトごとデータをArduinoに送ります。この時CPDATA構造体のコマンドにはCMD_WRITEDATAを指定します。
CPDATA data;
ZeroMemory( &data,sizeof(data) );
data.mHead.cSync        = SYNC_CODE;
data.mHead.cCommand     = CMD_WRITEDATA;

// データサイズを算出
if( (残りサイズ)<=ARDUINO_BUFFERSIZE ) {
    // 1回分のバッファサイズ以下なら最後のデータとする
    size                = (残りサイズ);
    data.cFlag          = DATAFLAG_LAST;
} else {
    // それ以上なら1回分のバッファサイズをセット
    size                = ARDUINO_BUFFERSIZE;
}
data.mHead.wSize        = sizeof(data) + size;

Arduino側は受信したデータをメモリベース128へ書き込んだあとにPCにその応答を返します。


PCから最後のデータを送る場合はcFlagにDATAFLAG_LASTを付けます。Arduino側ではこのフラグを見つけると書き込み後に自動的に待機モードに戻ります。

リードモードと同じく全データの書き込みを行うには、このライトコマンドを32回送ることになります。

これによりメモリベース128を低レベルで扱うことが可能になるので、メモリベース128のデータ仕様が分かるのであれば、独自でファイラーなどを作ることも可能だと思います。
コメントはまだ登録されていません。
コメントする
名前
コメント
※タグは使用出来ません
この記事に関連するタグ
ゲーム機