|
@@ -293,6 +293,9 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
std::vector<Cmd> init_seq, window0, window1;
|
|
std::vector<Cmd> init_seq, window0, window1;
|
|
|
std::vector<uint8_t> plane_cmds;
|
|
std::vector<uint8_t> plane_cmds;
|
|
|
bool per_plane = false;
|
|
bool per_plane = false;
|
|
|
|
|
+ bool dual_region = false; // two physical regions (per-region select + window)
|
|
|
|
|
+ int plane_width_val = 0; // decoded per-region width (dual_region only)
|
|
|
|
|
+ uint32_t region0_burst = 0; // region 0's image burst length, for line-order detection
|
|
|
|
|
|
|
|
if (bursts.size() >= 2) {
|
|
if (bursts.size() >= 2) {
|
|
|
// Multi-plane. Two observed layouts:
|
|
// Multi-plane. Two observed layouts:
|
|
@@ -359,6 +362,50 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
for (size_t k = 1; k < windows.size(); k++)
|
|
for (size_t k = 1; k < windows.size(); k++)
|
|
|
if (!cmds_equal(windows[0], windows[k]))
|
|
if (!cmds_equal(windows[0], windows[k]))
|
|
|
per_plane = true; // per-plane windows differ => separate regions
|
|
per_plane = true; // per-plane windows differ => separate regions
|
|
|
|
|
+
|
|
|
|
|
+ // Dual-region refinement (e.g. GDEY0579F51 / HX8717: a two-chip panel whose
|
|
|
|
|
+ // "planes" are two physical half-width regions, each gated by a region-
|
|
|
|
|
+ // select command before its own window + burst). The generic interleaved
|
|
|
|
|
+ // path above already recovers the per-region windows and plane commands;
|
|
|
|
|
+ // three things it can't are filled here.
|
|
|
|
|
+ if (interleaved && per_plane) {
|
|
|
|
|
+ dual_region = true;
|
|
|
|
|
+ std::set<uint8_t> wops; // region-select + window-set opcodes
|
|
|
|
|
+ for (const auto &c : window0)
|
|
|
|
|
+ wops.insert(c.cmd);
|
|
|
|
|
+ for (const auto &c : window1)
|
|
|
|
|
+ wops.insert(c.cmd);
|
|
|
|
|
+
|
|
|
|
|
+ // (1) INIT. A dual-region panel runs its whole _InitDisplay during the
|
|
|
|
|
+ // earlier writeScreenBuffer clear, and that init REUSES the region-select
|
|
|
|
|
+ // opcode (select master/slave for each region's PSR), so the single-plane
|
|
|
|
|
+ // "cut at the first window opcode" rule would cut at the very first init
|
|
|
|
|
+ // command. Instead take init from the clear stage and drop only the
|
|
|
|
|
+ // CONTIGUOUS trailing run of window-set commands before the first clear
|
|
|
|
|
+ // burst (the both-/first-region window-sets); the select/PSR pairs earlier
|
|
|
|
|
+ // in init survive because a non-window command (BTST, power-on, ...)
|
|
|
|
|
+ // separates them from that trailing run.
|
|
|
|
|
+ const size_t cb = plane_index(init_stage); // first clear burst
|
|
|
|
|
+ size_t cut = cb;
|
|
|
|
|
+ while (cut > 0 && wops.count(init_stage[cut - 1].cmd))
|
|
|
|
|
+ cut--;
|
|
|
|
|
+ init_seq.assign(init_stage.begin(), init_stage.begin() + cut);
|
|
|
|
|
+
|
|
|
|
|
+ // (2) plane_width: each region addresses its own half. Decode it from the
|
|
|
|
|
+ // window-set command's payload - first two big-endian 16-bit fields are
|
|
|
|
|
+ // x and x_end, as every window encoder observed so far lays them out.
|
|
|
|
|
+ for (const auto &c : window0)
|
|
|
|
|
+ if (c.data.size() >= 4) {
|
|
|
|
|
+ int x = (c.data[0] << 8) | c.data[1];
|
|
|
|
|
+ int xe = (c.data[2] << 8) | c.data[3];
|
|
|
|
|
+ plane_width_val = xe - x + 1;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // (3) line doubling: region 0's burst length, compared in the emit stage
|
|
|
|
|
+ // against plane_width*height*bpp/8 to flag PAIRED_EDGES.
|
|
|
|
|
+ region0_burst = (uint32_t) image_stage[bursts[0]].data.size();
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
// Single plane: window = the image call's pre-burst block; init comes
|
|
// Single plane: window = the image call's pre-burst block; init comes
|
|
|
// from the writeScreenBuffer stage, cut at the first window-set command
|
|
// from the writeScreenBuffer stage, cut at the first window-set command
|
|
@@ -407,16 +454,32 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
if (!bursts.empty())
|
|
if (!bursts.empty())
|
|
|
for (size_t i = bursts.back() + 1; i < image_stage.size() && image_stage[i].data.size() < PLANE_BURST_MIN; i++)
|
|
for (size_t i = bursts.back() + 1; i < image_stage.size() && image_stage[i].data.size() < PLANE_BURST_MIN; i++)
|
|
|
refresh_seq.push_back(image_stage[i]);
|
|
refresh_seq.push_back(image_stage[i]);
|
|
|
|
|
+ // A shared-region panel re-sets its one window in refresh() and we drop that
|
|
|
|
|
+ // duplicate; a dual-region panel's refresh instead sets a DISTINCT both-region
|
|
|
|
|
+ // window (its own select payload), which is part of the refresh - keep it.
|
|
|
for (const auto &c : refresh_stage)
|
|
for (const auto &c : refresh_stage)
|
|
|
- if (!all_wops.count(c.cmd))
|
|
|
|
|
|
|
+ if (dual_region || !all_wops.count(c.cmd))
|
|
|
refresh_seq.push_back(c);
|
|
refresh_seq.push_back(c);
|
|
|
|
|
+ // The refresh ends at its trigger (the command we busy-wait on). A dual-region
|
|
|
|
|
+ // driver re-sets the window AFTER the trigger to prime its own next writeImage;
|
|
|
|
|
+ // our engine always sets its own window per cycle, so that trailing window is
|
|
|
|
|
+ // dead in replay - trim anything past the last busy-wait.
|
|
|
|
|
+ if (dual_region) {
|
|
|
|
|
+ size_t last_wait = refresh_seq.size();
|
|
|
|
|
+ for (size_t i = 0; i < refresh_seq.size(); i++)
|
|
|
|
|
+ if (refresh_seq[i].wait_busy)
|
|
|
|
|
+ last_wait = i;
|
|
|
|
|
+ if (last_wait < refresh_seq.size())
|
|
|
|
|
+ refresh_seq.resize(last_wait + 1);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// --- emit ---
|
|
// --- emit ---
|
|
|
printf("// GENERATED by emit_descriptor from %s - candidate.\n", class_name);
|
|
printf("// GENERATED by emit_descriptor from %s - candidate.\n", class_name);
|
|
|
printf("// Command sequences, plane layout and plane_invert are observed here;\n");
|
|
printf("// Command sequences, plane layout and plane_invert are observed here;\n");
|
|
|
printf("// for a packed panel bits_per_pixel/palette/blank_code are probe-filled\n");
|
|
printf("// for a packed panel bits_per_pixel/palette/blank_code are probe-filled\n");
|
|
|
- printf("// when the colour family is recognised (4-colour), else left TODO.\n");
|
|
|
|
|
- printf("// plane_width / line_order (dual-region) still come from the diag passes.\n");
|
|
|
|
|
|
|
+ printf("// when the colour family is recognised (4-colour), else left TODO. A\n");
|
|
|
|
|
+ printf("// dual-region panel also gets plane_width and a candidate line_order; the\n");
|
|
|
|
|
+ printf("// exact PAIRED_EDGES row order still wants diag/driver confirmation.\n");
|
|
|
printf("#pragma once\n#include \"epd_command.h\"\n\nnamespace esphome::gxepd2 {\n\n");
|
|
printf("#pragma once\n#include \"epd_command.h\"\n\nnamespace esphome::gxepd2 {\n\n");
|
|
|
|
|
|
|
|
auto emit_seq = [&](const char *seqsuffix, std::vector<Cmd> &cmds) {
|
|
auto emit_seq = [&](const char *seqsuffix, std::vector<Cmd> &cmds) {
|
|
@@ -478,8 +541,30 @@ void emit_descriptor_impl(const char *class_name, const char *macro, const Packe
|
|
|
} else {
|
|
} else {
|
|
|
printf(" .plane_invert = {false, false}, // unused: packed panel\n");
|
|
printf(" .plane_invert = {false, false}, // unused: packed panel\n");
|
|
|
}
|
|
}
|
|
|
- if (per_plane)
|
|
|
|
|
- printf(" .plane_width = 0, // TODO: diag pass (separate-region width)\n");
|
|
|
|
|
|
|
+ if (per_plane) {
|
|
|
|
|
+ if (dual_region && plane_width_val > 0)
|
|
|
|
|
+ printf(" .plane_width = %d, // decoded from the region window-set (x_end-x+1); sanity-check\n",
|
|
|
|
|
+ plane_width_val);
|
|
|
|
|
+ else
|
|
|
|
|
+ printf(" .plane_width = 0, // TODO: diag pass (separate-region width)\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ // line_order: a dual-region panel whose per-region burst is twice the region's
|
|
|
|
|
+ // own pixel bytes is sending two wire lines per row (GxEPD2 writeNative's
|
|
|
|
|
+ // doubled, both-edges-to-middle emission). The doubling is observable here;
|
|
|
|
|
+ // the exact row order that PAIRED_EDGES encodes is NOT (a uniform fill can't
|
|
|
|
|
+ // show which buffer row each line carries), so emit it as a candidate the
|
|
|
|
|
+ // diag native-path pass / driver source confirms.
|
|
|
|
|
+ if (dual_region && plane_width_val > 0 && depth_bpp > 0) {
|
|
|
|
|
+ uint32_t expect = (uint32_t) plane_width_val * Driver::HEIGHT * depth_bpp / 8;
|
|
|
|
|
+ if (expect && region0_burst == 2u * expect)
|
|
|
|
|
+ printf(" .line_order = LineOrder::PAIRED_EDGES, // region burst %u B = 2x %u B (2 lines/row); "
|
|
|
|
|
+ "confirm edges-to-middle order via the diag native-path pass\n",
|
|
|
|
|
+ region0_burst, expect);
|
|
|
|
|
+ else if (expect && region0_burst != expect)
|
|
|
|
|
+ printf(" // NOTE: region burst %u B vs expected %u B (1 line/row) - line order unclear, "
|
|
|
|
|
+ "check with the diag native-path pass\n",
|
|
|
|
|
+ region0_burst, expect);
|
|
|
|
|
+ }
|
|
|
if (pv.is_1bpp) {
|
|
if (pv.is_1bpp) {
|
|
|
// 1bpp plane model: depth confirmed by the probe; palette/blank_code unused.
|
|
// 1bpp plane model: depth confirmed by the probe; palette/blank_code unused.
|
|
|
printf(" .bits_per_pixel = 1, // probe: white fill => all-bits wire byte 0x%02X\n", pv.w0_white & 0xFF);
|
|
printf(" .bits_per_pixel = 1, // probe: white fill => all-bits wire byte 0x%02X\n", pv.w0_white & 0xFF);
|