%第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に示します。
2 | 2バイト整数型 |
3 | 文字列型 |
4 | 単精度実数型 |
8 | 倍精度実数型 |
表2.23 レジスタAに代入される引数の型
リスト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コマンドレベルに戻る。
● 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以外に設定しておくと、配列の個々の要素ではなく、その配列の先頭アドレスが得られる。
● NEWSTT(4601H/MAIN)……………任意のテキストを実行する。
入力:HL←実行するテキストのアドレス
出力:――
機能:テキストの実行を行う。テキストは図2.17の状態となっている必要がある。
これらの内部ルーチンは、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