にゃごすwiki

techhan/2.4

%第1版第15刷 P.73-82


4章 マシン語とのリンク


 これまで説明したように、MSX BASIC ver2.0は強力な機能を備えていますが、さらに実行速度を上げたい、あるいはMSX2のハードウェア機能をとことんまで引き出したいと思ったら、やはりどうしても多少のマシン語は使用せざるを得ません。本章では、その必要が生じた時のために、BASICからマシン語プログラムを呼び出す方法、およびその際に知っておくべきいくつかの情報について説明します。

 4.1 USR関数


 BASICからのマシン語の呼び出しは、以下の手順で行います。USR関数のカッコの中は、引数としてマシン語に値を渡すために用いられます。引数は数式、文字式のいずれでもかまいません。

  ? DEF USR文で、マシン語プログラムの実行開始アドレスを指定する。
  ? USR関数でマシン語を呼び出す。
  ? マシン語からBASICに戻る場合は、RET命令(C9H)を実行する。

例) C000番地が実行アドレスであるマシン語プログラムを呼び出す。

DEF USR=&HC000
A=USR(0)

 4.2 USR関数の引数と戻り値によるデータの受け渡し


 BASICからマシン語に引数を渡す場合、マシン語側ではレジスタAの内容でその型を知ることができます(表2.23)。目的の値は引数の型により図2.13のような形式で格納されていますから、それに応じて値を受け取ります。例として、文字列型の引数を受け取るプログラムをリスト2.1に示します。

22バイト整数型
3文字列型
4単精度実数型
8倍精度実数型

表2.23 レジスタAに代入される引数の型



図2.13 引数による値のわたされ方


リスト2.1 文字型の引数の使用例

;============================================================
;  List 2.1    print string with USR function
;              to use, do  DEF USR=&HB000 : A$=USR("STRING")
;============================================================
;
CHPUT	EQU	00A2H		;character output

	ORG	0B000H

RDARG:	CP	3
	RET	NZ		;parameter is not string

	PUSH	DE
	POP	IX		;IX := string descriptor
	LD	A,(IX+0)		;get string length
	LD	L,(IX+1)		;get string pointer(low)
	LD	H,(IX+2)		;get string pointer(high)
	OR	A
	RET	Z		;if length=0

RD1:	PUSH	AF
	LD	A,(HL)		;get a character
	CALL	CHPUT		;put a character
	POP	AF
	DEC	A
	RET	Z
	INC	HL
	JR	RD1

	END


 逆に、引数として渡されたこれらの値をマシン語の中で書き換えることによってUSR関数の値としてBASICに引き渡すことができます。この場合、VALTYP(F663H)を変更することにより戻り値をBASICからの引数の型以外のものに変更することも可能です。ただし、文字列型の場合、文字数を変えることはできません。

 4.3 命令の増設


 MSXでは現在“CMD”と“IPL”という予約語が未使用になっており、この予約語のフック(それぞれFE0DH,FE03H)を自分で作成した任意のマシン語ルーチンにジャンプするよう書き換えるだけで、新しい命令を作成することができます。簡単な例をリスト2.2に示します。


リスト2.2 CMD命令の増設

;=================================================================
;  List 2.2   make CMD command ( turn on/off the CAPS LOCK )
;              to initialize command:   DEF USR=&HB000 : A=USR(0)
;              to use command:          CMD
;=================================================================
;
CHGCAP	EQU	0132H		;CAPS LAMP on/off
CAPST	EQU	0FCABH		;CAPS LOCK status
HCMD	EQU	0FE0DH		;CMD HOOK

	ORG	0BOOOH

;----- CMD initialize -----	この部分を実行するとCMDコマンドが増設される

	LD	BC,5		;NEW HOOK SET
	LD	DE,HCMD
	LD	HL,HDAT
	LDIR
	RET

;----- new HOOK data -----	フック(FE0DH)に書き込む5バイトデータ

HDAT:	POP	AF
	JP	CAPKEY
	NOP

;----- executed by CMD -----	CMDコマンドの実体

CAPKEY:	CALL	CHGCAP
	LD	A,(CAPST)
	CPL
	LD	(CAPST),A
	RET

	END


 フックに書き込む最初の“POP AF”は、この場合“CMD”の実行の時にスタックに積まれるエラー処理アドレスを捨てるためのものです。これを入れておかないと、“RET命令”でBASICに戻るはずがエラー処理ルーチンにジャンプしてしまいます。ユーザールーチン内でエラーを出したい場合はこのアドレスを取っておくのも1つの方法です。
 なお、これらのフックは本来は将来の拡張用にリザーブしてある場所なので、商用のアプリケーション・プログラムで使用してはいけません。

 4.4 CMD命令の拡張


 より複雑なステートメントの拡張を行うには、CMD命令にも引数を渡せると便利です。マシン語を呼び出した後、HLレジスタがBASICテキスト中の“CMD”の次の位置を指していますから、これを利用して後に続く文字列の評価を行えば目的は果たせます。これらを行うために有効と思われる内部ルーチンを、以下に示します。

● CHRGTR(4666H/MAIN)……………テキストから1文字取り出す(図2.14)。
入力:HL←テキストを指すアドレス
出力:HL←取り出した文字のアドレス
  A←取り出した文字
  Zフラグ←文末(:または00H)ならばON
  CZフラグ←0〜9ならばON
機能:(HL+1)の場所のテキストから1文字取り出す。スペースはスキップされる。

● FRMEVL(4C64H/MAIN)……………テキスト中の式を評価する(図2.15)。
入力:HL←テキスト中の式の先頭アドレス
出力:HL←式の次のアドレス
  [VALTYP(F663H)]←型に応じて2,3,4,8の値が入る。
  [DAC(F7F6H)]←式の評価結果
機能:式を評価し、型に応じて出力する。

● FRMQNT(542FH/MAIN)……………式を2バイト整数型で評価する。
入力:HL←テキスト中の式の先頭アドレス
出力:HL←式の次のアドレス
  DE←式の評価結果
機能:式を評価し、整数型(INT)で出力する。結果が2バイト整数型の範囲に納まらない場合には“Overflow”エラーを発生し、BASICコマンドレベルに戻る。


図2.14 CHRGTRの入出力状態


図2.15 FRMEVLの入出力状態


● GETBYT(521CH/MAIN)……………式を1バイト整数型で評価する。
入力:HL←テキスト中の式の先頭アドレス
出力:HL←式の次のアドレス
  A,E←式の評価結果(AとEには同じ値がはいる)
機能:式を評価し、1バイト型で出力する。結果が1バイト整数型の範囲に収まらない場合には“Illegal function call”エラーを発生し、BASICコマンドレベルに戻る。

● FRESTR(67D0H/MAIN)……………文字列を登録する。
入力:[VALTYP(F663H)]←型(文字列でないとエラーになる)
  [DAC(F7F6H)]←ストリング・ディスクリプタへのポインタ
出力:HL←ストリング・ディスクリプタへのポインタ
機能:FRMEVLで得た文字列型の結果を文字列領域に登録し、そのストリング・ディスクリプタを得る。文字列を評価した場合は前記のFRMEVLと組み合わせて以下のように使用するのが一般的である。

	 :
	CALL	FRMEVL
	PUSH	HL		;テキストポインタをセーブする
	CALL	FRESTR
	EX	DE,HL		;ストリング・ディスクリプタを得る
	POP	HL
	LD	A,(DE)		;入力された文字列の長さを得る
	 :

● PTRGET(5EA4H/MAIN)……………変数の格納アドレスを得る(図2.16)。
入力:HL←テキスト中の変数名の先頭アドレス
  [SUBFLG(F6A5H)]←0:単純変数,0以外:配列変数
出力:HL←変数名の次のアドレス
  DE←目的の変数の内容が格納されているアドレス
機能:変数(または配列変数)の格納アドレスを得る。目的の変数の領域が確保されていなかった場合は、領域の確保も行われる。[SUBFLG]の値を0以外に設定しておくと、配列の個々の要素ではなく、その配列の先頭アドレスが得られる。


図2.16 PTRGETの入出力状態


● NEWSTT(4601H/MAIN)……………任意のテキストを実行する。
入力:HL←実行するテキストのアドレス
出力:――
機能:テキストの実行を行う。テキストは図2.17の状態となっている必要がある。


図2.17 NEWSTTのためのメモリ設定


 これらの内部ルーチンは、BASICテキストを対象とするものであるため、エラーが発生した場合はBASICと同様のエラー処理が行われます。その場合、フックH.ERRO(FFB1H)の書き換えにより、ユーザーがエラー処理を行うこともできます(Eレジスタにエラー番号が入る)(リスト2.3)。


リスト2.3 エラー処理ルーチンの変更

;============================================================
;  List 2.3   Your own error
;             To use,  do DEF USR=&HB000 : A=USR(0)
;============================================================
;
HERR	EQU	0FFB1H		;error hook
SYNERR	EQU	2		;syntax err code
CHPUT	EQU	0A2H		;character output
NEWSTT	EQU	4601H		;run
READYR	EQU	409BH

	ORG	0B000H

;----- command initialize -----	この部分を実行するとエラー処理ルーチンが変更される

	LD	BC,5		;SET NEW HOOK
	LD	DE,HERR
	LD	HL,HDAT
	RET

HDAT:	JP	ERROR
	NOP
	NOP

;----- error routine -----	エラー処理ルーチンの実体

ERROR:	LD	A,E		;when in error, E holds error code
	CP	SYNERR		;syntax error ?
	RET	NZ		:no

	LD	HL,DATA1	;yes
LOOP:	LD	A,(HL)		;put new error message
	CP	'$'
	JR	Z,EXIT
	PUSH	HL
	CALL	CHPUT
	POP	HL
	INC	HL
	JR	LOOP

EXIT:	JP	READYR

DATA1:	DEFM	'キャハッ マチガイチャッタ'	;new error message
	DEFB	07H,07H,07H,'$'

	END


 4.5 割り込み使用法


 Z80CPUにはINTとNMIの2つの割り込み端子がありますが、MSXで使用しているのはINTのみです。INT端子には60[Hz]の信号が入力されており、1秒に60回のタイマ割り込みが実行されるようになっています。Z80の割り込みモードが1に設定されているため、割り込みがかかると38H番地がコールされ、そこからタイマ割り込みルーチンへジャンプし、キー入力をはじめとする種々の処理を行っています。
 このタイマ割り込みルーチンは、途中でフックH.TIMI(FD9FH)にジャンプします。このフックを利用することで、このタイマ割り込みルーチンに任意の機能を追加することが可能です。ここで注意しなければならないのは、通常ここにはRET命令が書かれているだけですが、ディスクなどの周辺機器が接続されていて、すでにこのフックが使用中の時です。この場合、知らずに書き換えるとそれら周辺機器が以後使用不能になってしまうので、どのマシンでも正常に実行できるようにするためには、あらかじめそれに対する処理をしておく必要があります。割り込みの使用例とともにこれらの対処の一例をリスト2.4に示します。


リスト2.4 タイマ割り込みフックの正しい使用法

;============================================================
;   List 2.4   How to use HOOK safely
;              This routine uses TIMER INTERRUPT HOOK
;                                  and turn on/off CAPS LOCK
;              To start, do  DEF USR=&HB000 : A=USR(0)
;              To end,   do  DEF USR=&HB030 : A=USR(1)
;============================================================
;
CHGCAP	EQU	0132H		;CAP LAMP on/off
CAPST	EQU	0FCABH		;CAP LOCK status
TIMI	EQU	0FD9FH		;timer interrupt HOOK
JPCODE	EQU	0C3H
TIMER	EQU	020H

	ORG	0B000H

;----- interrupt on -----	フック書き換え時には以前のフックを保存する

INTON:	DI
	LD	HL,TIMI		;OLD HOOK SAVE
	LD	DE,HKSAVE
	LD	BC,5
	LDIR

	LD	A,JPCODE	;NEW HOOK SET
	LD	(TIMI),A
	LD	HL,INT
	LD	(TIMI+1),HL
	EI
	RET

	ORG	0B030H

;----- interrupt off -----	保存しておいたフックを元に戻して終了する

INTOFF:	DI
	LD	HL,HKSAVE	;RECOVER HOOK
	LD	DE,TIMI
	LD	BC,5
	EI
	RET

;----- interrupt routine -----

INT:	PUSH	AF
	LD	A,(CAPST)
	OR	A
	JR	Z,CAPON

CAPOFF:	LD	A,(COUNT1)
	DEC	A
	LD	(COUNT1),A
	JR	NZ,FIN
	LD	A,TIMER
	LD	(COUNT1),A
	XOR	A
	LD	(CAPST),A
	LD	A,0FFH
	CALL	CHGCAP
	JR	FIN

CAPON:	LD	A,(COUNT2)
	DEC	A
	LD	(COUNT2),A
	JR	NZ,FIN
	LD	A,TIMER
	LD	(COUNT2),A
	LD	A,0FFH
	LD	(CAPST),A
	XOR	A
	CALL	CHGCAP

FIN:	POP	AF
	CALL	HKSAVE		;old HOOK call

	RET

COUNT1:	DEFB	TIMER
COUNT2:	DEFB	TIMER

HKSAVE:	NOP			;old HOOK save area
	NOP
	NOP
	NOP
	RET

	END