-
Notifications
You must be signed in to change notification settings - Fork 2
Timing and Interrupts
画面のちらつきを避けたり、一定の間隔で処理をしたりするには、 垂直帰線かタイマ割り込みを使います。
画面の描き直しの合間(垂直帰線)を待ってから VRAM を書き換えると、 書き換えの途中が表示されずに済みます。
帰線の状態は ポート 0x60 の bit5 で分かります。
| ビット | 意味 | 周期 |
|---|---|---|
bit5 (0x20) |
垂直帰線。1 のとき帰線中 | 約 56.4 Hz |
bit6 (0x40) |
水平帰線 | 約 24.9 kHz |
グラフィック側の GDC(ポート 0xA0)でも同じビットが同じように読めます。
waitvsync:
.busy:
in al, 0x60
and al, 0x20
jnz .busy ; いま帰線中なら、終わるのを待つ
.idle:
in al, 0x60
and al, 0x20
jz .idle ; 次の帰線が始まるのを待つ
ret「帰線が始まるのを待つ」だけでは足りません。 呼んだ時点ですでに帰線中だと、 待たずにすぐ返ってしまい、同じ帰線の間に何度も返ります。 一度抜けるのを待ってから、次の開始で返るようにしてください。
実測では、このルーチンを200回呼ぶのに 3557ms かかりました(56.2 Hz 相当)。 1回の呼び出しで確実に1フレームぶん待てています。
一定間隔で処理をしたいときはタイマ割り込み(INT 08h)を使います。
既定ではマスクされていて、ベクタを置いても呼ばれません。 使うには PIC のマスクを自分で外す必要があります。
in al, 0x02
and al, 0xFE ; bit0 が IRQ0(タイマ)のマスク
out 0x02, al
stiマスクを外すと、実測で 約100Hz(99.81 Hz)で呼ばれるようになりました。
ベクタを置いただけで呼ばれないときは、設定の失敗ではなくマスクを疑ってください。 ベクタが正しく置き換わっていても、マスクされていれば1回も呼ばれません。
帰線をポーリングで待つ代わりに、割り込みで受け取れます。
**IRQ2(INT 0Ah)**が垂直帰線です。
ベクタを置いてマスクを外すだけでは、1回も来ません。
ポート 0x64 への書き込みで許可する必要があります。
さらに、ハンドラの中でも毎回 0x64 へ書いて再武装してください。
; 有効にする
in al, 0x02
and al, 0xFB ; bit2 = IRQ2 のマスクを落とす
out 0x02, al
xor al, al
out 0x64, al ; 垂直帰線割り込みを許可する
sti
handler:
; ... 自分の処理 ...
xor al, al
out 0x64, al ; 次の帰線に備えて再武装する
jmp far [cs:old_off]実測では 56.5 Hz で呼ばれました。ポーリングで測った垂直帰線の周期(56.4 Hz)と 一致します。
「ベクタを置いたのに呼ばれない」ときは、許可のしかたを疑ってください。
タイマ(INT 08h)ではマスク、垂直帰線ではポート 0x64 と、有効にする方法が違います。
IRQ0(タイマ = INT 08h)・IRQ2(垂直帰線 = INT 0Ah)と同じ
「IRQn → INT(8+n)」の規則どおり、IRQ1(キーボード)= INT 09h です(実測)。
マスク(ポート0x02のbit1)は実測では既定で外れており、タイマのように
明示的に外す操作は必要ありませんでした。EOI(0x20をポート0x00へ)が
必要な点はタイマ・垂直帰線と共通です。詳細(データの読み方、離した
ことの判定、キーリピートとの関係、自分のハンドラから戻すときの注意)は
キーボードの「割り込みで直接受け取る(IRQ1)」を参照してください。
元のハンドラを退避しておき、自分の処理のあとで元へ渡します(チェイン)。
; 元のベクタを取っておく
mov ax, 0x3508 ; AH=35h ベクタ取得 / AL=割り込み番号
int 0x21
mov [old_off], bx
mov [old_seg], es
; 自分のハンドラを入れる
mov ax, 0x2508 ; AH=25h ベクタ設定
mov dx, handler
int 0x21
handler:
push ds
push ax
mov ax, 0xA100 ; 自分のデータを触るなら DS を入れ直す
mov ds, ax
add word [0], 1
pop ax
pop ds
jmp far [cs:old_off] ; 元のハンドラへ渡す
old_off: dw 0
old_seg: dw 0ハードウェア割り込みでは、元のハンドラへ渡してください。 元の処理が 割り込みコントローラへの応答(EOI)を行っています。
常駐して差し込む場合は 常駐プログラム(TSR) も参照してください。
8253 のチャネル0(制御を 0x77、分周値を 0x71 へ下位・上位の順)に書いて
周期を変えようとしましたが、割り込みの間隔は変わりませんでした(分周値 6000 でも
60000 でも約100Hz のまま)。
同じ書き方でビープ(チャネル1)は周波数が変わりますので、書き込み手順そのものが 誤っているとは考えにくいのですが、理由は分かっていません。
キーを押しっぱなしにすると、繰り返し入力が入ります。実測では、
- 最初の1回のあと 約500ms 待ってから
- 以降 約50ms間隔(毎秒20回)
この間隔は WebNP2 が設定している値です。 実機の値ではありません。 実機での間隔は確認していません。
生のキーボード割り込み(IRQ1)レベルでも同じ周期で再現します。
押しっぱなしの間、「離した(break)→押した(make)」のペアが繰り返し
送られてきており、INT 18h 側のキーリピートはそれがそのまま伝わった
ものです。詳細は キーボード を参照してください。
- 帰線の「開始」だけを待つ。 すでに帰線中だと待たずに返り、 同じ帰線で何度も返ります。一度抜けてから待ってください。
- タイマのベクタを置いただけで動くと思う。 既定ではマスクされています。
-
ハンドラの中で
DSを直さずに自分のデータを読む。 呼び出し元のDSが 入っています。 - 元のハンドラへ渡さない。 割り込みコントローラへの応答が行われません。
- 時間をゲスト側のループ回数で測る。 実行速度に左右されます。 帰線やタイマの回数で数えてください。
現時点で確認できているものはありません。 ただし 測定は np2kai 上で行っています。 実機での確認は行っていません。 特に周波数の絶対値は、エミュレータの実装を測っている面があります (56.4 Hz / 24.9 kHz / 100 Hz という値そのものより、 「垂直帰線はここで分かる」「タイマは既定でマスク」という性質のほうが確かです)。
- 元のハンドラへ渡さなかった場合に実際どうなるか
- マスクを外したまま終了した場合の後始末
- 20行モードや他の解像度での帰線の周波数
- タイマの周期を変える方法(8253 のチャネル0 へ書いても変わりませんでした)
検証: 2026-08-24 / WebNP2 (np2kai) + FreeDOS(98) /
再現: guest/probes/VSPY.ASM(変化するビットの特定),
VSCNT.ASM(周波数), TIMCNT.ASM / TIMCNT2.ASM(タイマ),
VSWAIT.ASM(上の待ちルーチンの検証)
検証: 2026-09-20 / WebNP2 (np2kai) + FreeDOS(98) /
再現: 上記とは別の方法で、p98lib検証用の使い捨てプローブ(コミットには
残していない)をnp2.sendKey(code, down)(WebNP2 docs/AUTOMATION.md)で
駆動して測定した。IRQ1=INT 09hであることと、キーリピートが生の割り込み
レベルでも同じ周期で再現することを確認した。詳細と根拠は キーボード
を参照。
時間はすべてホスト側の実時間で測っています。待ちルーチンは 「戻ってくること」ではなく「200回で何ミリ秒かかったか」で確かめました。
テキスト画面
キーボード
グラフィック画面
時間と割り込み
BIOS / DOS
ディスク
サウンド
メモリ