投稿日 : 2018/01/31 2:22:41
PC-G850の回路図とLCDの低レベル制御
前回の記事で作った画像表示ライブラリですが、これはほぼ全てC言語で書かれたものであり、画面いっぱいに画像を表示しようとするととてつもなく遅いという問題があります。このため、通常は更新のあった部分だけを書き直すようにして、それ以外は以前の画像を残すなどの最適化を行う必要がありました。

ということで、ゲーム用ライブラリと言っておきながら実はあんまり使い勝手が良くなかったので、本腰を入れてアセンブラでもっと高速化出来ないかを考えてみることにしましたが、いろいろ調べている最中にLCDのI/Oポートについて書かれたページがあり、LCDを直接制御することが出来ることが分かりました。

ちなみに今までは画像のビット列を最終的にIOCSを経由して画面に表示させていましたが、このIOCSで行っていることを自力でやることで、さらに高速化することが可能になります。

そしてさらにいろいろ調べていたところ、某ポケコンサイトにてポケコンの回路図が公開されており、その中にLCDを制御しているチップの型番を見つけました。そこでその型番のデータシートをネットで手に入れたところ、上記のLCDのI/Oマップとほぼ同等の機能となるコマンドが存在し、100%そのデータシートのチップと同じものがポケコンに入っていることが分かりました。

逆に言えば、このデータシートに書かれているコマンドをポケコンから直接LCDに送ることで、今まで機能が不明だったLCDの全制御が可能になるわけです。

いやぁ当時はインターネットなんてなかったから、こういった資料とか業者じゃないと持ってなかったりするので、いい時代になったなぁと今更ながら感じるわけでw

ということで、今回はLCDの制御方法についてまとめてみました。

ポケコン回路図(改)

LCDの前にまずは、こちらのサイトにあるPC-G850の回路図について、よく見たら11pinのVDDとGNDが逆になっていることに気づきました。

はじめポケコンにタイマーICなどを使って割込みを入れられないかと回路図を確認しようとしましたが、画像が粗いためクロスしている線がくっついているのか分かりにくかったので、先に線に色付けして見やすくしてから確認しようと思い、回路を追いながら順番に線を塗っていたところ、なぜか電源回路からのVDDとGNDがどうしても11pin端子のVDDとGNDに繋がらず、おかしいおかしいと思ってふと自分で作った基板とかを見直したら、VDDとGNDがこの回路図と逆の状態で問題なく動いていることに気づき、これで回路図のほうが間違っているという結論に至りました。

結果的に11pin側で割込みをさせることは叶わぬ夢だったわけだけど、この間違いを発見した時はかなりの衝撃を受けましたw

ちなみにこれは先生用の教材に載っているものだそうですが、PC-G850の発売から今まで誰も気づかなかったことを考えると、実質20年間くらい間違った回路図が載っていたということになるので、もしこの回路図を見て電源を逆に付けるハードウェアを作ってしまっていたら、ポケコンかそのハードウェアが壊れていた可能性もあるわけで、今までそういう話が出なかったことを考えると、ある意味奇跡だったのかも。

逆に言えば今まで先生自らがこの回路図を見ながら何かを作ろうとしたことは皆無とも言えるので、その点で言えば先生はあくまでも先生でこういうのには興味無いんだなぁとちょっとさみしかったり。
※当時ウチの高校の先生も全然ポケコンのことを分かってなかったので、まぁ先生というのはそんなもんなんだろうなぁと(教師用資料があるなら見せて欲しかったし言ってくれって感じ)

ということで、色付けしてVDDとGNDを直したものを改めてDL出来るようにしたので、必要なら以下の画像を右クリックして名前を付けて保存してください。
※色付けが間違っている可能性もあるので、完璧なものだとは思わないでください

ポケコンのLCD制御ドライバはエプソン製のSED1560シリーズ

上記回路図を見れば一目瞭然ですが、セグメントドライバとコモンドライバという2つのチップでLCDを表示させる仕組みとなっており、またZ80からはデータバスと制御線がSED1560というセグメントドライバに接続されています。

さらに上記のLCDのI/Oポートの詳細ページと読み比べてみることで、このセグメントドライバはポート0x40と0x41に接続されていることが分かるので、これでポケコンからLCDの全ての制御を行うことが可能となります。

データシートは「SED1560.pdf」とかでググれば出てきたりしますが、1560以外のものも含まれたものもあり探すのが大変だと思うので、ひとまずこちらにもコピーを置いておきます。(まずければ言ってください)
SED1560データシート SED1560.pdf
※ちなみにたいていこういうのは英語です

このデータシートの一番最後にコマンド表があり、これがポケコンで使えるすべての機能ということになります。

データシートの見方

コマンド表の見方について、ここではポケコンと連動した解説をします。

以下はこのデータシートから抜き出した表です。


まずCodeのA0というのは、ポケコンでいう0x40か0x41のポートということになります。
つまりこのポートの最下位ビットがLCDドライバのA0に繋がっていると言えます。

補足としてAとはアドレスの意味で、このLCDには1bit分のアドレス、つまり2byte分のデータを扱うことが出来ます。
例えばこちらのように、256kbit(32KB)のメモリはアドレスとしてA0~A14が存在しますが、
15bitというのは0~32767の範囲の値を表現出来るわけなので、このアドレスの数が扱えるメモリ量のちょうど上限と一致します。
※ポケコンのZ80はアドレスバスとして16bit分のピンが用意されており、
 またこの16bitのアドレスバスの他にバンク切り替え用のピンが7つあるため、
 実質これらもアドレスバスと考えれば、結果的に23bitのアドレス(最大8MBのメモリが使用可)とみなすことが出来ますが、
 Z80がアクセスできるのは常に16bit内(レジスタ2つの組み合わせで16bit)という制限があるため、
 64KBを超える範囲にアクセスするにはいったんバンク切り替えにより、次にアクセスするメモリをこの64KB以内に配置させる必要があり、
 これがZ80の64KBの壁とも言われます。
 (ちなみにポケコンの場合は前半32KBがRAM、後半32KBがROMで、もしバンク切り替えにて同時に前半のRAMも切り替わったとすると、
  今までの変数などのデータにアクセス出来なくなってしまうため、実際には後半のROM部分のみが切り替わるようになっています)


次にRDWRはこれは排他的になっているもので、ようは読み込み(IN)か書き込み(OUT)かを指定するものです。
Z80にはRDとWRという端子があり、それぞれがそのままLCDに繋がっており、IN命令かOUT命令でその都度CPUが自動的に出力を切り替えています。

ちなみにRDとWRの上の棒線はNOTを意味します。
つまり0の時、LCD側はその状態が有効であるということになるので、上記表では全てWRが0なので、全てのコマンドが書き込み用と言うことになります。


最後にD7~D0がありますが、これが実際に出力されるデータとなります。
0か1が書かれている部分はそのコマンドの固定値であり、そのコマンドを識別するものなので必ずそのビットを設定しておく必要があります。

そして可変となるビット列がそのコマンドの中で有効な値の範囲となり、意味はデータシートの後半部分に1つ1つ詳しく載っています。
※当たり前ですが全てのコマンドは他のコマンドとビットが被らないように定義されています

例として、上記サンプル内の「Page address set(書き込み先の行番号)」に「2行目」と書き込むには、以下のアセンブラ命令を実行します。
    LD  A,0B2H         ; D7~D4のビットはBなので、下位4bitに2行目という2をくっつけてB2となる
    OUT (040H),A       ; Z80にはポート値と出力値をどちらも数値で指定出来るOUT命令は無いため、
                         いったんAレジスタにデータ値を入れ、それを指定のポートに出力している

ちなみに同じポートに対してOUTのあとにINを行った場合は、最後に行ったOUTの値が取れる(変数のような扱い)というわけではありません。
上記以外の他のコマンドを見ると分かりますが、同じポートでもINとOUTでそれぞれ機能が異なります。
※最後に出力した値を入力で取ることが出来るハードウェアもありますが、それはあくまでもそのように作られているからです(メモリとか)

座標を指定してデータの読み書き

データシートからポケコンのLCDは166x64ドット(8行)分のメモリが内蔵されており、それぞれのドットが画面内のドットの他、RUNやBUSYといった文字表示に割り当てられています。

そして、このメモリにデータを書き込むことで、LCD上でドットを表示させることが出来るわけです。
例えばBASICではGPRINT関数を呼び出すだけでドットを表示させることが出来たり、また有志が解析したIOCSのとあるアドレスをコールすることで、レジスタにセットした座標やドットデータのアドレス、また表示する個数を見て高速にドット表示する機能もあります。

GPRINT関数やIOCSでは、LCDに対して書き込み処理を行うことでドットの表示をしていたわけですが、これにはLCDの仕様が分からなければ出来ません。しかし、今まではIOCSを解析することである程度の制御は出来ていましたが、今回はこのLCDの仕様書を手に入れることが出来たわけなので、IOCSの解析はもう必要ありません。

なお、ドット描画に関してはX座標とY座標(実際には縦8ピクセルで1行)を指定し、あとはポート書き込みを行うことでLCDにドットを出力することが出来るのは、既に有志が解析してやり方は分かっています。
ついでに言うと、1つのデータを出力するごとに自動的に書き込み先のX座標も1つ右に移動するので、GPRINTのように連続でデータを流し込むことで一気に画像を表示させることも出来ます。

同じくLCD上のメモリを読み出すことで現在のドット値を取得することも可能で、これもX座標とY座標を指定してからINで読み取るだけです。
また、同じくこれも連続で読み取ることでX座標が自動で右に移動するので、高速にスクリーンキャプチャを行うことも可能です。

注意点としては、LCDにコマンドを連続で書き込む場合、早すぎるとコマンド処理を受け付けられないことがあるようで、IOCSの解析にてコマンドを書き込む前にコマンドが受付可能な状態かを把握する処理が入っていることが分かっています。

現状はこれだけの情報があれば描画に関してはほぼ何でも出来てしまうので、ゲームによってはこれで十分だったわけですが、本格的にゲームを作ることを考えるとちょっと不便な点があります。

ポケコンというかこのLCDドライバは常に1行(縦8ドット)単位で読み書きが行われます。

例えば縦8ドットの画像があったとし、これをドット単位でのY=4に表示したいとします。
この場合は画像ビットの下位4bitと上位4bitをそれぞれ0行目と1行目に書き込みしなければなりません。

以下はハートの画像をY=4で描画した場合のサンプルです。(Xは適当)

※画像は見やすいように色を付けていますが、実際には黒一色です

最初の0行目では画像の下位4ビットを4ドット分シフトアップして書き込みますが、この時シフトしてずらした下位4bit分は0が入ってしまいます。
これをLCDに書き込むわけなので、もしLCD上に背景画像が存在した場合は、0行目のシフトで0が入った部分が常に白で上書きされてしまうことになります。

同じく1行目でも画像の上位4bitを4ドット分シフトダウンして書き込みますが、0行目の時と同じくずらした上位4bitには0が入るため、これをLCDに書き込むと0が入った上位4bitの部分が常に白に上書きされてしまうことになります。

背景が白であれば特にこの問題は関係ありませんが、背景が存在したり他にキャラクターと同時に重なるような場合、重なった方が重ねられた方のドットを消してしまうため、画面的に正しく表示されないということになります。

例えば以下のような背景が先に描画されており、この上にハートを合成する場合を考えます。


これにハートを描画した場合、理想的な表示は以下の通りです。


しかし、実際には画像が無い部分は行単位で消去されるため、結果的には以下のようになります。

※削られた部分は分かりやすく明るくしています

ちなみにGPRINTではこの辺りをきちんと解決しているおかげか、ドット単位でY座標を指定することが出来るようになっていますが、これを自前で実装するとなるとかなり手間なので、それが理由であまりアセンブラでドット単位で動くようなゲームは作られなかった(作れなかった)のではないかと思っています。

ちなみにこれを考慮して前の記事では仮想VRAMを使った表示エンジンを作りましたが、これはドット単位で合成は出来ますが、オールC言語のため速度が遅すぎてまったく実用的ではありませんでした。

リードモディファイライトモードについて

上記のドットが消えてしまう問題についていろいろ考えているときに、LCDのデータシートを見て発見したのがこのリードモディファイライトモードです。

恐らくこの機能はポケコン上でまだ誰も使ったことが無いんじゃないかと思われるほど画期的な機能で、でも実は他の液晶にもこの機能が普通に付いているようで、意外と一般的な機能であることが調べてて分かったのですが、簡単に言うと通常は読み書きでX座標が自動的に移動するのを、書き込み時のみ移動するという機能です。

何を言っているのか分からないかもしれないのでここで詳しく説明しますが、ようは上記の白くなってしまう部分について、先に元のLCDのドットデータを拾ってきて、その上に新しいドットを合成し、最後にLCDに書き戻すことで、背景を残しつつ新しい画像を合成することが出来るということです。

単語の意味を考えれば一目瞭然ですが、
 ①リード(LCD上のドット値を取得)           … この時はX座標は進まない
 ②モディファイ(CPU上で取得したドットを加工処理)   … ここでLCDのドットと表示したいドットをORで合成
 ③ライト(加工したドット値をLCDに書き込む)      … ここでようやくX座標が次に進む
と、今までの問題を一発でスッ飛ばしてくれる超強力な機能なわけです。


以下はハートの合成部分だけを抜き出した流れです。
 ①合成元の画像をLCDからレジスタにリード
  
 ②ハートの画像をシフトして0行目と1行目用のデータを生成
  
 ③LCDからリードした背景とORで合成
  
 ④これをLCDに書き戻すことで、結果的に合成した画像がLCD上に表示される
  

これらは実際には1列(縦8ドット)単位で行いますが、これをうまく使うことで高速に合成画像を表示することが出来ます。

もしこのモードを使わず同じようなことを行う場合、リードやライトにて常にX座標が移動してしまうため、リード後にドットを加工後、書き込み時に再度カーソルを手動で戻す必要があるため、このコマンドのオーバーヘッドでとてつもなく処理が遅くなってしまうわけですが、このモードを利用すると、リード時にX座標が変化しないため、書き込み時に再度カーソルをセットし直す手間が省けるので、CPU的にもLCD的にもとても高速に処理出来るわけです。

ちなみに某ポケコンエミュレータでもこの機能は実装されていなかったため、実はポケコンエミュレータのソースコードを自分で直してこのモードにも対応してみたりしました。
おかげでゲームの開発がとてつもなく楽になりましたw
この辺りはあとでまた記事にします次の記事参考

リードモディファイライトモードの注意点

リードモディファイライトモードを開始すると、その時のXカーソル位置が記録され、終了と同時にそのカーソル位置が元に戻ります。
これを利用して画面上のカーソルブリンクを行うことが出来るようで、その時の流れがデータシートにありました。

このデータシートをよく見ると、リードモディファイライトモード時にLCDからデータを読み込む際は、実データのリードの前に必ずダミーリードを行う必要あるようです。

※ピンクの部分

なお、実際に使ってみたところ以下の流れで連続した画像が問題無く表示出来ました。

①X座標のセット
②Y座標のセット
③リードモディファイライトモード開始
④LCDダミーリード(データは捨てる)
⑤LCD本番リード
⑥読み取ったデータに自分でドットデータを合成
⑦LCDに書き込み
⑧次の列のデータがあるなら④に戻る
⑨無ければライトモディファイライトモードを終了




それと、実はすでにこの機能を利用した描画エンジンを作ったのですが、実際に合成処理をやっていると1つ大きな問題を発見してしまいました。

それはLCDの描画タイミングがCPUとまったく関係なく非同期で動作しているため、LCD側で表示中にCPUからそのドット内容を書き換えると、LCD表示がチラチラと点滅しているように見えてしまうことです。
今までは描画速度が遅いせいであまり気にならなかったのが、速度が格段に上がったため問題が顕著に表れるようになってしまったのだと思われます。

LCD側にはVSYNC待ちで一気に表示といった機能は無く、走査的にドットデータを画面に表示する仕様のため、つまりは書き換えている状態が画面に反映されてしまっている状態で、こればかりはCPU側でもどうしようも出来ません。
※これは画面をクリアして新たに画像を表示するときに、クリアした瞬間の状態がタイミングによって画面に反映されてしまうため、それで点滅しているように見えてしまうということです
 (クリアせずに常にORで足したり、1回の書き込みでクリアと表示を同時に行えれば点滅は防げますが、
  1列ごとにそれを行うとなるとリードモディファイライトの意味がありません)

最終的にはやはり仮想VRAM方式に戻ってしまったので、この機能はそこまで気にしないゲームで使うと良いと思われます。
例えば点滅を1つのエフェクトと考えて、シューティングゲームとかに使えば逆に効果的かもしれません。

SED1560のコマンド表

データシートを解析して、自分なりにそのコマンドがどういう意味なのかを以下にまとめてみました。

まずはポート番号の意味です。
内容 ポート番号 プログラム例 補足
LCDステータス取得 0x40 データ = __inp( 0x40 ); ステータスは上位4bitのみ
 0x10 RESET中か(RESETコマンドなどで待つ際に使用)
 0x20 ディスプレイがOFF状態か(0ならON)
 0x40 画面の水平反転(ミラー)状態(0なら通常、1ならミラー)
 0x80 LCDコマンドが実行中か(0なら次のコマンドが受付可能)
    ※OUT 0x40にてLCDコマンドを書き込む場合は、
     必ずこのビットが0になるまで待つ必要がある
LCDコマンド 0x40 __outp( 0x40,データ ); ※下記コマンド表参照
LCD読み込み 0x41 データ = __inp( 0x41 ); 実行するとX座標が自動的に次に進む
※リードモディファイライトモードの時は進まない
LCD書き込み 0x41 __outp( 0x41,データ ); 実行するとX座標が自動的に次に進む


以下はLCDの全コマンドです。
制御 状態 データ 補足
X座標の下位4bitを指定 0x00~0x0F X座標は0~165が指定可能
※リードモディファイライトモード時は呼び出してはならない
X座標の上位4bitを指定 0x10~0x1F
ディスプレイのON/OFF OFF 0xAE OFFすると全ての表示が消えるが、液晶自体は動作しているため
電源を切っているわけではない
※あくまでもドットを全て白にしているだけ
ON 0xAF
Yスクロールオフセット 0x40~0x7F ドット単位でスクロール可能(0~63)
※LCD上に反映されるオフセット値であり、
 内部のメモリやカーソル位置は元のまま
行の指定
(データシート上ではページと表現)
0xB0~0xBF ドット単位でのY座標値を8で割った値
画面を水平反転(ミラー)するか しない 0xA0 内部的には横が166ドットあるため、
ONにすると先頭が画面右の外に行ってしまう
またRUNやBUSYなども該当するドットが逆転するため、
正しく表示されなくなる
する 0xA1
画面の白黒を反転するか しない 0xA6 BUSYなどの表示も反転するため実用性はあまりない
※自前でBUSYなどを消すことは可
する 0xA7
液晶全面を塗りつぶすか 通常 0xA4 BUSYなどもすべてONになる
※現在描画中のドットは保持されている
ON 0xA5
LCDドライバのデューティ比 1/48 0xA8 通常
1/64 0xA9 鮮明になるが全体的に若干薄くなる
LCDドライバのデューティ比に1を足すか 足さない 0xAA 足した場合は1/49か1/65になるが、
どちらも見た目上はほぼ変わらない
※何のためにあるのかは実はよく分かっていない
足す 0xAB
行反転駆動を指定の行で行わせる 0x31~0x3F 0x31では2行目(1行目の最後のあと)、
0x3Fでは16行目(15行目の最後のあと)で反転する
ポケコンにはこのタイミングで1S割込みが入る
※キャンセルする場合は0x30ではなく0x20で行う
 (0x30は使用してはならない)
行反転駆動をやめる 0x20 割込みタイミングも遅くなるが1S割込み自体は来る
リードモディファイライトモードの開始 0xE0 このモードでは読み込み時はX座標は変化せず、
書き込み時に次の座標へ移動する
※このモード中はX座標の変更コマンドは使用できない
リードモディファイライトモードの終了 0xEE リードモディファイライトモードは必ずこのコマンドで終了させる
※終了するとカーソルは開始時の位置に自動的に戻る
LCDリセット 0xE2 全てのLCDパラメータを初期状態に戻す
※このコマンドではLCD電源の制御は行われない
LCDスキャン方向とタイプをセット 0xC0+n nにはハードウェアにあった値を指定する(PC-G850はSED1560)
 [n]
 0x00 CASE6 SEG 166(SED1560/SED1561)  ※通常はこれを使う
 0x01 CASE5 SEG 134,COM32(SED1561)
 0x02 CASE4 SEG 134,COM32(SED1561)
 0x03 CASE3 SEG 134,COM32(SED1561)
 0x04 CASE2 SEG 102,COM64(SED1560)
 0x05 CASE1 SEG 102,COM64(SED1560)
 0x06 CASE6 SEG 166(SED1560/SED1561)
 0x07 CASE6 SEG 166(SED1560/SED1561)
LCD電源のON/OFF OFF 0x24 液晶自体の電源を切ることが出来る
ON 0x25 LCDのパラメータが初期状態に戻るので再度設定が必要
※このコマンド実行後は50~100ms程度のウェイトを置いてから
 次のコマンドを書き込むこと
LCD昇圧回路のON 0xED 電源を入れた後にこれを実行しないと画面にノイズが入った感じになる
1.cocot 投稿日:2018/09/27 1:43:55
11pinの5VとGNDについて資料をもらった時に先生におかしいと指摘した事があります。回路図ではGNDとVGGとなっていますので、5V側が接地されてるんじゃないかと適当な事を言われました。VCC/GND/VDD/VSSをごっちゃにするなと言いたいですし、VGGはFETのゲート電圧なのでそもそも意味不明ですね。
2.管理人 投稿日:2018/09/27 19:35:16
実はあまり気にしたことは無かったですが、VCCとかVDDはそういう意味があったんですね。ということはCMOSで作られたCPU等のデジタル回路は基本VDDと表記するのが正解なんでしょうかね。そう考えるとポケコンのZ80の端子はVCC表記になっているのでこのCPUはCMOSではないってことに。それとVGGは11ピン端子の先でFETのゲートに入れるための電圧に使ってね❤という話ならまぁギリ間違ってはいないかもしれませんが、そもそもプラスとマイナスが間違ってるレベルの回路図の時点で、深く考えたら負けだと思いますww
3.名無し 投稿日:2018/10/16 13:04:38
VGG,GNDの件は、歴史的な経緯です。
4.名無し 投稿日:2018/10/16 13:21:24
黎明期のLSIはP-MOSでした。PMOSではGNDが+でVDDが-です。初代機のPC-1201はメインCPUと液晶コントローラ兼11ピンI/O用のサブCPUが搭載されておりました。両者とも既にC-MOSでしたが、サブCPUは黎明期のLCDの設計との互換性を持たせる為、意図的に電源の呼称をP-MOSの流儀で記述しました。これが、11ピンI/O電源が逆転しているように見える由来です。
5.名無し 投稿日:2018/10/16 13:26:05
一方、40ピンI/Oは、N-MOSのZ-80に合わせてあるので、GNDが-です。これでは、P-MOS時代を知らない若者達が混乱するのも道理です。
6.名無し 投稿日:2018/10/16 13:30:30
訂正 誤:PC-1201 ⇒ 正:PC-1210
7.名無し 投稿日:2018/10/17 6:19:54
おっと記載漏れ。流石にシャープも11ピン側にVCCを使いたくなかった様で、VCCではなくVGGと表記しましたね。それと、参考文献:https://archive.org/details/PC-1_Service_Manual_19xx_Tandy/page/n33
8.管理人 投稿日:2018/10/17 19:58:52
CPUにもいろいろ経緯があったんですね。初代のポケコンを調べてみたら1980年発売で物心付く前の物でしたwポケコンも結構長く続いていたんですね。それにしても初代PC-G850が1996年発売と考えると、さすがにこの時点でもう+と-は統一されていてもいい気がしますが、昔の名残でそうしていたとしたらどこかに説明とかあってもいい気がします。というか、おそらく教師に聞いても答えられないんじゃないかとw
9.名無し 投稿日:2018/11/01 13:11:26
11ピンI/Oの仕様は元々一般公開する予定が無かったから、制定当時のままで良かったのです。シャープも、ポケコンが高専等の教材として使われる様になるとは思わなかったんでしょうね。
コメントする
名前
コメント
※タグは使用出来ません
この記事に関連するタグ
ポケコン(PC-G850)