Skip to content

Timing and Interrupts

Masakazu Yonehara edited this page Sep 20, 2026 · 4 revisions

時間と割り込み

画面のちらつきを避けたり、一定の間隔で処理をしたりするには、 垂直帰線かタイマ割り込みを使います。

垂直帰線

画面の描き直しの合間(垂直帰線)を待ってから 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 と、有効にする方法が違います。

キーボードの割り込み(IRQ1)

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回で何ミリ秒かかったか」で確かめました。

Clone this wiki locally