فهرست منبع

Add row_interleave: send rows in top/bottom-pair order, not sequential

New EpdPanelDescriptor flag; CycleEngine maps each wire-order byte
position through interleaved_index_() to the actual buffer offset when
set, everything else about step_write_plane_ (BYTE_BUDGET chunking,
invert, per-plane windows) unchanged.

Enabled for both GDEY0579F51 descriptors. A bar/ladder/diagonal test
pattern on the working (master) half came out vertically scrambled with
a broken/dashed diagonal - the documented signature of exactly this bug
class - sent in plain sequential order, matching Waveshare's own
epd5in79g.cpp reference driver which sends row j then row (H-1-j)
alternately. Not enabled for GDEY029F51 (single-chip, no master/slave
split) without the same kind of hardware evidence.
ZiB 1 ماه پیش
والد
کامیت
16f3505e91

+ 18 - 1
components/gxepd2/cycle_engine.h

@@ -59,6 +59,7 @@ class CycleEngine {
     uint32_t w = this->desc_->window_per_plane ? this->desc_->plane_width : this->desc_->width;
     this->plane_bytes_ = this->desc_->packed_2bpp_plane ? (w * this->desc_->height) / 4
                                                          : (w * this->desc_->height) / 8;
+    this->bytes_per_row_ = this->plane_bytes_ / this->desc_->height;
     this->cursor_ = 0;
     this->plane_ = 0;
     this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
@@ -271,7 +272,8 @@ class CycleEngine {
     if (end > this->plane_bytes_)
       end = this->plane_bytes_;
     for (; this->cursor_ < end; this->cursor_++) {
-      uint8_t b = buf != nullptr ? buf[this->cursor_] : 0xFF;
+      uint32_t src = this->desc_->row_interleave ? this->interleaved_index_(this->cursor_) : this->cursor_;
+      uint8_t b = buf != nullptr ? buf[src] : 0xFF;
       this->write_byte_(invert ? static_cast<uint8_t>(~b) : b, true);
     }
     if (this->cursor_ >= this->plane_bytes_) {
@@ -294,6 +296,20 @@ class CycleEngine {
     }
   }
 
+  // Maps a wire-transmission-order byte position to the buffer's actual
+  // (plain top-to-bottom) byte index, for panels whose controller expects
+  // rows in interleaved top/bottom pairs (row 0, row H-1, row 1, row H-2,
+  // ...) instead of sequential order - see EpdPanelDescriptor::row_interleave.
+  // wire_pos counts up linearly (0..plane_bytes_-1) the same way it always
+  // has; only which buffer byte it reads changes.
+  uint32_t interleaved_index_(uint32_t wire_pos) const {
+    uint32_t wire_row = wire_pos / this->bytes_per_row_;
+    uint32_t byte_in_row = wire_pos % this->bytes_per_row_;
+    uint32_t pair = wire_row / 2;
+    uint32_t buf_row = (wire_row % 2 == 0) ? pair : (this->desc_->height - 1 - pair);
+    return buf_row * this->bytes_per_row_ + byte_in_row;
+  }
+
   void start_delay_(uint16_t delay_ms, State return_to) {
     this->deadline_ = millis() + delay_ms;
     this->delay_return_state_ = return_to;
@@ -378,6 +394,7 @@ class CycleEngine {
   uint8_t plane_{0};
   const uint8_t *planes_[2]{nullptr, nullptr};
   uint32_t plane_bytes_{0};
+  uint32_t bytes_per_row_{0};
   uint32_t deadline_{0};
   State delay_return_state_{State::DONE};
   uint32_t busy_wait_deadline_{0};

+ 12 - 0
components/gxepd2/epd_command.h

@@ -73,6 +73,18 @@ struct EpdPanelDescriptor {
   // own, not the full 792x272. Unused otherwise.
   uint16_t plane_width{0};
 
+  // true if rows must be sent in interleaved top/bottom-pair order (row 0,
+  // row H-1, row 1, row H-2, ...) instead of plain sequential top-to-
+  // bottom - GDEY0579F51's HX8717 pair apparently scans each half from
+  // both edges toward the middle, per Waveshare's own epd5in79g.cpp
+  // reference driver (confirmed 2026-09-19: a bar/ladder/diagonal test
+  // pattern came out vertically scrambled with a broken diagonal - the
+  // documented signature of a row-reordering bug - sent in plain
+  // sequential order). Not assumed for any other packed_2bpp_plane panel
+  // (e.g. GDEY029F51, a single-chip panel with no master/slave split at
+  // all) without the same kind of hardware confirmation.
+  bool row_interleave{false};
+
   // true for the JD79667/HX8717-family 4-color panels (GDEY029F51,
   // GDEY0579F51): pixels are 2 bits each, 4 per byte, built from a
   // white/black/yellow/red classification at draw time - a fundamentally

+ 1 - 0
components/gxepd2/panel_579c_gdey0579f51.h

@@ -128,6 +128,7 @@ inline const EpdPanelDescriptor PANEL_579C_GDEY0579F51 = {
     .plane_write_cmd = {0x10, 0x10},
     .plane_invert = {false, false},
     .plane_width = 396,  // each region's own local width
+    .row_interleave = true,
     .packed_2bpp_plane = true,
     .refresh_sequence = PANEL_579C_GDEY0579F51_REFRESH,
     .refresh_sequence_len = sizeof(PANEL_579C_GDEY0579F51_REFRESH) / sizeof(PANEL_579C_GDEY0579F51_REFRESH[0]),

+ 1 - 0
components/gxepd2/panel_579c_gdey0579f51_master_only.h

@@ -115,6 +115,7 @@ inline const EpdPanelDescriptor PANEL_579C_GDEY0579F51_MASTER_ONLY = {
     .plane_count = 1,
     .plane_write_cmd = {0x10, 0x00},
     .plane_invert = {false, false},
+    .row_interleave = true,
     .packed_2bpp_plane = true,
     .refresh_sequence = PANEL_579C_GDEY0579F51_MO_REFRESH,
     .refresh_sequence_len = sizeof(PANEL_579C_GDEY0579F51_MO_REFRESH) / sizeof(PANEL_579C_GDEY0579F51_MO_REFRESH[0]),