Top Page


  ROMライタ / W-ROM 製作
 秋月電子 Z80 Cコンパイラ

 Z80は、1976年に発表されたイニシエのマイコンであることは確かだが、インテルからザイログに移籍した嶋正利氏がFederico Faggin氏と共に開発したCPUの中の金字塔ともいえる名品。

世界初のマイクロチップCPU i4004,i8080に続いて彼が生み出したZ80は、雨後の竹の子のように出ては消えていくあまたのプロセッサの中で、依然として世界中で現役を務めている唯一のプロセッサであることは否定できない。

Z80は、現在のPICやAVRなどのワンチップのマイクロコントローラーがRAMやプログラムメモリーだけでなく、タイマーやシリアル通信モジュールなど、ほとんどの機能を内蔵しているのに対し、これらの機能は必用に応じ、下記のようなモジュールを組み合わせて構成する。Z84C015のように、CPU,PIO,SIO,CTCなど、複数の重要なモジュールが組み込まれたチップも製造されている。

 《Z80ファミリーおよび関連デバイス》
  Z80CPU  :中央演算処理モジュール
  Z80SIO   :シリアル通信モジュール
  Z80CTC  :カウンタータイマー・モジュール
  Z80PIO   :周辺入出力インターフェース・モジュール
  Z80DMA  :ダイレクトメモリーアクセス・モジュール
  8255(PPI) :周辺入出力インターフェース・モジュール
  PROM    :128/256kビット ROM
  RAM     :128/256kビット RAM

確かにこれらのモジュールを使いこなすのは容易ではないが、例えば、多数のセンサーとステッピングモーター群を有するようなシステムを高速制御する場合、ワンチップ・マイクロコントローラーでは明らかに役不足になる場合でも、Z80の高度な割込み処理を利用することで対応可能となる可能性がある。

これらのZ80ファミリーは上位互換品を含め、HD64180、μPD9001,R800,TMPZ84C** 等々、数多くのセカンドソースが世界各国で作られ、使用されてきている。多くの高性能なCPUが消えてゆく中、もしかして、Z80はこの先もずっと残ってゆくのではないか? この名品をほっておく手はない。


Z80 Cコンパイラ
ザイログのZ80ニーモニックはインテルのニーモニックに比べ馴染み易かったこともあり、プログラムはアセンブラで記述されることも多いのですが、アセンブラでの開発は負担が大きいため、フリーCコンパイラのSDCCが使われることも多いようです。ここでは秋月のZ80 Cコンパイラ、それも「AKI-80用モニターROM&Cコンパイラセット」に付属しているCコンパイラを使ってみることにします。価格は一式で1000円。今回使うのは主役のモニターROMではなく、おまけの方のCコンパイラのCDであるが、ROM(27C256)も無論、書き換えればZ80で使うことができます。

なお、その説明書に書いてあるように、このCコンパイラは某社が開発したものであるが、秋月としてはおまけで付けているだけなので、その某社への質問等はできない。このセットについてくるガイダンスは、幾らひいき目に見ても褒められたものではなく、これでは、サイトに情報が上がってこないのもうなづける。ただしこのCコンパイラ、エディタを含む統合開発環境が使える。コンパイラやリンカのコマンドオプションに翻弄されることも、makeに惑わされなくて済むのも大きい。

しかし、C言語にすればZ80システムが簡単に使いこなせる訳ではない。Z80CTCでさえ厄介な上に、Z80SIOを前に心が折れてしまった話も耳にする。当然ながら、このページも、非力な管理人が幾日かを費やしての手探りの結果ではあるが、デバイスとコンパイラ、共に十分な資料も入手できないままのやっつけ仕事ゆえ、万全でないことは平にご容赦頂きたい。




【秋月電子/マイコン関連/各種マイコン/Z80  】 より 2023/4




Z80関連の入手

2023年の春現在、秋月電子からは、AKI-80ゴールドボード(基板単体、保守部品)、AKI-80ゴールドボード、スーパーAKI-80(基板単体、保守部品)、が販売されています。AKI-80ゴールドボードは保守品扱いにはなっていない。つまりこの先も入手可能らしい。
AKI-80ゴールドボードは、 Z84C015 (Z80CPU,PIO,SIO,CTC内蔵)とRAM(256k)、ROMソケット、Xtal(12.288MHz)がマウントされています。
スーパーAKI-80はZ84C015の他に PPI×2 、RAM(256k)、ROMソケット、Xtal(9.8304MHz)、MAX232がマウントされています。

Z80の本家ザイログからも Z84C015 という型番で東芝TMPZ84C015の互換品をネット販売しているようです。またアマゾンからは KL5C80B12CFP 始め、Z80CPU、Z80PIO、Z80CTC や、EPROMの27C128、27C256も入手することができます。

問題はEPROMにプログラムを書き込むROMライターですが、AmazonからTL866系 ライタが8千円前後で販売されているようです(2023/4月現在)。EPROMイレーザーも同じく2~3千円で販売されているようです。
私はZ80を使った年代物手作りのROMライターと、これも手作りの、デバッグ中のEPROM書き換えの手間を省くためにRAMを使ったROMエミュレーターを使っています(このページトップの「ROMライタ/W-ROM製作」参照)

それから、今時 RS232Cポート付のPCなど持っている人はいないので、シリアル-USBコンバーターが必要。USB RS232C コンバーターはAmazonでも手に入ります。XPなど32ビット対応を確認してください。


ともあれ、まずは秋月のZ80 Cコンパイラをいじってみようではないか! GAIOのCコンパイラと言わないのは敢えてジコ責任で使うから”秋月のCコンパイラ”なのである。

環境は Windows 98,2000,XP ただし私は Windows10 のVMware work station 14 player(無償版) の上で走る仮想XP を使っています。仮想マシンに関してはtoolsを参照してください。



インストール

「AKI-80用モニターROM&Cコンパイラセット」のCDのSETUPを実行すると、C:\akiz80 フォルダにインストールされます。 スタート→プログラム にGAIO の中の「秋月Z80」の「Z80統合開発環境」が実行されるフレームワーク。表示された「Z80統合開発環境」を右クリックで押さえてドラッグして離して「ここにコピー」でアイコンを作っておくと便利です。
アンインストールはインストールしたフォルダごと直接削除できます。

下は「Z80統合開発環境」のアイコン。使うのはメモ帳とRS232Cのドライバーソフト。ドライバーは予めWindowsに入っているか、USB-RS232Cコンバーターを購入すると付いてきます。あるいは、このHPに掲載している ComOutを使うこともできます。





新しいプロジェクトを作る

予めエクスプローラでc:\akiz80\sanpleの下、または適当なフォルダに新規プロジェクト用の作業フォルダを作っておきます。そしてそのフォルダの中にメモ帳でソースファイルの元を作っておきます。中身は下のような空っぽでかまいません。

アセンブラによるスタートプログラムのソースファイル
ファイル名の例 " tsst.xas "
最初の中身はend文だけでokです。


   END

c言語によるソースファイル
ファイル名の例 " ts.c"
最初の中身は

   main()
   {
   }


「Z80統合開発環境」をスタートします
ファイル→プロジェクトの新規作成 で アプリケーションを選び、作業フォルダの参照をクリックして作っておいたフォルダを選択しOK。
開発環境の詳細設定の画面では、リンクはリンケージマップを出力、HEXファイルは8ビットHEXフォーマットにチェックを入れOKします。

ソースファイルの登録
ファイルリストの「ソースファイル」を右クリックして「プロジェクトへファイルを登録」を指定します。




ファイル登録画面では、ファイルの種類に、アセンブラソースな場合は「アセンブラソースファイル(*.xas) 」
Cファイルを登録する時は「cソースファイル (*.c) 」を指定してそれぞれ組み込みます。



例は、TS.c とTSST.xas が組み込まれた状態です。


オプションパラメーターの指定
次にcプログラムとアセンブラプログラムをリンクするためのパラメーターを指定します。
「オプションファイル」欄のリスト(下図参照)をクリックし




表示されている短い中身を削除したら下のテキストをメモ帳でコピーして貼り付けます。



;-------------------------------+
; LINK 設定
;-------------------------------+
;-------------------------------+
; RAM Area |
;-------------------------------+
/ADDR=8000 ;RAMのトップアドレス
/SECT=D_*|COMM (data =_DATA)
/SECT=B_*|COMM (bss =_BSS)
/SECT=_STACK
;-------------------------------+
; ROM Area |
;-------------------------------+
/ADDR=100H ;C記述プログラムROMエリア
/SECT=C_*|CODE
/SECT=I_*
;-------------------------------+
; init data section +
;-------------------------------+
/init_section = _INIT_DATA (_DATA)
;-------------------------------+
; リンクするファイル
;-------------------------------+
/Name=TS
/entn = TSST.xao ;先頭モジュール
/modl = TSST.xao ;リンクするxasモジュール
/modl = TS.xao ;リンクするcモジュール
/SYSLIB=C:\akiz80\LIB\Z80\CS\CSZE1.XLB ;ライブラリ




なお、このオプションパラメータで配置されるメモリのセクションは
RAMエリア
 D 初期化変数
 B 未初期化変数
ROMエリア
 C プログラムコード
 I 初期化されたデータ

となります。
また、メモリーマップを確認するには、ビルド→設定 のリンクタブの一般の「リンケージマップを出力する」にチェックを入れ「セクションの割付情報をアドレス順にソート」にして、ビルドしたあと一旦統合開発環境を終了後、再立ち上げすると「その他」のファイルのmapに生成されます。



ところで、このサイトの画面をメモ帳などでコピーして、作成するZ80の開発環境へコピー&ペーストすると、行番号や余分な文字記号が混入する場合がありますから、それらは削除してください。基本的には、修正するのは一番下の部分、「リンクするファイル」の欄に表示されている先頭モジュールとリンクする xas モジュールと c モジュール名を、作成するプロジェクトのファイル名に書き換えるだけで済むはずです。

なお、上のリンク条件文はインストールされたサンプルのままですが、super AKI-80の場合、RAMの領域は8000H~、プログラムのROMエリアは100H~の領域を指定しています。


最低限の用意ができたら、ビルド→ビルド してみます
下に、 

  Create HEX...
  Success...

と表示されたらokです。エラーが表示されたら、エラー文の先頭あたりをクリックすると、対応するソース文の付近が表示されますから修正します。

●後の作業はひたすらプログラムの完成まで、少し進めてはビルドしてエラーをチェックすることです。この後に掲載するアセンブラソースとCソースの原型をコピーして使っても構いません

重要な注意事項ですが、この秋月Cコンパイラが生成する出力はobjフォルダに生成されるのですが、HEXファイルを指定しても出来上がるのは何故か xho となっています。しかし内容的にはインテルHEXになっているので、そのままROMライタに送ることができますが.HEXしか受け付けないライタの場合はリネームしてください。

●EPROMへの書き込みは、USBコンバーターを接続して、Windowsのスタートマークを右クリック→デバイスマネージャー→ポート(COMとLPT)に表示されるUSB Serial Port(COM?)のCOMポート番号を確認します。
このCOMに対して完成したHEXデータを、8ビット、パリティ無し、2stop(または1stop)、 9600bps で送信します。




Z80系デバイスの使い方

Z80には前述したようなファミリーがあります。
中心はZ80CPUですが、CTCもSIOもその使い方、特に割り込み処理の方法はサイトで探してもなかなか見つからないということになっています。やっと見つけても、説明が不足していて、どう使うか分からない場合が多いようです。以下の説明は、なんとか見つけた資料に基づく私なりの解釈です。細かく説明し過ぎると逆に分からなくなってしまうこともあり、最低限必用な部分の説明にとどめてあります。



Z80PIO (パラレル入出力デバイス)
ザイログの資料です Z80PIO

AKI-80のPIOには次のI/Oアドレスが割付けられています。

PIOAD:  0x1C   Aデータポート
PIOAC:  0x1D   Aコントロールポート
PIOBD:  0x1E   Bデータポート
PIOBC:  0x1F   Bコントロールポート

PIOの動作モードは、AおよびBのコントロールポートにコントロールワード(制御語)を書くことで決まります。幾つかのモードがあるのですが、通常は一番便利なビット制御モードと呼ばれるモード3が使われます。
コントロールポートに

 11001111  (0xCF)

を書き込み、続いて、出力ポートにしたいビットには 0 を、入力ポートにしたいビットには 1 を書き込みします。例えばビット3とビット7を入力にして、他のビットは出力ポートにする場合は、

 10001000  (0x88)

を書き込みむことで入力と出力のポートを任意に決めることができます。なお、入力ポートはフロート状態になりますから4.7kとか10kでプルアップします。
データの入出力はデータポートに対し実行します。

PIOで割込みを掛ける場合は、AまたはBの該当するコントロールポートへ以下の制御語を書きます。

割込みベクトル
V7 V6 V5 V4 V3 V2 V1 0 

割込み制御語
ビット 7:  割込み 可=1 不可=0
ビット 6:  割込み条件 OR=1 AND=0
ビット 5:  レベル条件 L=0 H=1
ビット 4:  マスク指定 しない=0 する=1
ビット 3:  0
ビット 2:  1
ビット 1:  1
ビット 0:  1

注)ザイログの解説書では、ビット5はエッジ判断ではなく「ACTIVE HIGH」という「レベル判断」のように読めるので、安全を採れば、割込みに入ったところでレベル条件を反転させて重複割込みを避けた方が良いかもしれません。ただし、ヒゲパルスやチャタリングがあると厄介なので、ハード的に防止する必要があります。

マスク語(前の割込み制御語でbit4=1にした場合、マスクしないビットが監視されます)
ビット 7:  マスクしない=0 する=1
ビット 6:  マスクしない=0 する=1
ビット 5:  マスクしない=0 する=1
ビット 4:  マスクしない=0 する=1
ビット 3:  マスクしない=0 する=1
ビット 2:  マスクしない=0 する=1
ビット 1:  マスクしない=0 する=1
ビット 0:  マスクしない=0 する=1




8255 (PPI) (プログラマブル周辺インターフェース)
PPIはインテルで開発されたデバイスで、Z80との相性も良く、ひとつのPPIに3×8本のポートが入っているため広く使われてきましたが製造中止の方向にあるようです。
super AKI-80には2個のPPIが搭載され、次のI/Oアドレスが割付けられています。

PPI0A:   0x30   Aデータポート
PPI0B:   0x31   Bデータポート
PPI0C:   0x32   Cデータポート
PPI0W:   0x33   コントロールポート

PPI1A:   0x34   Aデータポート
PPI1B:   0x35   Bデータポート
PPI1C:   0x36   Cデータポート
PPI1W:   0x37   コントロールポート

設定はコントロールポートに、動作を決める以下のコントロールワードを書き込みます
PPIはPIOと違い、個々のビットに対して入出力を設定することはできません。

ビット7: 1
ビット6: 0
ビット5: 0
ビット4: Aポート設定 1で入力、0で出力
ビット3: Cポート上位4ビット設定 1で入力、0で出力
ビット2: 0
ビット1: Bポート設定 1で入力、0で出力
ビット0: Cポート下位4ビット設定 1で入力、0で出力

入出力はデータポートに対して実行します。




Z80CTC (カウンター/タイマーコントローラー)
ザイログのCTCの資料はZ80CTCを参照してください。

外部信号を入力して計数する機能がカウンター、Z80CPUのクロックをカウントするのがタイマー動作です。
AKI-80のTMPZ84C015のCTCには次の4チャンネルのI/Oアドレスが割付けられています。

CTC0: 0x10
CTC1: 0x11
CTC2: 0x12
CTC3: 0x13

CTCを制御するにはこれらのチャンネルに対し制御語(コントロールワード)を書き込みます。


 ビット7 (CTC割り込み)
   1: 割り込み許可
   0: 割り込み不許可
 ビット6 (動作モード)
   1: カウンタ動作
   0: タイマ動作
 ビット5 (プリスケール比)
   1: 1/256
   0: 1/16
 ビット4 (CLK/TRG エッジ選択)
   1: 立ち上がり
   0: 立下り
 ビット3 (タイマーのトリガ)
   0: 自動(時定数ロードによって)
   1: CLK/TRG パルスで動作(タイマーモードのみ)
 ビット2 (後続データの有無)  注参照
   0: データ無し
   1: データ有
   続く1バイトは、時定数または割り込みベクトルのLow8bit
 ビット1 (リセット)
   0: 継続動作
   1: リセット(ソフトウェアによるCTCのリセット))
 ビット0 (後続データの内容) 注参照
   1: 制御語
   0: ベクトル

注)タイムコンスタント(時定数)レジスタ、あるいはCTCベクトルレジスタに書き込みするためには、その前に一旦、ビット2に1を書き込みます。また、このビット2を1にするときはビット1も1に設定します

CTCは4チャンネル入りのタイマー/カウンタです。カウンターモードで使う場合はCLK/TRG端子へカウント信号を入れる必用があります。タイマーモードはシステムクロックのプリスケール比でで動作します。
書き込んだ時定数値がダウンカウントされ、0になったら時定数が再ロードされ、割り込み許可されている場合はCTC割り込みが発生します。プリスケール比よりもっと落としたい場合は2個のCTCをシリーズに接続して落としたり、あるいは、割り込みの中でソフト上のカウンターを作って落とす方法があります。





Z80SIO (シリアル通信入出力デバイス)
ザイログの資料をここに見つけましたので根気のある方は読んでみてください。

Z80SIOは、RS-232CやRS-422のようなシリアル信号の送受信を実行します。
AKI-80のTMPZ84C015のSIOには次のI/Oアドレスが割付けられています。

SIOAD:  0x18   チャンネルAデータポート
SIOAC:  0x19   チャンネルAコントロールポート
SIOBD:  0x1a   チャンネルBデータポート
SIOBC:  0x1b   チャンネルBコントロールポート

SIOの制御は、SIO内の複数の書き込み用WRレジスタに必用な設定データを書き込むことで行います。どのレジスタに書き込むかは、レジスタWR0で指定します。
RRレジスタという読み出し用レジスタもあって、送信完了などはRRレジスタをinp()することで看視することができます。


WR0
SIOのWR0は、このWR0の次に送られるデータをどのWRレジスタに書き込むかを決めるレジスタです。D2~D0でSIOの書き込みWRレジスタを指定します。例えば、SIOに対して1を書くと、次のデータはWR1に書きこむデータになります。
ただし、D2~D0を 000 にするとこの1バイトだけで実行する命令になります。例えばチャンネルリセット 00011000 (0x18) がこれです。



WR1
割り込みの設定をします。
受信割り込みだけをする場合は 0x10
送受信とも割込みする場合は下記(参考1)


ChBの Status Affects Vector のビットが1の場合、割り込みアクノリッジから返されるベクタは、以下の割り込み条件に従って変化します。



WR2
割り込みベクトルを書き込みます(チャンネルBだけです)



WR3
受信の設定です。8ビット,受信可にする場合は 0xc1 です。


WR4
受信の クロック分周速度、ストップビット数、パリティ有無 の指定です。


WR5
送信設定  DTR on,8ビット、送信可,RTS on なら 0xea です。



WR6,WR7 はSLDC用なので使いません。



RR0~RR2は読み出すためのレジスタで、 inp(SIOのアドレス)で読み出すことができます。

RR0
読み出しレジスタRR0は、ビット2で送信バッファの空、ビット0で受信データ有効を判断できます。


RR1
ビット6~4で通信エラー、ビット0は送信完了です。


RR2
ベクトルレジスタの読み出しです。チャンネルB だけです。



参考1) SIO CH-Aを送受信とも割込みにする場合の制御語の例
(注:WR4はシステムクロック周波数およびCTC3の設定条件によって変わります。)

;CH-A
DB SIAC,00011000B ;CH-A リセット
DB SIAC,4       ;次は受信設定
DB SIAC,01000100B ; *16 STP=1 PARITY NONE 9600PBS
DB SIAC,1       ;次は割込み設定
DB SIAC,00010110B ; 受信割込有、送信割込有
DB SIAC,3       ;次は受信設定
DB SIAC,11100001B ; 8BIT
DB SIAC,5       ;次は送信設定
DB SIAC,11101010B ; 8BIT

;CH-B
DB SIBC,00011000B ;CH-B リセット
DB SIBC,2       ;次は割込ベクトル
DB SIBC,20H     ; ベクトル値
DB SIBC,4      ;次は受信設定
DB SIBC,01000100B ; *16 STP=1 PARTY NONE 9600BPS
DB SIBC,1      ;次は割込み設定
DB SIBC,00000100B ; 割込みベクトル有効
DB SIBC,3      ;次は受信設定
DB SIBC,11100001B ; 8BIT
DB SIBC,5       ;次は送信設定
DB SIBC,11101010B ; 8BIT



送信割込み手順: 送信データの最初の1バイトをSIOのデータポートに書き込む。
SIOAの送信バッファが空になった時に送信割込み(iisia())が発生するが、次のデータが存在しない場合は、WR0 の Reset TxINT Pending (保留中の送信割込みのリセット)を実行。


参考2) Super AKI-80ではCTC3の出力端子 ZC/TO3 がSIOのクロック入力端子 TXCAとRXCA に接続されているため(下図参照)、CTC3のタイマー動作でSIOのクロック信号を生成することができます。Super AKI-80以外のボードの場合はボード外部で接続してください。




Z80の割り込み

プログラミングの検討に入る前に、割り込み処理について考えてみます。割り込み処理は、メインのプログラムが何処を実行していようが、割り込みが禁止されていない時に、割り込みが発生すると、予め定めておいた特定アドレスにジャンプして、そこに書かれたプログラムを実行し、割り込みからの復帰命令によって、中断していたメインプログラムに復帰するという仕組みです。

 電源ダウンを検出し、重要データを退避させるノンマスカブルインタラプト、
 センサーの周期監視、ステップモーター駆動、PWM制御のタイマー割り込み、
 シリアルデータの受信/受信割込み、
 装置の動きを検出するエンコーダー信号割込み

などが代表的な割込みです。

PICは、どんな割り込みが発生しても4番地にジャンプして、その要因は割込みフラグを調べて必用な処理をします。AVRは、 Int0の割り込みは1番地、Timer0 がオーバーフローしたら6番地、とうように固定したアドレスが割り振られています。このアドレスが並べられたテーブルはベクトルテーブル(ベクターテーブル)と呼ばれます。

Z80はもっとフレキシブルで、IM2と呼ばれるモードでは、ベクトルテーブルのアドレスを指定することができます。ベクトルテ―ブルのアドレスの上位8ビットは、CPUのIレジスタに書き込んだ値が適用され、下位8ビットは割り込みを発生するデバイスから送られてくる割込みベクトルが使われます。その各、デバイスのベクトルレジスタには、予めこのベクトルを書いておきます。ひとつのモジュールが複数の割込み機能を持っている場合は、先頭のベクトル値を書込みます。
そして、ベクトルテーブルには、割込処理プログラムの開始アドレスを書いておきます。このアドレスを CPU が読みとり、実際の割込処理プログラムにジャンプします。

通常、ベクトルテーブルは
  0000番地 JP START  ;コールドスタートプログラムへジャンプ
の後の、疑似命令
  SECT CODE,ORG=010H
によって先頭アドレスが割り当てられて、続いて 
  DW 割込みベクトル
   、、、
によってジャンプ先が指定されます。



下はCTCとSIOに割り当てられる下位の割込みベクトルの例です。



例えば、SIOAを受信割込みに設定し、
Iレジスタに 00H を書き、
SIOBには 2 に続く 20H によって、ベクトルレジスタにSIO割込みベクトル先頭値の20Hを書き込み、
割込み準備が済んだら EI (割込み許可)をします。

SIOのChAに受信割込み(Ch A Receive Charactor Available)が発生すると、
ChA 受信割り込み用アドレス 002CH のベクトルテーブルに書かれたアドレスにジャンプすることになります。

割込みが受付られると、割込み先の処理が終わって、EI に続く RETI 命令で、中断していたメインに復帰し、再度割込みが受付可能となります。ここでのEIは即有効になるのではなく、RETIの後に有効となります。
通常、この割込み処理部分は C言語ではなくアセンブラで記述されます。

 vecadr:    ;ベクトルテーブルで指定する割込み処理アドレス
 push af
 push bc
 push de
 push hl
 call _cprog  ;Cプログラム呼び出し
 pop hl
 pop de
 pop bc
 pop af
 ei       ;割込み許可
 reti      ;中断していたメインへ復帰

push で各レジスタペアが退避されて、cプログラムが呼ばれ、cプログラムから戻ったところで、逆順で各レジスタペアがpop されて、ei , reti  で割込みプログラムからメインプログラムに復帰すると同時に、新たな割込みを受け付けます。

複数の割込み要求が同時に発生した場合、割込みの優先順は、IEI - IEO 信号のデイジーチェーン回路によって決定されます。



割込みに関係する注意:1
メインと割込みの双方で同じ変数、フラグ、関数などを利用していると、メインでこれらをアクセスしている最中に割込みが入ってしまい、メインに帰ってきた時、全く予期しなかったことが発生する危険性があります。これを避けるためには、

 1)割込み先でアクセスする変数、フラグ、関数を、メイン側でアクセスする際は、必ず di() ei() で保護する。
 2)メインと割込みに共通する関数の場合、関数の途中にei()が入ると再割込みされてしまうため、 di() ei() を入れたメイン用関数と、di() ei() を入れない割込み用関数の2種類用意する。


割込みに関係する注意:2
割込み時にアクセスする変数には
    valatile 変数;
または
    volatile static 変数;
のように、volatileを宣言して、安定したメモリを確保する必要があります。タイマー割込みなどは、static だけでは最適化によって非安定メモリー領域が割り当てられてしまう恐れがあるためです。




 Super AKI-80 による
アセンブラプログラム

さて、ここからが秋月のCコンパイラによるプログラミングの説明です。
ほとんどの機能のプログラムはCで書けますが、最低限のプラットフォーム部分はアセンブラで記述することをお勧めします。例えば電源ONで実行されるスタート部分、ベクトルテーブル、割り込み処理の入り口と出口部分などです。

AVR studioやPICのMPLAB IDEのCプログラムは一見、Cだけで書かれているように見えますが、やはりこれらのプログラムはアセンブラで書かれたプラットフォームに乗っています。

ところで、このコンパイラには ei()、di() がマクロとして備わっていますが、入力関数inp() や出力関数 outp() が付いていないため、下記のサンプルプログラムには、アセンブラで組み込んでおきました。少なくとも入出力ができないと、画面とキーボードの無いPCと同じで、手も足もでませんから最低限必用という訳です。

以下はアセンブラソースのひな型 TST.xas です。
なお、以下のcプログラムは、TMPZ84Z015で動く Super AKI-80を使っていますが、Z80とZ80CTCとZ80SIOから構成されるシステムであれば基本的には動作可能のはずです。
ただし Super AKI-80以外のシステムを利用する場合はZC/TO3 をSIOのクロック入力端子 TXCAとRXCA に接続してください。

このアセンブラのプラットフォームの上でcプログラムが動くことが確認できれば、後はかなり高度なプログラムも、cだけで記述できる筈です。


;********************************
; TST.xas
; アセンブラ ソースファイル
;********************************

;別ファイルで定義する関数(このファイルで使うため)
EXTNAL _main
EXTNAL _iict0
EXTNAL _iisia

;関数の広域宣言(別ファイルで使えるために)
global _outp
global _inp
;ラベルの広域宣言(cファイルで使うため)
global ISIOB0
global ISIOB1
global ISIOB2
global ISIOB3
global ISIOA0
global ISIOA1
global ISIOA2
global ISIOA3
global ICT0
global ICT1
global ICT2
global ICT3

;定数
STK EQU 0FFFFH ;Stack address

;*******************************

INIT SECT CODE,ORG=0
JP START

;CTC 割込みベクトルテーブル
SECT CODE,ORG=010H
DW ICT0 ; 10H  CTC0
DW ICT1 ; 12H  CTC1
DW ICT2 ; 14H  CTC2
DW ICT3 ; 16H  CTC3

;SIO 割込みベクトルテーブル
SECT CODE,ORG=020H
DW ISIOB0 ; 20H  SIOB TX BF EMPTY
DW ISIOB1 ; 22H  EXT STATUS
DW ISIOB2 ; 24H  RX AVAILABLE
DW ISIOB3 ; 26H  SPECIAL RX
DW ISIOA0 ; 28H  SIOA TX BF EMPTY
DW ISIOA1 ; 2AH  EXT STATUS
DW ISIOA2 ; 2CH  RX AVAILABLE
DW ISIOA3 ; 2EH  SPECIAL RX

SECT CODE,ORG=038H
halt

;********* Power on Cold Start ***************

C_START SECT CODE
START:
LD SP,STK       ; スタックの設定
XOR A
LD I,A             ; 割込みテーブルの上位アドレスをIにセット
IM 2               ; 割込みモード2

CALL _main      ;Cプログラムへ

HALT

;********* Cプログラム用アセンブラ **********
; ポート入力
; 変数 = inp(addr);
; cレジにポートaddrがセットされ,戻りドレスがpushされてcからcallされる
_inp:
ld hl,2            ;戻りアドレス分の2バイトをシフト
add hl,sp
ld c,(hl)          ;c <-- ポートaddr
in a,(c)          ;accに入力
ld c,a            ;c <-- 入力データ
ld b,0            ;b <-- 0
ret

;ポート出力
; outp(addr,data); で呼ばれる
; dat,ポートaddr,戻りアドレス の順に各2バイトがpushされている 
_outp:
ld hl,2          ;戻りアドレス分の2バイトをシフト
add hl,sp      ;addr データをポイント
ld c,(hl)       ;c <-- addr
inc hl
inc hl
ld b,(hl)       ;data
out (c),b      ;出力
ret

;******** 割込みのアセンブラ処理*********
;割込み発生でこのベクトルテーブルの下位バイトと
;Iにセットされた上位バイトのアドレスが実行される

ISIOB0:      ; SIOB TX BF EMPTY
ei
reti
ISIOB1:      ; EXT STATUS
ei
reti
ISIOB2:      ; RX AVAILABLE
ei
reti
ISIOB3:      ; SPECIAL RX
ei
reti
ISIOA0:      ; SIOA TX BF EMPTY
ei
reti
ISIOA1:      ; EXT STATUS
ei
reti

ISIOA2:      ; RX AVAILABLE 受信割込み
push af
push bc
push de
push hl
call _iisia     ;Cプログラム呼び出し
jr retint

ISIOA3:      ;1E SPECIAL RX
EX AF,AF'
EXX
LD A,30H         ;ERROR RESET
OUT (19H),A    ;SIOAC
EXX
EX AF,AF'
ei
reti
ICT0:             ; CTC0 オーバーフロー割込み
push af
push bc
push de
push hl
call _iict0       ;Cプログラム呼び出し
jr retint

ICT1:           ; CTC1
ei
reti
ICT2:           ; CTC2
ei
reti
ICT3:           ; CTC3
ei
reti

retint:
pop hl
pop de
pop bc
pop af
ei
reti

END



上のアセンブラプログラムは以下のような働きをします。

① 電源が投入された時に開始されるPower on Cold Start
スタックポインタ、Iレジスタ設定、割り込みモードを設定してからCのmainに飛んでいます。

② 割り込みのベクトルテーブルの配置と、このテーブルを参照して実行される割り込みルーチンの入り口と、cで記述される割り込み処理が終了した時に実行される割り込み復帰命令の reti 命令。
(割り込みプログラム中で ei をしない限り割り込みは再割り込みしません。ei に続く reti で割り込みが可能になります)
ベクトルテーブルでは、CTCの割込み下位アドレスは10H、SIOは20Hに設定しています。この例では、I レジスタで決める上位ベクトルは0Hにしています。後述のCTCとSIOの割り込みで使うのがこれらのアドレスですから、頭の隅へ入れておいてください。

③ ポート入力 inp() とポート出力 outp() 関数用のアセンブラプログラム。
cプログラム化した時、どうしても避けて通れないのが di, ei そしてこのポートへの入出力でしょう。cプログラム中にインラインアセンブラやマクロでこういった機能を記述する方法もありますが、このcコンパイラはどうもdi,eiのインラインしかなかったので、アセンブラで入出力ルーチンを記述し、これをcの中から呼ぶ方式を採っています。この入出力ルーチンは御覧のように使っているレジスタをpush,popで保護していません。もし弊害があったら追加して、その分sp(スタックポインタ)をシフトしてください。



 Super AKI-80 による
Cプログラム

下は、このサイトで使ったcプログラムの TS.c です。当初組み込んだ
main()
{
}

を削除して、以下の内容に差し替えてください。
なお、CTC割り込みiicty0()、SIO受信割り込み iisia()、そしてmain() の中も殆どがコメントにしてありますので、確かめる時は /* */を外してください。また、前のアセンブラソースもそうでしたが、タブが無効になってしまっています。見やすくなるように必用にに応じて挿入してください。


/* super AKI-80 c program */

global main();
global inp(char addr);
global void outp(char addr,char dat);

/* 定数 */


/* インラインアセンブラ */
#define DI() __asm(" di")
#define EI() __asm(" ei")
#define BUFSIZE 10

/*====================
I/O map
====================*/
/***** CTC *****/
#define CTC0 0x10
#define CTC1 0x11
#define CTC2 0x12
#define CTC3 0x13

/***** SIO *****/
#define SIAD 0x18
#define SIAC 0x19
#define SIBD 0x1a
#define SIBC 0x1b

/***** PIO *****/
#define PIOAD 0x1c
#define PIOAC 0x1d
#define PIOBD 0x1e
#define PIOBC 0x1f

/***** PPI(8255) *****/
#define PPI0A 0x30
#define PPI0B 0x31
#define PPI0C 0x32
#define PPI0W 0x33

/***** PPI(8255) *****/
#define PPI1A 0x34
#define PPI1B 0x35
#define PPI1C 0x36
#define PPI1W 0x37

/***** プロトタイプ宣言 ******/
void lptm(unsigned int);
void ioini(void);
void on_ctc(char,char);
void off_ctc(char);

/*** 外部変数 ****/
volatile char rxbf;
volatile char t_dat;

/********************************
割込み処理
********************************/

/***** CTC ch0 タイマー割込み ******/
iict0()
{
/* 動作確認
t_dat ^= 0xff;
outp(PIOAD,t_dat);
*/
}

/****** SIO chA 受信割込み ******/
iisia()
{
/* 動作確認
rxbf = inp(SIAD);
outp(PIOAD,rxbf);
*/
}

/********************************
メイン処理
********************************/
main()
{
ioini();
EI();

t_dat = 0;
/* on_ctc(CTC0,0xf0); */

while(1)
{
/* SIO 送信(要TX-RX接続)
lptm(300);
outp(SIAD,t_dat++);
lptm(300);
*/
}
}

/***** IO 初期化 *****/
void ioini(void)
{
DI();
/* PI0A */
outp(PIOAC,0xcf);    /* com, M3 */
outp(PIOAC,0x0);     /* com, 0=output 1=input */
outp(PIOAC,0x07);    /* com, DI */
outp(PIOAD,0xff);     /* dat, all H */
/* PI0B */
outp(PIOBC,0xcf);    /* com, M3 */
outp(PIOBC,0xff);    /* com, 0=output 1=input */
outp(PIOBC,0x07);   /* com, DI */
outp(PIOBD,0xff);    /* dat, all H */
/* CTC load int vector */
outp(CTC0,0xa4);    /* b0=0 --> next word is vector */
outp(CTC0,0x10);    /* vector Low */
/* CTC for SIO clock */
outp(CTC3,0x07);    /* NOT INT,clk/16,TC LOAD,RESET START */
outp(CTC3,4);        /* 153600Hz/16 = 9600BPS) */
/* SIO  TXDA(割込無し)、RXDA(受信割込) */
outp(SIAC,0x18);     /* channel RESET */
outp(SIAC,0x14);     /* WR4*/
outp(SIAC,0x44);     /* clk*16,STP1,NP*/
outp(SIAC,3);          /* WR3*/
outp(SIAC,0xc1 );    /* 8BIT,RX enable*/
outp(SIAC,5);         /* WR5*/
outp(SIAC,0xea);     /* DTRon,8BIT,TXen,RTSon*/
outp(SIAC,1);         /* WR1 */
outp(SIAC,0x10);     /* all char enable */

outp(SIBC,0x18);     /* channel RESET */
outp(SIBC,2);         /* WR2*/
outp(SIBC,0x20);     /* LOW INTTBL addres*/
outp(SIBC,0x14);     /* WR4*/
outp(SIBC,0x44);     /* clk*16 STP1,PN */
outp(SIBC,3);         /* WR3*/
outp(SIBC,0xc1);     /* 8BIT,RX enable*/
outp(SIBC,5);          /* WR5*/
outp(SIBC,0xea);      /* DTRon,8BIT,TXen,RTSon */
outp(SIBC,1);          /* WR1*/
outp(SIBC,0x14);      /* all char en,save affect vector*/
}

/***** CTC 起動(CTC ch,時定数)*****/
void on_ctc(char ch,char time)
{
outp(ch,0xa7);         /* 割込,clk/256,次時定数(b0=1),reset&start*/
outp(ch,time);         /* 時定数 */
}
/***** CTC 停止 (CTC ch)******/
void off_ctc(char ch)
{
outp(ch,0x3);        /* 停止 */
}

/***** 1ms×N ループタイマー******/
void lptm(unsigned int ms)
{
unsigned int n;
while (ms)
{
for (n = 60; n > 0; n--); /* */
ms--;
}
return;
}


訂正: 上記 cプログラムにおける I/O map の PPI 設定の記述に誤りがありました。記載記事の範囲内では問題となりませんが、PPIを使うよう変更した場合、エラーとなりますので、上記 I/O map設定に変更願います。2023/5



秋月のZ80cコンパイラが持つプリプロセッサ機能の詳細は不明ですが、試したところ、以下のような簡単なマクロとインラインアセンブラの機能を持っているようです。

マクロ定義
#define in_PIA inp(PIOAD)
#define out_PIA(a) outp(PIOAD,(a))
#define in_PIB inp(PIOBD)
#define out_PIB(a) outp(PIOBD,(a))

これにより、

out_PIA(in_PIB);

のような簡潔な表現ができるようになります。


ンラインアセンブラ

__asm(" ld c,(hl) ");
__asm(" inc hl");

のようにcソースファイル中にアセンブラ命令を入れることができます。


最適化

ビルド→設定→コンパイル → 設定カテゴリ 「最適化」から 「サイズ優先」か「実行スピード優先」か指定し適用します。



サンプルプログラムについて
このサンプルプログラムは、CTCのチャンネル0がタイマー割り込みで動作し、SIOチャンネルAが受信割り込みで動作するプログラムになっています。
RS-232Cのクロス接続は下図のような接続ですが、送信したデータを自身で受信して確認する場合は、2-3、4-6、7-8 をループバック接続ます。




なお、私もこのCコンパイラとの付き合いは日が浅いのですが、数値表現が10進と16進だけで、2進表現は受け付けてくれないなど、残念な面もありますが、二次元配列も扱える等、そこそこのレベルを持っているのではないかと思われます。コンパイル後のサイズや実行速度は決して優れているとは言えないようですが、簡易的とは言え統合開発環境の下のc言語である秋月のcコンパイラ、なかなかの安定性を示してくれています。改めてZ80を日の当たる場所に引き摺り出してくれることを期待したいものです。


LCD表示とEEPROM
-Super AKI80-

Z80を実用化するためには、書き換えたデータを保持できるEEPROMと、LCD表示が欠かせません。
EEPROMは2線式の24C16を使いましたが、I2Cインターフェースを持つデバイスなら、これより大きな容量のEEPROMでも勿論構いません。
LCDは秋月の16桁×2行(SC1602)です。

Super AKI80のポートとの接続

LCD - CPU基板
14 D7-- PIOB7
13 D6-- PIOB6
12 D5-- PIOB5
11 D4-- PIOB4
10
9 
8 
7 
6 E  -- PIOB3
5 R/W-- GND
4 RS -- PIOB2
3 Vo ----4.7k----GND
2 Vss -- GND
1 Vdd -- 5V

24C16 - CPU基板
8 Vcc -- 5V
7 Wp -- GND
6 SCL -- PIOA6 ---10k---Vcc
5 SDA -- PIOA7 ----10k---Vcc
4 GND -- GND
3 A2 -- GND
2 A1 -- GND
1 A0 -- GND


プログラムリスト

Z80_TRY.zip

訂正: Z80 TRY.zip における I/O map の PPI 設定の記述に誤りがありました。記載記事の範囲内では問題となりませんが、PPIを使うよう変更した場合、エラーとなりますので、I/O map設定を上記 Z80 TRY.zip に差し替え願います。2023/5

解凍して、そのまま秋月の統合開発環境を使います。

写真はW-ROMを使ってEEPROMの書き込みと読み出しの実行




PICやAVRなどの場合はコントローラーの型番を開発環境へ入力するとメモリ領域も自動的に決定されますが、Z80の場合はRAMもROMもジャンパー線で設定するようになっていて、開発環境とは切り離されています。このため誤った設定にすると誤動作してしまう危険性が出てきます。少なくともオプションパラメーターにおけるRAM、ROMの領域設定と、基板上のRAM、ROMのサイズ設定と実際に差し込むROMのサイズが一致している必用があります。狭い領域しかアクセスできない場合はもちろんプログラムはまともに走りませんが、逆の場合はフロートしたアドレスがアクセスされることになるため、再現性のないトラブルに見舞われることになるので要注意です。


秋月のZ80ボード使う際の注意

秋月電子から発売されているZ80ボードは Super AKI-80 と AKI 80ゴールドボードがあります。
これらのボードのポートを入力ポートとして使う場合は必ず外付けの4.7k~10kΩでプルアップする必要があります。

なお、当管理人が設計製造に関わったZ80ボードでは、ポートだけでなく、データバス(D7~D0)もアドレスバス(A15~A0)も10kΩでプルアップ処理しています。これはZ80に限らず、3ステート回路はライン全体が浮いた状態(ハイインピーダンス)となるため、周辺回路からの電磁誘電や静電誘導の影響を受け易く、接続されているMOSゲートに思わぬ誤動作を引き起こしたり、時には素子の破壊を招くため、回路中の1点でプルアップ(またはプルダウン)するという原則に基づく対処です。残念ながら、Super AKI80の上位アドレスは出ていないため処置できませんが、


Super AKI-80のコネクタマップとIOマップ  (秋月電子 Z80 資料より)


AKI-80 ゴールドボードのコネクタマップとIOマップ




資料

C言語で制御する限り、Z80のレジスター構成や命令を熟知する必用はありませんが、アセンブラ記述部分に手を加えたり、どのようにcコンパイルされたか知りたい場合はやはり最低限のZ80のハード構成と命令の理解が必要となります。参考までに添付します。

Z80 CPU




メモリ空間
 ROM 0000H ~ 7FFFH
 RAM 8000H ~ FFFFH

I/O空間
 00H ~ FFH

レジスタ構成(ビット数)
 
 A: アキュムレータ―(演算、入力、出力の中心レジスタ)
 F: フラグレジスタ(ゼロフラグ、キャリーフラグなど)
 B、C、D、E、H、L : 8ビットレジスタ または AF、BC,DE,HLの16ビットレジスタペア
 IX,IY: インデックスレジスタ
 SP: スタックポインタ
 PC: プログラムカウンタ
 A'~L’: 裏レジスタ
 I: インタラプトレジスタ
 R: リフレッシュレジスタ


Z80 命令


 r/r' :A,B,C,D,E,H,L
 d :+127~-128
 n :8ビットの値 (0~FFH)
 nn :16ビットの値 (0~FFFFH)
 nnL :nnの下位8ビット
 nnH :nnの上位8ビット
 dd :BC,DE,HL,SP
 (nn) :nn番地の内容
 ddL :レジスタペアの下位レジスタ
 ddH :レジスタペアの上位レジスタ
 qq :AF,BC,DE,HL
 s  :r,n,(HL),(IX+d),(IY+d)
 m  :r,(HL),(IX+d),(IY+d)
 ss :BC,DE,HL,SP
 pp :BC,DE,IX,SP
 rr :BC,DE,IY,SP
 b :0~7
 m :r,(HL),(IX+d),(IY+d)
 cc :フラグコンディション















フラグコンディション
算術論理演算系の命令が実行されると、Fレジスタのゼロフラグやキャリーフラグなどは
結果に応じて変化し、分岐命令のコンディション cc は以下のように解釈されます。

 Z   :ゼロフラグが1の場合
 NZ  :ゼロフラグが0の場合
 C   :キャリーフラグが1の場合
 NC  :キャリーフラグが0の場合
 P   :符号フラグが0の場合(結果が+か0だった場合)
 M   :符号フラグが1の場合(結果が-だった場合)
 PE   :パリティーフラグが0の場合
 PO   :パリティーフラグが1の場合

例)
 JP NZ,8100H       ;ゼロフラグが立たなかったら、8100Hへジャンプ
 RET C                ;キャリーフラグが立っていたらリターン




アクセスカウンター







  






inserted by FC2 system