Skip to content

Keyboard

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

キーボード

キーの入力は キーボード BIOS(INT 18h) で受け取ります。 押されたキーの「位置」と「文字」の両方が得られるので、文字を持たないキー (ファンクションキー、カーソルキーなど)も区別できます。

基本

INT 18h を AH = 0x00 で呼ぶと、キーが押されるまで待って結果を返します。

レジスタ 返るもの
AH スキャンコード(キーの位置。押されたキーごとに固有)
AL 文字コード(JIS X 0201)。文字を持たないキーは 0x00

同じ文字でもキーの位置が違えばスキャンコードは違います。 例えばメイン列の 1 と テンキーの 1 は、どちらも AL は 0x31 ですが、AH は 0x01 と 0x4A で別です。

前提条件

  • AL だけを見るプログラムは、文字を持たないキーをすべて取りこぼします。 ファンクションキーもカーソルキーも AL = 0x00 で返るため、AL で分岐すると 同じ「0」として1つに潰れます。位置で判定したいときは必ず AH を見てください。
  • 修飾キー(SHIFT / CTRL など)自体を押しても、このキー入力は返ってきません。 修飾キーの状態は別の呼び出しで取ります(後述)。

使い方

ESC が押されるまで、打った文字をそのまま表示し続ける例です。

.loop:
        mov     ah, 0x00
        int     0x18            ; AH=スキャンコード AL=文字コード
        cmp     al, 0x1B        ; ESC で終了
        je      .done
        or      al, al
        jz      .loop           ; AL=0 は文字を持たないキー。ここでは読み飛ばす
        mov     dl, al
        mov     ah, 0x02
        int     0x21            ; DOS の1文字出力
        jmp     .loop
.done:

キーがあるか調べる(待たずに)

INT 18h を AH = 0x01 で呼ぶと、待たずに「キーが来ているか」を調べられます。

レジスタ 返るもの
BH 0 ならキーは無い。0 以外ならある
AH / AL キーがある場合、その先頭のスキャンコードと文字コード

この呼び出しはキーを消費しません。 何度呼んでも同じキーが返り、件数も減りません。 実際に取り出すには AH = 0x00 を呼びます。

キーが無いときの AH / AL は不定です。 呼び出しごとに違う値が返るので、 必ず BH で判定してください。

フラグでの判定はできません。 キーがある状態でも SF が立つときと立たないときがあり、 実測で一貫しませんでした。判定に使えるのは BH だけです。

        mov     ah, 0x01
        int     0x18
        or      bh, bh
        jz      .nokey          ; キーは来ていない
        mov     ah, 0x00        ; 来ているので取り出す
        int     0x18
.nokey:

キーバッファの構造

押されたキーは、BIOS が持つリングバッファに積まれます。 AH = 0x00 や AH = 0x01 を使う限り直接触る必要はありませんが、 件数を自分で確認したい場合や、バッファを横取りする常駐プログラムを書く場合に必要になります。

アドレス サイズ 内容
0x0502〜0x0521 32バイト キューの実体。1件2バイト(+0 文字コード / +1 スキャンコード)
0x0524 ワード 読み出し位置(0x0502〜0x0521 のアドレスそのもの)
0x0526 ワード 書き込み位置(同上)
0x0528 バイト たまっている件数
  • 収容できるのは 16件 です
  • 書き込み位置は1件ごとに2進み、0x0520 の次は 0x0502 に戻ります
  • 満杯になると、それ以降のキーは黙って捨てられます。 件数は16で止まり、 書き込み位置も動かなくなります。エラーも音も出ません

修飾キーの状態

INT 18h を AH = 0x02 で呼ぶと、AL に修飾キーの状態がビットで返ります。

ビット キー 押している間だけ / ロック
0x01 SHIFT 押している間だけ
0x02 CAPS ロック(1回押すと切り替わる)
0x04 かな ロック(1回押すと切り替わる)
0x08 GRPH 押している間だけ
0x10 CTRL 押している間だけ

修飾キーを押しながら文字キーを打つと、AL に返る文字コードも変わります。 a を打った場合の実測値です。

修飾 AL
なし 0x61 a
SHIFT 0x41 A
CAPS 0x41 A
かな 0xC1
GRPH 0x9E
CTRL 0x01

AH = 0x02 は起動直後に呼んでも問題ありません。

スキャンコード一覧

AL が — のものは、キーを押しても INT 18h AH=00h からは返ってきません (修飾キーと、割り当ての無いコード)。

code キー AL code キー AL code キー AL code キー AL
0x00 ESC 0x1B 0x20 f 0x66 0x40 KP_MINUS 0x2D 0x60 STOP —
0x01 1 0x31 0x21 g 0x67 0x41 KP_DIVIDE 0x2F 0x61 COPY —
0x02 2 0x32 0x22 h 0x68 0x42 KP7 0x37 0x62 F1 0x00
0x03 3 0x33 0x23 j 0x6A 0x43 KP8 0x38 0x63 F2 0x00
0x04 4 0x34 0x24 k 0x6B 0x44 KP9 0x39 0x64 F3 0x00
0x05 5 0x35 0x25 l 0x6C 0x45 KP_MULTIPLY 0x2A 0x65 F4 0x00
0x06 6 0x36 0x26 ; 0x3B 0x46 KP4 0x34 0x66 F5 0x00
0x07 7 0x37 0x27 : 0x3A 0x47 KP5 0x35 0x67 F6 0x00
0x08 8 0x38 0x28 ] 0x5D 0x48 KP6 0x36 0x68 F7 0x00
0x09 9 0x39 0x29 z 0x7A 0x49 KP_PLUS 0x2B 0x69 F8 0x00
0x0A 0 0x30 0x2A x 0x78 0x4A KP1 0x31 0x6A F9 0x00
0x0B - 0x2D 0x2B c 0x63 0x4B KP2 0x32 0x6B F10 0x00
0x0C ^ 0x5E 0x2C v 0x76 0x4C KP3 0x33 0x6C 0x00
0x0D \ 0x5C 0x2D b 0x62 0x4D KP_EQUALS 0x3D 0x6D 0x00
0x0E BS 0x08 0x2E n 0x6E 0x4E KP0 0x30 0x6E 0x00
0x0F TAB 0x09 0x2F m 0x6D 0x4F KP_COMMA 0x2C 0x6F 0x00
0x10 q 0x71 0x30 , 0x2C 0x50 KP_PERIOD 0x2E 0x70 SHIFT —
0x11 w 0x77 0x31 . 0x2E 0x51 NFER 0x00 0x71 CAPS —
0x12 e 0x65 0x32 / 0x2F 0x52 — — 0x72 KANA —
0x13 r 0x72 0x33 — — 0x53 — — 0x73 GRPH —
0x14 t 0x74 0x34 SPACE 0x20 0x54 — — 0x74 CTRL —
0x15 y 0x79 0x35 XFER 0x00 0x55 — — 0x75 — —
0x16 u 0x75 0x36 ROLLUP 0x00 0x56 — — 0x76 — —
0x17 i 0x69 0x37 ROLLDOWN 0x00 0x57 — — 0x77 — —
0x18 o 0x6F 0x38 INS 0x00 0x58 — — 0x78 — —
0x19 p 0x70 0x39 DEL 0x00 0x59 — — 0x79 — —
0x1A @ 0x40 0x3A UP 0x00 0x5A — — 0x7A — —
0x1B [ 0x5B 0x3B LEFT 0x00 0x5B — — 0x7B — —
0x1C ENTER 0x0D 0x3C RIGHT 0x00 0x5C — — 0x7C — —
0x1D a 0x61 0x3D DOWN 0x00 0x5D — — 0x7D — —
0x1E s 0x73 0x3E HOME 0x00 0x5E 0x00 0x7E — —
0x1F d 0x64 0x3F HELP 0x00 0x5F — — 0x7F — —

KP_ で始まるものはテンキーです。

0x5E と 0x6C〜0x6F は、どのキーにも割り当てられていません。 入力自体は返りますが、意味のある値ではありません。

0x52〜0x56 は vf1〜vf5 のキーですが、この環境では入力が返ってきません。

文字を持つかどうかは修飾キーで変わります

0x33 のように、単独では何も返さないキーがあります。 このキーは SHIFT や かな と一緒に押したときだけ文字を返します。

押し方 返る値
単独 何も返らない
SHIFT と一緒 AH=0x33 AL=0x5F(_)
かな と一緒 AH=0x33 AL=0xDB

ファンクションキーも同様に、修飾キーで値が変わります。 実測では CTRL + f1 が AH=0x92 を返しました(単独では 0x62)。

「押しても何も来ない」=「そのキーが無い」ではありません。 修飾キーとの組み合わせで初めて値を持つキーがあります。

修飾キーを組み合わせたとき

AH=0x02 が返す値は、押している修飾キーのビットが OR されたものです。

CTRL + SHIFT なら 0x10 | 0x01 = 0x11 になります。

CAPS と かな はロック式なので、一度押すと状態が残ります。 明示的に戻さない限り、以降の測定や動作に影響し続けます。

キーリピート

キーを押しっぱなしにすると繰り返し入力が入ります。実測では、最初の1回のあと 約500ms 待ってから、以降 約50ms間隔(毎秒20回)でした。

この間隔は WebNP2 が設定している値です。 実機の値ではありません。

この繰り返しは、INT 18h より下のレベル(生の割り込み。後述)でも 同じ周期で起きます。 押しっぱなしの間、内部的には「離した(break)→ 押した(make)」のペアが繰り返し送られてきており、INT 18h の 繰り返し入力はそれがそのまま伝わったものだと分かりました。 自分でIRQ1を受け取る場合、「直前がdownでなければ新規の押下」という 単純な判定では、この繰り返しと「素早く離してすぐ押し直す」を 区別できません(後述)。

割り込みで直接受け取る(IRQ1)

INT 18h を経由せず、キーボードの割り込みを自分のハンドラで直接 受け取ることもできます。「離した」ことを自分で判定したい場合はこちらが 必要です(INT 18h は押しっぱなし/離した、を区別する情報を返しません)。

キーボードは IRQ1 です。 時間と割り込み にある タイマ(IRQ0 = INT 08h)・垂直帰線(IRQ2 = INT 0Ah)と同じ 「IRQn → INT(8+n)」の規則どおりで、IRQ1 = INT 09h でした(実測)。

項目 内容
割り込み番号 INT 09h
マスク ポート 0x02 の bit1。実測では既定で外れていました(タイマのように明示的に外す操作をしなくても届きました)
データ ポート 0x41 から1バイト読みます。下位7bitがスキャンコード、bit7が立っていれば「離した」(break) です
後始末 ポート 0x00 へ 0x20 を書きます(EOI)。これを忘れると2回目以降の割り込みが来なくなります(実測: EOIを送らないハンドラでは、押す・離すを6回繰り返しても最初の1回しか割り込みが来ませんでした)
handler:
        in      al, 0x41        ; スキャンコードを読む
        test    al, 0x80
        jnz     .released
        ; ... 押された(make)ときの処理。alの下位7bitがスキャンコード ...
        jmp     .eoi
.released:
        and     al, 0x7F        ; bit7を落として元のスキャンコードに戻す
        ; ... 離された(break)ときの処理 ...
.eoi:
        mov     al, 0x20
        out     0x00, al        ; EOI (非特定EOI)
        iret

「離した」の判定は bit7 です。 INT 18h はこの情報を返しませんが、 生の割り込みでは、押した(make)ときと同じスキャンコードに bit7 を立てた値 (離した = スキャンコード | 0x80。break)が送られてきます。

自分のハンドラから戻すときの注意(CAPSのずれ)

自分のIRQ1ハンドラを外して元のハンドラへ戻したあと、CAPSロックの状態が 戻した側とずれることがあると分かりました。自分のハンドラがCAPSキーの 押下を処理していた間の変化が戻した側に伝わらないのは想定どおりですが、 それだけでなく、戻したあとに改めてCAPSキーを押しても、大文字/小文字が 切り替わらないことがありました(原因不明)。同じスキャンコードを同じ 方法で注入したとき自作ハンドラ側は正しく反応することは別途確認済みで、 注入経路や割り込みベクタの復元自体に問題は無いようでした。常駐プログラム などで一時的にIRQ1を奪って戻す場合は、CAPS/かなロックの状態を自分で 退避・復元することを検討してください。

SHIFTでの記号の変化(実測)

0x33(前述)以外の記号キーも、 SHIFTを押しながらだと違う文字になります。数字・記号・テンキー計44個の スキャンコードについて、単独とSHIFT付きの両方を実測しました。

scancode キー 単独 SHIFT
0x01-0x09 1-9 1-9 !"#$%&'()
0x0A 0 0x30 0x30(変化無し)
0x0B - 0x2D 0x3D(=)
0x0C ^ 0x5E 0x60(`)
0x0D \ 0x5C 0x7C(|)
0x1A @ 0x40 0x7E(~)
0x1B [ 0x5B 0x7B({)
0x28 ] 0x5D 0x7D(})
0x26 ; 0x3B 0x2B(+)
0x27 : 0x3A 0x2A(*)
0x30 , 0x2C 0x3C(<)
0x31 . 0x2E 0x3E(>)
0x32 / 0x2F 0x3F(?)
0x33 (無し) (無し) 0x5F(_)
0x00,0x0E,0x0F,0x1C,0x34 ESC/BS/TAB/ENTER/SPACE — 変化無し
0x40-0x50 テンキー全部 各数字/記号 変化無し

0x0C(^)と 0x1A(@)を混同しないよう注意してください。 どちらも記号キーで見た目が紛らわしいですが、SHIFT時の変化先は 0x0C→`、0x1A→~ です(逆ではありません)。

未シフト側はこのページの元々の表(スキャンコード一覧)どおりで、 変化はありませんでした。テンキーとESC/BS/TAB/ENTER/SPACEはSHIFTを 押しても文字が変わりません。

よくある間違い

  • AL だけでキーを判定して、ファンクションキーやカーソルキーが効かない。 これらは AL = 0x00 で返るので、AL での分岐では全部同じ値になります。AH を見てください。
  • メイン列とテンキーを区別しようとして AL を見る。 両者は AL が同じです。 区別できるのは AH だけです。
  • 修飾キーの押下をキー入力として待ってしまう。 SHIFT や CTRL を押しても AH=0x00 の呼び出しは返りません。AH=0x02 で状態を読んでください。
  • STOP キーと COPY キーを待ち続ける。 この2つは押しても INT 18h AH=00h に返ってきません(実測)。文字コードを持たないキーです。
  • 単独で反応しないキーを「無い」と判断する。 0x33 は修飾キーと一緒でないと 値を返しません。
  • AH=0x01 の有無を DH で判定する。 BH です(下の「未検証」の下も参照)。
  • AH=0x01 の戻り値でキーの有無をフラグで判定する。 フラグは一貫しません。BH を見てください。
  • AH=0x01 を呼んだだけでキーが減ると思い込む。 この呼び出しは消費しません。 取り出すには AH=0x00 が要ります。逆に、消費せず調べ続けるとバッファは満杯のままになります。
  • バッファが満杯なのに気づかない。 16件を超えたキーは黙って捨てられます。 ゲスト側が長時間キーを読まない処理をしていると、その間の入力は失われます。
  • CAPS と かな をロックだと知らずに扱う。 この2つは押している間だけではなく、 1回押すと状態が切り替わったままになります。
  • 自分のIRQ1ハンドラでEOI(0x20をポート0x00へ)を送り忘れる。 最初の 1回は来ますが、2回目以降の割り込みが永久に来なくなります。
  • 0x0C(^)と0x1A(@)のSHIFT変化先を逆に覚える。 正しくは 0x0C→`、0x1A→~ です。

実機との差異

現時点で確認できているものはありません。 ただし このページの測定はすべて np2kai 上で行っています。 実機での確認は行っていません。

未検証

  • AH = 0x02 の bit5〜bit7(全128コードの掃引でも一度も立たなかった)
  • 0x52〜0x56(vf1〜vf5)がこの環境で返ってこない理由
  • 自分のIRQ1ハンドラから元へ戻したあと、CAPSロックの状態がずれる現象の 原因(割り込みで直接受け取る参照)
  • キーリピート(生の割り込みレベルでの break→make の繰り返し)と、 人間が素早く離してすぐ押し直した場合とを、スキャンコードの並びだけから 区別する方法

検証: 2026-08-24 / WebNP2 327d061 (np2kai) + FreeDOS(98) / 再現: guest/probes/KEYSPY.ASM(キーコード), guest/probes/NOOP.ASM + SENSE.ASM(バッファと AH=01h), guest/probes/WIKIEX2.ASM(上の使用例)

スキャンコードと AL の値、修飾キーのビットはすべて実測値です。 キーの名前だけは測定では決まらないため、WebNP2 のキーマップ (src/api/keymap.ts。テンキー部分は NP2kai sdl/kbtrans.c を出典として明記)から取り、 実測値と矛盾しないことを確認したうえで載せています。

検証: 2026-09-20 / WebNP2 (np2kai) + FreeDOS(98) / 再現: 上記とは別の方法で、p98lib(このwikiの利用例プロジェクトの1つ)の 検証用に書いた使い捨てプローブ(コミットには残していない)を、 np2.sendKey(code, down)(WebNP2 docs/AUTOMATION.md。PC-98スキャンコードを 外部から直接注入するAPI)で駆動して測定した。IRQ1=INT 09h・EOI必須・ SHIFT記号変換の全数・キーリピートの生割り込みレベルでの再現・CAPSの ずれは、いずれもこの方法での実測。ROM内の変換テーブルは読み出していない (キーを与えたときの外部からの挙動だけを見た)。

Clone this wiki locally