X1シリーズ用コンソールエミュレータ
2014.6.8 暫定公開
対応モニタ/アップスキャンコンバータに続く、レトロPC画面表示の第三の方法。言ってみれば一種のビデオキャプチャなのですが、ちょっとしたヒントだけでそれほど難しくもなく作ってしまえるとは思ってもみませんでした。 |
またしてもレトロPC用モニタで悩む…
先日初代X1やX1Fを入手しまして、特にX1はこんなボードを開発するためのベンチとしていつも座るPCデスクに鎮座させようと思ったのですが、例によって悩ましいのが表示環境。もちろんCRTを置くスペースはありませんのでメインマシンとして使っているMac miniにつないでいる液晶モニタを共有するか、別の小さいモニタを置くかというところなのですが…メインのモニタ(EIZO M1950-R)は幸いにして入力が2系統ありますので切り替えて使うことはできますが、切り替えるとMacの画面は見えなくなるし、切り替えボタンが小さくて使いにくい。別のモニタも含めて今時のモニタを使うにはアップスキャンコンバータが必要ですが、自作したのはひとつしかありませんからこいつ専用にするわけにも行かない。ましてやもう一つ作るなんて面倒…。
ハード・ソフト共にクロス開発することを考えると、X1の画面がMacやWindowsのアプリの画面として見えるようになっていると便利でかっこいいような気がします。まぁさすがにレトロPC専用のビデオキャプチャなんて製品はありませんから、いやなんか似たのを見たことあったような気もしますが、改めて探しても出てこないので、ここは一般的な製品の組み合わせで実現するしかありません。
X1シリーズには純正のRFビデオコンバータ・CZ-8VCという製品があり、これを使用することでRGB出力をコンポジットビデオ信号に変換することができます。このビデオ信号をUSB接続かなんかのビデオキャプチャで取り込めば、MacやPCの画面上にX1の画面をウィンドウ表示させられることになります。とはいっても、やはりそこはコンポジットビデオ、解像度の低さはいかんともしがたく…。
RFビデオコンバータ・CZ-8VC |
カラー画面。RFではなくコンポジットビデオの映像なんですが、輪郭がぼやけてとても綺麗とは言えず…昔もこんな感じだったかなぁ…。 | …おっ、白黒モードだと意外に良い感じじゃないですか。これぐらいならRGBモニタにもひけをとりませんよ? |
…つまり色信号をカットして輝度信号だけにしたら割と解像度が低くないということですね。当然これはS映像の解像度がコンポジットビデオより高い理屈と意味は同じなのですが、同様にY-C混合する前に外に出してあげればコンポジットビデオより良い映像が得られるのではないでしょうか?
幸いにしてCZ-8VCはほとんどディスクリート部品で構成されているので、その気になればどこからでも途中の信号を取り出すことができます。アナログやRF関係の知識はお世辞にも深いとは言えませんが、どうせY-C混合部は出口に近いところでしょうし、回路図を描いてみるにしても全部じゃなくて良さそうです。
ちょっと想像を巡らせばわかることですが、回路の途中から信号を取り出しても出力レベルとしてはたいしたことないはず(ケーブルをつなぐと負荷が重くて信号が減衰してしまう)なので、なにがしかのアンプ回路が必要になります。トランジスタで組むかビデオアンプICを使うか考えないといけません。なんか定番のICとかあるみたいだし、参考用の回路はないかといろいろ探してみると…西田ラヂオ DIGITALのサイトに何やら製作例があるのだとか。ということで見てみると、実はその記事は古いものなのか見つからなかったのですが、代わりに「PC-6001 mkII / 6601 用 USB CRT エミュレータ」「PC-8001 mkII PC-8801 mkII 用 USB CRT エミュレータ」という作品のページを発見…いや以前見た「似たようなの」ってこれじゃんか!
ざっと見た感じ複雑な部品を使っているわけではないようなので、自分で作れそうな気がします。特にPC-8001/8801用のはX1の仕様に近いので参考になりそうですしね。え、さっき「作るの面倒」って言ったじゃんかって?そんな昔のことなんかもう忘れちゃいましたよ…。
仕様を考える
オリジナルのCRTエミュレータでは、主要な半導体部品としてUSBコントローラ・CPLD・SRAMが使われています。そのままのものを作って動かせれば楽なんですが、残念ながらボードの回路図とCPLDのロジックデータのソースは公開されていません。それにPC-8001/8801はCRT出力コネクタにドットクロック信号があり、CPLDはこれを使って動作しているため、そういう信号がないX1では別のアプローチが必要です(というかそれがあったらアップスキャンコンバータの時に苦労しなかったんだけど…)。クロックをいろいろ作ってなどと考えればCPLDではなくFPGAを使った方が断然楽になるでしょう。
CPLDの中身はわからなくとも、「他の環境への移植用」としてWindows用とMacOSX用のソースが公開されていますので、転送フォーマットは推測可能です。
USBコントローラはFTDIのFT245Rが使われており、手元に秋月電子通商のFT2232Dモジュールがあったので流用すればアプリもそのまま使えるかと考えたのですが、その後いろいろ資料を漁るうちに最近はUSB2.0の能力を生かす「同期FIFOモード」というものがあって、それをサポートしたチップであるFT232Hを搭載したモジュールが秋月から入手できることがわかったので、こちらを使ってみることにしました。
高速に転送できるとなると、別にオリジナルの転送フォーマットにこだわる必要はなくなってきます。オリジナルでは転送時間を短縮するためか、フレームをSRAMにキャプチャしておき次のフレームとの差分をランレングス圧縮するという手法をとっていました。差分だけではそのうちゴミが溜まってきますので32フレームごとにまるまる1フレーム分送るということもしています。もしそのまるまる1フレーム分の処理が間に合ってしまうなら別に圧縮とか余計な処理は必要なくなるのではないでしょうか。ましてや転送が高速化されているのなら…。
FT232Hは60MHzのクロックを基準に同期でデータの入出力を行います。60MHzというのはつまりUSB2.0 Hi-Speedの480Mbpsの1/8、バイト単位に入出力する最大速度のためのクロックそのものですね。これはあくまで性能という意味では公称値ですから、本当に60MB/secで転送できるわけではありません。送信バッファは1KBしかないのであっという間に満杯になりますし、その前にプロトコル上送受信するための準備もありますから、それを差し引くと30〜40MB/secがいいところのようです。
1ライン分のデータが確定したら送信するという方式を考えます。水平ブランキング期間もありますのでそれなりの時間は稼げそうです。それに1ラインごとにデータ送信できれば1フレームまるまるとか記憶するためのRAMも必要ありませんしね。
データ送信は水平走査に従いますので、1ラインあたり640ドットを送ることになります。1ドット=1ピクセルをPC側アプリでの1ピクセルに対応して考えるならそれだけで1ワード、つまり1ライン640ワード必要です。本当はワードという表現も不正確で今時の仕様なら1ピクセルあたり3バイトで考えないといけませんが、幸いにもデジタル8色なので1バイトにまとめても構いません。ということで640バイトの送信…これを200ライン繰り返すわけです。1フレーム16.7msですから
640×200÷0.0167=約7.7MB/sec
てことで、実効30MB/secと見積もってもその1/3以下で転送可能ということになります。でもですね、RGBの3つのピクセルを1バイトにまとめているといっても3bitしか使ってないのですからもったいない。残り5bitも余らせてないで詰めたらどうでしょう?
(640÷8×3)×200÷0.0167=約2.9MB/sec
さらに半分以下になりました。実際に8bitまるまるデータにしてしまうとその先頭がどこなのか判別することができなくなるので、データ部は7bitにして最上位ビットが立っていれば先頭とか示すようにする必要があります。それでも
(640÷7×3)×200÷0.0167=約3.3MB/sec
というぐあいですね。後の心配はビットシフトなどをして表示データとして展開する処理が転送と転送の間の時間でやりきれるかということですが…PCの性能やプログラムの構造にもよりますし、きっといけると信じてチャレンジしてみましょう。
ボードについて
というわけで作ってみたのがこちらのボード。
使用したユニバーサル基板はサンハヤトのICB-93SGH。部品探しを兼ねてジャンク箱を漁ってみたら、以前実験用にICとかコネクタとかちょっとはんだ付けしたボードが出てきましたので、ちょうど5V→3.3V変換入力用にそこに載ってたHC04も使えることですし、流用することにしました。 中央に鎮座するのはヒューマンデータのXP68-01というFPGAボード。68ピンPLCCソケットに収まる形状になっており、こういうコンパクトにまとめたい工作にはぴったりの製品です。このボードにはザイリンクスのSpartan-6シリーズFPGAが搭載されています。 一方、基板の上の方の横長の基板は秋月のFT232HLモジュールです。ソケット実装にしようかと思いましたがモジュール付属の連結ヘッダがちょっと太くて力をかける必要があり、ソケットに入らないわけではないのですが端子の数が多くて押し込みきれなかったことから、直接実装することにしました。 D-Subコネクタがありますが、これはVGA出力です。USB接続が前提なのにVGA出力があるのはデバッグ目的です。スキャンコンバータ機能も入れることで、X1の画面がちゃんとFPGAに取り込まれているのを確認することができるからです。 |
ハードを具体化する際、できるだけ手持ち部品で済ませようとして、当初はSpartan-3 Starter KitのXC3S1000搭載バージョンにしようかと考えたのですが、Spartan-3はツールの最新版ではサポートされていない…ことはなかったのですがそう思い込んでいたため、かと言ってDE0は使用する目的が別にありますので、この際せっかくなので使う動機が見つからなかったDE0-nanoを買ったのですけれど、届いた翌日XP68-01を死蔵させていたことを思い出した次第。なおヒューマンデータの製品は形状が同じだと搭載デバイス・メーカーを問わず電源・端子機能とその数が同じになるようデザインされているので、もしFPGAの機能や容量に不足があれば別のものに交換することができるようになっています。
こちらは基板の裏面。配線は基本的にUEWを使っています。ただUEWは被覆を融かすためにハンダごての温度を上げる必要があり、そうするとピンヘッダのプラスチック製ベースも融けてしまうため、一部はスズメッキ線にて配線するようにしています。 そして見たところスキあらば銅箔シールが貼りまくられていますが…これはFT232Hが60MHzクロックを出力すると盛大にノイズを発生し電源がバウンスしてしまう現象を抑え込むための方策です。アップスキャンコンバータ機能だけならばもっと簡単な電源でも問題は起こらなかったのですが、さすがにこれだけの速度のクロックには耐えられなかったということなのでしょう。 理屈ではいけそうとは思いつつ、失敗するかもしれないので簡単に作りたいと思いながら作業していたはずなのですが、出来上がってみると前作のRGBI対応アップスキャンコンバータより綺麗な仕上がりになっちゃいましたね…。 |
で、サンハヤトのICB-93SGH基板を使ったということは、まぁタカチのMXシリーズアルミケース(PDF)に収めたいという魂胆があったからで…一応「最悪でもアップスキャンコンバータとして使える」ことを確認した後にケースを発注してるので、自分の中では辻褄を合わせているつもりなのですが(^^;;。なお今回は黒色をチョイスしました。
上面より |
出力側より | 入力側より |
ケースに詰め込んでみるとこんな感じですね。基板の切り欠きはDINコネクタの入る場所を確保するためでした。 |
出力側のコネクタ。VGAコネクタが前作アップスキャンコンバータより低い位置についていますが、コネクタ基板の取り付け方を工夫したおかげです。 右のはミニUSB-Bコネクタ、FT232Hモジュールのコネクタがそのまま見えてます。 |
入力側のコネクタ。左の角形8ピンはおなじみデジタルRGBコネクタですね。ヒロセの製品がまだ手に入ると思っていたらなんと2013年6月頃に生産終了となっていて、流通在庫品すら手に入らなくなっていました。あきらめて別のコネクタを使用するのが本来のスジですが、古い東芝のアダプタを知人より譲っていただき、そこから部品取りして使うことにしました。ちなみにこれはSMK製ですね。 一方右の丸DIN形8ピンはテレビコントロール端子です。今回X1用に割り切りましたので、逆にX1で使えるものは積極的に使っていこうということで採用しています。とは言え、実際に使っているのは電源だけなのでこのコネクタでなければならないこともなかったんですがね…。 真ん中の小さい穴はミニジャックで、キーボードとマウスの信号を出す4極タイプのものを使っています。単純に考えるとキーボードで3極・マウスで5極の端子を持つコネクタを使用しているのですから信号は8本あるように思うかもしれませんが、どちらも電源(+5V)とGNDがあるのでこれは共通化できます(しかも電源は別端子から供給するので接続不要、マウスにGND端子が2つあるので合わせて5本の電源がGND1本だけにできる)し、残りはキーボードの1本とマウスの2本、すると4極プラグでまかなえてしまうのですよ。 それぞれのコネクタがケースに対して小さくないので無理矢理並んでるようにも見えますが、こんな無茶も今回限りのネタというやつですね。現実的にはD-subとかその他入手しやすいコネクタでもなんら問題はありません。昔のケーブルをそのまま使えないというだけのことです。 |
4極プラグケーブルの方は、さすがにマウスとキーボードを合体させたケーブルが既製品で存在するわけはないので、ジャンク品も活用して作りました。特にミニDINコネクタはコネクタ単品のものを使うと軸が太すぎてマウスコネクタに入らなくなるんですよね。 |
こういうACアダプタを接続できるケーブルを作っておけば、別にX1でなくても電源供給できますよね。 |
最後に回路図をどうぞ(リンク先は縮小前のものになります)。といってもFPGAにいろいろぶら下がってるという以上の情報はなきに等しいのが最近の私のパターンになりつつありますね。機能実現の肝はFPGAロジックとPC側アプリですから…。
作った物を公開するという考えからそのまま・ありのままの回路図になっています。図中FPGAからFT232HLモジュールに対してAC4(SIWU#)への配線は不要です。あと5V→3.3V変換のHC04も反転バッファを使用する意味はなく、単に手持ち部品を流用したからそうなっているに過ぎません。F125もF
TTLである理由も必要もなく(3.3V→5V変換用なのでさすがにCMOSは使えない)、使い方も単なるバッファとして使うつもりがオープンコレクタでないといけないことがわかってFPGAのバッファ出力みたいなことになってますけど、本当はLS07で十分です。
そして一番の間違いはJTAGのプルアップ・プルダウン抵抗です。XP68-01のページを見るとわかりますが、逆にしてしまっているのです。最大の目的が端子の固定なので一応実害はありませんが、端子の意味を考えるとせめてTCKはプルダウンであるべきですしTMSはプルアップであるべきでした…。
そういった注意点はともかく、モノ自体は大きくない基板ですしここに技巧を凝らす手間を省きたかったため、単純にFPGAに対して入出力するだけのものにしたつもりです。
中身のお話し
★ドットクロック再び
前述の通りCRTエミュレータ機能の実装の前に、確実にX1の画面が取り込めていることの確認のためアップスキャンコンバータ回路をこちらに移植することにしました。ですがそのCRTエミュレータの存在を前提にすると前回の4倍オーバーサンプリングそのままでは都合が悪そうなので、もう一度その取り込み部分について考える必要がありそうです。
4倍オーバーサンプリングでは、文字通り4倍速いクロックを使用して、外部から入力された1ピクセルをコンバータ内部で便宜上4ピクセルとする考え方です。言い換えればできるだけアナログに近い動きになるようデジタルながら工夫するということになります。ですが当然ながらそれは4倍速のクロックで出力することを前提とした考え方であり、PC上で表示させるまでにどこかで1/4に絞り込まないといけません。
そうではなくて、最初から送出側のドットクロックと同じ速度で取り込みできれば、USBへそのまま送出することができるわけですから構成も楽になります。前作でもそうしたかったのですが、機種によって異なるドットクロックの違いを吸収するためやむなく4倍オーバーサンプリングを採用したのでした。今回はX1用と割り切ってますので、そのドットクロックである14.318MHzを使えば問題はなさそうです。
…というほど簡単な話ではなく。
X1とこの回路は完全に独立した部品ですから、位相関係を決めることはできません。回路も違えば電源が投入されるタイミングもまちまちなので、例え最初の立ち上がりエッジがいつ出現するかわかっていたとしてもその時間差までは確定できないのは当たり前です…と偉そうに語ってますが簡単になるのでこれで動けばいいなぁと一度は組んでみましたよ。ところがというかやっぱりというか、EIZOのM1950-Rではそのクロックの違いで発生したジッタによって画面はブレブレ、CenturyのLCD-10000V2に至っては「信号が入ってない」とまで言われてしまいました。つまりはこういうことです。
送り側クロックで送出されたドット(図中ではパルス)を基準に見ると、受け側のクロックエッジはドットの始まりから終わりまでの存在確率で出会います。受け側回路に取り込まれた後のパルスと比較すると、最大1クロック分の差が発生することになります。
そしてもう一つ考慮しておかないといけないのは、送り受けそれぞれのクロックは正確に同じ周波数とは限らないということです。受け側回路の方の誤差が大きいという可能性だけでなく、実は送り側の方の誤差が大きいとか、それぞれ逆方向にずれているとか、いろんな可能性が考えられます。もちろんFPGAの方が内蔵のクロック生成ブロック(ザイリンクスだとDCM、アルテラだとPLL)でもジッタが増えるという要因を抱えているという事情もあります。それぞれの理由により、単に位相差が定義できないだけでなく、それは刻々と変化するという性質を持つためジッタを発生し、結果として画面のブレとして現れるのです。
ではここから解決編です。
まずジッタの抑えこみから考えます。さすがにゼロにすることはできませんので、その量を減らす工夫をするわけです。
ジッタの幅は使用しているクロックの周期に支配されます。もし1周期以上ずれたとしても、その時は隣のクロックエッジに担当が変わるからですね。ということは、速いクロックを使えばジッタの幅が少なくなることになります。というわけで以前と同じ4倍速のクロックを使用することにします。これでジッタはドットクロックと同じ周波数だった場合の1/4になります。
4倍速のクロックで、まず水平同期信号の始まりを捉えます。それ自体はラインダブラーのためのメモリ書き込みアドレス決定のために以前からやっていましたが、今回はちょっと取り扱いが違います。この水平同期信号の始まりから2ビットのカウンタを回すのです(図ではわりとすぐにカウントスタートしてますが、実際には数段のフィルタが入っています)。4カウント1周ということはつまり本来のドットクロックの周期で特定のカウント値が現れるので、そのタイミングでドットを取り込めば本来やりたかった「1ドット1クロック」と同じ動きになるわけです。
「特定のカウント値」は、取り込むドットのできるだけ真ん中になるよう選択します。ジッタによって前後にずれますが、ドットの真ん中であれば安定しているはずなので取り込めたり取り込めなかったり…などという現象は発生しないはずです。もちろんそれでも相変わらずジッタは発生しますが、LCD-10000V2でもこの程度のジッタなら誤差として吸収してくれるようです。これにより以前の方式ではモアレが発生していたような絵でも今回はキレイに表示できるようになりました。
なお、24kHz(400ライン)でもドットクロックが21.477MHzになるだけで要領は変わりません。できるだけ誤差の少ないドットクロックを作るようにして、ジッタの影響を受けないタイミングを見いだす(現物突き合わせが一番確実)ことでキレイに取り込めるようになりました。
★送信データフォーマット
オリジナルのCRTエミュレータでは、先頭を表す特定のバイト列があり、その後1画面分のデータが続くという構成で転送されるようです。水平ブランキング期間でも画面を取り込みデータ送信しているようで、何ドットごとに改行するのかの設定が正しくないと画面がナナメに崩れるみたいです。左右に大きく余白がありますがおそらくこれが水平ブランキング期間なのでしょう。とはいえランレングス圧縮はしてるので増加量も許容範囲なのだと思います。
今回非圧縮にてデータ転送をするにあたり、それでもできるだけデータ量は減らしておきたいと考えました。水平同期の理屈からして同期信号から画素までの時間は(個体差や設計差はともかく)決まってるでしょうから必要分だけ抜き出すことは可能なはずであり、毎ラインの水平同期信号を見てますからカウントしてライン番号を付けて必要な範囲だけ使うのも可能なはず。いろいろ試行錯誤の末このようなフォーマットで送信することにしました。
ライン番号 上位 |
ライン番号 下位 |
赤 | 緑 | 青 | 赤 | 緑 | 青 | … | 赤 | 緑 | 青 | 末尾・ 画面モード |
先頭は何ライン目のデータかを示すライン番号です。200ラインモードでは1〜399の奇数で、400ラインモードでは0〜399にて表現されています。200ラインモードでも400ラインのような番号付けになっていますが、PC側のアプリで処理をできるだけ単純化するためにこうなっています。
その後は276バイト分表示データが続きます。RGB交代で並んでいます。末尾コードだけbit7が'1'になってますので、これに出会うまで1ライン分として取り込むようにしています。
また末尾コードは現在の画面モードを示します。200ラインモードだと0x80、400ラインモードだと0x88です。これで画面表示のためのデータ展開時の挙動(200ラインモードでは2ラインずつ描画する)を変更するようにしています。
このようにデータごとに描画されるべき場所が示されていると、ライン単位であれば順番にデータ送信しなくとも良くなるというメリットが生まれます。垂直同期周波数は60HzなのでX1は60fpsにて表示を行っているわけですが、例えばあるフレームは偶数番目のラインを送り、次のフレームで奇数番目のラインを送れば、2フレームで1画面を構成する=2フレームごとに表示することになり、つまり半分の30fpsでの表示が簡単に実現できます。同様に4ラインごと(1/4…15fps)、8ラインごと(1/8…7.5fps)のモードも作って、必要に応じてFPSレート変更ができるようにしました。
この方式の欠点は最終的に表示する画像が実際には複数のフレームからの切り貼りになってしまうことで、動きの激しい絵ではかなりグチャグチャな見た目になってしまいます。理想的にはある1フレームをゆっくり送り出すのが見た目もいいのですが、外付けRAMを端折ったこの回路でそれをやると1フレームだけは60fpsの速度で送信することになり、PC側に問題が起こりそうなので回避しました。ラインごとの送信間隔が広がることで処理能力が低いマシンでも表示しやすくなると考えたのです。
★キーボード、マウス、そして設定
作っているといろいろ妄想も広がるもので、ふと回路を見直すとそれが実現可能だったりなんかして、部分的に作り直しながら実装した機能があります。それがキーボードとマウスの模擬機能で、CRTエミュレータと言わず「コンソールエミュレータ」と名乗るもとになっています。キーボードは単方向のシリアル通信、マウスは要求−応答によるシリアル通信とどちらもハード的には簡単に実装できるものです。
FT232Hを介した通信はFPGA→PCの一方的な使い方しか考えてなかったのですが、もちろんPC→FPGAの方向の通信も可能です。FT232Hのデータポートも双方向なので半二重のような調停が必要ですが、それさえできればちょっとしたデータを送るのにたいした支障はなさそうに思えます。というわけでこのようなデータフォーマットを考えてみました。
ヘッダ | データ1 | データ2 | データ3 |
ヘッダはキーボードかマウスかのデータ種別判定に用いるコードが収まり、その後に続くペイロードはマウスのデータを想定して3バイトとしています。というようなことをつらつら考えていたら、これはボードへの設定値送信にも使えるじゃないかと気づき、そんなこんなで次のようなデータを送り受けするようソフト・FPGA双方に実装しました。
ヘッダ | データ1 | データ2 | データ3 |
0xC0 | 動作 FPS |
(空き) | (空き) |
0xC1 | 200ライン用 X方向オフセット |
200ライン用 Y方向オフセット |
(空き) |
0xC2 | 400ライン用 X方向オフセット |
400ライン用 Y方向オフセット |
(空き) |
0xC8 | フラグ | アスキーコード | (空き) |
0xCC | ボタン 移動量演算指示 |
X軸移動量 | Y軸移動量 |
コード0xC0は動作状態の決定で、アプリがまともに動いてないのにデータ送信があると(しかも大量に)ドライバが困っちゃうかと思い、アプリ側から能動的にオン・オフできるほうが良いと思って実装しました。FPSは上で説明したFPS変更のための設定ですね。
コード0xC1と0xC2は送られて来るデータが生データのどの位置のものを切り取るのかの指定です。CRTC設定により微妙に異なることを想定し、200ラインモードと400ラインモードで別の設定ができるようになっています。
コード0xC8はキーボードのデータで、ここのデータがそのままX1本体に送信されます。フラグというのはSHIFTとかCAPSとかが押されているか・今回の送信のアスキーコードは有効かというのを示すものですが、本体ではあまり使われてなさげです。コード無効のフラグ設定でも使用可能なアスキーコードでデータが入っていれば入力されてしまいます。なのでアプリもフラグ設定は簡単に済ませるようにしています。
コード0xCCはマウスのデータで、PC側アプリのマウスボタン操作と移動操作の度に送っています。X1turboのマウスは移動量を本体に送るようになっていますが、PC側操作と実際のマウスデータ取得タイミングは一致しませんから、PC側で検知した移動量をボードに送る度ボード内でそれを加算・減算するようにしています。加算か減算かは演算指示ビットで示します。本来は符号付8bitで移動量を表せば十分なのですが、マウスのデータとしてオーバーフローフラグとアンダーフローフラグが別々にあるのを再現するためには加減算を分けて扱った方が都合良いと考えたためです(オーバーフローにしてもアンダーフローにしてもキャリーまたはボローが発生するが、符号付数値の足し算だけではそれがどちらか判別できないため)。
ボードだけだと設定のためにスイッチや表示器が欲しくなりますが、今回はPCとの接続が前提となっているのでこういう手段での設定ができるのがありがたいですね。設定やマウス・キーボードデータは毎ラインのデータを送信した後取り込まれます。
★FT232Hについて
型番こそ"232"ですがパラレル通信を示す"245"のモードを持っており、あらかじめFTDIの配布するユーティリティであるFT_Progにて
のように設定しています(最後のはFT_OpenEx(〜)にて「FT_OPEN_BY_DESCRIPTION」を指定しているため)。また同期FIFOモードで動作させるにはFT_SetBitMode(〜)でそれを指示する必要があります。
同期FIFOモードではCLK(AC5)端子より60MHzクロックが出力されますが、これはFT_SetBitMode(〜)で指定して初めて出力されるようになります。しかし一旦出力され始めると、FT_Close(〜)で接続を閉じても止まることはありません。
送受信バッファはそれぞれ1KBありますが、これがどのタイミングで送られるのかよくわからなかったので、積極的にSIWU#信号(AC4端子)を使おうと考えていました。今時のシリアル通信は送る準備が整うまでが面倒だったり時間がかかったりするのでできるだけ一度にまとめて送るようにする方が全体としては効率が良くなるため、バッファに送信データが書き込まれてもすぐには送信せず追加データを待つことがよくあります。次のデータまで間隔が空くことが分かっていれば即送信してもらった方がいいんじゃないかと思ったのですね。
SIWU#信号はSend Immediate(即送信)とWake Up(起床)を兼ねた信号で、省電力のためスリープモードに入っている場合はその解除を指示する信号として使われます。スリープモードでなければ即時送信を指示する信号になります。データシートも確認しながらFPGAとアプリの設計を進めたのですが…。
これは開発中のアプリの様子。そりゃあいきなりうまく動くとは思ってませんでしたが、さてこれはどうしたものか…と悩んでいた時のものです。おおよそには表示できているものの、この乱れっぷりは…やっぱり取りこぼしとか発生してるんかなぁ? DirectXを使えば解消できる? それ以外の原因で取りこぼししてたら…? |
うんうん唸りながら、横目でDirectXの使い方をレクチャーしてくれるページを探しつつ、何が起こっているのか考えていたんですが…ふと気づきました。もしかして本来表示すべきところじゃないラインに表示されてたりする?
取りこぼしが発生するならば、例えば先頭から何バイトかが欠けてその後だけ受信されるとか、1ラインの中でデータが失われるのではないか? このノイズだらけの画面はそうやってデータが左右にずれたがゆえにノイズだらけになったのではないかと考えたのですが、そうだとするとちょっとおかしいのですよね。というのも、この時はまだ試行錯誤中で最終形ではなかったとは言え、送信フォーマットは上記のようなRGB交代でデータを配置したものになっていました。もし受信時にデータ欠けがあるなら、色だってずれることがあるはずなので、どのラインも都合良く3バイトの倍数で欠けたか全てのラインで偶然色ずれが起こらなかったということになります。それはちょっと変。
そしてX1のロゴのところをよく見てみると、もうちょっと上とか下とか他のラインのところに描画されてれば合ってたんじゃないの?と思いたくなるような線があるような気がするのです。そういえば上のSHARPのロゴ、HやRやPの縦棒って妙に綺麗ですよね…Sとかすごい乱れてるにも関わらず。絵の乱れがライン単位での左右のずれならSと同じぐらいHも乱れるはず…?
ちょっと試しに、真ん中に縦線1本、左上から右下への斜め線1本を表示させてみたところ、こんな画面に。 ふむ、縦線は間違いなく乱れてないのに斜め線だけ何かの分布図のようにバラけています。縦線が整っている以上これは左右ではなく上下にずれたということになりますね。 |
でもなんで? データはとりあえず概ね欠けてないと考えて良さそうですが、上下にずれるということは、ライン番号が化けているとか、違う番号のデータがついてるとか、そういう齟齬が発生しているということになるのですが…FPGAにデバッグ用のデータを出力するよう仕掛けたりいろいろ調べても、化けたりずれたりしている様子もなく…。
…ほとほと困り果てていたある時、データシートのSIWU#信号の説明にこんな文章があるのに気づきました。
The Send Immediate portion is used to flush data from the chip back to the PC. |
即時送信するとデータが消えるよ、というのはそりゃ「送って」とお願いしたらバッファにあるデータは全部送っちゃうのだからバッファからは消えるよね…という意味だと思ってたのですが、まさかもしかして、送らずに消すこともあるという意味なんですか? まさかね…と思いつつSIWU#を何も操作しないように回路を変更したらほとんど化けなくなった! どうして?!
これでうまく動いているのですからSIWU#信号の操作は必要なかったと言うことなんでしょうね。何が起こっていたのかチップの中のことなのでよく分かりませんが、ライン番号と何データか送られた後SIWU#が有効になったことによりバッファがクリアされ、別のデータに中身がすげ替わった…ということなのでしょうか。実際には時々更新されるような変な表示頻度だったので、表示に使えた「一見正しそうなデータ」の数もあまり多くなかったのかもしれません。
でもバッファ中に今あるデータを送って欲しいということを指示するためにSIWU#があるわけですから、SIWU#でバッファの中身を消しちゃったら意味がありません。もしかしたら送信動作中だったりしたら消すとか?
なんにしても使いにくい機能だな…。
というわけでせっかく配線したSIWU#は使わないことにしました。多分数バイトとか少量のデータをすぐ送りたいという用途のものなのでしょう。今回のように高速に大量のデータを送りたいという目的には合わないのだと思います。
★PC側アプリケーションについて
あまり語りたくないな…(ぉ
まず馴染みのある(慣れている、ではない)Windows用から着手しました。基本的にはオリジナルのCRTエミュレータのソースがありますので、それをベースにしながら…のはずだったのですが、転送フォーマットを独自に決め、同期FIFOによる高速データ受信への対応、キーボードとマウスの機能…などいろいろ盛り込む内に原形がなくなりました。比較すれば面影ぐらいは残ってることがわかりますが…。
FIFOデータ受信のための手法はヒューマンデータのこちらのページを参考にしました。肝は受信専用のスレッドを用意することで、受信スレッドでは受信以外の処理をできるだけ避ける必要から、リングバッファを介してメインのスレッドの描画ルーチンに受信データを渡すようにしています。なおリングバッファのデータ書き込み(USBデータ受信部)側ではデータ読み出し(描画部)のポインタがどこにあるかチェックしていません。高FPS設定だとUSBデータ受信が描画処理を上回って追い抜くこともあり得そうですが、読み出し側でそのデータが使用できるかチェックしてますので問題は発生しないと思います。まぁ要するに「手抜き」なんですが…止まっちゃうよりはマシかと…それに見たところ表示される画面もノイズっぽいものも含めて気になるものは現れてませんしね。
でもやはり描画処理にはそれなりに時間がかかっていると見えて、データチェックの甘かった時は高FPSにて異常終了することが多々ありました。DirectX導入も何度か考えたのですが、それまでにできることをやろうということで地味でベタですが一部をループ展開したり演算をテーブル引きにすることで安定して動作できるようになりました。
上でも述べたFPSを落とす対策は、アプリ側では特に何もしておらず、399ライン目のデータが来たらその時の描画バッファを画面に転送するということをしているだけです。200ラインモードでもライン番号を1〜399の奇数にしているのはこのためです(199ライン目は399ラインとして送られてくる)。200ラインモードでもドットバイドットだと縦に圧縮されちゃいますから、描画データの準備では隣接する2ラインに同じデータを入れて「ラインダブラー」として動作するようにしています。
X1の画面が60fpsで更新されていることから高FPS対応の動作が必要と考え、というかオリジナルでのTimerイベントにある1/60秒周期動作なんてそもそもできないので、ゲーム製作関係のブログ等を参考にしてテンプレにあるGetMessage(〜)関数でのループ(イベント発生まで動作停止)ではなくPeekMessage(〜)関数によるループ(こまめにチェックしイベント発生なければ他の動作)に変えました。おかげで高速な処理が可能になったのですけど、メインスレッドがスリープする瞬間がないので負荷が高くなり、冷却ファンが猛烈に回転することに…。
試しにUSBデータを受信するスレッドにてリングバッファにデータ書き込みした時SendMessage(〜)やPostMessage(〜)関数でメインスレッドに描画処理するようメッセージを送るようにもしてみましたが、取りこぼしが多いのか思ったようには表示できませんでした。見た目には画面がほとんど更新されない…というものですが表示できないと思ったデータは捨てますしいちばん下のラインが来ないと表示動作に入りませんから、データがボロボロになってるんだと考えています。
ボックスやアプリの初期値だけで動かしているのは賢くないので、iniファイルに設定値を書いておけばそれを取り込むようにしました。オリジナルはそうだけど今風のアプリならレジストリだろう…とは思うもののつい面倒で…。ただこの設定値をボックスに送信するタイミングが早いのか、機能を入れてからちょっと不安定さが増したような気がします。というか最初は2回目が起動しないという困った状態になり、いろいろ悩んだところ、I/Fを初期化するFT_OpenEx(〜)関数を実行した後しばらくFT_Write(〜)関数が実行できないらしいと判断、最初のFT_Write(〜)の直前に数百msの待ち時間を入れることでだいたい安定しました。これがなぜかFT_OpenEx(〜)の直後だと効果がないみたいなんですよね。何かのファンクションでFT_Write(〜)が使用可能かどうかわかればいいんですがそういうのはないみたいですし、仕方ないとは言えかなりイケてないプログラム…。
なんとか完成
気になるパフォーマンスなんですが、なんとか60fpsでも表示できてます。開発に使用した環境がMac mini(2012later、Core i7/2.7GHz)上のParallelsで動かすWindows7(64bit)という速いのか遅いのかよくわからん構成なのですが、それでもなんとかなってるようですね。さすがに部屋が暑くなってしまうと微妙にパフォーマンスを落とされるのか間に合わない動きもチラホラ見受けられますが…。
カラーイメージボードIIとの組み合わせ。単にソフトを実行してるだけだとX1エミュレータと区別つきませんので、考えた末実機でないとできない芸当をということでチョイス。 黒ばっかりで見にくいですがノートPCの左にカラーイメージボードの外部ユニット、その左にコンソールエミュレータがあります。カメラは右手手前からその外部ユニットを捉えています。 |
キーボードも使えます…てことで上海。カーソルがテンキーで移動するゲームですね。 うーん、詰んだか…。 |
カラーイメージボードIIとの組み合わせで作ってみた動画です。カメラが遠いと文字も読めないくらい小さくなるので、Windows7の拡大鏡ツールで2倍にしています。取り込みの色が淡すぎて見にくいな…。
FPGAおよびアプリのソースコードはこちらからどうぞ。