あなたが現在見ているのは Raspi Zero 2W ベアメタル開発 04 ( MZ80KのVRAM表示編)

Raspi Zero 2W ベアメタル開発 04 ( MZ80KのVRAM表示編)

前回、画面へのキャラクタ描画はうまく行ったので、もっとエミュレータ開発に使づくように、今日はもう一歩前進してみましょう。

MZ80Kのメモリ領域全体は、8bitマシンということで、64KBとなります。

unit mem;
interface
var
memory: array[0..65535] of Byte;
implementation
end.

メモリ内容を保持する unit として、上記のように準備しました。

ここに、MZ-80KのモニタROM (SP-1002) を読み込んでみましょう。

//==============================================================================
// Load MONITOR ROM
//==============================================================================
procedure LoadMonitorRom;
var
FS: TFileStream;
MonitorPath: string;
I: Integer;
begin
ConsoleWindowWriteLn(Console1, 'Waiting for drive...');

// C:\ または fat:\ が使えるのを待つ
while not DirectoryExists('C:\') and not DirectoryExists('fat:\') do
Sleep(100);

if DirectoryExists('C:\') then
MonitorPath := 'C:\mon_rom'
else
MonitorPath := 'fat:\mon_rom';

ConsoleWindowWriteLn(Console1, 'Drive is ready.');

if FileExists(MonitorPath) then
begin
ConsoleWindowWriteLn(Console1, 'Loading monitor ROM file...');
FS := TFileStream.Create(MonitorPath, fmOpenRead);
FS.Read(MonitorRom, SizeOf(MonitorRom));
FS.Free;
ConsoleWindowWriteLn(Console1, 'Monitor ROM loaded successfully!');
end
else
begin
ConsoleWindowWriteLn(Console1, 'Monitor ROM file not found at ' + MonitorPath);
end;

// 読み込んだ内容を memory[] にコピー
for I := 0 to SizeOf(MonitorRom) - 1 do
memory[I] := MonitorRom[I];
end;

メモリの先頭 $0000番地から4KBのモニタROMを読み込んでセットします。

今後、Z80のエミュレータがうまく動けば、このモニタ部分を実行して、VRAMへの初期表示もされることになります。

まだそこまでは行けないので、今回はVRAMとして割り当てられた $D000 からの1000byte の内容を画面に表示するところまでやってみましょう。

つまり画面表示部分だけ先にテストしておこうというわけです。

VRAMの内容を画面に表示する

とりあえずざっとソースプログラムを見てみましょう。

//==============================================================================
// スクリーン描画スレッド
//==============================================================================
procedure DrawScreenThread;
var
FTimeValue : QWord;
FTimeDis : QWord;
FStartVSync: QWord;
FEndVSync : QWord;
FVSyncTime : QWord;
Amin : Integer;
Asec : Integer;
DrawFreq : Integer;
i, j : Integer;
begin
// すでにスレッドが開始されていたら停止
if Assigned(ScreenThread) then
begin
flgStop := True;
ScreenThread.Terminate;
FreeAndNil(ScreenThread);
end;

// スレッド開始
flgStop := False;
ScreenThread := TDrawScreenThread.Create(False); // False = 自動起動
ScreenThread.FreeOnTerminate := False;
end;

//==============================================================================
// Draw Screen Thread Execeute
//==============================================================================
procedure TDrawScreenThread.Execute;
var
FStartVSync, FEndVSync, FVSyncTime: QWord;
begin
while not Terminated do
begin
FStartVSync := GetTickCount64;

//if hw80.v_gate then
if true then // z80動かすまでの仮設定
begin
update_scrn;
end
else
begin
ClearScreenFast(BG); // 事前定義された背景色
end;

FEndVSync := GetTickCount64;
FVSyncTime := FEndVSync - FStartVSync;

if SYNCTIME > FVSyncTime then
Sleep(SYNCTIME - FVSyncTime);

Sleep(1);
end;
end;

//==============================================================================
// 画面アップデート処理 (v-blank) [書き換えが必要部分だけ画面を書き換える]
//==============================================================================
procedure update_scrn;
var
i: Integer;
CharCode: Byte;
Row, Col: Integer;
begin
for i := 0 to 999 do
begin
CharCode := Memory[$D000 + i];
if ScrChar[i] <> CharCode then
begin
Row := i div 40;
Col := i mod 40;
DrawCharToSurface(Col * 8, Row * 8, CharCode);
ScrChar[i] := CharCode;
end;
end;
end;

//==============================================================================
// 指定した X,Y (40x25) にキャラを表示
//==============================================================================
procedure DrawCharToSurface(X, Y: Integer; CharCode: Byte);
var
Line, Bit: Integer;
Px, Py: Integer;
Data: Byte;
Color: LongWord;
begin
for Line := 0 to 7 do
begin
Data := FontRom[CharCode * 8 + Line];
for Bit := 0 to 7 do
begin
Px := X + Bit;
Py := Y + Line;

if (Data and (1 shl (7 - Bit))) <> 0 then
Color := FG
else
Color := BG;

PutPixel(Px, Py, Color);
end;
end;
end;

//==============================================================================
// 点を描画
//==============================================================================
procedure PutPixel(X, Y: Integer; Color: LongWord);
var
Offset: PtrUInt;
begin
Offset := FBInfo.PixelAddr + Y * FBInfo.Pitch + X * 4;
PLongWord(Pointer(Offset))^ := Color;
end;

//==============================================================================
// 画面を高速クリア
//==============================================================================
procedure ClearScreenFast(Color: LongWord);
var
PixelPtr: PLongWord;
Count: Integer;
begin
PixelPtr := PLongWord(Pointer(FBInfo.PixelAddr));
Count := (FBInfo.Pitch div 4) * FBInfo.Height;

while Count > 0 do
begin
PixelPtr^ := Color;
Inc(PixelPtr);
Dec(Count);
end;
end;

画面を一定周期で更新するThread を定義して実行します。

Thead内から、update_scrn (画面の更新処理) を呼び出します。

update_scrn では、高速化のためVRAM 上の値が前回と違う部分だけ更新するようにしています。

この中で、指定のX,Y 座標に文字を書き込む処理 DrawCharToSurface(X,Y,CharCode) を呼びだしています。

DrawCharToSurfaceでは、フォントデータに基づいてドットで画像を生成 ( PutPixel) しています。

PutPixel では、文字単位ではなくドット単位で書き込む処理が記述されています。

このように階層的に処理をしています。

ランダムな情報をVRAMに書き込む

まだ Z80が動いていないので、VRAMエリアに情報が書き込まれない状態ですので、とりあえずVRAMエリアにランダムに値を書き込みます。

procedure FillVRAMWithRandomChars;
var
i: Integer;
begin
Randomize;
for i := 0 to 999 do
begin
Memory[$D000 + i] := Random(256); // ランダムなキャラクターコード(0〜255)
end;
end;

これで、元データはできあ゛かりますので、テストしてみましょう。

それと、プログラムがちゃんと動作していることを確かめるため、MZ700風に背景を青にして描画するようにしてみましょう。

うまく行きました。

Z80部分もコンパイルできるところまでは修正したのですが、まだうまく動いていないので、とりあえず今日はVRAMの表示をスレッドでできたというところまで。

今回は、Z80の実装とMZ80のハードウェア定義もすべてソースに含まれていますが、まだZ80の実装部分のテストができていないので、動作からは外しています。

しかし、Z80部分のDEBUGをどうやってやろうかな。今の開発のやり方だと、まともにデバッグできないので、Raspi の エミュレータを開発環境に絡めてやるしかないんだろうな。。また開発環境の設定を触るしかないね。

コメントを残す

CAPTCHA