|
|
@@ -335,6 +335,118 @@ template<typename Driver> PackedResolverFn make_resolver_bw_packed(uint8_t p0cmd
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+// Plane-sized (>= PLANE_BURST_MIN) burst commands one call makes, from a
|
|
|
+// fresh instance with an untraced writeScreenBuffer() first (same trick
|
|
|
+// probe_plane_invert_values uses) to absorb the lazy _InitDisplay()/
|
|
|
+// _initial_write clear. Shared by probe_full_refresh_extra_write_cmd below so
|
|
|
+// it can tell a genuinely NEW burst from one the plain single-bitmap
|
|
|
+// writeImage() already makes on its own - a 2-plane (3-colour) driver's
|
|
|
+// single-bitmap writeImage(bitmap,...) forwards to writeImage(black, NULL,
|
|
|
+// ...), which bursts to BOTH its real plane_write_cmd[0] (real data) AND
|
|
|
+// plane_write_cmd[1] (a NULL-color blank-fill burst, same size) every time,
|
|
|
+// with no full-refresh involved at all - diffing against just plane_write_cmd
|
|
|
+// [0] would misread that second plane's ordinary burst as a full-refresh-only
|
|
|
+// extra write.
|
|
|
+template<typename Driver, typename Call> std::set<uint8_t> plane_burst_cmds(Call call) {
|
|
|
+ constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
|
+ Trace::set_dc_pin(DC);
|
|
|
+ Peek<Driver> d(CS, DC, RST, BUSY);
|
|
|
+ Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
|
|
|
+ d.selectSPI(SPI, SPISettings());
|
|
|
+ d.init(0);
|
|
|
+ d.writeScreenBuffer(); // untraced: absorb lazy init + the _initial_write clear
|
|
|
+
|
|
|
+ const uint32_t plane_bytes = (uint32_t) Driver::WIDTH * Driver::HEIGHT / 8;
|
|
|
+ auto *buf = new uint8_t[plane_bytes];
|
|
|
+ memset(buf, 0xA5, plane_bytes);
|
|
|
+ Trace::start_event_record();
|
|
|
+ call(d, buf);
|
|
|
+ Trace::stop_event_record();
|
|
|
+ delete[] buf;
|
|
|
+
|
|
|
+ std::set<uint8_t> cmds;
|
|
|
+ for (const auto &c : group(0, Trace::events.size()))
|
|
|
+ if (c.data.size() >= PLANE_BURST_MIN)
|
|
|
+ cmds.insert(c.cmd);
|
|
|
+ return cmds;
|
|
|
+}
|
|
|
+
|
|
|
+// Detects a controller whose writeImageForFullRefresh() diverges from plain
|
|
|
+// writeImage() - e.g. SSD1680/SSD1619-family controllers write the same
|
|
|
+// bitmap to a "previous RAM" bank (a different command) before the normal
|
|
|
+// "current RAM" write plane_write_cmd[plane] already captures. GxEPD2_EPD's
|
|
|
+// base class defaults writeImageForFullRefresh() to call writeImage()
|
|
|
+// unchanged, so this probe generalizes across every controller without
|
|
|
+// hardcoding the known-affected families: it diffs the two calls' burst-
|
|
|
+// command sets and returns whatever writeImageForFullRefresh() adds. Returns
|
|
|
+// 0x00 (no divergence - by far the common case) or the extra command byte.
|
|
|
+template<typename Driver> uint8_t probe_full_refresh_extra_write_cmd() {
|
|
|
+ auto write_cmds = plane_burst_cmds<Driver>(
|
|
|
+ [](Peek<Driver> &d, uint8_t *buf) { d.writeImage(buf, 0, 0, Driver::WIDTH, Driver::HEIGHT); });
|
|
|
+ auto full_cmds = plane_burst_cmds<Driver>(
|
|
|
+ [](Peek<Driver> &d, uint8_t *buf) { d.writeImageForFullRefresh(buf, 0, 0, Driver::WIDTH, Driver::HEIGHT); });
|
|
|
+ for (uint8_t c : full_cmds)
|
|
|
+ if (!write_cmds.count(c))
|
|
|
+ return c;
|
|
|
+ return 0x00;
|
|
|
+}
|
|
|
+
|
|
|
+// Prints just the plane_write_cmd_for_full_refresh{} line (for splicing into
|
|
|
+// an existing hand-maintained panel_*.h, same workflow as
|
|
|
+// print_power_sequences / tools/capture.py --power). Self-contained like
|
|
|
+// print_power_sequences: derives p0cmd itself (the first plane-sized burst a
|
|
|
+// plain writeImage() produces) rather than trusting a shipped panel_*.h's
|
|
|
+// plane_write_cmd, which may have been hand-adjusted since it was captured.
|
|
|
+template<typename Driver> void print_full_refresh_extra_write() {
|
|
|
+ constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
|
|
|
+ Trace::set_dc_pin(DC);
|
|
|
+ Peek<Driver> probe(CS, DC, RST, BUSY);
|
|
|
+ Trace::last_busy_release_value = probe.busy_level() == HIGH ? LOW : HIGH;
|
|
|
+ probe.selectSPI(SPI, SPISettings());
|
|
|
+ probe.init(0);
|
|
|
+ probe.writeScreenBuffer(); // untraced: absorb lazy init + the _initial_write clear
|
|
|
+
|
|
|
+ const uint32_t plane_bytes = (uint32_t) Driver::WIDTH * Driver::HEIGHT / 8;
|
|
|
+ auto *buf = new uint8_t[plane_bytes];
|
|
|
+ memset(buf, 0xFF, plane_bytes); // 0xFF/0x00, not a filler byte: doubles as the 1bpp-vs-packed check below
|
|
|
+ Trace::start_event_record();
|
|
|
+ probe.writeImage(buf, 0, 0, Driver::WIDTH, Driver::HEIGHT);
|
|
|
+ Trace::stop_event_record();
|
|
|
+ delete[] buf;
|
|
|
+
|
|
|
+ uint8_t p0cmd = 0x00;
|
|
|
+ int w0_white = -1;
|
|
|
+ for (const auto &c : group(0, Trace::events.size()))
|
|
|
+ if (c.data.size() >= PLANE_BURST_MIN) {
|
|
|
+ p0cmd = c.cmd;
|
|
|
+ w0_white = c.data[0];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // A packed (>1bpp) driver's writeImageForFullRefresh expects its own packed
|
|
|
+ // bit depth, not the 1bpp-sized buffer above - probing it the same way a
|
|
|
+ // 1bpp panel is probed can over/under-read and crash (seen on GDEP073E01,
|
|
|
+ // 4bpp 7-colour). A 1bpp panel's white-fill wire byte is always 0x00 or
|
|
|
+ // 0xFF (see probe_plane_invert_values); anything else means packed, so bail
|
|
|
+ // instead of guessing - this field isn't meaningful there anyway (see
|
|
|
+ // emit_descriptor_impl's pv.is_1bpp gate).
|
|
|
+ if (w0_white != 0x00 && w0_white != 0xFF) {
|
|
|
+ printf(" // SKIPPED: packed (>1bpp) panel - writeImageForFullRefresh's dual-write behaviour is "
|
|
|
+ "specific to the 1bpp plane model, not applicable here\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const uint8_t extra = probe_full_refresh_extra_write_cmd<Driver>();
|
|
|
+ if (extra)
|
|
|
+ printf(" .plane_write_cmd_for_full_refresh = {0x%02X, 0x00}, // writeImageForFullRefresh also "
|
|
|
+ "re-writes this \"previous RAM\" bank before the 0x%02X write above - captured for future "
|
|
|
+ "partial-refresh use, not read by this full-refresh-only engine yet\n",
|
|
|
+ extra, p0cmd);
|
|
|
+ else
|
|
|
+ printf(" // writeImageForFullRefresh == writeImage for this controller: no "
|
|
|
+ ".plane_write_cmd_for_full_refresh entry needed (0x00 default already correct)\n");
|
|
|
+}
|
|
|
+
|
|
|
// Captures powerOff() or hibernate() as its own standalone command sequence -
|
|
|
// NOT folded into the refresh_seq phase-segmentation above, and NOT captured
|
|
|
// from the same live session as each other. Every GxEPD2 driver's powerOff()
|
|
|
@@ -705,6 +817,18 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
std::vector<Cmd> &power_off_seq = power_captured.first;
|
|
|
std::vector<Cmd> &hibernate_seq = power_captured.second;
|
|
|
|
|
|
+ // plane_write_cmd_for_full_refresh - computed here, BEFORE the "// GENERATED"
|
|
|
+ // marker below, not alongside the printf()s that use it: every probe in this
|
|
|
+ // function runs its own fresh Peek<Driver> through an un-traced init()/
|
|
|
+ // writeScreenBuffer() first (see plane_burst_cmds), which prints raw
|
|
|
+ // pinMode()/digitalWrite()/etc lines straight to stdout outside the
|
|
|
+ // Trace::event_recording bracket - harmless for every OTHER probe above
|
|
|
+ // because capture()/run_program() in tools/capture.py slice captured stdout
|
|
|
+ // from the FIRST "// GENERATED" marker onward, dropping that noise. Calling
|
|
|
+ // this probe any later - after the marker's printed - would leak its raw
|
|
|
+ // trace lines into the middle of the emitted descriptor instead.
|
|
|
+ const uint8_t full_refresh_extra = pv.is_1bpp ? probe_full_refresh_extra_write_cmd<Driver>() : 0x00;
|
|
|
+
|
|
|
// --- emit ---
|
|
|
printf("// GENERATED by emit_descriptor from %s - candidate.\n", class_name);
|
|
|
printf("// Command sequences, plane layout and plane_invert are observed here;\n");
|
|
|
@@ -781,6 +905,18 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
}
|
|
|
printf(" .plane_count = %zu,\n", nplanes);
|
|
|
printf(" .plane_write_cmd = {0x%02X, 0x%02X},\n", p0cmd, p1cmd);
|
|
|
+ // full_refresh_extra was computed above, before the "// GENERATED" marker -
|
|
|
+ // see that computation's comment for why. A packed (>1bpp) driver's
|
|
|
+ // writeImageForFullRefresh takes the same single-bitmap buffer shape, but
|
|
|
+ // its _writeImage expects a buffer already packed to its own bit depth -
|
|
|
+ // probing it with a 1bpp-sized buffer over/under-reads and crashes some
|
|
|
+ // drivers (e.g. GDEP073E01, 4bpp 7-colour), hence the pv.is_1bpp gate above;
|
|
|
+ // packed panels just keep the {0,0} default.
|
|
|
+ if (full_refresh_extra)
|
|
|
+ printf(" .plane_write_cmd_for_full_refresh = {0x%02X, 0x00}, // writeImageForFullRefresh also "
|
|
|
+ "re-writes this \"previous RAM\" bank before the 0x%02X write above - not read by this "
|
|
|
+ "full-refresh-only engine yet, captured for future partial-refresh use\n",
|
|
|
+ full_refresh_extra, p0cmd);
|
|
|
// Fields below are emitted in struct-declaration order (plane_invert,
|
|
|
// plane_width, line_order, bits_per_pixel, palette, palette_len,
|
|
|
// blank_code) so the designated initializer stays well-formed.
|