KZ80マイコンにYM2203 FM音源を搭載してVGMデータの音楽を鳴らすことができるようになりました。ただ、音楽データはオンメモリーを前提としているためマイコンの搭載メモリー以上のデータは演奏できません。
KZ80マイコンにはGrantさんのCP/Mを移植して搭載していますので、CP/Mのディスク(CFカードですが)に音楽データを入れれば大きなファイルも演奏できるはず。
CP/M用にVGMプレーヤーを改造
CP/Mでファイルを扱うプログラムは、わたしもCP/M上のHitech-Cを使ったC言語でのプログラムしか経験がありませんでした。またいかんせん古いOSのため、英語の解説サイトは多数あるのですが、日本語のサイトはそれほど多くありません。昔はCP/Mの専門書が本屋に多数あったものですが…
(入門CP/Mとか応用CP/Mとか、以前わたしも持ってました。)
CP/Mの機能の呼び出し方
CP/Mでファイルを読み書きする方法ですが、CP/Mそのものの解説ではないのですが CP/M互換のMSX-DOSでのファイル読み書き方法が以下のサイトでZ80アセンブラソース例つきで紹介されています。ありがたや。
CP/Mの内部ルーチンを呼び出すためには、BDOSコールという00005h番地を呼び出す方法が提供されています。上記リンク先にも例がでてますが、以下のようなコードになります。
LD A, 00H
LD C, 01FH
CALL 0005H
システムコールの種類によりますが、パラメーターはAレジスタやDEレジスタへセットし、Cレジスタにファンクション番号というシステムコールの種類(ファイルオープン、ファイル読み出し…etc)をセットして0005h番地をCallするという流れです。
戻り値はシステムコールの種類によりますが、Aレジスタなどに0/1などが返ってきます。
これ以降は私のVGMプレーヤー CP/M版でどのようにCP/Mの機能呼び出しをしたかを書いていきます。ソースはgithubにUPしました。
ファイルオープンまでの流れ
CP/MはMS-DOSの原型とも言えるOSで、Windowsのコマンドプロンプトによく似たインターフェースを持っています。ドライブ名があるところがMS-DOSそっくりですね。ただし階層ディレクトリはありません。以下の例だと”A>”はCP/Mシステムが出力してきます。カレントドライブはAドライブにいますよという表示です。
A>コマンド パラメーター1 パラメーター2
“PLAY M1.VGM” みたいな感じで入力した場合は、”PLAY”が”PLAY.COM”という実行ファイルを実行しなさいということで、第一パラメーターに読み込むファイル名”M1.VGM”を指定しているということです。
CP/Mの実行ファイルが起動されるとCP/Mが持っているFCB(ファイルコントロールブロック)というところに第一パラメーターのファイル名がセットされた状態でプログラムがスタートするらしいです。プログラムでは第一パラメーターのファイルを読んだり、もしくはそのファイルへ書き込んだりということをするはずなのでおぜん立てしてくれているのはたすかります。
FCBとは以下の構造になっていて、ファイル名などファイルをOPENしたりするための情報を管理するCP/Mのメモリー領域です。アドレスは005Chからの固定となっています。
アドレス | 説明 |
5Ch | ドライブNo. |
5Dh~64h | ファイル名(本体) |
65h~67h | ファイル名(拡張子) |
68h | カレントエクステント(ブロック) |
69h~6Ah | システム予約 |
6Bh | エクステント数 |
6Ch~7Bh | ディレクトリエントリイメージ |
7Ch | エクステント内の現レコード |
7Dh~7Fh | ランダムアクセスレコード番号 |
ファイルのオープンにはBDOSコールの機能番号0Fhを使います。
入力パラメーターはDEレジスタにFCBのアドレス(CP/Mデフォルトで05Ch)をセット、エラーコードはAレジスタに戻りますので正常(A=0)かどうかを判定します。
FCB EQU 005CH
;
LD DE,FCB
LD C,0FH ;ファイルOPEN
CALL BDOS
OR A ;成功?
JP NZ,OPEN_ERROR
ファイルバッファの指定とシーケンシャル読み出し
CP/MではデフォルトでDMAと呼ばれるファイルバッファ(0080h番地~)が用意されているようですが、MSXテクハンの作例でも自分のプログラム内にバッファを準備していたので、それに倣ってNEWDMAという128バイトのエリアを準備しました。FM音源の音楽データをファイルから順次(シーケンシャル)読み出しをしながら演奏しようと思います。CP/Mのシーケンシャル読み出し機能は128バイト固定長で読み出されるようです。
CP/Mにこちらから準備した入力バッファのアドレスを知らせるために機能番号1AhのBDOSコールを呼び出してバッファを指定します。また、それに続いて1つ目の128バイト分のデータをNEWDMAエリアへ機能番号1AhのBDOSコールを使って読み出します。読み出す際の入力パラメーターは、DEレジスタにファイルをオープンしたFCBのアドレスを指定します。
音楽データはHLレジスタでポイントしてなめることにするので、ファイルデータを読み出したNEWDMAの先頭アドレスを指すことにします。
MAIN1:
LD DE,NEWDMA
LD C,1AH ;set DMA address function
CALL BDOS
LD DE,FCB
LD C,014h
CALL BDOS
OR A
JP NZ,READ_ERROR
;
LD HL,NEWDMA
演奏用に1バイト取り出しルーチンを作成
FM音源データの演奏にあたって1バイト分取得しては処理をするという動きが基本となります。またデータによっては続く2バイトに意味があったりする場合もありますので1挙動につき1~数バイトを取得することになります。CFカード(ディスク)からCP/Mが読み出せるデータは128バイトなので、129バイト目が欲しいと言われたらまたCFカード(ディスク)から128バイト分読み出す必要があります。以下のようなコードとしました。
;
GETDATA:
PUSH DE
LD A,(HL) ; GET COMMAND
PUSH AF
INC HL
;
; バッファEND判定
;
LD DE,DMAEND
EX DE,HL
SBC HL,DE
JP NZ,GETDATA_END
;
; シーケンシャル読み出し
;
LD DE,FCB
LD C,014h
CALL BDOS
OR A
JP NZ,READ_ERROR
;
LD DE,NEWDMA
;
GETDATA_END:
EX DE,HL
POP AF
POP DE
RET
演奏データをポイントしているHLレジスタのアドレスをインクリメント(+1番地)してNEWDMAエリアのアドレスを超えたらCFカード(ディスク)から読み出しを行って、ポインターとなっているHLレジスタをNEWDMAエリアの先頭へ戻します。(アドレスをDEレジスタへ設定してますが最後に EX命令でHLレジスタとひっくり返します。)
割込み処理などを使ってバックグラウンドでちょこちょこ読んでおく方がパフォーマンスに影響が出ないと思うのですが、今回は力業のコーディングでまずは動かしてみます。
ERMSG1: DB 'Cannot open that file.$'
ERMSG2: DB 'file read error.$'
NEWDMA DS 128
DMAEND EQU $
END
ちなみに、ファイルオープンエラーやファイルREADエラーが出た場合に、機能番号09hのBDOSコールでエラーメッセージを表示するために文字列も定義しておきます。C言語のようにNULL(00h)が終端ではなく、CP/Mでは”$”が終端文字のようです。
CP/Mのコマンドファイル(COMファイル)の作成
CP/Mで実行できるファイルはCOMファイルというバイナリファイルです。COMファイルは0100h番地から始まるZ80(8080)のバイナリマシン語データであれば良いようです。COMファイルの作り方の王道としてはCP/M上のアセンブラやCコンパイラでCOMファイルを作ったり、IntelHex形式のテキストデータをCP/MのLOADコマンドでCOMファイルにすることのようです。
今回はいつも使っている The Macroassembler AS についているp2binというオブジェクトをバイナリ化いるプログラムでCOMファイルを作ることにしました。
以下がビルド用のMakefileです。
#
# Makefile
#
.SUFFIXES: .asm .p .hex .sr .com
all: VGM2203.hex m1.hex VGMCPM.com
.p.com:
p2bin $*.p $*.com
.p.hex:
p2hex -F Intel $*.p $*.hex
.p.sr:
p2hex -F Moto $*.p $*.sr
.asm.p:
asl -L $*.asm
clean:
rm -f *.p *.hex *.sr *.lst
いつもはMac/Windows上で The Macroassembler ASを使ってオブジェクトファイル(pファイル)を作ったのち、p2hexでIntelHex形式を作成しています。今回はCOMファイルを作りたいので “.p.com:”という節を追加してp2binを起動するルールを書きました。
“.SUFFIXES:”に”.com”を追加するのと、”all:”節に “VGMCPM.com”という出来上がりのファイル名を指定しておきます。最終的にはTeraTERMのXMODEM送信機能でKZ80のCP/MマイコンへはCOMファイルのデータを送りますので拡張子が小文字でも良いかなと。
$ make
Makefileの準備ができたら、 “make”コマンドでビルドが実行されます。
ちなみに私はWIndows10 WSL2環境で The Macroassembler AS を使用しています。(なので、アセンブリコマンドは “asl”です。)
asl -L VGMCPM.asm
Macro Assembler 1.42 Beta [Bld 182]
(x86_64-unknown-linux)
(C) 1992,2020 Alfred Arnold
Motorola MPC821 Additions (C) 2012 Marcin Cieslak
68RS08-Generator (C) 2006 Andreas Bolsch
Mitsubishi M16C-Generator also (C) 1999 RMS
XILINX KCPSM(Picoblaze)-Generator (C) 2003 Andreas Wassatsch
TMS320C2x-Generator (C) 1994/96 Thomas Sailer
TMS320C5x-Generator (C) 1995/96 Thomas Sailer
Panafacom MN1610/1613-Generator (C) 2019 Haruo Asano
MIL-STD 1750 Generator also (C) 2019 Oliver Kellogg
Assembling VGMCPM.asm
PASS 1
VGMCPM.asm(180)
PASS 2
VGMCPM.asm(180)
0.08 seconds assembly time
180 lines source file
2 passes
0 errors
0 warnings
p2bin VGMCPM.p VGMCPM.com
P2BIN/C V1.42 Beta [Bld 182]
(C) 1992,2020 Alfred Arnold
Deduced address range: 0x00000100-0x00000209
VGMCPM.p==>>VGMCPM.com (266 Bytes)
無事120kバイトOverのVGMデータが鳴った~♪
上記で作ったCOMファイル(実行ファイル)と音楽バイナリデータをTeraTERM経由でKZ80マイコンへXMODEMで送信します。
以下の記事でも書いてますが、CP/Mに標準コマンドといっしょに skyriverさん作のXMODEMも送っておくと大変便利です。おすすめ。
KZ80-ZilogIOでGrant’s CP/M(2) ROM準備とCP/M起動
オンメモリー版だったVGMプレーヤーに上記のCP/Mファイル読み出し部分の改造をしたおかげで、CP/Mファイルシステムに登録しておいた120kバイトOverのVGM音楽データを鳴らすことができました。いままでは、オンメモリーでしかFM音源音楽データを鳴らせなかったので32kバイト程度が限界でしたが、これからはもっと長い楽曲データも鳴らすことができるようになりました!
コメント