-
Notifications
You must be signed in to change notification settings - Fork 2
Keyboard
キーの入力は キーボード 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でなければ新規の押下」という
単純な判定では、この繰り返しと「素早く離してすぐ押し直す」を
区別できません(後述)。
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)が送られてきます。
自分のIRQ1ハンドラを外して元のハンドラへ戻したあと、CAPSロックの状態が 戻した側とずれることがあると分かりました。自分のハンドラがCAPSキーの 押下を処理していた間の変化が戻した側に伝わらないのは想定どおりですが、 それだけでなく、戻したあとに改めてCAPSキーを押しても、大文字/小文字が 切り替わらないことがありました(原因不明)。同じスキャンコードを同じ 方法で注入したとき自作ハンドラ側は正しく反応することは別途確認済みで、 注入経路や割り込みベクタの復元自体に問題は無いようでした。常駐プログラム などで一時的にIRQ1を奪って戻す場合は、CAPS/かなロックの状態を自分で 退避・復元することを検討してください。
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内の変換テーブルは読み出していない
(キーを与えたときの外部からの挙動だけを見た)。
テキスト画面
キーボード
グラフィック画面
時間と割り込み
BIOS / DOS
ディスク
サウンド
メモリ