|
@@ -1,33 +1,20 @@
|
|
|
#pragma once
|
|
#pragma once
|
|
|
|
|
|
|
|
-// Hand-derived from vendor/src/epd3c/GxEPD2_213_Z98c.{h,cpp} (GDEY0213Z98,
|
|
|
|
|
-// SSD1680 controller, 128x250 RAM window (122 columns visible), black +
|
|
|
|
|
-// red). This is the panel actually on the bench for this first slice of the
|
|
|
|
|
-// native chunked engine; see the plan this was implemented from (and
|
|
|
|
|
-// gxepd2_task_watchdog_status in project memory) for why GxEPD2's own
|
|
|
|
|
-// control flow is being dropped in favor of just its per-panel data.
|
|
|
|
|
|
|
+// GDEY0213Z98, SSD1680 controller, 128x250 RAM window (122 columns visible),
|
|
|
|
|
+// black + red. Derived from vendor/src/epd3c/GxEPD2_213_Z98c.{h,cpp}.
|
|
|
|
|
|
|
|
#include "epd_command.h"
|
|
#include "epd_command.h"
|
|
|
|
|
|
|
|
namespace esphome::gxepd2 {
|
|
namespace esphome::gxepd2 {
|
|
|
|
|
|
|
|
-// GxEPD2_213_Z98c::_InitDisplay(). Unlike GxEPD2_154_Z90c (the panel this
|
|
|
|
|
-// slice originally targeted), this controller's SWRESET is bracketed by
|
|
|
|
|
-// fixed 10ms delays rather than a busy-pin wait, and it has an extra
|
|
|
|
|
-// "Display update control" command (0x21) 154_Z90c doesn't. Minus the
|
|
|
|
|
-// trailing _setPartialRamArea() - CycleEngine's SET_WINDOW state (used for
|
|
|
|
|
-// every write, not just init) covers that.
|
|
|
|
|
|
|
+// _InitDisplay(): SWRESET (0x12) bracketed by fixed 10ms delays (not a
|
|
|
|
|
+// busy-pin wait), driver output control, data entry mode, border waveform,
|
|
|
|
|
+// temp sensor, and display update control (0x21). The trailing
|
|
|
|
|
+// _setPartialRamArea() is dropped - CycleEngine's SET_WINDOW covers it.
|
|
|
inline const uint8_t PANEL_213_Z98C_DRIVER_OUTPUT_CONTROL[] = {0xF9, 0x00, 0x00};
|
|
inline const uint8_t PANEL_213_Z98C_DRIVER_OUTPUT_CONTROL[] = {0xF9, 0x00, 0x00};
|
|
|
inline const uint8_t PANEL_213_Z98C_DATA_ENTRY_MODE[] = {0x03};
|
|
inline const uint8_t PANEL_213_Z98C_DATA_ENTRY_MODE[] = {0x03};
|
|
|
inline const uint8_t PANEL_213_Z98C_BORDER_WAVEFORM[] = {0x05};
|
|
inline const uint8_t PANEL_213_Z98C_BORDER_WAVEFORM[] = {0x05};
|
|
|
inline const uint8_t PANEL_213_Z98C_TEMP_SENSOR[] = {0x80};
|
|
inline const uint8_t PANEL_213_Z98C_TEMP_SENSOR[] = {0x80};
|
|
|
-// Stock GxEPD2 vendor value. Was swapped to {0x80, 0x80} (matching the
|
|
|
|
|
-// epaper_spi reference) while chasing the solid-black/solid-red hardware
|
|
|
|
|
-// symptom - that turned out to be an unrelated bug (display.py never
|
|
|
|
|
-// wired the configured lambda to set_writer() at all, so nothing but
|
|
|
|
|
-// auto_clear's default fill ever rendered). Rolled back now that the real
|
|
|
|
|
-// cause is fixed, to test whether the stock GxEPD2 protocol works as-is
|
|
|
|
|
-// on this panel or genuinely needs epaper_spi's deviations.
|
|
|
|
|
inline const uint8_t PANEL_213_Z98C_DISPLAY_UPDATE_CONTROL[] = {0x00, 0x80};
|
|
inline const uint8_t PANEL_213_Z98C_DISPLAY_UPDATE_CONTROL[] = {0x00, 0x80};
|
|
|
|
|
|
|
|
inline const EpdCommand PANEL_213_Z98C_INIT[] = {
|
|
inline const EpdCommand PANEL_213_Z98C_INIT[] = {
|
|
@@ -39,13 +26,8 @@ inline const EpdCommand PANEL_213_Z98C_INIT[] = {
|
|
|
{0x21, PANEL_213_Z98C_DISPLAY_UPDATE_CONTROL, 2},
|
|
{0x21, PANEL_213_Z98C_DISPLAY_UPDATE_CONTROL, 2},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// GxEPD2_213_Z98c::_Update_Full()/_Update_Part() - stock vendor sequence,
|
|
|
|
|
-// a single combined trigger that assumes it handles power internally.
|
|
|
|
|
-// Bracketed this with explicit power-on/off (matching epaper_spi) while
|
|
|
|
|
-// chasing the solid-black/solid-red symptom, which turned out to be
|
|
|
|
|
-// unrelated (see PANEL_213_Z98C_DISPLAY_UPDATE_CONTROL's comment). Rolled
|
|
|
|
|
-// back to the stock single-trigger sequence to test it now that the real
|
|
|
|
|
-// bug is fixed.
|
|
|
|
|
|
|
+// _Update_Full(): a single combined trigger (0x22 update control + 0x20) that
|
|
|
|
|
+// handles power internally.
|
|
|
inline const uint8_t PANEL_213_Z98C_UPDATE_MODE[] = {0xF7};
|
|
inline const uint8_t PANEL_213_Z98C_UPDATE_MODE[] = {0xF7};
|
|
|
|
|
|
|
|
inline const EpdCommand PANEL_213_Z98C_REFRESH[] = {
|
|
inline const EpdCommand PANEL_213_Z98C_REFRESH[] = {
|
|
@@ -53,12 +35,8 @@ inline const EpdCommand PANEL_213_Z98C_REFRESH[] = {
|
|
|
{0x20, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true},
|
|
{0x20, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// GxEPD2_EPD::_setPartialRamArea(0, 0, WIDTH, HEIGHT) - SSD1680's
|
|
|
|
|
-// byte-column addressing (0x44's operands are RAM byte columns, not raw
|
|
|
|
|
-// pixels), no Y-reversal. Was hardcoded into CycleEngine as the assumed-
|
|
|
|
|
-// universal window layout until GxEPD2_426_GDEQ0426T82/GxEPD2_583_GDEQ0583T31
|
|
|
|
|
-// showed two other controllers do this differently - moved here as plain
|
|
|
|
|
-// descriptor data, computed for this panel's fixed 128x250 RAM window.
|
|
|
|
|
|
|
+// _setPartialRamArea(0, 0, WIDTH, HEIGHT): SSD1680 byte-column X addressing
|
|
|
|
|
+// (0x44's operands are RAM byte columns, not raw pixels), no Y-reversal.
|
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_X[] = {0x00, 0x0F}; // xe/8 = 127/8
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_X[] = {0x00, 0x0F}; // xe/8 = 127/8
|
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_Y[] = {0x00, 0x00, 0xF9, 0x00}; // ye = 249
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_Y[] = {0x00, 0x00, 0xF9, 0x00}; // ye = 249
|
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_XC[] = {0x00};
|
|
inline const uint8_t PANEL_213_Z98C_WINDOW_XC[] = {0x00};
|
|
@@ -71,31 +49,23 @@ inline const EpdCommand PANEL_213_Z98C_WINDOW[] = {
|
|
|
{0x4F, PANEL_213_Z98C_WINDOW_YC, 2},
|
|
{0x4F, PANEL_213_Z98C_WINDOW_YC, 2},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// full_refresh_time/partial_refresh_time from the vendor header are both
|
|
|
|
|
-// 15000ms; used as the ceiling for every busy wait in this slice, not just
|
|
|
|
|
-// the refresh one it was measured for - simpler than carrying a separate
|
|
|
|
|
-// timeout per wait site, and erring long only delays reporting a genuine
|
|
|
|
|
-// timeout, it doesn't cause one.
|
|
|
|
|
|
|
+// Vendor full_refresh_time is 15000ms; used as the ceiling for every busy
|
|
|
|
|
+// wait (erring long only delays reporting a genuine timeout).
|
|
|
inline const EpdPanelDescriptor PANEL_213_Z98C = {
|
|
inline const EpdPanelDescriptor PANEL_213_Z98C = {
|
|
|
- .width = 128, // RAM window width - _setPartialRamArea works in this, not WIDTH_VISIBLE (122)
|
|
|
|
|
|
|
+ .width = 128, // RAM window width (addressing works in this, not WIDTH_VISIBLE=122)
|
|
|
.height = 250,
|
|
.height = 250,
|
|
|
.busy_active_level = true, // GxEPD2_EPD(..., /*busy_level=*/HIGH, ...)
|
|
.busy_active_level = true, // GxEPD2_EPD(..., /*busy_level=*/HIGH, ...)
|
|
|
- .busy_timeout_ms = 60000, // temporarily generous - diagnosing a busy-never-clears hang, see commit message
|
|
|
|
|
|
|
+ .busy_timeout_ms = 30000,
|
|
|
.init_sequence = PANEL_213_Z98C_INIT,
|
|
.init_sequence = PANEL_213_Z98C_INIT,
|
|
|
.init_sequence_len = sizeof(PANEL_213_Z98C_INIT) / sizeof(PANEL_213_Z98C_INIT[0]),
|
|
.init_sequence_len = sizeof(PANEL_213_Z98C_INIT) / sizeof(PANEL_213_Z98C_INIT[0]),
|
|
|
.window_sequence = {PANEL_213_Z98C_WINDOW, nullptr},
|
|
.window_sequence = {PANEL_213_Z98C_WINDOW, nullptr},
|
|
|
.window_sequence_len = {sizeof(PANEL_213_Z98C_WINDOW) / sizeof(PANEL_213_Z98C_WINDOW[0]), 0},
|
|
.window_sequence_len = {sizeof(PANEL_213_Z98C_WINDOW) / sizeof(PANEL_213_Z98C_WINDOW[0]), 0},
|
|
|
.plane_count = 2,
|
|
.plane_count = 2,
|
|
|
.plane_write_cmd = {0x24, 0x26},
|
|
.plane_write_cmd = {0x24, 0x26},
|
|
|
- // Stock GxEPD2 wire convention for the color plane, matched to
|
|
|
|
|
- // DISPLAY_UPDATE_CONTROL={0x00, 0x80} above: vendor's writeImage()
|
|
|
|
|
- // does _writeData(~data) where data's own "blank" default is 0xFF, so
|
|
|
|
|
- // wire 0x00 = blank/not-red there - the opposite of epaper_spi's
|
|
|
|
|
- // {0x80, 0x80} + inverted-buffer pairing. These two are coupled, not
|
|
|
|
|
- // independent facts - confirmed by testing {0x00,0x80} register with
|
|
|
|
|
- // invert=true still on: it cross-inverted (background/black text came
|
|
|
|
|
- // out red, drawn-red text came out white). invert=false here matches
|
|
|
|
|
- // this register value's actual polarity instead.
|
|
|
|
|
|
|
+ // Color-plane wire convention: vendor writeImage() does _writeData(~data)
|
|
|
|
|
+ // with a 0xFF "blank" default, so wire 0x00 = not-red; our buffer's 1=red
|
|
|
|
|
+ // bit matches that, so no inversion. (DISPLAY_UPDATE_CONTROL={0x00,0x80}
|
|
|
|
|
+ // above sets the matching plane polarity.) Bench-confirmed.
|
|
|
.plane_invert = {false, false},
|
|
.plane_invert = {false, false},
|
|
|
.refresh_sequence = PANEL_213_Z98C_REFRESH,
|
|
.refresh_sequence = PANEL_213_Z98C_REFRESH,
|
|
|
.refresh_sequence_len = sizeof(PANEL_213_Z98C_REFRESH) / sizeof(PANEL_213_Z98C_REFRESH[0]),
|
|
.refresh_sequence_len = sizeof(PANEL_213_Z98C_REFRESH) / sizeof(PANEL_213_Z98C_REFRESH[0]),
|