|
|
@@ -0,0 +1,105 @@
|
|
|
+#pragma once
|
|
|
+
|
|
|
+// Drives a fast-partial-capable vendor driver PAST the full-refresh capture
|
|
|
+// cutoff, to record the sequences fast partial needs that a plain
|
|
|
+// writeImage()+refresh() trace (run_harness.h) deliberately discards:
|
|
|
+//
|
|
|
+// [B] the previous-buffer sync - writeImageAgain() writes the frame to the
|
|
|
+// controller's "previous" buffer (0x26) then "current" (0x24), so the
|
|
|
+// next differential refresh baselines correctly.
|
|
|
+// [C] the partial refresh waveform - refresh(x,y,w,h) -> _Update_Part(),
|
|
|
+// a different 0x22 "display update control 2" byte (and often a much
|
|
|
+// shorter busy wait) than the full _Update_Full().
|
|
|
+// [D] the parameterized RAM-window encoder - a COORDINATE SWEEP calling
|
|
|
+// the public refresh(x,y,w,h) at several distinct rects (its internal
|
|
|
+// _setPartialRamArea is private, so we drive it through the public
|
|
|
+// API; with x/w kept multiples of 8 the driver's coordinate rounding
|
|
|
+// is a no-op, so the window bytes in each trace are exactly our rect).
|
|
|
+// We diff the resulting traces to SOLVE the (x,y,w,h) -> wire-byte
|
|
|
+// formula by observation instead of hand-computing it (the latter is
|
|
|
+// what produced the GDEQ0583T31 0x8F-vs-0x87 off-by-one smear). This
|
|
|
+// sweep is NOT fast-partial-specific: every panel sets a window from
|
|
|
+// coordinates, so the same technique derives and cross-checks the
|
|
|
+// window math for any driver that exposes a coordinate-parameterized
|
|
|
+// window routine. The full-frame (0,0,W,H) case in the sweep is the
|
|
|
+// same window our existing full-refresh capture already records, so
|
|
|
+// it is a built-in cross-check on the derived encoder.
|
|
|
+
|
|
|
+#include <cstdint>
|
|
|
+#include <cstdio>
|
|
|
+#include <cstring>
|
|
|
+
|
|
|
+#include "spy/Arduino.h"
|
|
|
+#include "spy/SPI.h"
|
|
|
+#include "trace.h"
|
|
|
+
|
|
|
+template<typename Driver> struct PartialPeek : Driver {
|
|
|
+ using Driver::Driver;
|
|
|
+ int busy_level() const { return this->_busy_level; }
|
|
|
+ uint16_t reset_duration() const { return this->_reset_duration; }
|
|
|
+};
|
|
|
+
|
|
|
+template<typename Driver> void run_partial_harness(const char *name) {
|
|
|
+ printf("=== %s: FAST-PARTIAL capture : WIDTH=%u HEIGHT=%u ===\n", name, (unsigned) Driver::WIDTH,
|
|
|
+ (unsigned) Driver::HEIGHT);
|
|
|
+
|
|
|
+ constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
|
+ Trace::set_dc_pin(DC);
|
|
|
+
|
|
|
+ PartialPeek<Driver> d(CS, DC, RST, BUSY);
|
|
|
+ printf("busy_level=%d reset_duration=%u\n", d.busy_level(), d.reset_duration());
|
|
|
+ Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
|
|
|
+ d.selectSPI(SPI, SPISettings());
|
|
|
+
|
|
|
+ const uint32_t plane_bytes = (static_cast<uint32_t>(Driver::WIDTH) * Driver::HEIGHT) / 8;
|
|
|
+ auto *frame0 = new uint8_t[plane_bytes];
|
|
|
+ auto *frame1 = new uint8_t[plane_bytes];
|
|
|
+ memset(frame0, 0xA5, plane_bytes); // distinctive, != 0x00/0xFF
|
|
|
+ memset(frame1, 0x5A, plane_bytes); // a different frame, to make the partial diff non-trivial
|
|
|
+
|
|
|
+ // [PHASE A] Full init + full update. Same as the existing full-refresh
|
|
|
+ // capture; also clears the driver's _initial_refresh so the partial
|
|
|
+ // refresh in phase C actually takes the _Update_Part path (the vendor
|
|
|
+ // driver forces the very first refresh to be full regardless).
|
|
|
+ printf("\n--- [PHASE A] init(0) + writeImage(full) + refresh(false) (full baseline) ---\n");
|
|
|
+ d.init(0);
|
|
|
+ d.writeImage(frame0, 0, 0, Driver::WIDTH, Driver::HEIGHT);
|
|
|
+ d.refresh(false);
|
|
|
+
|
|
|
+ // [PHASE B] Previous-buffer sync.
|
|
|
+ printf("\n--- [PHASE B] writeImageAgain(full) (previous 0x26 then current 0x24) ---\n");
|
|
|
+ d.writeImageAgain(frame0, 0, 0, Driver::WIDTH, Driver::HEIGHT);
|
|
|
+
|
|
|
+ // [PHASE C] A real partial update: new content to current, then a
|
|
|
+ // windowed refresh -> _Update_Part waveform + the partial window bytes.
|
|
|
+ printf("\n--- [PHASE C] writeImage(new) + refresh(16,24,32,48) (partial update) ---\n");
|
|
|
+ d.writeImage(frame1, 0, 0, Driver::WIDTH, Driver::HEIGHT);
|
|
|
+ d.refresh(16, 24, 32, 48);
|
|
|
+
|
|
|
+ // [PHASE D] Window-encoder sweep via refresh(x,y,w,h). Distinct rects with
|
|
|
+ // x/w multiples of 8 (so the driver's rounding is a no-op and the window
|
|
|
+ // bytes are exactly the rect); y/h deliberately non-round to expose the
|
|
|
+ // y encoding and the inclusive (y+h-1) end math. D0 is the full frame, a
|
|
|
+ // cross-check against the already-captured full window. Each refresh also
|
|
|
+ // emits an _Update_Part waveform (expected trailing noise); the window
|
|
|
+ // bytes are what appear between the ">>> SWEEP" line and the 0x22 0xFC.
|
|
|
+ printf("\n--- [PHASE D] window-encoder sweep (refresh(x,y,w,h) per rect) ---\n");
|
|
|
+ struct Rect {
|
|
|
+ uint16_t x, y, w, h;
|
|
|
+ const char *label;
|
|
|
+ };
|
|
|
+ const Rect rects[] = {
|
|
|
+ {0, 0, Driver::WIDTH, Driver::HEIGHT, "D0 full (0,0,W,H) - cross-check vs captured full window"},
|
|
|
+ {0, 0, 8, 8, "D1 origin-min (0,0,8,8)"},
|
|
|
+ {16, 24, 8, 8, "D2 offset (16,24,8,8)"},
|
|
|
+ {32, 40, 40, 48, "D3 block (32,40,40,48)"},
|
|
|
+ {120, 288, 8, 8, "D4 far-corner (120,288,8,8) - high y byte"},
|
|
|
+ };
|
|
|
+ for (const auto &r : rects) {
|
|
|
+ printf(">>> SWEEP %s\n", r.label);
|
|
|
+ d.refresh(r.x, r.y, r.w, r.h);
|
|
|
+ }
|
|
|
+
|
|
|
+ delete[] frame0;
|
|
|
+ delete[] frame1;
|
|
|
+}
|