以前解説したPC-6001 画像コンバータ公開PC-6001 カラー制御の記事情報を元に、今回は screen 3 の画面に、英数字から成る文字列を表示するための Z80 プログラムの説明をします。文字列に制御コードを埋め込んで処理する方法も解説します。文字列表示

この表示はPC-6001 カラー制御の記事で使用したプログラムのメインに、下記の記述を追加して表示しました。

ld hl, TestString
ld de, LOCATE(13, 50)
call GRPDrawStrings TestString: db COLOR.Red db "Valgus Presents", STR.CRLF db COLOR.Yellow, "PC-6001 mk2", db STR.LOCATE dw LOCATE(13, 120) db COLOR.White, "Program.", STR.END

今回のサンプルプログラムは下記からダウンロードしてください。
P6StringsTest.zip



※ 相変わらず安い。下記自己責任ですが改造ツールです…


  • データの準

画像コンバータのページから、P6GrphConv と Fonts.png をダウンロードします。Fonts.png は横4ドット×縦8ドットの構成です。また、色を付けるためにはシルエット形状が必須なので、これも設定します。基準は左上のままとしてください。
P6GrphConvからデータ出力
この状態で出力ボタンを押すと Fonts.inc が出力されます。なお、バイナリ形式でも構いませんが、その場合は自分でラベルの設定と、include 種別の変更が必要です。AILZ80ASM なら include "Fonts.bin",b となります、
FontsInc


  • 定数と色データの定義
色番号でそのまま記述するとわかりにくいので、色に名前を付けます。

COLOR: .Transparent equ 0 ; 透明 .BluePurple equ 1 ; 青紫 .Orange equ 2 ; 橙 .RedPurple equ 3 ; 赤紫 .BlueGreen equ 4 ; 青緑 .SkyBlue equ 5 ; 空色 .YellowGreen equ 6 ; 黄緑 .Gray equ 7 ; 灰色 .Black equ 8 ; 黒 .Blue equ 9 ; 青 .Red equ 10 ; 赤 .Magenta equ 11 ; マゼンタ .Green equ 12 ; 緑 .Cyan equ 13 ; シアン .Yellow equ 14 ; 黄 .White equ 15 ; 白

あと、制御コードとしては色指定改行位置指定文字列の終了が欲しいので、先に少し仕様を検討します。英数文字は半角スペースから文字として有効となります。スペースの文字コードは $20(32)なので、0 ~ 31 は自由に使用する事が出来ます。なので、0 ~ 15 はそのまま色コードを直接置くとして、それ以外の 16 ~ 31 に機能を割り当てます。あと、もし文字の高さを変更したくなった時のために、高さも定数として定義してしまいます。

STR: .Space equ ' ' ; 有効文字開始コード .Height equ 8 ; 文字の高さ .END equ 16 ; 終了コード .CRLF equ 17 ; 改行コード .LOCATE equ 18 ; 表示位置 SCRWidth equ 40 ; 画面横幅 SCRHeight equ 200 ; 画面縦幅

色番号から実際の色を示すデータテーブルは、カラー制御で説明した内容と全く同じとなります。ただ、ラベルが重複するのでこちらは ATRB: と改名しました。ALIGN 2 は AILZ80ASM の独自命令でORGを2のべき乗で割り切れるアドレスに自動補正する命令です。ALIGN 16 とすると 0xNNN0 から出力されます。今回は ALIGN 2 なので、偶数からの配置が保証されます。この ALIGN 2 ですが、一般的なアセンブラでは org ($ + 1) / 2 * 2 と記述します。

ALIGN 2 ; 偶数開始に ATRB: ; +$0000 +$2000 .trans db 00000000b, 00000000b ; 透明 .bprple db 01010101b, 00000000b ; 青紫 .orange db 10101010b, 00000000b ; 橙 .prlred db 11111111b, 00000000b ; 赤紫 .turqse db 00000000b, 01010101b ; 青緑 .sky db 01010101b, 01010101b ; 空色 .ygreen db 10101010b, 01010101b ; 黄緑 .gray db 11111111b, 01010101b ; 灰色 .black db 00000000b, 10101010b ; 黒 .blue db 01010101b, 10101010b ; 青 .red db 10101010b, 10101010b ; 赤 .magnta db 11111111b, 10101010b ; マゼンタ .green db 00000000b, 11111111b ; 緑 .cyan db 01010101b, 11111111b ; シアン .yellow db 10101010b, 11111111b ; 黄 .white db 11111111b, 11111111b ; 白

この記事を書きながら思ったのですが COLOR と ATRB の色の名前は共通にするべきでした💦

※ これを使っています。キュキュッと音がして磨けます。健康は歯ぐきから!(マジ


  • 文字表示

文字列を表示するには、まず1文字表示するプログラムが必要です。そのサブルーチンのレジスタエントリを決めます。最初から一発で上手くいかない事もあるかと思いますので、その場合はまた処理の都合がつけやすいレジスタ構成にします。私は下記のように指定しました。

; 半角文字を表示する ; Acc = 文字コード ; DE = 表示アドレス ; C = 色番号(0-15) GRPDrawChar::


HL を表示アドレスにしていないのは、文字表示は HL のデータから DE にコピーする流れとなるためです。Acc が文字コードなのは、最初に8倍するためです。ということで、まずは文字コードからフォントデータの位置を算出してしまいます。

sub STR.Space ; スペースが文字データの先頭 add a, a ld l, a ; * 2 ld a, c ; Accに色コードを逃がす ld h, 0 add hl, hl add hl, hl ; * 8 ld bc, Fonts add hl, bc ; HL フォントデータ


Acc の文字コードからスペースの文字コードを引いて 0 からの連番に変えます。それを 8倍するのですが、文字コードは最大で 0 ~ 95 の連番の範囲ですので、8倍すると桁あふれが起きる可能性があります。95×2 = 190 ですので、最初の2倍だけは絶対桁あふれはしないので、先に Acc を2倍してから HL に入れ直して2倍2倍としてトータル8倍とします。これで処理速度を少し早くなります。最後にフォントデータの先頭アドレスを加算して HL にデータアドレスが入ります。

先頭アドレスを求めるために BC を使う必要がありましたが、C には色番号が入っていますので、これを途中で Acc に入れて保存します。

add a, a add a, Atrb ld c, a ld b, Atrb / 256

その Acc を2倍することで、今度は色テーブルの先頭アドレスを BC に作り出しています。

ld iyl, STR.Height .loop ld a, (bc) ; 色データを inc c and (hl) ; フォント形状でくりぬいて ld (de), a ; 画面に落とす ld a, (bc) ; 色データを dec c ; 色テーブルを戻す and (hl) ; フォント形状でくりぬいて set 5, d ; アドレスを H に移す ld (de), a ; 画面に落とす res 5, d ; アドレスを L に戻す inc hl ld a, e add a, SCRWidth ld e, a jr nc, $ + 3 inc d ; 表示アドレスを1ライン下げる dec iyl jp nz, .loop


文字表示のメイン処理部です。動作説明はコメントに記述しましたが、要は色テーブルの値を文字のシルエットで AND して文字の形状をした色付きの画像データを生成して VRAM に配置しているだけです。色は 0x0000 から始まる L の位置と、0x2000 から始まる H にデータを配置しなければならないため、set 5, d として描画アドレスを H に移動させています。

1ラインアドレスを下げるためには SCRWidth を加算する必要がありますが、ペアレジスタは全て使い切っているので、空いている Acc で下位アドレス計算を行い、桁あふれ CF=1 になったら上位アドレスを +1 することで、1ライン下げる計算をしています。

色テーブル(ATRB)は ALIGN 2 により偶数配置が保証されています。偶数アドレスを+1しても絶対に桁上がりしないのと、奇数アドレスから-1しても絶対に桁下がりしないので、色テーブルのアドレス変更は16bitではなく下位8bitだけ操作して僅かながらも高速化しています。この辺りは Z80 アライメントによる高速化で説明していますので、興味があればご確認ください。※ 少しだけ重めですが強くて折り畳める点が素晴らしいと思います。


  • 文字列表示
こちらも最初に文字列表示に必要なレジスタエントリを決めます。

; 文字列を表示する ; HL = 文字列アドレス ; DE = 表示アドレス GRPDrawStrings::


STR.LOCATE は表示位置指定なので、続く2バイトは dw で設定しています。直接 16進数でアドレスを書いてしまうと、後から見て訳が分からなくなるので、AILZ80ASM の Function 機能を使って、LOCATE(x,y) と記述することで可読性を上げています。Function は下記のような指定としています。

Function CENTER(wdh, hgt) => (SCRWidth - wdh) / 2 + (SCRHeight - hgt) / 2 * SCRWidth


さて、改行を実現するためには、元のアドレスを保存する必要があります。レジスタは既に使い切っています。裏は空いてますが、受け渡しは push pop を使うことになり、あまり好ましくないです。IX は空いていますが、こちらはもっと遅いです。そのため、一時ワークを使ってしまいます。プログラム書き換えも考えましたが、PC-6001系は ROMデバイスもあり得るので、素直にワークを使うことにしました。

このような一時ワークのために専用のワークを用意するのはメモリがもったいないので、ワークに一時使用専用のテンポラリワークを作ってしまいます。
※ テンポラリワークの重複使用によるバグにご注意ください。

;======================================================================= ; ワークエリア ;======================================================================= WRK: .IsPC6001 equ $FE00 ; 1 機種判別コード .SystemCounter equ .IsPC6001 + 1 ; 1 システムカウンター .KeyData1 equ .SystemCounter + 1 ; 1 キー入力データ1 .KeyData2 equ .KeyData1 + 1 ; 1 キー入力データ2 .GameKey equ .KeyData2 + 1 ; 1 ゲームキーの状態 .Temp00 equ .GameKey + 1 ; 1 テンポラリワーク00 .Temp01 equ .Temp00 + 1 ; 1 テンポラリワーク01 .Temp02 equ .Temp01 + 1 ; 1 テンポラリワーク02 .Temp03 equ .Temp02 + 1 ; 1 テンポラリワーク03


WRK.Temp00 だとわかりにくいので、ローカルラベルで別名を付けてしまいます。

GRPDrawStrings:: .Start equ WRK.Temp00


さて、これで表示アドレスを格納する位置が出来たので、文字列表示処理の最初に、このワークにアドレスを保存します。

GRPDrawStrings:: ld c, COLOR.White .store ld (.Start), de ; 改行用のアドレスを保存


C レジスタには色コードを入れておきました。さて、これで初期化も出来たので HL のデータアドレスから順次データを解析していきます。

.loop ld a, (hl) ; Acc 文字列 inc hl cp STR.Space ; 0-31 範囲確認から jr c, .cmd ; コマンドチェック call GRPDrawChar ; 文字表示 inc de ; 表示位置を右に jr .loop


データアドレスから取得した Acc が STR.Space 未満なら制御コードなので処理を別に移しています。それ以外はそのまま表示すれば良いので、先に作成した GRPDrawChar を呼び出します。その後、表示位置を右にズラして、また次のデータを取得してと、STR.END の制御コードが見つかるまでループします。

; コマンドチェック .cmd cp STR.END ; 色コードの数より jr nc, .ext ; 大きければ拡張命令


コマンドチェックですが、最初に色コードか判定します。STR.END は色コードの一番大きな数値 15 の次の番号なので、これと比較してそれより大きければ拡張機能判定にジャンプしています。

; 色変更 .color ld c, a ; C 色コード更新 jr .loop ; 登録して継続


Acc が色コードと分かったので、そのまま C レジスタにコピーして、文字列制御のループに戻します。

.ext cp STR.CRLF jr z, .crlf ; 改行か ret c ; 終了か


残る制御コードは STR.END, STR.CRLF, STR.LOCATE の3種類です。そのため、その中央値である STR.CRLF と比較(CP)して、ZF=1 なら STR.CRLF、CF=1 なら STR.END、CF=0 なら STR.LOCATE と判断出来ます。なるべく比較回数を減らして少しでも処理速度を上げます。また、よく使う処理ほど速い流れの部分に記述すると、全体が高速化します。

; 位置変更 .locate ld e, (hl) inc hl ld d, (hl) ; 表示アドレス更新 inc hl jr .store


STR.LOCATE は、続く 2バイトに次の文字表示開始アドレスが格納されています。それを直接 DE に取り出して更新します。ループに戻す際はあらかじめラベルを付けておいた .store に戻すことで、改行用のワークエリア格納も行っています。

; 改行 .crlf ex de, hl ; HL を DE に待避 ld hl, (.Start) ; HL 最初のアドレス ld a, c ; C を Acc に待避 ld bc, SCRWidth * STR.Height add hl, bc ; 改行アドレスに ld c, a ; C 元に戻す ld (.Start), hl ex de, hl ; レジスタ構成を元に戻す jr .loop


改行は少し実装に悩みました。元のアドレスに SCRWidth * STR.Height を加算しなければならないため、どのみちペアレジスタを空けて、HL で加算処理を行うのが良いと思います。幸い BC のペアレジスタは C しか使っていないので、C を一時的に Acc に逃がして BC を使って表示アドレスを1行下に移動する計算処理を行っています。

以上で文字列表示処理の説明となります。ちなみに画像コンバータのパラメータを弄ると、文字表示がいろいろ変化します。これはシルエット出力をマスク出力に変更して実行した結果です。
マスク
こちらは基準を右下にして出力した結果です。データが下側から羅列されるので、結果として文字が上下に反転しています。
データ基準方向を右下に指定
※ 最近購入して励んでます。結構いい汗かきます。気軽に運動できるのでオススメです!