今日はAKI-80、AKI-ROMライタともに完成したということで、TAIKIさんに私の部屋に来ていただきました(遠いところをご苦労様です)。
とりあえずマイコンを使って工作をする環境は整ったわけですから、とりあえず動作確認もかねて簡単なものを作っていただき感触をつかんでいただこうととになりました。
最初の課題はずばり、「LED(発光ダイオード)を点滅させる」というものです。
本来ならハードの設計が先にあることが多いのですが、いきなりということもあるので私の方でプログラムを用意して(といってもKOKEさんに手伝っていただきながらその場で適当に作りました)、それを説明しながらTAIKI氏にハードを組み立てて頂くという手順を選びました。今回はLSIC-80のアセンブラとリンカを利用してROM化用のHEXファイルを生成します。
細かい説明は今後行うとして、今回は大雑把に流れだけ理解して下さい。
; ---------------------------------------------------------------------
; TAIKI氏に捧げるLED点灯プログラム
; ---------------------------------------------------------------------
; ----- ポート定義 (1)
PIOA equ 01ch
PIOAC equ 01dh
; ----- コードセグメント開始 (2)
cseg
start:
; ----- スタックポインタ初期化 (3)
ld sp,0
; ----- PIO初期化 (4)
ld a,0cfh
out (PIOAC),a ; PIOAをビットモードに
ld a,000h
out (PIOAC),a ; すべて出力
ld a,007h
out (PIOAC),a ; 割り込みは使用しない
; ----- 点滅 (5)
loop:
ld a,0aah
out (PIOA),a ; 交互に点灯
call Wait ; ウェイトルーチンを呼び出し (6)
ld a,055h
out (PIOA),a ; さっきの反転で点灯
call Wait ; 1秒程度待つ
jp loop ; 無限に繰り返す
; ----- ウェイト (7)
Wait:
ld de,1000 ; 1ms待ちを1000回行う
Wait_loop:
call Wait1ms ; 1ms待つ
dec de ; デクリメント
ld a,d
or e
jr nz,Wait_loop ; ループ終了条件比較
ret ; リターン
; ----- 1msec待ち
Wait1ms:
ld bc,375 ; 1msec待つためのループ数 (8)
Wait1msLoop: dec bc
ld a,c
or b
jr nz,Wait1msLoop
ret
end
大変大雑把にですが、プログラムの説明をします
ここでは equ という擬似命令を使って定数を宣言しています。C言語の #define と同じと考えていただければ分かりやすいでしょう。で、何を定義しているかです。
今回はLEDを点滅させるためにZ80-PIOというパラレル制御のためのLSIを操作します。CPUにはI/Oポートという外部機器を制御するためのポートが備わっていて、それぞれのポートにいろいろな機能のLSIを繋ぐことが出来ます。AKI-80の場合はTMPZ84C015BFというワンチップマイコンが載っていますのでこのZ80-PIOが内蔵されており 0x1c からのポートがこのPIOの制御ポートとして割り当てられています。
マイコン用のプログラムではコード(プログラム)、初期化されるデータ、初期化されないデータなどに応じてROMやRAMに配置してやらなければなりません。実際のアドレスはリンク時に指定するのですが、プログラムの中ではとりあえず何処がどういう部分か指定してやります。 cseg 擬似命令は「これ以降はコードセグメントというコードの入ったブロックだよ」とアセンブラに指示してやるためのものです。
アセンブラをやる上で常に無視できないのがスタックです。スタックとはどういう物かというと結局データの格納場所で格納したデータを格納した順とは逆順で取り出せる機構です。データの一時待避やサブルーチンコール時の戻り場所の保存、C言語などでは自動変数の割り当てにも利用されます。そしてその機構を一人で背負って立つのが sp(スタックポインタ)レジスタなのです。
spはデータの格納の必要が生じるとそのサイズ分値を減算し、そのアドレスにデータを書き込みます(push)。逆にデータを取り出す時は、データを取り出した後、そのザイズ分spを加算します(pop)。つまりスタックはメモリの高位アドレスから低位アドレスに向かってデータを格納していきます。そのため通常はRAM領域の最後にspを設定します。AKI-80の場合は 0000〜7fff がROM、8000〜ffff までがROMですから、spはffffの次のアドレスである 0000 に初期設定します。
PIOとはパラレルI/Oのことでデータを8bitの入出力端子を持ったデバイスです。今回はPIOのAポートの端子にLEDを取り付けて、各ビット毎に1出力で点灯させ、0出力で消灯させます。そのため、PIOのAポートをそれに適したモードに初期設定せねばなりません。PIOのAポートを制御するIOポートは2つあり、上記プログラムではPIOAとPIOACで定義しています。ここでPIOAは8本の端子と直接データを入出力するものであり、PIOAC がモード設定などを行うものです。
とりあえず詳しい説明は今後ということにしますが、ここで行っている処理は、PIOをビットモードという単純な入出力を行うモードにしてすべてのピンを出力に設定しています。よって今後 PIOA に書き込んだ値はそのまま2進数として各端子に0のときはLレベル(0Vに近い電圧)、1のときはHレベル(5Vに近い電圧)が出力されます。
いよいよ、点滅させるルーチンです。今回は各ビットを交互に点灯させようということで 10101010 (AA) と 01010101 (55)を間にウェイトを入れながら繰り返し出力することにしました。PIOのモード設定は終わっていますので後は PIOA に AA と 55 を交互に出力するだけです。
Z80と言えども馬鹿にしてはいけません。4MHzでもPC-8801やMSXなどでガンガンゲームが楽しめるわけで、AKI-80の場合 10MHzで動いてるわけですからその速度はたいした物です。大雑把に1命令に数μ秒と見積もっても単純ループさせると1秒間に10万回程度点滅を繰り返します。秒間30コマのTVにしっかり騙されてる人間がこんなものを見たって点滅には見えません(これを利用してダイナミック点灯という技がありますが)。というわけでウェイトを入れます。C言語などですと関数呼び出しに当たるのがこの call 命令です。どういう動作をするかと言うと、現在実行中の番地(PC)をスタックに積み、オペランドで指定されたアドレスにジャンプします。サブルーチンから帰るときは ret というC言語の return に相当する命令を利用して帰ってきます。 ret はスタックからアドレスを取り出し、PC(プログラムカウンタ)にセットする働きがあります。
PC <- PC + 3 (自分の命令サイズ分PCを進める) SP <- SP - 2 (PCのサイズ分スタックポインタを移動) (SP) <- PC (スタックにPCを保存) PC <- nn (指定されたアドレスにジャンプ)
PC <- (SP) (スタックから戻るアドレスを取り出す) SP <- SP + 2 (取り出したサイズ分スタックポインタを戻す)
「1msecってどうやって決めるの?」TAIKI氏から鋭い突っ込みを受けました。
この辺がマイコンのマイコンたるところで割り込みや他のタスクが走っている普通のPCではあまり見かけないテクニックですね。
当たり前の話ですが、CPUが命令を実行する時間というのは初めからわかっていて、どの命令にステート利用するかはわかっていますからクロックから計算可能なわけです。
Wait1msLoop: dec bc --- 6ステート ld a,c --- 4ステート or b --- 4ステート jr nz,Wait1msLoop --- 12ステート(ジャンプ時)
上記のようにループ1回につき26ステートです。AKI-80を 9.8304 MHzで動かした場合1ステートは1クロックですので 9,830,400 ÷ 26 ≒ 378回ループすればいい計算になります。上記プログラムではこれからさらに関数の呼び出しとリターンの時間を差し引いて375回のループを行っています。
いやー、相変わらず実に大雑把ですね。今回の説明だけでは何が何やらという人も多いと思います。ぼちぼち補完していきますんでご容赦下さい。m(_ _)m