Files
TeslaRel410/restoration/PlasmaNew/replica/MatrixPortalPlasma/PlasmaDisplay.h
T
CydandClaude Fable 5 bd082d8c4a PlasmaNew: ESC X/x box commands in the replica + MW4 bench stream
Implements the firmware-disasm-confirmed Firestorm box protocol in the
MatrixPortal replica (ESC X outline / ESC x mode-fill via a BOXOP operand
collector, plus the ESC Y region op), updates FIRMWARE.md with the findings,
and adds FS-plasma-bench.ps1 -- a simulated MW4 serial stream for benching
the display without a pod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:02:37 -05:00

98 lines
3.5 KiB
C++

// PlasmaDisplay — the Babcock PD01D221 command parser + 128x32 framebuffer,
// ported line-for-line from vRIO's C# VPlasmaDevice (src/VPlasma.Core/Device/
// VPlasmaDevice.cs). Keep the two in sync: vPLASMA is the reference oracle.
//
// Feed received wire bytes to feed(); read the framebuffer with frame() and
// render it however the panel wants. Each pixel is a flag byte (PIX_LIT /
// PIX_HALF / PIX_FLASH), so the renderer can dim half-intensity dots and blink
// flashing ones. See PlasmaNew/FIRMWARE.md for the recovered command set.
#pragma once
#include <Arduino.h>
#include "plasma_fonts.h"
class PlasmaDisplay {
public:
static const int WIDTH = 128;
static const int HEIGHT = 32;
// Per-pixel flag bits in the framebuffer.
static const uint8_t PIX_LIT = 0x01;
static const uint8_t PIX_HALF = 0x02;
static const uint8_t PIX_FLASH = 0x04;
enum Orientation { HORIZONTAL, VERTICAL };
enum CursorMode { CURSOR_HIDDEN, CURSOR_STEADY, CURSOR_FLASHING };
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() { 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_; }
bool takeDirty() { // true (and clears) if the frame changed
bool d = dirty_;
dirty_ = false;
return d;
}
CursorMode cursorMode() const { return cursorMode_; }
int cursorX() const { return cx_; }
int cursorY() const { return cy_; }
int fontWidth() const { return face_->width; }
int fontHeight() const { return face_->height; }
Orientation orientation() const { return orient_; }
private:
uint8_t pixels_[WIDTH * HEIGHT]; // always physical 128x32
// Text-mode state.
int font_;
const PlasmaFace *face_;
uint8_t attrs_; // low 4 bits: 1=half 2=underline 4=reverse 8=flash
int cx_, cy_; // cursor, logical pixels
CursorMode cursorMode_;
Orientation orient_;
// Parser state.
enum State { TEXT, ESCAPE, OPERAND, GHEADER, GDATA, BOXOP };
State state_;
uint8_t pendingCmd_;
uint8_t header_[5]; // screen, y, x, w, h
int headerFill_;
int dataIndex_, dataLength_;
uint8_t boxCmd_; // ESC X / x / Y multi-operand collector
uint8_t box_[5]; // X: l,t,r,b x: mode,l,t,r,b Y: mode,srcA,srcB,dest
int boxFill_;
bool dirty_;
int logicalW() const { return orient_ == HORIZONTAL ? WIDTH : HEIGHT; }
int logicalH() const { return orient_ == HORIZONTAL ? HEIGHT : WIDTH; }
void plot(int lx, int ly, uint8_t flags);
uint8_t peek(int lx, int ly) const;
void stepText(uint8_t b);
void stepEscape(uint8_t b);
void applyOperand(uint8_t cmd, uint8_t op);
void stepBoxOperand(uint8_t b);
void drawBoxOutline(int l, int t, int r, int b);
void applyBoxRegion(int mode, int l, int t, int r, int b);
void beginGraphics();
void stepGraphics(uint8_t b);
void drawChar(uint8_t code);
void advanceCursor();
void nextLine();
bool faceHas(uint8_t code) const { return code >= face_->first && code <= face_->last; }
uint16_t faceRow(uint8_t code, int row) const {
if (!faceHas(code) || (unsigned)row >= (unsigned)face_->height) return 0;
return pgm_read_word(&face_->rows[(code - face_->first) * face_->height + row]);
}
};