PD4 display test: cycle diagnostic patterns, not just all-on

The real display's power-on test (JP1 jumper 5, firmware $B888) runs through
several drawing sequences — a solid fill, a border+grid, more — each held
briefly, to expose dead dots and addressing faults; it isn't just full on/off.

Both vPLASMA and the replica firmware now cycle 5 representative patterns:
solid, border+grid, horizontal stripes, vertical stripes, checkerboard.
- VPlasmaDevice.ShowTestPattern(int) + TestPatternCount; the app cycles them
  on a 1.2s timer while jumper 5 is installed.
- PlasmaDisplay::showTestPattern(int) + TEST_PATTERN_COUNT (shared logic); the
  Matrix Portal sketch's DOWN button toggles the cycling test.

(The firmware's exact byte patterns live in the display's native scan-buffer
layout, which we haven't mapped, so these stand in for that sequence rather
than reproducing it byte-for-byte.)

Verified: 29 C# tests pass; the C++ patterns render distinctly on the host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 20:34:33 -05:00
co-authored by Claude Opus 4.8
parent ab36cdd826
commit ebad6ee5fe
6 changed files with 82 additions and 13 deletions
@@ -47,6 +47,12 @@ static int demoScreen = 0;
static uint32_t lastDemoStep = 0;
static const uint32_t DEMO_MS = 2800;
// Panel test (DOWN button): cycle the diagnostic patterns.
static bool testRunning = false;
static int testPattern = 0;
static uint32_t lastTestStep = 0;
static const uint32_t TEST_MS = 1200;
// ---- helpers -------------------------------------------------------------
static bool frameHasFlash = false;
@@ -120,6 +126,7 @@ void loop() {
bool up = digitalRead(BUTTON_UP);
if (upPrev == HIGH && up == LOW) { // pressed
demoRunning = !demoRunning;
testRunning = false;
if (demoRunning) { demoScreen = 0; feedDemoScreen(0); demoScreen = 1; lastDemoStep = millis(); }
else { display.reset(); }
}
@@ -129,19 +136,26 @@ void loop() {
static bool downPrev = HIGH;
bool down = digitalRead(BUTTON_DOWN);
if (downPrev == HIGH && down == LOW) { // pressed
testRunning = !testRunning;
demoRunning = false;
display.showTestPattern();
if (testRunning) { testPattern = 0; display.showTestPattern(0); lastTestStep = millis(); }
else { display.reset(); }
}
downPrev = down;
#endif
// 3) Advance the demo on its timer.
// 3) Advance the demo / test on their timers.
uint32_t now = millis();
if (demoRunning && now - lastDemoStep >= DEMO_MS) {
feedDemoScreen(demoScreen);
demoScreen = (demoScreen + 1) % plasmaDemoCount;
lastDemoStep = now;
}
if (testRunning && now - lastTestStep >= TEST_MS) {
testPattern = (testPattern + 1) % PlasmaDisplay::TEST_PATTERN_COUNT;
display.showTestPattern(testPattern);
lastTestStep = now;
}
// 4) Blink phase for flashing pixels.
if (now - lastBlink >= BLINK_MS) {
@@ -32,8 +32,25 @@ void PlasmaDisplay::setOrientation(Orientation o) {
dirty_ = true;
}
void PlasmaDisplay::showTestPattern() {
for (int i = 0; i < WIDTH * HEIGHT; ++i) pixels_[i] = PIX_LIT;
// The real display's power-on test (JP1 jumper 5, firmware $B888) runs through
// several drawing sequences — solid, border+grid, and more — to expose dead
// dots and addressing faults. These stand in for that; the sketch cycles them.
static bool testDot(int index, int x, int y) {
switch (index) {
case 0: return true; // all on
case 1: return x == 0 || x == PlasmaDisplay::WIDTH - 1 || // frame
y == 0 || y == PlasmaDisplay::HEIGHT - 1 ||
(x % 16) == 0 || (y % 8) == 0; // + grid
case 2: return ((y / 2) % 2) == 0; // horizontal stripes
case 3: return ((x / 4) % 2) == 0; // vertical stripes
default: return (((x / 8) + (y / 8)) % 2) == 0; // checkerboard
}
}
void PlasmaDisplay::showTestPattern(int index) {
for (int y = 0; y < HEIGHT; ++y)
for (int x = 0; x < WIDTH; ++x)
pixels_[y * WIDTH + x] = testDot(index, x, y) ? PIX_LIT : 0;
dirty_ = true;
}
@@ -25,10 +25,13 @@ class PlasmaDisplay {
PlasmaDisplay();
static const int TEST_PATTERN_COUNT = 5; // JP1 jumper-5 diagnostic patterns
void reset(); // power-on: dark glass, home cursor
void feed(uint8_t b); // one received wire byte
void feed(const uint8_t *buf, size_t n);
void showTestPattern(); // all dots lit (JP1 jumper 5)
void showTestPattern() { showTestPattern(0); } // all dots lit
void showTestPattern(int index); // JP1 jumper 5: solid/grid/stripes/checker
void setOrientation(Orientation o); // JP1 jumper 4
const uint8_t *frame() const { return pixels_; }
+3 -1
View File
@@ -66,7 +66,9 @@ display listens), exactly as the cockpit drove the real panel.
- **UP** — toggle the built-in **firmware demonstration** (the real 10-screen
PLASMADOT demo, `demo_screens.h`, extracted from the ROM).
- **DOWN** — the **panel test pattern** (all dots lit; a dead-dot check).
- **DOWN** — toggle the **panel test**: cycles diagnostic patterns (solid,
border+grid, horizontal/vertical stripes, checkerboard) to expose dead dots
and addressing faults — the multi-pattern sequence the real firmware runs.
- **Power-on** briefly lights every dot (confirms both panels), then clears.
## Files
+15 -2
View File
@@ -30,8 +30,10 @@ internal sealed class MainForm : Form
private readonly System.Windows.Forms.Timer _uiTimer = new() { Interval = 500 };
private readonly System.Windows.Forms.Timer _demoTimer = new() { Interval = 2800 };
private readonly System.Windows.Forms.Timer _testTimer = new() { Interval = 1200 };
private int _selfTestPage;
private int _demoScreen;
private int _testPattern;
// ---- config panel controls -------------------------------------------
private readonly ComboBox _portBox = new() { Width = 168, DropDownStyle = ComboBoxStyle.DropDownList };
@@ -118,6 +120,7 @@ internal sealed class MainForm : Form
_jp5.CheckedChanged += (_, _) => ApplyTestPattern();
_jp6.CheckedChanged += (_, _) => ApplyDemo();
_demoTimer.Tick += (_, _) => StepDemo();
_testTimer.Tick += (_, _) => StepTestPattern();
// Default straps: jumper 2 installed = 9600 (the game's rate). Jumper 4
// unstrapped = horizontal (the normal cockpit orientation).
@@ -130,6 +133,7 @@ internal sealed class MainForm : Form
{
_uiTimer.Dispose();
_demoTimer.Dispose();
_testTimer.Dispose();
_service.Dispose();
_pipeService.Dispose();
};
@@ -288,16 +292,25 @@ internal sealed class MainForm : Form
{
if (_jp5.Checked)
{
_device.ShowTestPattern();
PrependLog("Jumper 5 installed — panel test pattern (all dots lit)");
PrependLog("Jumper 5 installed — panel test (cycling diagnostic patterns)");
_testPattern = 0;
_device.ShowTestPattern(0);
_testTimer.Start();
}
else
{
_testTimer.Stop();
_device.Reset();
PrependLog("Jumper 5 removed — test pattern cleared");
}
}
private void StepTestPattern()
{
_testPattern = (_testPattern + 1) % VPlasmaDevice.TestPatternCount;
_device.ShowTestPattern(_testPattern);
}
private void ApplyDemo()
{
if (_jp6.Checked)
+25 -5
View File
@@ -197,21 +197,41 @@ public sealed class VPlasmaDevice
FlushEvents();
}
/// <summary>Number of diagnostic patterns the JP1 jumper-5 test cycles.</summary>
public const int TestPatternCount = 5;
/// <summary>Show the first test pattern (all dots lit).</summary>
public void ShowTestPattern() => ShowTestPattern(0);
/// <summary>
/// Light every dot — the panel test pattern JP1 jumper 5 runs at power-on
/// (the firmware's dead-dot check). Clear it with <see cref="Reset"/>.
/// Paint diagnostic pattern <paramref name="index"/> (0..<see
/// cref="TestPatternCount"/>-1). The real display's power-on test (JP1
/// jumper 5, firmware <c>$B888</c>) runs through several drawing
/// sequences — a solid fill, a border+grid, and more, each held briefly —
/// to expose dead dots and addressing faults; these stand in for that.
/// </summary>
public void ShowTestPattern()
public void ShowTestPattern(int index)
{
lock (_sync)
{
for (int i = 0; i < _pixels.Length; ++i)
_pixels[i] = PixelLit;
for (int y = 0; y < Height; ++y)
for (int x = 0; x < Width; ++x)
_pixels[y * Width + x] = TestDot(index, x, y) ? PixelLit : (byte)0;
_dirty = true;
}
FlushEvents();
}
private static bool TestDot(int index, int x, int y) => index switch
{
0 => true, // all on
1 => x == 0 || x == Width - 1 || y == 0 || y == Height - 1 // frame
|| x % 16 == 0 || y % 8 == 0, // + grid
2 => (y / 2) % 2 == 0, // horizontal stripes
3 => (x / 4) % 2 == 0, // vertical stripes
_ => ((x / 8) + (y / 8)) % 2 == 0, // checkerboard
};
/// <summary>Feed <paramref name="count"/> received wire bytes.</summary>
public void OnReceived(byte[] buffer, int count)
{