|
|
@@ -23,6 +23,7 @@
|
|
|
|
|
|
#include <cstdint>
|
|
|
#include <cstdio>
|
|
|
+#include <cstdlib>
|
|
|
#include <set>
|
|
|
#include <vector>
|
|
|
|
|
|
@@ -158,43 +159,153 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
|
|
|
auto image_stage = group(s1, s2);
|
|
|
auto refresh_stage = group(s2, s3);
|
|
|
|
|
|
- const size_t ip = plane_index(init_stage);
|
|
|
- const size_t wp = plane_index(image_stage);
|
|
|
-
|
|
|
- // Window set = the commands the image call issues before its plane burst.
|
|
|
- std::vector<Cmd> window_seq(image_stage.begin(), image_stage.begin() + wp);
|
|
|
- std::set<uint8_t> window_ops;
|
|
|
- for (const auto &c : window_seq)
|
|
|
- window_ops.insert(c.cmd);
|
|
|
-
|
|
|
- // Init = init-stage commands before its plane burst, BUT cut at the first
|
|
|
- // window-set command: SSD16xx-style drivers set the RAM window inside
|
|
|
- // writeScreenBuffer too, and that belongs in window_sequence, not init.
|
|
|
- size_t init_end = ip;
|
|
|
- for (size_t i = 0; i < ip; i++)
|
|
|
- if (window_ops.count(init_stage[i].cmd)) {
|
|
|
- init_end = i;
|
|
|
- break;
|
|
|
+ auto cmds_equal = [](const std::vector<Cmd> &a, const std::vector<Cmd> &b) {
|
|
|
+ if (a.size() != b.size())
|
|
|
+ return false;
|
|
|
+ for (size_t i = 0; i < a.size(); i++)
|
|
|
+ if (a[i].cmd != b[i].cmd || a[i].data != b[i].data)
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Every pixel-plane burst in the image call. One => single-plane panel;
|
|
|
+ // N => N planes (two colour planes sharing a region, or two physical
|
|
|
+ // regions - told apart below by whether their window-sets match).
|
|
|
+ std::vector<size_t> bursts;
|
|
|
+ for (size_t i = 0; i < image_stage.size(); i++)
|
|
|
+ if (image_stage[i].data.size() >= PLANE_BURST_MIN)
|
|
|
+ bursts.push_back(i);
|
|
|
+ const size_t nplanes = bursts.empty() ? 1 : bursts.size();
|
|
|
+
|
|
|
+ if (getenv("EMIT_DEBUG")) {
|
|
|
+ fprintf(stderr, "image_stage %zu cmds; bursts:", image_stage.size());
|
|
|
+ for (auto b : bursts)
|
|
|
+ fprintf(stderr, " [%zu]=0x%02X", b, image_stage[b].cmd);
|
|
|
+ fprintf(stderr, "\n");
|
|
|
+ for (size_t i = 0; i < image_stage.size(); i++)
|
|
|
+ fprintf(stderr, " [%zu] 0x%02X data=%zu\n", i, image_stage[i].cmd, image_stage[i].data.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ // pre_block(k) = commands after burst k-1 (or the start) up to, but not
|
|
|
+ // including, burst k's own plane-write command - i.e. the window set for
|
|
|
+ // plane k, possibly with the lazily-run _InitDisplay in front for k==0.
|
|
|
+ auto pre_block = [&](size_t k) {
|
|
|
+ size_t lo = (k == 0) ? 0 : bursts[k - 1] + 1;
|
|
|
+ return std::vector<Cmd>(image_stage.begin() + lo, image_stage.begin() + bursts[k]);
|
|
|
+ };
|
|
|
+
|
|
|
+ std::vector<Cmd> init_seq, window0, window1;
|
|
|
+ std::vector<uint8_t> plane_cmds;
|
|
|
+ bool per_plane = false;
|
|
|
+
|
|
|
+ if (bursts.size() >= 2) {
|
|
|
+ // Multi-plane. Two observed layouts:
|
|
|
+ // interleaved: [init][W0][burst0][W1][burst1] - each plane's window
|
|
|
+ // sits between its burst and the previous one (dual region);
|
|
|
+ // batched: [init][W][W][burst0][burst1] - all windows precede all
|
|
|
+ // bursts (SSD1680 3-colour: writeImage sets the black and
|
|
|
+ // red RAM windows, then bursts both planes).
|
|
|
+ for (size_t k = 0; k < bursts.size(); k++)
|
|
|
+ plane_cmds.push_back(image_stage[bursts[k]].cmd);
|
|
|
+ const size_t n = bursts.size();
|
|
|
+ std::vector<Cmd> pre = pre_block(0);
|
|
|
+ std::vector<std::vector<Cmd>> windows;
|
|
|
+
|
|
|
+ bool interleaved = false;
|
|
|
+ for (size_t k = 1; k < n; k++)
|
|
|
+ if (bursts[k] - bursts[k - 1] > 1) {
|
|
|
+ interleaved = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (interleaved) {
|
|
|
+ std::vector<Cmd> gap0(image_stage.begin() + bursts[0] + 1, image_stage.begin() + bursts[1]);
|
|
|
+ const size_t wl = gap0.size();
|
|
|
+ if (wl <= pre.size()) {
|
|
|
+ windows.push_back(std::vector<Cmd>(pre.end() - wl, pre.end()));
|
|
|
+ init_seq.assign(pre.begin(), pre.end() - wl);
|
|
|
+ } else {
|
|
|
+ windows.push_back({});
|
|
|
+ init_seq = pre;
|
|
|
+ }
|
|
|
+ for (size_t k = 1; k < n; k++)
|
|
|
+ windows.push_back(std::vector<Cmd>(image_stage.begin() + bursts[k - 1] + 1, image_stage.begin() + bursts[k]));
|
|
|
+ } else {
|
|
|
+ // Batched: pre = init + n copies of the window. Peel the smallest block
|
|
|
+ // that repeats n times at the tail; the remainder is init.
|
|
|
+ size_t L = 0;
|
|
|
+ for (size_t cand = 1; cand * n <= pre.size(); cand++) {
|
|
|
+ bool ok = true;
|
|
|
+ for (size_t b = 0; b < n && ok; b++)
|
|
|
+ for (size_t j = 0; j < cand && ok; j++) {
|
|
|
+ const Cmd &a = pre[pre.size() - cand * n + b * cand + j];
|
|
|
+ const Cmd &c = pre[pre.size() - cand + j];
|
|
|
+ if (a.cmd != c.cmd || a.data != c.data)
|
|
|
+ ok = false;
|
|
|
+ }
|
|
|
+ if (ok) {
|
|
|
+ L = cand;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (L > 0) {
|
|
|
+ for (size_t b = 0; b < n; b++)
|
|
|
+ windows.push_back(std::vector<Cmd>(pre.end() - L * (n - b), pre.end() - L * (n - b - 1)));
|
|
|
+ init_seq.assign(pre.begin(), pre.end() - L * n);
|
|
|
+ } else {
|
|
|
+ windows.push_back({});
|
|
|
+ init_seq = pre;
|
|
|
+ }
|
|
|
}
|
|
|
- std::vector<Cmd> init_seq(init_stage.begin(), init_stage.begin() + init_end);
|
|
|
|
|
|
- const uint8_t plane_cmd = ip < init_stage.size() ? init_stage[ip].cmd : 0x00;
|
|
|
- const uint8_t clear_fill = (ip < init_stage.size() && !init_stage[ip].data.empty()) ? init_stage[ip].data[0] : 0x00;
|
|
|
+ window0 = windows.empty() ? std::vector<Cmd>{} : windows[0];
|
|
|
+ window1 = windows.size() > 1 ? windows[1] : std::vector<Cmd>{};
|
|
|
+ for (size_t k = 1; k < windows.size(); k++)
|
|
|
+ if (!cmds_equal(windows[0], windows[k]))
|
|
|
+ per_plane = true; // per-plane windows differ => separate regions
|
|
|
+ } else {
|
|
|
+ // Single plane: window = the image call's pre-burst block; init comes
|
|
|
+ // from the writeScreenBuffer stage, cut at the first window-set command
|
|
|
+ // (SSD16xx sets the window there too - that belongs in window_sequence).
|
|
|
+ const size_t wp = plane_index(image_stage);
|
|
|
+ window0.assign(image_stage.begin(), image_stage.begin() + wp);
|
|
|
+ std::set<uint8_t> wops;
|
|
|
+ for (const auto &c : window0)
|
|
|
+ wops.insert(c.cmd);
|
|
|
+ const size_t ip = plane_index(init_stage);
|
|
|
+ size_t cut = ip;
|
|
|
+ for (size_t i = 0; i < ip; i++)
|
|
|
+ if (wops.count(init_stage[i].cmd)) {
|
|
|
+ cut = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ init_seq.assign(init_stage.begin(), init_stage.begin() + cut);
|
|
|
+ plane_cmds.push_back(wp < image_stage.size() ? image_stage[wp].cmd : 0x00);
|
|
|
+ }
|
|
|
|
|
|
- // Refresh = post-write commands (issued after the image's plane burst but
|
|
|
- // before any next burst, e.g. UC8179's 0x92 "partial out" - they fire after
|
|
|
- // the byte burst and before the trigger, so they lead refresh), then the
|
|
|
- // refresh call itself minus any window-set it repeats.
|
|
|
+ // blank/clear fill byte, observed from the writeScreenBuffer clear burst.
|
|
|
+ const size_t cip = plane_index(init_stage);
|
|
|
+ const uint8_t clear_fill = (cip < init_stage.size() && !init_stage[cip].data.empty()) ? init_stage[cip].data[0] : 0x00;
|
|
|
+
|
|
|
+ // Refresh = post-write commands after the LAST burst (e.g. UC8179's 0x92),
|
|
|
+ // then the refresh call minus any window-set it repeats.
|
|
|
+ std::set<uint8_t> all_wops;
|
|
|
+ for (const auto &c : window0)
|
|
|
+ all_wops.insert(c.cmd);
|
|
|
+ for (const auto &c : window1)
|
|
|
+ all_wops.insert(c.cmd);
|
|
|
std::vector<Cmd> refresh_seq;
|
|
|
- for (size_t i = wp + 1; i < image_stage.size() && image_stage[i].data.size() < PLANE_BURST_MIN; i++)
|
|
|
- refresh_seq.push_back(image_stage[i]);
|
|
|
+ if (!bursts.empty())
|
|
|
+ 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]);
|
|
|
for (const auto &c : refresh_stage)
|
|
|
- if (!window_ops.count(c.cmd))
|
|
|
+ if (!all_wops.count(c.cmd))
|
|
|
refresh_seq.push_back(c);
|
|
|
|
|
|
// --- emit ---
|
|
|
printf("// GENERATED by emit_descriptor from %s - candidate, segmentation only.\n", class_name);
|
|
|
- printf("// bits_per_pixel/palette/plane_invert still come from the diag passes.\n");
|
|
|
+ printf("// bits_per_pixel/palette/plane_invert/plane_width/row_interleave still\n");
|
|
|
+ printf("// come from the diag passes.\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) {
|
|
|
@@ -214,7 +325,9 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
|
|
|
};
|
|
|
|
|
|
emit_seq("INIT", init_seq);
|
|
|
- emit_seq("WINDOW_SEQ", window_seq);
|
|
|
+ emit_seq("WINDOW_SEQ0", window0);
|
|
|
+ if (per_plane)
|
|
|
+ emit_seq("WINDOW_SEQ1", window1);
|
|
|
emit_seq("REFRESH", refresh_seq);
|
|
|
|
|
|
printf("inline const EpdPanelDescriptor %s = {\n", macro);
|
|
|
@@ -224,14 +337,25 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
|
|
|
printf(" .busy_timeout_ms = %u,\n", (unsigned) Driver::full_refresh_time + 5000u);
|
|
|
printf(" .init_sequence = %s_INIT,\n", macro);
|
|
|
printf(" .init_sequence_len = sizeof(%s_INIT) / sizeof(%s_INIT[0]),\n", macro, macro);
|
|
|
- printf(" .window_sequence = {%s_WINDOW_SEQ, nullptr},\n", macro);
|
|
|
- printf(" .window_sequence_len = {sizeof(%s_WINDOW_SEQ) / sizeof(%s_WINDOW_SEQ[0]), 0},\n", macro, macro);
|
|
|
- printf(" .plane_count = 1,\n");
|
|
|
- printf(" .plane_write_cmd = {0x%02X, 0x00},\n", plane_cmd);
|
|
|
+ if (per_plane) {
|
|
|
+ printf(" .window_sequence = {%s_WINDOW_SEQ0, %s_WINDOW_SEQ1},\n", macro, macro);
|
|
|
+ printf(" .window_sequence_len = {sizeof(%s_WINDOW_SEQ0) / sizeof(%s_WINDOW_SEQ0[0]), "
|
|
|
+ "sizeof(%s_WINDOW_SEQ1) / sizeof(%s_WINDOW_SEQ1[0])},\n",
|
|
|
+ macro, macro, macro, macro);
|
|
|
+ printf(" .window_per_plane = true, // per-plane windows differ => separate regions\n");
|
|
|
+ } else {
|
|
|
+ printf(" .window_sequence = {%s_WINDOW_SEQ0, nullptr},\n", macro);
|
|
|
+ printf(" .window_sequence_len = {sizeof(%s_WINDOW_SEQ0) / sizeof(%s_WINDOW_SEQ0[0]), 0},\n", macro, macro);
|
|
|
+ }
|
|
|
+ printf(" .plane_count = %zu,\n", nplanes);
|
|
|
+ printf(" .plane_write_cmd = {0x%02X, 0x%02X},\n", plane_cmds.empty() ? 0x00 : plane_cmds[0],
|
|
|
+ plane_cmds.size() > 1 ? plane_cmds[1] : 0x00);
|
|
|
printf(" .plane_invert = {false, false}, // TODO: diag pass\n");
|
|
|
printf(" .bits_per_pixel = 1, // TODO: diag pass (clear fill byte observed: 0x%02X)\n", clear_fill);
|
|
|
printf(" .palette = nullptr, // TODO: diag pass\n");
|
|
|
printf(" .palette_len = 0,\n");
|
|
|
+ if (per_plane)
|
|
|
+ printf(" .plane_width = 0, // TODO: diag pass (separate-region width)\n");
|
|
|
printf(" .blank_code = 0x00, // TODO: derive from clear fill 0x%02X\n", clear_fill);
|
|
|
printf(" .refresh_sequence = %s_REFRESH,\n", macro);
|
|
|
printf(" .refresh_sequence_len = sizeof(%s_REFRESH) / sizeof(%s_REFRESH[0]),\n", macro, macro);
|