ブートローダのコマンド
マニュアルの目次に戻る
ブートローダの概略
ブートローダは、H8の内蔵EEPROMに焼かれています。(H8CPUの新品時から焼かれているわけではありません。MSX−H8カートリッジに載せる前にあらかじめ秋月H8マザーボードで焼く必要があります。)
MSX−H8カートリッジがリセットされた直後、最初にMSX側のプログラムと対話するのはこのブートローダです。カートリッジの存在確認もブートローダが正しい応答を返すかどうかによって行います。
その後、ブートローダは、MSXからのコマンドを受け取り解釈し、MSXからH8へのデータやプログラムの転送、H8上でのプログラムの実行などを行います。
また、ブートローダは以下のような極めて簡素ながら一応のデバッグ機能ももっています。
・H8側のメモリをMSX側にダンプできる。
・ユーザープログラム(サブルーチン)実行前にレジスタの値を設定することができ、また実行後のレジスタの値を知ることができる。
・ユーザープログラムの実行中にNMIがかけられた場合、あるいはユーザープログラム上で TRAPA #3 命令が実行された場合、ユーザープログラムの実行を中断し、その時点のH8のレジスタの値を知ることができる。
(ただし、中断したプログラムの再開は考えていません。どのみちハンドシェーク手順は壊されますから、完全な実行再開は不可能です。)
ユーザープログラム中断機能
・リセット
8255のポートBのビット7を "1" にするとH8にリセットがかかります。確実にリセットをかけるため、ビットが "1" の状態を10μ秒程度続けてください。その後このビットを "0" にすると、ブートローダが起動し、ブートローダはリセットアクノリッジコード(0F5H)を8255のポートAに返し、ブートローダコマンド受け付け状態になります。MSX側は8255ポートCのIBFビットが "1" になるのを待った上でポートAを読み、それがリセットアクノリッジコードであることを確認してからコマンドを送出してください。
なお、すでにH8上で何らかのアプリケーションが走っており、そのアプリケーションがリセットをかけられる直前にポートAにデータを書き込んでいる可能性もあります。その場合、リセットをかけた後最初にポートA読み出される値はリセットアクノリッジコードと違うこともあります。もし違っていたらもう一度ポートAを読んでください。MSX−H8カートリッジが正常であれば2度目は必ずリセットアクノリッジコードが読み出せるはずです。(この2度目の読みこみのときにはIBFビットのチェックは必要ありません)
・NMI
8255のポートBのビット6に "0"→"1"→"0" のパルスを送るとH8にNMIがかかります。リセットと違い、NMIはパルスのエッジでかかるので、パルス幅は短くてもかまいません。
NMIをかけられると、その時点のCPUのレジスタの値がRAM上のレジスタ保存エリアに保存され、プログラムの実行権はユーザープログラムからブートローダに戻ります。そしてブートローダはNMIアクノリッジコード(0F7H)を8255のポートAに返し、ブートローダコマンド受け付け状態になります。
NMIアクノリッジコードも、リセットと同様1回目は違う値が読み出されることがありますので、その場合もう一度読み出してください。
すでにブートローダコマンド受け付け状態のときにNMIをかけると、NMIアクノリッジコードは返しますが、その時点のレジスタ値の保存は行いません。
・トラップ
ユーザープログラム実行中に TRAPA #3 命令(命令コード H'5730 )が実行されると、その時点のCPUのレジスタの値がNMIの時と同様にRAM上のレジスタ保存エリアに保存され、プログラムの実行権はユーザープログラムからブートローダに戻ります。これを簡易ブレークポイントとして使用することができます。ユーザープログラムが正常終了ではなく、トラップによって中断された場合CALLコマンドの戻り値が 0F2H ではなく 0F3H なので、正常終了との判別ができます。
ブートローダコマンドの送りかた
ブートローダのコマンドは8255のポートAからコマンドをバイトデータ列として送ることによって行います。コマンドによってはH8から戻り値を返してくることもあります。言うまでもないことですが、それら1バイト1バイトのやり取りは8255モード2のハンドシェーク手順にしたがって行います。
コマンドをやり取りする際、パラメータや戻り値として2バイト〜4バイト数値を扱うこともありますが、そのバイト並びの順は「モトローラ方式」、すなわち高位バイトから先に送ります。
好ましいことではないのですが、コマンドのデータ列やH8から戻ってくる戻り値をやり取りしている最中に、エラーなどによってどうしても中断せざるを得ないときがあります。中断した後、次のコマンドを送ろうとしてもブートローダ側はコマンド受け付け状態になっていません。そのような場合はNMIをかければコマンド受け付け状態にすることができます。
ここで言うエラーとはMSX側の問題です。たとえばH8側のメモリ内容を大量にダンプしてディスクに書き出す等の処理を行っている最中にディスクがいっぱいになってしまった、といったケースなどです。
基本的にMSXとH8の間のデータ化け等のエラーは無いものとしています。MSX本体に供給されているAC100Vの瞬間的な停電、電子ライターの着火時の火花、違法無線局等の強烈な電波、あるいは宇宙のかなたからやってくる宇宙線などにより、データ化けが起こる可能性は皆無とは言い切れませんが、対処のしようがありません。
|
ブートローダコマンドリファレンス
HELOコマンド
コマンド: 080H
戻り値 : 0F0H (GOOD) または 0F1H (BAD)
HSX−H8カートリッジの存在確認のためのコマンドです。このコマンドを受け取るとブートローダはカートリッジ上の1MバイトのRAMを簡単にチェックし、問題なさそうであればGOODコードを返し、異常があればBADコードを返します。
HELOコマンドによるRAMのチェックは極めて簡易的なものです。徹底的に厳密なRAMチェックをしないと気が済まない人は自前でRAMチェックプログラムを作って、それをH8内蔵RAMにでも転送して実行してください。
VERコマンド
コマンド: 081H
戻り値 : 'H' '8' VERh VERl
MSX−H8カートリッジのバージョンを4バイトで返します。最初の2バイトはアスキーコードで 'H8' 、次の1バイトはメジャーバージョン番号(小数点以上)のバイナリ値、その次がマイナーバージョン番号(小数点以下)のバイナリ値です。現在のバージョン番号は0.1ですので(まだ本格的に公開できるバージョンではありません(^^;)、以下のようなバイト列がMSX側に返されることになります。
048H 038H 000H 001H
なお、さらに詳しいバージョン情報はH8の H'000100 番地から256バイトに書かれていますので、必要ならそこを参照してください。
LOADコマンド
コマンド: 096H ADRh ADRm ADRL LENh LENm LENl {データ列}
戻り値 : なし
H8上のメモリに連続でデータまたはプログラムを書き込みます。コマンドコードに続けてH8のメモリのアドレスと転送する長さ(バイト単位)をそれぞれ3バイトづつ送ります。その後に続けて指定の長さ分だけデータを送ります。
H8側ではアドレス範囲のチェックなどは一切行っておりません。ブートローダのワークエリアやH8のI/Oエリアなどに不用意にデータを書き込まないよう注意してください。
DUMPコマンド
コマンド: 097H ADRh ADRm ADRL LENh LENm LENl
戻り値 : {データ列}
H8上のメモリの内容をMSX側に読み込みます。コマンドコードに続けてH8のメモリのアドレスと転送する長さ(バイト単位)をそれぞれ3バイトづつ送ると、H8側からは指定の長さ分だけデータが送られてきます。
POKE/POKW/POKLコマンド
コマンド(POKE): 090H ADRh ADRm ADRl DATA
(POKW): 092H ADRh ADRm ADRl DATAh DATAl
(POKL): 094H ADRh ADRm ADRl DATAhh DATAhl DATAlh DATAll
戻り値 : なし
H8上のメモリに1バイト数値、2バイト数値、4バイト数値を書き込みます。なお、2バイト数値、4バイト数値を書き込む場合、アドレスは偶数でなければいけません。奇数アドレスを指定してもエラーにはなりませんが、正しい書きこみは行われません。
PEEK/PEKW/PEKLコマンド
コマンド(PEEK): 091H ADRh ADRm ADRl
(PEKW): 093H ADRh ADRm ADRl
(PEKL): 095H ADRh ADRm ADRl
戻り値(PEEK) : DATA
(PEKW) : DATAh DATAl
(PEKL) : DATAhh DATAhl DATAlh DATAll
H8上のメモリから1バイト数値、2バイト数値、4バイト数値を読み出します。なお、2バイト数値、4バイト数値を読み出す場合、アドレスは偶数でなければいけません。奇数アドレスを指定してもエラーにはなりませんが、正しい読み出しは行われません。
LDCMコマンド
コマンド: 098H ADRh ADRm ADRL LENh LENm LENl {データ列}
戻り値 : なし
LOADコマンドと同様の働きをするのですが、ベタのバイト列ではなく圧縮されたデータを転送します( LDCM の 'CM' は CoMpress の略)。それをH8側で展開してメモリに書きこみます。圧縮データの形式については現在煮詰めている最中なので、いましばらくお待ちください。
RELCコマンド
コマンド: 09AH PADRh PADRm PADRl TADRh TADRm TADRL RADRh RADRm RADRL
戻り値 : なし
H8側のRAM上にあるユーザープログラムをリロケートします。発想としてはMSXのH−FORTHのXRLと同じで、H8は完全リロケータブルのプログラムを作るのは難しいので(不可能ではないですが)、ブートローダでリロケートをサポートしよう、というものです。 PADR はリロケートしたいプログラムの先頭番地、TADR はリロケート後の番地、 RADR はリロケーションテーブルの先頭番地です。リロケーションテーブルの構造については現在煮詰めている最中ですのでしばらくお待ちください。
MOVEコマンド
コマンド: 099H SRCh SRCm SRCl LENh LENm LENl DSTh DSTm DSTl
戻り値 : なし
H8側のRAM上にあるプログラムまたはデータをブロック転送します。転送元と転送先がダブっている場合の転送方向もちゃんと考慮されています。SRC は転送元アドレス、LEN は長さ(バイト単位)、DST は転送先アドレスです。
CALLコマンド
コマンド: 09FH ADRh ADRm ADRl
戻り値 : 0F2H(正常終了)または 0F3H(TRAPA #3 による中断)
H8側のRAM上のユーザープログラムを実行します。ジャンプではなくコール(JSR)を行っているので、ユーザープログラムがRTSで終わるようになっていればブートローダに戻ってきます。その場合戻り値として 0F2H を返します。ユーザープログラムが TRAPA #3 命令で中断された場合は 0F3H を返します。
当たり前の話ですが、ユーザープログラムがその中で無限ループするようなプログラムの場合、ブートローダには戻ってきません。当然戻り値も返ってきません。(リセットかNMIをかければ戻ってきますが)
CALLコマンドの具体的な動作を説明しますと、まずレジスタ保存エリアの ER0〜ER6 の値がCPUのレジスタにロードされ、スタックポインタ(ER7)に H'FFFF00 が設定され、指定された番地が呼び出されます。
そしてRTSで戻ってきた場合はCPUレジスタの ER0〜ER6 がレジスタ保存エリアに保存され、 0F2H を返します。 TRAPA #3 で戻ってきた場合は CCR、PC、ER0〜ER7 がレジスタ保存エリアに保存され、 0F3H を返します。
よって、実行前にPOKLコマンド等でレジスタ保存エリアにデータを書きこめばレジスタの値を指定することができますし、PEKLコマンド等によって実行後のレジスタの値を知ることができます。
レジスタ保存エリアは以下の番地に存在します。
H'FFEF10
|
CCR(最上位バイト)、PC(下位3バイト)
|
H'FFEF14
|
ER0
|
H'FFEF18
|
ER1
|
H'FFEF1C
|
ER2
|
H'FFEF20
|
ER3
|
H'FFEF24
|
ER4
|
H'FFEF28
|
ER5
|
H'FFEF2C
|
ER6
|
H'FFEF30
|
ER7
|
「MSXからのアクセス」には、ブートローダコマンドのタイムアウトチェックはせいぜい数ミリ秒で良い、と書きましたが、MOVEコマンド、RELCコマンドは処理すべきデータ量によっては、次のコマンドを受け付けるまでに秒近い時間がかかることがありますので気を付けてください。