さて、前回は画面に文字を書きましたが、今度は MZ-80K のフォントデータ 8×8 ドット 256文字 = 2048 byte のデータを読み込んで、FrameBuffer に直接書き込むテストをしてみます。
ファイルの読み込みの準備
ファイルをmicroSDから読み込む想定で考えていきます。
調べたところ、uses には色々と追加しなければならない。
uses
RaspberryPi3, // ← ここが機種名
GlobalConst, GlobalTypes,
Platform, Console, SysUtils,
Framebuffer, // HDMI出力のある場合
BCM2837, // SoC定義(Zero2Wならこちら)
Classes, // TFileStreamやTStringList用
FileSystem, // コアファイルシステム
FATFS, // FATファイルシステム
MMC, // SDカードアクセスの中核
BCM2710, // Raspberry Pi向けMMC実装
BCMSDHOST; // SD Hostドライバ
この後、画面出力もあるので、 Framebuffer も一緒に追加しますが、なんか色々と追加。参考資料がないとお手上げですね。
ファイル読み込み
Raspiでは、C:ドライブが規定のドライブになるらしい。またドライブの準備ができるまでは、C: が見つからないのでエラーになってしまうから、C:の読み込み準備ができるまで待つ。ベアメタルの場合はいきなり起動するので、こういった処理も必要なんですね。
procedure LoadFontRom;
var
FS: TFileStream;
begin
ConsoleWindowWriteLn(Console1, 'Waiting for C:\ drive...');
while not DirectoryExists('C:\') do Sleep(100);
ConsoleWindowWriteLn(Console1, 'C:\ is ready.');
if FileExists('C:\cg_rom') then
begin
ConsoleWindowWriteLn(Console1, 'Loading font file...');
FS := TFileStream.Create('C:\cg_rom', fmOpenRead);
FS.Read(FontRom, SizeOf(FontRom));
FS.Free;
ConsoleWindowWriteLn(Console1, 'Font loaded successfully!');
end
else
begin
ConsoleWindowWriteLn(Console1, 'Font file not found.');
end;
end;
今回は、Dlephi で開発した MZ-80K エミュの時に作成したフォントデータファイル [cg_rom] をそのまま使うことにする。
ちなみにフォントデータ用に配列を次のように定義している。
FontRom: array [0..2047] of Byte; // キャラクタフォントデータ
こちらご自由にお使いください。
フォントを画面に書き込む
フォントを 16 × 16 で順番に並べ画面に書き込む。
procedure DrawFontGridFromProperties;
var
FB: PFramebufferDevice;
Props: TFramebufferProperties;
BaseAddr: PtrUInt;
ch, cx, cy, x, y, bit: Integer;
PixelAddr: PLongWord;
begin
FB := FramebufferDeviceGetDefault;
FramebufferDeviceGetProperties(FB, @Props);
// 画面クリア(黒)
for y := 0 to Props.VirtualHeight - 1 do
for x := 0 to Props.VirtualWidth - 1 do
begin
PixelAddr := PLongWord(Props.Address + y * Props.Pitch + x * 4);
PixelAddr^ := COLOR_BLACK;
end;
// フォント描画(8x8)
for ch := 0 to 255 do
begin
cx := ch mod 16;
cy := ch div 16;
for y := 0 to 7 do
for bit := 0 to 7 do
if (FontRom[ch * 8 + y] and (128 shr bit)) <> 0 then
begin
x := cx * 8 + bit;
PixelAddr := PLongWord(Props.Address + (cy * 8 + y) * Props.Pitch + x * 4);
PixelAddr^ := COLOR_WHITE;
end;
end;
end;
コンパイルして実行
先ほどの [cg_rom] をmicroSDのルートにコピーして、コンパイル済の kernel7.img もコピーして、実機を起動。
表示されたが、なにやら緑の線がいっぱい。でもしばらく放っておいたら落ち着いたのか、ちゃんと表示されました。

準備が整うまで、少し待つというのが基本なのかな? ベアメタル。
ところで、このキャラクタの並びですがこれはディプレイコードというものです。MZ-80Kは、キャラクターコード (ASCIIコードみたいなもの) とディスプレイコードというのが別になっていて、プログラム中で扱う文字としてはキャラクターコードなのですが、VRAMに表示するときは、このディスプレイコードに基づいて表示されるのです。私も長年不思議におもっていましたし、小学生の時は意味が分かりませんでした。
エミュレータをつくる際に、マシンの回路図を見ていて思ったのが、当時はこの方式が回路をシンプルにするのに都合がよかったのでしょうね。上から、4段ずつみていくと、キーボードと対応しています。SHIFT + A で クローバーが表示されますし、カナモードにしてAキーを押すと「チ」が表示されます。そしてカナ文字の部分は海外バージョンでは、小文字や面白い記号が割り当てられています。キーを押したときにCGROMから読み出すのが簡単だったのでしょうね。またそれをディスプレイに展開するのも単純になったのだと思います。
下は、手持ちのMZ-80K2のメイン基板です。右中央で水色のコンデンサの左上にあるのが CPU (Z80) で、Z80の左に少し行くと、大きなIC がありますが、これが CG-ROM です。このROMを差し替えると、表示される文字が変わります。


あれ、CG-ROMの右側の掃除ができていなかった。
プロジェクトソース全体
program hellopi;
uses
RaspberryPi3, // ← ここが機種名
GlobalConst, GlobalTypes,
Platform, Console, SysUtils,
Framebuffer, // HDMI出力のある場合
BCM2837, // SoC定義(Zero2Wならこちら)
Classes, // TFileStreamやTStringList用
FileSystem, // コアファイルシステム
FATFS, // FATファイルシステム
MMC, // SDカードアクセスの中核
BCM2710, // Raspberry Pi向けMMC実装
BCMSDHOST; // SD Hostドライバ
var
Console1: TWindowHandle;
FontRom: array [0..2047] of Byte; // キャラクタフォントデータ
procedure InitFramebuffer320x200;
var
FB: PFramebufferDevice;
Props: TFramebufferProperties;
begin
FB := FramebufferDeviceGetDefault;
FillChar(Props, SizeOf(Props), 0);
Props.PhysicalWidth := 320;
Props.PhysicalHeight := 200;
Props.VirtualWidth := 320;
Props.VirtualHeight := 200;
Props.Depth := 32; // 32bit(ARGB)
FramebufferDeviceSetProperties(FB, @Props);
end;
procedure LoadFontRom;
var
FS: TFileStream;
begin
ConsoleWindowWriteLn(Console1, 'Waiting for C:\ drive...');
while not DirectoryExists('C:\') do Sleep(100);
ConsoleWindowWriteLn(Console1, 'C:\ is ready.');
if FileExists('C:\cg_rom') then
begin
ConsoleWindowWriteLn(Console1, 'Loading font file...');
FS := TFileStream.Create('C:\cg_rom', fmOpenRead);
FS.Read(FontRom, SizeOf(FontRom));
FS.Free;
ConsoleWindowWriteLn(Console1, 'Font loaded successfully!');
end
else
begin
ConsoleWindowWriteLn(Console1, 'Font file not found.');
end;
end;
procedure DrawFontGridFromProperties;
var
FB: PFramebufferDevice;
Props: TFramebufferProperties;
BaseAddr: PtrUInt;
ch, cx, cy, x, y, bit: Integer;
PixelAddr: PLongWord;
begin
FB := FramebufferDeviceGetDefault;
FramebufferDeviceGetProperties(FB, @Props);
// 画面クリア(黒)
for y := 0 to Props.VirtualHeight - 1 do
for x := 0 to Props.VirtualWidth - 1 do
begin
PixelAddr := PLongWord(Props.Address + y * Props.Pitch + x * 4);
PixelAddr^ := COLOR_BLACK;
end;
// フォント描画(8x8)
for ch := 0 to 255 do
begin
cx := ch mod 16;
cy := ch div 16;
for y := 0 to 7 do
for bit := 0 to 7 do
if (FontRom[ch * 8 + y] and (128 shr bit)) <> 0 then
begin
x := cx * 8 + bit;
PixelAddr := PLongWord(Props.Address + (cy * 8 + y) * Props.Pitch + x * 4);
PixelAddr^ := COLOR_WHITE;
end;
end;
end;
begin
Console1 := ConsoleWindowCreate(ConsoleDeviceGetDefault, 0, True);
if Console1 <> -1 then
begin
ConsoleWindowWriteLn(Console1, 'Hello from Ultibo on Pi Zero 2 W!');
end;
InitFramebuffer320x200;
LoadFontRom;
DrawFontGridFromProperties;
while True do Sleep(1000);
end.
プロジェクトファイル一式
まとめ
FrameBuffer への直接書き込みとファイルの読み込みテストができましたので、これで一気に目処が立ちましたね。
私は、新たな環境で開発を行う際に、まず文字の入出力、画面描画、キー入力、文字列の処理、データベースの読み書きなどのあたりを付けます。これらがうまく行けば、手足と羽がついたようなもので、あとは気兼ねなくプログラミングということになります。
プログラムの基本は、代入、条件分岐、繰り返し。これしかないんです。