標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第559回]

●電子オルガンプログラム

前回は8080アセンブラの実行例として、電子オルガンプログラムをアセンブルしてみました。
ND80ZVのキーを押すとそのキーに割り当てられた高さの音がスピーカーから出力される、というプログラムです。

このプログラムは、以前にどこかで紹介したことがあるような気がして捜してみましたら、[第363回]にありました。
MYCPU80の操作説明書の内容紹介のところです。
キーと音の高さの対応図もありますので、そちらのページも参照してみてください。
プログラムリストもそこに掲載してあります。

前回紹介しました電子オルガンプログラムとほとんど同じものですが、CPUクロックが異なっているために、その部分は違っています。
MYCPU80のCPUクロックは2MHzで、命令の実行クロック数もオリジナルの8080とは異なっています。
今回のND80ZVのCPUクロックは6MHzですし、CPUはZ80ですから、やはり命令によってクロック数は8080と違っているものもあります。
ただ今回紹介しました電子オルガンプログラムは、初心者の方の参考にもなるように、と考えて、ソースプログラムは、命令数の少ない8080用のプログラムとして、8080ニーモニック(インテルニーモニック)で書きました。

Z80は8080の命令をマシン語レベルでは8080と全く同じように実行しますから、ソースプログラムの命令を8080のニーモニックで書いても全く問題はありません。
あ。もちろん8080ニーモニックで書いたソースプログラムは8080アセンブラでなければ、マシン語コードに翻訳することはできません。

同様にZ80ニーモニック(ザイログニーモニック)で書いたソースプログラムはZ80アセンブラでなければ、マシン語に翻訳することはできません。
しかしどちらの手続きによっても、アセンブルされてマシン語に翻訳された結果は同じものになります。
もっともZ80にはあるけれども8080にはない命令もたくさんありますから、そのような命令はZ80アセンブラでなければマシン語に翻訳することはできません。

ND80ZVはマシン語やアセンブラを勉強中の方にもぜひ使っていただきたい、と思っております。

そう思って、あらためて[第363回]も読み直してみたのですけれど、プログラムリストと、プログラムの動作の説明がしてあるだけで、プログラムそのものの説明はないようです。
まあ。めちゃめちゃ忙しかったんですものねえ。

でも、せっかくサンプルとして電子オルガンのプログラムリストをお見せしたことですから、この機会にプログラムそのものの説明をしてみたいと思います。
とっても簡単なプログラムですから、どなたもご理解いただけることと思います。

あ。
そういえば、突然に思い出しました。

●8080アセンブラ注記

前回は、そのもうひとつ前の回でND80ZVとDOS/VパソコンをUSBで接続して、DOS窓上のリモートプログラムを実行して、ND80ZVのキー入力の代わりに、パソコンのキーから入力して、ND80ZVをリモート操作する、という説明をしたあとで、突然8080アセンブラの説明を割り込んではじめてしまいました。
今回もまだその続きで、またもや脱線したまま走っております。

リモートプログラムの実行も、8080アセンブラの実行も、同じDOS窓(DOSプロンプト)の中で行いますし、その前の回のリモートプログラムの説明の次にいきなり8080アセンブラの話になってしまったものですから、ひょっとすると、お読みいただいている方のなかには、8080アセンブラもUSBで接続したND80ZVが実行しているのではないか、と誤解された方もみえるかもしれません。

同じDOSプロンプトで実行しますけれど、8080アセンブラはDOS/Vパソコンの上だけで動作します。
8080アセンブラは8086の命令で書かれたプログラムです(8080の命令ではありません)。
うーん。ややこしーい。

8080アセンブラは、その実行によって、8080のマシン語コードファイルを生成しますが、その生成の過程では、ND80ZVの接続を全く必要としません。
8080アセンブラを使うときには、ND80ZVをUSBから切り離しておいても全く構いません。

●電子オルガンプログラムの説明

さて、では前回もお見せしましたsound6.lstを少しずつ切り分けながら説明をしていくことにいたします。

2010/7/20  21:25  sound6.txt
END=8056
              ;;;SOUND6.TXT
              ;;; sound for ND80Z3 clock=6MHz
              ;;; 10/3/18 10/6/15 7/20
              ;;;
              	ORG $8000
              ;
              	KEY=$0247
              ;
8000 CD4702   SND:CALL KEY
8003 3C       	INR A
8004 CA0080   	JZ SND
8007 3D       	DCR A
8008 CD0E80   	CALL SNDSB
800B C30080   	JMP SND
プログラムの本体、メインプログラムはこれだけです。
非常に簡単です。
TK80モニタプログラムのキー入力サブルーチンKEYをCALLしています。
TK80のアプリケーションプログラムでは、このようにハードウェアに密接につながるところでは、モニタプログラムのサブルーチンをCALLすることが一般的です。
TK80に限らず、パソコンでも考え方は同じで、そのようにすることで、ハードウェアの底辺の面倒な処理をいちいちプログラムしなくても済むようになり、プログラムの作成が比較的容易になります。
もっともそのためには、モニタサブルーチンの機能や動作について一通りは理解しておくことが求められます。
ここではCALL KEYのあと、INR Aを実行して、結果が0のときは、また8000に戻ってCALL KEYを実行します。

何をやっているのでしょうか?
じつは、ここでキー入力の有無をチェックしているのです。
KEYサブルーチンはキーが押されていれば、そのキーに対応する数値(00〜17)をAレジスタに入れてリターンします。
キーが押されていなければ、FFを入れてリターンします。
INR A(Aレジスタを+1加算する)の実行の結果、Z(ゼロフラグ)が立つのは、加算前の値がFFのときだけです。
ですから、INR Aを実行してZフラグが立ったときは、Aの値がFFのとき、つまりキーが押されていないときですから、なにもしないで、再びキー入力を確認しにいくのです。
それがJZ SNDです。

では、キーが押されているときは、といいますと、JZ SNDをスルーして、その下の命令を実行します。
DCR Aです。
FFコードをチェックするために、Aレジスタの値を一律に+1してしまいましたから、ここでもとの値に戻しているのです。
DCR AはAレジスタの値を−1します。
そうしておいて、サブルーチンSNDSBをCALLします。
SNDSBが電子オルガンプログラムのサウンド出力部分です。
SNDSBはAレジスタの値に対応した(つまりキーに対応した)高さの音を一定期間出力します。
その後再びキー入力を確認するため、8000に戻ります。

ではSNDSBの中身を見ていくことに致します。

              ;
800E F5       SNDSB:PUSH PSW
800F E5       	PUSH H
8010 D5       	PUSH D
8011 C5       	PUSH B

SNDSBではA、HL、DE、BCの全てのレジスタを使いますから、最初にスタックに退避しておきます。
PUSHはレジスタをスタックに退避する命令です。
8080アセンブラのニーモニックでは、A、B、D、Hレジスタしか退避されないように思ってしまいますが、スタックへの退避は、AF(Aレジスタとフラグレジスタ)、BC、DE、HLの各ペアレジスタ単位で行われます。
ですからたとえばPUSH Hは、HLレジスタをスタックに退避します。
その点はZ80ニーモニックの方が直感的に把握できます。
Z80ニーモニックでは、PUSH HLと書きます。

ところで、実は、この電子オルガンプログラムでは、SNDSBでレジスタを退避させておく必要はありません。
さきほどのメインルーチンを見てみますと、SNDSBを実行したあとは、また8000のCALL KEYに戻っていますから、どのレジスタの値も保存しておく必要はないからです。

それではなぜ?
ということになるのですが、それはこのSNDSBを電子オルガンプログラム以外のプログラムでも利用する、という場合を考えたからです。
Aレジスタに音の高さを示すコードを入れて、SNDSBをCALLすることで、その高さの音をわずかな時間ですが出力することができます。
電子オルガンのプログラムでは、どのレジスタの値も保存しておく必要はなかったのですけれど、別のプログラムでは、SNDSBをCALLする前と後で、レジスタの値が変化してしまっては困る、ということも十分考えられます。

ここでのPUSH命令はそのためにあります。
なお、PUSHしたレジスタは、最後に、PUSHしたときと逆の順番でPOP命令を使って、もとの値に戻しておく必要があります。

8012 213F80   	LXI H,SNDTBL
8015 85       	ADD L
8016 6F       	MOV L,A
8017 46       	MOV B,M
8018 1E1A     	MVI E,1A

LXI H,SNDTBLで、プログラムの終わりにあるサウンドテーブルの先頭のアドレスをHLレジスタに入れています。
サウンドテーブルの中身は音の高さを決定するデータです。
ADD L と次のMOV L,A でHLレジスタの値(つまりサウントテーブルのアドレス)がAレジスタの値だけ先に進みます。
ここで注意が必要なのは、このときのサウンドテーブルの上位アドレスが全部同じ(H=80)でなければならない、ということです。
マシン語のプログラムはこういう点に注意しなければならないときがあります。
もしもサウンドテーブルが、H=80とH=81にまたがっていた場合には、このプログラムの書き方では正しく実行されません。

さて、MOV B,Mで、選択したサウンドテーブルの値がBレジスタに入れられます。
その次の、MVI E,1Aは繰り返し回数です。
Eレジスタに1A(=26)が入れられます。

801A 50       SNDS1:MOV D,B
801B 3EEF     	MVI A,EF;sp out=H,DMAoff
801D D398     	OUT 98
801F E5       SNDS2:PUSH H;11---------
8020 E5       	PUSH H;11       | 11+11+10+10+4+4+10=60
8021 E1       	POP H;10        | 60/6=10
8022 E1       	POP H;10        |
8023 00       	NOP;4;          | 10microsec
8024 15       	DCR D;4         |
8025 C21F80   	JNZ SNDS2;10----

いよいよ音を出力するところです。
ここでは、サウンドテーブルの値(Bレジスタに入っている)をダウンカウンタとして使い、一定の長さの時間を作り出します。
しかしBレジスタはこの後のところでも使いますから、ここで壊してしまわないように、MOV D,B命令で、Bレジスタの代わりにDレジスタをダウンカウンタとして使うためにBレジスタの値をDレジスタにコピーしています。
MOVはmoveですが、値がBレジスタからDレジスタに移動するのではなくて、Bレジスタの値がDレジスタにコピーされます。

I/Oアドレス98のビット5をHにすると、スピーカーへの出力がHになります。
と同時に音の高さを正確にするため、LED表示のためのDMAを禁止します。
I/Oアドレス98のビット4をLにするとDMAが禁止されます。

その次が、サウンドルーチンの中心になる部分です。
PUSH HからNOPまでの命令は、単に時間をかせぐためだけのダミー命令です。
次のDCR DとJNZ SNDS2で、この部分がサウンドテーブルの値の回数繰り返し実行されます。
PUSH HからDCR DとJNZ SNDS2までの部分を1回実行するのにかかる時間は、各命令の実行クロック数の合計から求めることができます。
合計は60クロックです。
ND80ZVのCPUクロックは6MHzですから、1クロックの時間は、1/6μsecです。
ですからこの部分を1回実行するのにかかる時間は、60/6=10μsecです。
ということは、Dレジスタの値(=Bレジスタの値)、つまりサウンドテーブルの値×10μsecの長さの時間がここで消費されることになります。
その時間、スピーカーへの出力がHになっています。

8028 50       	MOV D,B
8029 3ECF     	MVI A,CF;sp out=L,DMAoff
802B D398     	OUT 98
802D E5       SNDS3:PUSH H;11---------
802E E5       	PUSH H;11       | 11+11+10+10+4+4+10=60
802F E1       	POP H;10        | 60/6=10
8030 E1       	POP H;10        |
8031 00       	NOP;4;          | 10microsec
8032 15       	DCR D;4         |
8033 C22D80   	JNZ SNDS3;10----

ここも上と全く同じルーチンのようですが、繰り返しに入る前に、I/Oアドレス98にCFをOUTしています。
ビット5がLですから、ここでスピーカー出力がLになります。
つまりさきほどと同じ長さの時間、こんどはスピーカーへの出力がLになります。
上の部分と合わせると、同じ期間のHとLのパルスが出力されることになります。

8036 1D       	DCR E
8037 C21A80   	JNZ SNDS1

そのH、L出力をEレジスタの値の回数1A(=26回)繰り返します。
この26回に深い意味はありません。
ここが余り短いと、キー入力を見に行く間、音が途切れる期間が無視できなくなります。
また余り長いと、キーを押しても、すぐに反応してくれません。

803A C1       	POP B
803B D1       	POP D
803C E1       	POP H
803D F1       	POP PSW
803E C9       	RET

最後にスタックに退避しておいたレジスタの値をPOP命令でもとに戻してからメインルーチンにリターンします。

ここから下がサウンドテーブルです。
うーん。
これだけではわかりにくいですねえ。

              ;
              ; SOUND TABLE
803F 7F       SNDTBL:DB 7F;so4
8040 77       	DB 77;so#4
8041 71       	DB 71;ra4
8042 6A       	DB 6A;ra#4
8043 5F       	DB 5F;do5
8044 59       	DB 59;do#5
8045 54       	DB 54;re5
8046 4F       	DB 4F;re#5
8047 47       	DB 47;fa5
8048 43       	DB 43;fa#5
8049 3F       	DB 3F;so5
804A 3B       	DB 3B;so#5
804B 35       	DB 35;ra#5
804C 32       	DB 32;si5
804D 2F       	DB 2F;do6
804E 2C       	DB 2C;do#6
804F 25       	DB 25;mi6
8050 27       	DB 27;re#6
8051 2A       	DB 2A;re6
8052 4B       	DB 4B;mi5
8053 38       	DB 38;ra5
8054 64       	DB 64;si4
8055 23       	DB 23;fa6
8056 21       	DB 21;fa#6
              ;END
KEY          =0247  SND          =8000  SNDS1        =801A  
SNDS2        =801F  SNDS3        =802D  SNDSB        =800E  
SNDTBL       =803F  

ああ。
本日は時間がなくなってしまいましたので、ここの部分の詳細はまた次回にすることにいたします。

本日は、参考までに、アドレス8053のラ5についてのみ、簡単に説明を致します。
キーコード14に対応する音のデータです。
キーコード14は[READ INC]キーです。
ラ5はオクターブ5のラの音です。
オクターブ5のラの音の周波数は、880Hzです。
ということは、その逆数1/880≒0.001136secが周期になります。
1136μsecです。
その1/2の568μsecが、HまたはLのパルスの長さ、ということになります。
そこでアドレス8053の値を見ていただきますと、38になっています。10進数に直すと56です。

さきほどのパルス出力のところで、サウンドテーブルの長さ×10μsecがHまたはLのパルスの長さになります、という説明をしました。
ということは、56×10=560μsです。
880Hzから算出した値568μsecよりも8μsec短いですけれど、この部分は、クロックの計算からは落ちているスピーカーへの出力のH、Lを切り換えるためのOUT命令や、MOV D,B命令、DEC E、JNZ命令などの時間を考慮して、理論値を超えない値にしてあります。

スピーカーへのラ5の音(880Hz)の出力波形です。


2010.7.21upload

前へ
次へ
ホームページトップへ戻る