|
|
@@ -0,0 +1,197 @@
|
|
|
+#pragma once
|
|
|
+
|
|
|
+// Diagnostic-fill harness, Pattern A (byte/row ordering) - see the
|
|
|
+// gxepd2-diag-fill-spec memory. Instead of the uniform 0xA5 fill, it fills
|
|
|
+// the 1bpp plane buffer with a positional TAG, records the exact wire-byte
|
|
|
+// stream (trace.h recording), and inverts the tag to recover the driver's
|
|
|
+// buffer->wire row permutation (identity / reversed / row_interleave /
|
|
|
+// unknown). Host-only, no hardware.
|
|
|
+//
|
|
|
+// Scope: 1bpp byte-preserving writeImage path (mono, and the per-plane
|
|
|
+// bursts of a BW+accent panel). Packed panels that re-pack 1bpp planes into
|
|
|
+// 2bpp/4bpp codes in writeImage do NOT preserve byte identity, so Pattern A
|
|
|
+// there needs the native (writeNative) path or address-sequence analysis -
|
|
|
+// a documented follow-on, see the spec memory.
|
|
|
+//
|
|
|
+// Two fills:
|
|
|
+// ROW_TAG: each row r -> byte[0]=(r>>8)&0xFF, byte[1]=r&0xFF, rest 0.
|
|
|
+// Group the plane burst into rows of bpr; [hi,lo] of each group
|
|
|
+// recovers that wire-row's source row -> the permutation.
|
|
|
+// COL_TAG: byte[c]=c&0xFF across every row -> each wire row should read
|
|
|
+// 0,1,2,... confirming within-row order is identity (catches a
|
|
|
+// byte-stride/offset bug - the shape of the 583 smear).
|
|
|
+
|
|
|
+#include <cstdint>
|
|
|
+#include <cstdio>
|
|
|
+#include <cstring>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+#include "spy/Arduino.h"
|
|
|
+#include "spy/SPI.h"
|
|
|
+#include "trace.h"
|
|
|
+
|
|
|
+template<typename Driver> struct DiagPeek : Driver {
|
|
|
+ using Driver::Driver;
|
|
|
+ int busy_level() const { return this->_busy_level; }
|
|
|
+};
|
|
|
+
|
|
|
+// Pull the plane-sized data bursts out of the recorded (is_cmd,byte) stream:
|
|
|
+// maximal runs of consecutive data bytes whose length == plane_bytes.
|
|
|
+inline std::vector<std::vector<uint8_t>> extract_plane_bursts(uint32_t plane_bytes) {
|
|
|
+ std::vector<std::vector<uint8_t>> bursts;
|
|
|
+ std::vector<uint8_t> run;
|
|
|
+ auto flush = [&]() {
|
|
|
+ if (run.size() == plane_bytes) {
|
|
|
+ // Skip all-uniform bursts (e.g. a writeScreenBuffer prime) - they
|
|
|
+ // carry no tag, only the tagged writeImage burst is of interest.
|
|
|
+ bool uniform = true;
|
|
|
+ for (uint8_t v : run)
|
|
|
+ if (v != run[0]) {
|
|
|
+ uniform = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!uniform)
|
|
|
+ bursts.push_back(run);
|
|
|
+ }
|
|
|
+ run.clear();
|
|
|
+ };
|
|
|
+ for (const auto &ev : Trace::record) {
|
|
|
+ if (ev.first) { // command byte -> ends any data run
|
|
|
+ flush();
|
|
|
+ } else {
|
|
|
+ run.push_back(ev.second);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ flush();
|
|
|
+ return bursts;
|
|
|
+}
|
|
|
+
|
|
|
+// true if most data bytes are 0xFF rather than 0x00 - the driver inverted
|
|
|
+// the plane (our tag is mostly 0x00 bytes), so decode with ^0xFF.
|
|
|
+inline bool burst_is_inverted(const std::vector<uint8_t> &b) {
|
|
|
+ uint32_t zeros = 0, ones = 0;
|
|
|
+ for (uint8_t v : b) {
|
|
|
+ if (v == 0x00)
|
|
|
+ zeros++;
|
|
|
+ else if (v == 0xFF)
|
|
|
+ ones++;
|
|
|
+ }
|
|
|
+ return ones > zeros;
|
|
|
+}
|
|
|
+
|
|
|
+template<typename Driver> void analyze_row_tag(const char *name) {
|
|
|
+ const uint16_t W = Driver::WIDTH, H = Driver::HEIGHT;
|
|
|
+ const uint32_t plane_bytes = (uint32_t) W * H / 8;
|
|
|
+ const uint32_t bpr = (uint32_t) W / 8;
|
|
|
+ if (bpr < 2) {
|
|
|
+ printf(" [row-tag] skipped: bpr<2 (needs 2-byte row tag)\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto *black = new uint8_t[plane_bytes]();
|
|
|
+ auto *color = new uint8_t[plane_bytes]();
|
|
|
+ for (uint16_t r = 0; r < H; r++) {
|
|
|
+ black[r * bpr + 0] = color[r * bpr + 0] = (r >> 8) & 0xFF;
|
|
|
+ black[r * bpr + 1] = color[r * bpr + 1] = r & 0xFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ DiagPeek<Driver> d(10, 11, 12, 13);
|
|
|
+ Trace::set_dc_pin(11);
|
|
|
+ Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
|
|
|
+ d.selectSPI(SPI, SPISettings());
|
|
|
+ d.init(0);
|
|
|
+ d.writeImage(black, color, 0, 0, W, H); // prime: clears _initial_write (its screen-buffer prime is untracked)
|
|
|
+ Trace::start_record();
|
|
|
+ d.writeImage(black, color, 0, 0, W, H); // tagged write, now with no priming burst
|
|
|
+ Trace::stop_record();
|
|
|
+ delete[] black;
|
|
|
+ delete[] color;
|
|
|
+
|
|
|
+ auto bursts = extract_plane_bursts(plane_bytes);
|
|
|
+ printf(" [row-tag] %s %ux%u: bpr=%u, plane_bytes=%u, plane bursts found=%zu\n", name, W, H, (unsigned) bpr,
|
|
|
+ (unsigned) plane_bytes, bursts.size());
|
|
|
+
|
|
|
+ for (size_t p = 0; p < bursts.size(); p++) {
|
|
|
+ const auto &data = bursts[p];
|
|
|
+ bool inv = burst_is_inverted(data);
|
|
|
+ uint8_t x = inv ? 0xFF : 0x00;
|
|
|
+ std::vector<uint32_t> recovered(H);
|
|
|
+ for (uint16_t g = 0; g < H; g++)
|
|
|
+ recovered[g] = (uint32_t) ((data[g * bpr] ^ x) << 8) | (data[g * bpr + 1] ^ x);
|
|
|
+
|
|
|
+ bool identity = true, reversed = true, interleave = true;
|
|
|
+ for (uint16_t g = 0; g < H; g++) {
|
|
|
+ if (recovered[g] != g)
|
|
|
+ identity = false;
|
|
|
+ if (recovered[g] != (uint32_t) (H - 1 - g))
|
|
|
+ reversed = false;
|
|
|
+ uint32_t expect = (g % 2 == 0) ? (g / 2) : (H - 1 - (g / 2));
|
|
|
+ if (recovered[g] != expect)
|
|
|
+ interleave = false;
|
|
|
+ }
|
|
|
+ const char *verdict = identity ? "SEQUENTIAL (identity)"
|
|
|
+ : reversed ? "REVERSED rows"
|
|
|
+ : interleave ? "ROW_INTERLEAVE (top/bottom pairs)"
|
|
|
+ : "UNKNOWN permutation";
|
|
|
+ printf(" plane %zu: invert=%s -> %s\n", p, inv ? "yes" : "no", verdict);
|
|
|
+ printf(" first rows:");
|
|
|
+ for (uint16_t g = 0; g < 6 && g < H; g++)
|
|
|
+ printf(" %u", recovered[g]);
|
|
|
+ printf(" | last rows:");
|
|
|
+ for (uint16_t g = (H > 6 ? H - 6 : 0); g < H; g++)
|
|
|
+ printf(" %u", recovered[g]);
|
|
|
+ printf("\n");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+template<typename Driver> void analyze_col_tag(const char *name) {
|
|
|
+ const uint16_t W = Driver::WIDTH, H = Driver::HEIGHT;
|
|
|
+ const uint32_t plane_bytes = (uint32_t) W * H / 8;
|
|
|
+ const uint32_t bpr = (uint32_t) W / 8;
|
|
|
+
|
|
|
+ auto *black = new uint8_t[plane_bytes]();
|
|
|
+ auto *color = new uint8_t[plane_bytes]();
|
|
|
+ for (uint16_t r = 0; r < H; r++)
|
|
|
+ for (uint32_t c = 0; c < bpr; c++)
|
|
|
+ black[r * bpr + c] = color[r * bpr + c] = c & 0xFF;
|
|
|
+
|
|
|
+ DiagPeek<Driver> d(10, 11, 12, 13);
|
|
|
+ Trace::set_dc_pin(11);
|
|
|
+ Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
|
|
|
+ d.selectSPI(SPI, SPISettings());
|
|
|
+ d.init(0);
|
|
|
+ d.writeImage(black, color, 0, 0, W, H); // prime (untracked)
|
|
|
+ Trace::start_record();
|
|
|
+ d.writeImage(black, color, 0, 0, W, H); // tagged write
|
|
|
+ Trace::stop_record();
|
|
|
+ delete[] black;
|
|
|
+ delete[] color;
|
|
|
+
|
|
|
+ auto bursts = extract_plane_bursts(plane_bytes);
|
|
|
+ for (size_t p = 0; p < bursts.size(); p++) {
|
|
|
+ const auto &data = bursts[p];
|
|
|
+ bool inv = burst_is_inverted(data);
|
|
|
+ uint8_t x = inv ? 0xFF : 0x00;
|
|
|
+ // Within-row order is identity iff every row reads 0,1,2,...,bpr-1 (mod 256).
|
|
|
+ bool ok = true;
|
|
|
+ uint16_t bad_row = 0, bad_col = 0;
|
|
|
+ for (uint16_t g = 0; g < H && ok; g++)
|
|
|
+ for (uint32_t c = 0; c < bpr; c++)
|
|
|
+ if ((uint8_t) (data[g * bpr + c] ^ x) != (uint8_t) (c & 0xFF)) {
|
|
|
+ ok = false;
|
|
|
+ bad_row = g;
|
|
|
+ bad_col = c;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (ok)
|
|
|
+ printf(" plane %zu: within-row order OK (0..bpr-1 each row)\n", p);
|
|
|
+ else
|
|
|
+ printf(" plane %zu: within-row MISMATCH at row %u col %u\n", p, bad_row, bad_col);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+template<typename Driver> void run_diag_harness(const char *name) {
|
|
|
+ printf("=== %s: DIAGNOSTIC FILL (Pattern A: row/byte ordering) ===\n", name);
|
|
|
+ analyze_row_tag<Driver>(name);
|
|
|
+ analyze_col_tag<Driver>(name);
|
|
|
+}
|