PIC32MXで作るMZ-80エミュレータの製作 2013.11.3

    [Japanese][English]

    update: 2014/4/12,2014/6/8

    1.はじめに

     その昔、シャープの8ビットマイコンMZ-1200を所有していたことがあります。MZ-1200は、MZ-80シリーズの最後の機種で、このマイコン用の「整数型ベーシックコンパイラ」などを作った思い出があります。

     時折、その当時を偲んで、Windows上のMZ-80のエミュレーターソフトでプログラムを動かしてみることがあるのですが、一月程前のこと、ネットで、PIC32MXマイコン1個で、MZ-80のエミュレーターを製作している方がいるのを知って自分も製作してみたくなりました。

     かれこれ10年も前に、Linuxで動くMZ-80のエミュレータを一度製作したことがあります。海外のサイトで、mz700emというMZ-700用のエミュレータソフトが公開されていましたので、これを基にMZ-80のエミュレータを製作しました。

     今回は、PIC32MXで製作しようという訳です。

    SBDBT32
    USBAコネクタ,microSDスロットが搭載されている
     手元に、ランニングエレクトロニクスから販売されているPICマイコンボードSBDBT32があります。 このボードは、以前に、USBハブのドライバー開発とそのデモ用のシステムを紹介するために使いましたが、その後、何の活用も見いだせぬまま、部品箱の中で眠らせていました。今回は、これを使ってみます。

     SBDBT32は、高性能なPIC32MX695F512Hを搭載し、小さなボードながらもUSBのAコネクタやSDカードスロットを備えており便利に使えそうな気がするボードですが、残念ながら、外部出力ピンは、わずか10ピンしかありません。しかも、その中の半分の5ピンは、電源入力を兼ねたPICkit3接続用のピンとなっています。残りの5ピンは、UARTのインタフェースに限定されています。[SBDBT32回路図]

     ネットの先人のエミュレータの製作例では、PIC32MXに必要なピンは僅か数ピンしかありません。その中から、TVに接続するためのNTSCコンポジット信号の2ピン(同期信号とビデオ信号)とサウンド出力用の1ピンの3ピンだけが取り出せれば、SBDBT32でもなんとか実現できそうです。

     本来なら、この他にキーボード接続用のピンやカセットテープインタフェースのためのピンなどが必要になるのですが、SBDBT32は、USBのAコネクタやSDカードインタフェースを搭載していますので、キーボードは、USBキーボードが使えますし、カセットテープの代わりにSDカードを使えば、これらのための外部ピンは特に必要ありません。SBDBT32のボード一枚と外付けの抵抗数本でエミュレータが出来てしまいます。


    2.エミュレータの仕様

     製作するエミュレーターの仕様は、次のように決めました。

      ・表示装置は、NTSCコンポジットビデオの入力端子があるTVを使うことにする。

      ・キーボードは、パソコン用のキーボードを使う。SBDBT32のピン数の制約からUSBインタフェースのキーボードとする。キーの配列が、MZ-80のキーボードと異なるのでプログラムで変換する。

      ・サウンドは、小さなスピーカーに直接出力する。

      ・MZのプログラムはすべてSDカードにファイルとして格納する。SDカードは、パソコンとの連携を考慮してFAT32でフォーマットしたものを使う。FAT32のアクセスには、Chan氏が公開されているFatFsを使わせてもらう。

      ・SDカードには、複数のプログラムファイルを格納するので、モニターやベーシックインタプリタのLOADコマンド実行時には、TV画面にファイル一覧を表示してファイルを選択できるようにする。

      ・プログラムは、以前にLinuxで作ったものを元に作る。USBキーボードの処理は、新たにコーディングが必要。ビデオ信号の生成は、先人のプログラムを参考に必要な処理を追加する。

      ・製作する回路は、SBDBT32のボードと抵抗が数本なので小さなケースにスピーカーとともに収める。


    MZ-80A
     エミュレーターの製作を思い付いてから、色々ネットを検索した結果、MZ-1200の海外向けモデルがMZ-80Aであること。MZ-80Aでは、ハードウェアスクロールがサポートされており、海外向けだけに英小文字なども使える。 モニタープログラムは、SP-1002ではなく、SA-1510である。ベーシックインタプリターも、SP-5030ではなく、SA-5510 であること、などが判りました。

     さらに、情報収集の過程で、MZ-80Aのオーナーズマニュアルや、モニタープログラム、ベーシックインタプリターが入手できましたので、MZ-80Aの機能もエミュレートできるようにします。

     MZ-80Aで動かすか、MZ-1200で動かすかは、起動時に選択出来るようにし、MZ-80Aが選択された時は、モニターSA-1510と海外向けのCGROMを、それ以外の場合には、SP-1002と国内向けのCGROM を SDカードから読み込むようにします。両機では、キーマトリックスも異なりますのでプログラムで対処することにします。



    3.ピン割り当てと周辺モジュールの選択

    3-1 出力ピンの選択

     SBDBT32から取り出す3ピンですが、UART用の5ピンの中から、その候補ピンを選択します。UART用に出ているピンは、次のとおりです。

     SBDBT32 コネクタJ3
      6番ピンRTS---SCK3/U1RTS/OC2/RD1
      7番ピンTX ---SDO3/U1TX/RD3 ビデオ信号
      8番ピンRX ---SDI3/U1RX/RD2
      9番ピンCTS---U1CTS/RD9 同期信号
      10番ピンSTO---RB9 サウンド出力

     上記の5ピンから、同期信号、ビデオ信号、サウンド出力を選択します。後述しますが、同期信号とサウンド出力は、タイマーモジュールの割り込み処理の中でオンオフしますので、デジタル出力できるピンであれば何でもいいです。一方、ビデオ信号は、SPIモジュールのデータ出力(SDO)ピンを使いますので、自ずと7番ピン(SDO3/RD3)を選択することになります。

     6番から8番までは、SPI3モジュールに関連するピンですから、ビデオ信号出力用に7番ピンを使う他は、未使用にしておきます。結果、9番ピンを同期信号に10番ピンをサウンド出力に割り当てることにしました。

     使用するピンが決まりましたので、製作するエミュレーターの回路図を描いて見ました。図1に示すとおりシンプルです。使用した抵抗については、後述します。


    3-2 周辺モジュールの選択

     PIC32MX695Fに内蔵の周辺モジュールで、MZ-80のエミュレータを実現するために使用したモジュールは次のとおりです。各モジュールとも、割り込みを使います。

    図1 エミュレータ回路図
    ブレッドボードでちょこちょこっと試せるほどの簡単な回路です。
     TIMERモジュール
      T1 1msec システムタイマー MZ-80の各種ペリフェラルをシュミレート
      T2 NTSC 同期信号立下り 出力ピンは、RD9 を使用
      T3 サウンド生成 出力ピンは、RB9 を使用

     SPIモジュール
      SPI2 SDカードインタフェース MZ-80のプログラムファイルの読み書き
      SPI3 NTSC ビデオ信号 送信割り込みを使ってビデオ信号を送出

     OC(出力比較)モジュール
      OC1 NTSC 同期信号立上がり 出力ピンは、RD9 を使用
      OC2 NTSC ビデオ信号の開始 SPI3の送信割り込みを許可

     USBモジュール
      U1 HID ドライバー USBキーボードのキー読み取り


    4.動かしてみる

     完成したエミュレータは、以下の写真のとおりです。

     MZ-80のモニタープログラムやCGROMデータ、ゲーム、ベーシックインタプリタなど、エミュレータの動作に必要なプログラムやデータを、SDカードにファイルにして格納しておきます。

    モニタープログラムとCGROMデータのファイル名は、必ず次のようなファイル名でSDカードに保存してください。

      英語版: SA-1510.ROM SA-CG.ROM
      日本版: SP-1002.ROM SP-CG.ROM

     電源を投入し、エミュレータの起動直後に何かキーを押すとMZ-80Aモードで立ち上がり、モニターSA-1510が起動します。何もキーを押していなければ、MZ-1200モードとなり、モニターSP-1002が起動します。

     LOADコマンドを実行し、画面に、↓ PLAY が表示されたら、ファンクションキー10を押します。すると画面中央に、SDカード内のファイル(拡張子.mzf)一覧が表示されますので起動するプログラムを選択できます。

     エミュレータ動作中に、ファンクションキー11を押すとマシンをリセットします。PIC32MX自体のリセットをかけていますので電源投入直後の状態に戻ります。この時に動作モードを切り替えることができます。


    ケースに収納する関係から、SBDBT32の外部端子はコネクタ側に付け替えた。SDカードには、MZ-80のプログラムが入っている。 SBDBT32は、抵抗と電源コネクタを取り付けたユニバーサル基板に差し込んで使う。 ビデオ出力端子とスピーカーと一緒に一つのケースに収めた。

    ケースの蓋を取り付けて完成。透明ケースは、秋月電子から購入。 電源、USBキーボード、液晶TVを接続して動作確認。 LOADコマンドを実行するとSDカード内のファイル一覧が表示される。

    ゲームSTAR WARSが問題なく動いた。 海外バージョンのモニターSA-1510も起動する。 海外バージョンのベーシックインタプリタSA-5510を起動してみる。英小文字が表示されている。

    懐かしいインベーダーゲーム。
    海外のゲームWILHELM TELLの起動画面。スペースでゲームが開始する。 落ちてくるリンゴを矢で射つ単純なゲーム。でも結構ハマってしまう。


    5.エミュレータプログラム

     それでは、次に製作したエミュレータのプログラムについて説明します。プログラムは、Microchip の統合開発環境 MPLAB IDE v8.92 の上で、 C32コンパイラを使い、すべてC言語で作成しています。プロジェクトファイル一式は、次のとおりです。

      Project File: mz80em_SBDBT32_20140412.zip(SBDBT32ブートローダー非対応版)
      Project File: mz80em_SBDBT32_20140608.zip(SBDBT32ブートローダー対応版)

      2014.4.12
       システム立ち上げ時、キーボード認識が不安定だったのを修正しました。
       システム立ち上げ時、拡張子.mzt ファイルも検索するようにしました。
       ビルド時に unixio.c のコンパイルでエラーがでた時の対処方法を本ページ末尾に記載しました。
      2014.6.8
       SBDBT32の初期ファームウェアのブートローダに対応しました。

     機能毎に、以下に要点のみ記述します。詳細は、ソースプログラムを参照してください。


    5-1 MZ-80のペリフェラルのシミュレート(rtc.c,mz1200.c)

       MZ-80のハードウェアを構成するペリフェラルには、次のようなものがあります。

     サウンドジェネレーター、カーソル点滅用タイマー、音楽演奏用テンポタイマー、時計用クロックカウンターです。

     サウンドジェネレーターは、タイマーモジュールT3を使い、T3の周期的な割り込み処理の中で、RB9の出力ピンをトグルして音を出すようにしました。実機では、2MHzのクロックを分周して音を出していますが、T3のクロックは、周辺モジュールのクロックを80MHzに設定した関係から、2.5MHzとなりました。そこで、実機で設定される分周値を、エミュレータでは、1.25倍して辻褄を合わせるようにしました。 

     カーソル点滅用タイマー、音楽演奏用テンポタイマー、時計用クロックカウンターは、タイマーモジュールT1を使ってシミュレートしました。具体的には、T1の周期的な割り込みが1msecで発生するようにし、その割り込み処理の中でそれぞれのタイマー変数の値をカウントダウンしたりトグルしたりするようにしました。

     カーソル点滅用タイマーは、500msec毎にその変数の値をトグルします。音楽演奏用テンポタイマーは、5msec毎にトグルして100Hzのテンポ周波数を生成しています。時計用クロックは、1sec毎にカウントダウンします。


    5-2 キーボードエミュレーション(keyboard.c, key.c)

     USBのキーボードの入力処理は、Microchip が提供するApplication Libralyのサンプルデモプログラムを流用しました。なお、エミュレータ処理に不要な部分は削除しています。

       キーボードの読み取りは、先のT1の割り込み処理の中から10msecインターバルで行っています。読み取ったUSBのキーコードを変換し、MZ-80AやMZ-1200の該当するキーが、あたかも押されたかのようにキーボードマトリックスの値を設定しています。

     MZ-80AとMZ-1200では、キーのマトリックスは異なりますので、それぞれの機種用の変換テーブルを作成しました。

     
    5-3 Z80CPUの命令エミュレーション(z80.c)

     Z80CPUの命令エミュレーションについては、以前にLinux用のエミュレータを作成した時のソースファイルを少し修正して作りました。このプログラムの原型は、Linux版のMZ700エミュレーター'mz700em'の中のZ80のエミュレーションプログラムです。 オリジナルのソースコードは、Z80の命令コードを、switch文による分岐で処理するように書かれていましたが、関数テーブルを使って分岐するように全面変更しています。

     Z80の命令コードそのもののエミュレート処理については、オリジナルのコードをそのまま採用しています。ただし、オリジナルのソースファイルでは、マクロが多用されていますが、そのマクロを展開した結果を使っています。

     0xEDFC から 0xEDFF までの4つの未定義命令に、カセットテープ(SDカードファイル)の読み書き処理を割り付けました。エミュレータ起動時、SDカードからMONITORプログラムをRAMエリアに読み込んだ後、MONITOR プログラムの中のテープの読み書きルーチンの入り口部分の先頭2バイトをこの未定義命令に置き換えています。

     実機の2MHzクロックによる実行速度をエミュレートするために、2.5MHzクロックで動作するタイマーT5を使って時間調整を行いました。

     冒頭で述べました「整数型ベーシックコンパイラ」で、その昔8QUEENを実行した際、実機で90秒程度を要していた記憶がありますが、今回製作したエミュレータでは、85秒となりました。この程度の差なので良しとしました。シューティングゲームなどを動かして見ても特に違和感はありませんので問題ないと判断しました。


    5-4 テープ装置のリード・ライト(SDカードの読み書き)(fselect.c,mzfile.c,unixio.c)

     LOADコマンド実行時に、画面に表示するファイル一覧リストは、エミュレータ起動時に、SDカードのルートディレクトリを検索して作成しています。拡張子が .mzf のファイルのみを検索します。  (2014.4.12: 最新版では、拡張子が .mzt のファイルも検索するようにしました。)

     ファイル名の一覧表示は、ファイル名の文字コードをMZ-80A/MZ-1200のディスプレーコードに変換し、直接ビデオRAMエリアに書き込んでいます。なお、ファイル選択操作の間は、10msecインターバルのキー読み込み処理は停止させています。

     SDカードのファイルの読み込みや書き込み処理は、Linux用に作ったソース(open, close, read, writeなどの低レベル入出力関数が使われている)がそのまま使えるように、unixio.c を今回のプロジェクトでも使いました。

     MZ-80の文字コードは、一般的な記号や数字、英大文字は、標準のアスキー文字コードと一致していますが、英小文字や半角カナ文字は、コードが異なっています。そのため、ベーシックのSAVEコマンドで作成されるファイル名をそのままSDカードのファイル名に使うと、SDカードをWindowsで読んだ時に文字化けを起こしてしまいます。逆もしかりです。そこで、限定的ではありますが、標準のアスキーコードとMZのアスキーコードの変換処理を組み込みました。


    5-5 NTSCビデオ信号の生成(video.c)

     NTSCコンポジットビデオ信号の生成については、後関哲也氏の電子工作の実験室「PIC24F応用製作例紹介」ページの中の"NTSCビデオ出力のオシロスコープ"のプログラムをほとんどそのまま使わせて頂きました。

     SBDBT32に合わせて一部修正した他、SPI3モジュールは、送信割り込みによって、32ビットの送信を行うように変更しています。また、スクリーンのリバース表示への対応や、MZ-80Aのハードウェアスクロールのエミュレートのための処理を追加しています。

     このプログラムで生成している同期信号や等価同期信号などは、簡易なものですがうまく表示できています。

     ビデオ信号を作るSPIモジュールのクロック周波数は、次のようになります。

     水平同期信号の周波数は、15.72KHzで、1周期あたり63.6usになります。同期信号に4.7us、ビデオ信号立ち上がりまでの 時間が同じく4.7us、ビデオ信号終了後の空き時間が1.5usとすると、ビデオ信号生成時間は、最大で52.7usとなります。

     MZ-80のスクリーン表示は、横40文字の320ドットですので1ドットに取れる時間は、52.7us/320=164.7nsです。周波数に直すと 6.07MHzとなりますので、SPI3モジュールのクロック周波数は、6MHz以上に設定すればよいことになります。

     周辺モジュールのクロックが80MHzですので、SPI3のクロックは、40MHz,20MHz,13.33MHz,10MHz,8MHz,6.66MHz,5.7MHz,5MHz, ... から選択できます。今回は、6.66MHzと8MHzの2種類で試してみましたが、6.66MHzでは、TV画面の左右への広がりが大きく見づらかったので、8MHzを採用しました。

     同期信号とビデオ信号を合成する外付けの抵抗回路は、TVのビデオ端子の入力抵抗が75オームであるとすると、同期信号側に580オーム、ビデオ信号側に250オームの抵抗を接続すれば、計算では次のような電圧値になりベストの選択となります。なお、PIC32MXの出力電圧は3.3Vとします。

      白レベル 0.99v  黒レベル 0.299v  同期信号  0v

     ところが、ぴったりな抵抗がないため、回路図に示したような4本の抵抗を使うことになりました。試していませんが、TV側の許容範囲が大きいので、560オームと240オームでも問題なく表示できると思われます。



    6.最後に

     TV画面の表示品質は、決して綺麗というレベルではありません。結構チラツキがあります。同期信号やビデオ信号を生成するための割り込みのタイミングが安定していないのかも知れませんが詳細な原因究明はしていません。

     それにしても、小さなPICマイコン1石で、その昔のコンピュータが簡単に再現できてしまうことに、技術の進歩とはいえ、改めて感心します。

     今回、エミュレータプログラムの公開にあたり、MONITORプログラムやCGROM、その他ベーシックインタプリタ、ゲームなどは、シャープやその他の製作者の著作物であり、公開はしておりません。このエミュレータで遊ぶには、別途入手してください。


    20140412: unixio.c コンパイル時のエラー対処について

     ソースファイル一式を、MPLAB IDE でビルドした時、unixio.c のコンパイルで、次のようなエラーが出てビルドが停止することがあります。

    
    In file included from c:\program files\microchip\mplabc32\v2.02\pic32mx/include/fcntl.h:58:0,
         from unixio.c:18:
    c:\program files\microchip\mplabc32\v2.02\pic32mx/include/sys/fcntl.h:189:5: error: conflicting types for 'open'
    c:\program files\microchip\mplabc32\v2.02\pic32mx/include/unixio.h:21:12: note: previous declaration of 'open' was here
    c:\program files\microchip\mplabc32\v2.02\pic32mx/include/sys/fcntl.h:190:5: error: conflicting types for 'creat'
    c:\program files\microchip\mplabc32\v2.02\pic32mx/include/unixio.h:26:12: note: previous declaration of 'creat' was here
    
    

     これは、ヘッダファイル unixio.h と fcntl.h のどちらにも、 open と creat のプロトタイプ宣言があり、その内容が異なっているために出るエラーです。

    回避策として、c:\program files\microchip\mplabc32\v2.02\pic32mx\include\sys\fcntl.h の中の open と creat のプロトタイプ宣言をコメントアウトしてください。

    
    #ifndef _KERNEL
    //int	open (const char *, int, ...);
    //int	creat (const char *, mode_t);
    int	fcntl (int, int, ...);
    #ifndef _POSIX_SOURCE
    int	flock (int, int);
    #endif /* !_POSIX_SOURCE */
    #endif
    
    #ifdef __cplusplus
    }
    #endif
    #endif /* !_SYS_FCNTL_H_ */
    


    20140412: CG-ROMファイルのフォーマットについて

     このエミュレータで使うCG-ROMファイルについて補足しておきます。他のエミュレータで動いていたCG-ROMファイルを、このエミュレータで使った時に文字化けするような場合には、フォーマットが合致しているかどうか確認してください。

     MZの1文字は、縦8ライン、横8ドットで構成されています。例えば、’A’の文字であれば、下の図のようになります。

      文 字
      16進コード
      1ライン○○○●●○○○
      0x18
      2ライン○○●○○●○○
      0x24
      3ライン○●○○○○●○
      0x42
      4ライン○●●●●●●○
      0x7e
      5ライン○●○○○○●○
      0x42
      6ライン○●○○○○●○
      0x42
      7ライン○●○○○○●○
      0x42
      8ライン○○○○○○○○
      0x00

     各ディスプレーコードに対するキャラクタジェネレータコードの対応は、下表のようになります。ディスプレーコードは、全部で 256 あり、それぞれに8バイトのキャラクタジェネレータコードがありますので、全体では2048バイトになります。

      ディスプレーコード
      キャラジェネコード
      0x00
      空白
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 
      0x01
      'A'
       0x18,0x24,0x42,0x7e,0x42,0x42,0x42,0x00 
      0x02
      'B'
       0x7c,0x22,0x22,0x3c,0x22,0x22,0x7c,0x00 
      0xFF
       0x00,0x77,0x77,0x77,0x00,0x77,0x77,0x77 

     上表の2048バイトのキャラクタジェネレータコードをファイルに格納するにあたって、2通りの方法があります。

    1.ファイルの先頭から、ディスプレコードの順に、8バイトのキャラクタジェネレータコードを順に詰めていく方法。

      この場合には、次のようになります。

      0000 00 00 00 00 00 00 00 00 18 24 42 7e 42 42 42 00
      0010 7c 22 22 3c 22 22 7c 00........
      07F0........00 77 77 77 00 77 77 77


    2.ファイルの先頭から、ライン順かつディスプレコード順に該当ラインのキャラクタジェネレータコードを順に詰めていく方法。

      この場合には、次のようになります。

      0000 00 18 7c .......
      0100 00 24 22 .......
      0200 00 42 22 .......
      0300 00 7e 3c .......
      0400 00 42 22 .......
      0500 00 42 22 .......
      0600 00 42 7c .......
      0700 00 00 00 .......

    一つの文字に対する8バイトのキャラクタジェネレータコードは、256バイト置きに配置されることになります。

     このエミュレータでは、2番目の方法で作られたCG-ROMファイルを使うようにしています。

     1の方法で作られたCG-ROMファイルを使うと文字化けしますので、2の方法にフォーマット変換した上でご利用ください。