#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 #include #include #include "spy/Arduino.h" #include "spy/SPI.h" #include "trace.h" #include "window_sweep.h" template struct PartialPeek : Driver { using Driver::Driver; int busy_level() const { return this->_busy_level; } uint16_t reset_duration() const { return this->_reset_duration; } }; template 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 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(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 - the shared run_window_sweep (window_sweep.h), // same routine the standard full-refresh capture now runs for every panel. // Here it also reveals each rect's _Update_Part waveform (0x22 0xFC). run_window_sweep(d, Driver::WIDTH, Driver::HEIGHT); delete[] frame0; delete[] frame1; }