「VSYNC割り込み処理をCで記述する」の編集履歴(バックアップ)一覧はこちら
追加された行は緑色になります。
削除された行は赤色になります。
ROMBASICにはインターバルタイマーを利用したタイマー割り込み処理がある。
これは便利な機能だが、C言語で同様な処理を行なわせる場合、タイマー割り込みのフック
(ジャンプテーブル)を書き換え、自前の割り込み処理関数をコールバックの形で呼び出す
仕組みにすればよい。
割り込み処理のフックから、C言語の割り込み処理関数をコールするように変更する場合は
若干工夫が必要になる。
実際の割り込みが生じた場合の処理フローは、
(Z80 Interupt) -> BIOS(0x0-0x3FFF) -> HOOK(0xFD9F) -> DISKCALL(RST30h) -> RET
ここを自前の関数で置き換える必要がある。
(Z80 Interupt) -> BIOS(0x0-0x3FFF) -> HOOK(0xFD9F) -> VSYNC_Int( MyFunc ) -> DISKCALL(RST30h) -> RET
そこで、ワークエリアのHOOK(0xFD9F)を見ると、MSX2(1FDD)の場合、
RST 30h
.db 0x8b
.dw 0x77CE
RET
というコードが書かれている。この処理はBIOS(API)の0x0030を使い特定のスロットをコール
するもの。パラメータはインラインで書かれているのでRST30hのすぐ後ろの3byteがアドレスだ。
ディスク装置が接続されている場合に追加された処理らしい。
これはメモリダンプツールなどで確認すると判る。
もしディスク装置が搭載されていない場合は、フックのジャンプテーブルには単純にRETが書かれている。
(Z80 Interupt) -> BIOS(0x0-0x3FFF) -> HOOK(0xFD9F) -> RET
理想的な方法論は、タイマー割り込みが生じた際のHOOK,つまりジャンプテーブル(0xFD9F)部分に、
JP命令で、特定のC関数へのアドレスにジャンプするように書き換えるというものだろう。
しかし、実際はRST30hなので、このフック部分の4byte部分を全て書き換える必要がある。
そのためにポインタ経由で命令を書き換えることを考える。
書き換えはおそらく、以下のようなコードで可能。
void main(void){
unsigned char * vsync_hook;
vsync_hook=(unsigned char *)0xFD9F;
// --- JP 0x3000
vsync_hook[0]=(unsigned char)0xC9;
vsync_hook[1]=(unsigned char)0x00;
vsync_hook[2]=(unsigned char)0x30;
}
HOOKのジャンプテーブルを書き換えたので、
このコードで1/60secのタイミングで毎回0x3000にあるアセンブラコードが呼ばれる。
特定のアドレスにアセンブラでインターバルタイマー処理を記述しておけば、
タイマー割り込み処理をついかすることができる。
(0x3000から始まるアセンブラにはディスク装置の処理も最後に追加しておく必要があるだろう)
そして、C言語でインターバルタイマーを使う実際のコードは以下のようなものになる。
タイマー割り込み処理は,割り込みのタイミングで呼ばれるコールバック関数
callback_vsync()に記述すればよい。(長い処理は書いてはいけない)
main.c
#define H_TIMER 0xFD9F
void callback_vsync();
void main(void){
unsigned char* vsync_hook;
vsync_hook=(unsigned char*)H_TIMER;
// instruction rewriting
// --- JP 0x8000 (C9 00 80)
vsync_hook[0]=(unsigned char)0xC9;
vsync_hook[1]=(unsigned char)0x00;
vsync_hook[2]=(unsigned char)0x80;
}
void callback_vsync(){
// 1/60 interrupt process
}
timer.asm
.module INTVSYNC
.area _INTCODE (ABS)
;
.globl _callback_vsync
;
INT_VSYNC::
;
call _callback_vsync
;
;
rst 0x30
.db 0x8b
.dw 0x77CE
;
ret
>sdcc -c -mz80 main.c
>sdasz80 -o timer.rel timer.asm
>sdld -b _CODE=0x100 -b _DATA=0x300 -b _INTCODE=0x8000 -i main.ihx main.rel timer.rel
この例では0x8000に配置されたアセンブラが割り込み処理のエントリーポイントで、
アセンブラで書かれた割り込み処理からC言語の関数を呼び出している。
これでインターバルタイマー処理をCALLBACK関数を使って実装することができるだろう。
アドレスは必要に応じて変えることも出来る。
アセンブラからC関数を呼ぶ場合は、まず関数名を.globlでグローバル指定し、
その後、実行したい関数名をラベルとして記述し、CALL命令で呼ぶだけ。
ラベルはC関数名であることを示すアンダースコアが先頭に追加される。
最後のRST30h命令のある数行は、元々フックに書かれていたDISK装置の処理。
この部分はオリジナルのコードをそのまま記述するが、ハードウエアによってはコードに違いが
あるかもしれない。
機種依存する部分なので、該当するハードのフック内容を再確認し、書き換える命令を
変更するなどしてほしい。
割り込み処理は、フックへのジャンプテーブルのアドレスから自前の処理に飛ぶが、
このとき割り込みはDI(禁止)されている。
割り込み期間処理で注意が必要なポイントもう一点あって、それは処理をCで書く場合でも
アセンブラであっても、関数内やアセンブラ内でループを使わない事。
この割り込み期間処理は1/60secのタイミングで呼び出しされ、終わればそのままmain()に抜けるので、
ループは使ってはいけない。
HOOKを元に戻す際はジャンプテーブルを元の値に書き戻せばよい。