Skip to content

Text VRAM

Masakazu Yonehara edited this page Aug 24, 2026 · 5 revisions

テキスト VRAM

PC-98 のテキスト画面は、メモリに文字コードを書き込むだけで表示できます。 BIOS や DOS を経由しないので速く、画面のどの位置にも直接書けます。

基本

テキスト画面は 文字コード面 と 属性面 の2つに分かれています。 文字コード面が「何の字か」、属性面が「どんな見た目か」を持ちます。

面 先頭アドレス 役割
文字コード面 0xA0000 表示する文字のコード
属性面 0xA2000 色・反転・下線など

どちらも 1セル(1文字ぶん)につき2バイト、1行につき160バイト、 画面は 80桁 x 25行 = 2000セル です。両方の面で配置は同じなので、 文字コード面のオフセットをそのまま属性面にも使えます。

セルのオフセット = 行 * 160 + 桁 * 2

文字コード面は 0xA0000 から 2000セル分 (0xA0FA0 の手前まで) が画面に対応します。 それ以降のアドレスに書いても画面には出ません。

24行目もテキスト VRAM の一部です。 ファンクションキーの表示が出ている行で、 画面全体を埋めるとこの表示も消えます。

前提条件

  • テキスト画面が表示されている状態であること。グラフィック画面のみを表示する 設定になっている場合、書き込んでも見えません。
  • 属性面を勝手な値で埋めないこと。 属性の下位ビットには「文字を表示する」 ビットが含まれており、0x00 で埋めると エラーも出ずに画面が真っ黒になります。 安全なのは、書き換える前に既存の値を1セル読んでおき、それを複製する方法です。 白で表示したいだけなら 0x00E1 を書けば表示されます。

文字コードの書き方

セルの2バイトは、下位バイトと上位バイトで役割が違います。

上位バイト 意味
0x00 下位バイトを 半角文字コード として表示する
0x00 以外 全角文字として、このセルと次のセルの2セル幅で表示する

半角

上位バイトを 0x00 にして、下位バイトに文字コードを入れます。 A を 0行0桁に出すなら、0xA0000 に 16bit 値 0x0041 を書きます。

全角

全角は JIS コードを次のように分解して入れます。

下位バイト = JIS の第1バイト - 0x20
上位バイト = JIS の第2バイト

例: 「亜」は JIS 0x3021 なので、下位 = 0x30 - 0x20 = 0x10、上位 = 0x21。 書き込む16bit値は 0x2110 です。

全角は2セル分の幅で描かれます。右half のセルには何を書いても構いません。 描画には使われず読み飛ばされます(空白のままでも、同じ値を書いても、結果は同じです)。

ただし 次のセルは全角に占有されるので、そこに別の文字を置くことはできません。 全角を置いたら、次の文字は2セル先へ進めてください。

属性のビット割り当て

属性面に書く値のビットは次のとおりです。

ビット 意味
b7 緑
b6 赤
b5 青
b4 縦線
b3 下線
b2 反転
b1 点滅
b0 文字の表示

色は上位3ビットの組み合わせです。

値 色 値 色
0x00 黒 0x80 緑
0x20 青 0xA0 水色
0x40 赤 0xC0 黄
0x60 紫 0xE0 白

これに 0x01(文字の表示) を足した値が、実際に使う属性になります。 白い普通の文字は 0xE1 です。

b0 は文字だけを制御します。 反転・下線・縦線・点滅は b0 が 0 でも描かれるので、 0xE4(反転・文字なし) は白い四角として表示されます。

使い方

0行0桁に A、0行1桁に B を白で表示する例です。

        ; 前提: テキスト画面が表示されていること
        mov     ax, 0xA000      ; 文字コード面
        mov     es, ax
        mov     word [es:0], 0x0041     ; 'A'
        mov     word [es:2], 0x0042     ; 'B'

        mov     ax, 0xA200      ; 属性面
        mov     es, ax
        mov     word [es:0], 0x00E1     ; 白・表示あり
        mov     word [es:2], 0x00E1

画面全体を空白で消す場合は、文字コード面を 0x0020 で 2000セル分埋めます。 このとき 属性面は触らないでください(前提条件の項を参照)。

        mov     ax, 0xA000
        mov     es, ax
        xor     di, di
        mov     ax, 0x0020      ; 空白
        mov     cx, 2000
        rep     stosw

半角カナ

半角カナ(0xB1 以降)は、普通の半角文字と同じです。 上位バイトを 0x00 にして、下位バイトにコードを入れます。1セルに1文字入り、 全角のように2セルを占有することはありません。

属性面の上位バイト

意味はありません。 実測では 0x00 と 0xFF で表示がまったく変わりませんでした。 0 を入れておけば十分です。

点滅の速さ

点滅(属性の b1)の周期は 約1.7秒でした。点灯している時間のほうが長く、 点灯:消灯 ≒ 3:1(点灯 約1.28秒 / 消灯 約0.41秒)です。

繰り返し測っても ±0.4% に収まりました。

スクロールすると、書いた内容も動きます

テキスト画面に表示ページの切り替えはありません。 画面上の位置と VRAM のオフセットの対応は固定です。

そして DOS のスクロールは、VRAM の内容そのものを移動させます。 実測では、1行スクロールすると 0xA0000 の内容が2行目のものに置き換わりました。

→ VRAM へ直接書いた内容も、DOS が出力してスクロールすれば一緒に動きます。 画面の一部を固定表示したい場合は、スクロールのたびに書き直すか、 スクロールしない領域(最下行など)を使ってください。

カーソルを動かす・消す

カーソルはテキスト BIOS (INT 18h) で制御します。

AH 機能
0x11 カーソルを表示する
0x12 カーソルを消す
0x13 カーソルの位置を決める(DX で指定)

AH = 0x13 の DX は、セル番号ではなく「テキスト VRAM のバイトオフセット」です。 つまり、このページの冒頭にある式と同じものを渡します。

DX = 行 * 160 + 桁 * 2

実測では DX = 0 で 0行0桁、DX = 160 で 1行0桁、DX = 1998 で 24行79桁でした。 桁数で渡すと位置が半分になります。

        mov     ah, 0x13
        mov     dx, 5*160 + 10*2        ; 5行目10桁目
        int     0x18

        mov     ah, 0x12                ; カーソルを消す
        int     0x18

行数と文字幅を切り替える

INT 18h の AH = 0x0A で、画面の行数と文字の幅を変えられます。 AL の下位2ビットだけが意味を持ちます。

AL のビット 0 のとき 1 のとき
bit0 25行(1行16ライン) 20行(1行20ライン)
bit1 通常の文字幅 横倍角(文字が横に2倍)

bit2 以上は無視されます(AL = 0x04〜0x07 は 0x00〜0x03 と同じ結果でした)。

        mov     ax, 0x0A01              ; 20行モードにする
        int     0x18

行数を変えても、1行が160バイトであることは変わりません。 変わるのは1行の高さ(画面上のライン数)だけです。実測では、オフセット 0 / 160 / 320 に 書いた文字が、どちらのモードでも連続する3行に並びました。

20行モードでは、VRAM の 20行目以降(オフセット 3200 以降)が画面に出ません。 内容は残っていますが表示されないだけです。

フォントを読む(KCG)

漢字のフォントは KCG(漢字キャラクタジェネレータ)に入っていて、 I/O ポート経由で読み出せます。

ポート 内容
0xA1 JIS コードの下位バイト
0xA3 JIS コードの上位バイト − 0x20
0xA5 ライン番号(0〜15)。bit5 (0x20) を立てると左半分
0xA9 そのラインの 8 ドット(読み出し・書き込み)

全角1文字は 16×16 ドットで、左半分と右半分に分けて 16 バイトずつ読みます。

        ; 「亜」(JIS 0x3021) の左半分・0ライン目を読む
        mov     al, 0x21        ; JIS の下位バイト
        out     0xA1, al
        mov     al, 0x10        ; JIS の上位バイト 0x30 から 0x20 を引く
        out     0xA3, al
        mov     al, 0x20        ; 左半分・ライン0
        out     0xA5, al
        in      al, 0xA9

0xA3 に渡す値の作り方は、テキスト VRAM に全角を書くときの下位バイトと同じです (JIS の第1バイトから 0x20 を引く)。

実測では、読み出した 16×16 が画面に表示されている字と1ドットも違わずに一致しました。

0xA5 の bit4 と bit6 は無視されます。 ライン番号は下位4ビットだけが効きます。

自分で作った文字(外字)

0xA9 へ書き込むと、KCG の内容を書き換えられます。 外字の領域(86区・87区)に書けば、自分で作った文字を持てます。

実測では、書いた値はそのまま読み返せました。 書き込み自体は成立しています。

ただし、この環境では画面に左半分しか出ませんでした。 KCG には左右とも正しく入っているのに、表示は左8ドットだけになります。 原因は分かっていません(下の「未検証」を参照)。

よくある間違い

  • 属性面を 0x00 で埋めて画面が消える。 b0(文字の表示) が落ちるうえ、 色も黒になります。エラーは出ず、書き込みは成功しているのに何も見えません。 文字が出ないときは、まず属性面に 0xE1 を書いてみてください。
  • 全角の JIS コードをそのまま書いて別の字が出る。 下位バイトから 0x20 を 引く必要があります。引き忘れると、そのぶんずれた位置の字が表示されます。 何も表示されないのではなく もっともらしい別の漢字が出る ので気づきにくいです。
  • 全角の上位バイトに 0x21 未満の値を入れて何も出ない。 字が割り当てられて いない位置なので空白になります。エラーにはなりません。
  • 全角の隣のセルに文字を置いて消える。 全角は2セル幅を占有します。 次の文字は2セル先へ。
  • 1行を80バイト進めてしまう。 1セットが2バイトなので、1行は160バイトです。
  • カーソル位置に桁数をそのまま渡す。 INT 18h AH=0x13 の DX はバイトオフセットです。 桁数で渡すと位置が半分になります。
  • 0xA0FA0 以降に書いて何も起きない。 画面は 2000セルちょうどです。 それ以降のアドレスは表示に使われません。

実機との差異

  • 下線(b3)は、描画位置が4ドット右にずれます。 下線を持つセルが連続している場合、 その区間全体が右へ4ドットずれ、下線を持たない次のセルへ4ドットはみ出します。 文字のグリフ自体はセル境界に正しく揃っているので、下線だけの現象です。 これは np2kai 上での観測です。実機で同じかどうかは未確認です。

未検証

  • 下線の4ドットずれが実機でも起きるか
  • 外字(86区・87区)が画面に左半分しか出ない理由
  • KCG へ書き込むときのアクセスモード(表示期間中に書くか、垂直帰線の間だけにするか)

検証: 2026-08-24 / WebNP2 327d061 (np2kai) + FreeDOS(98) / 再現: guest/probes/TVRAM1.ASM, guest/probes/TVRAM2.ASM, guest/probes/GIO3.ASM(カーソル・行数モード)

Clone this wiki locally