From c2ce3337c325cf615999f8305aaf6665ea74d27d Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 29 May 2026 14:52:11 +0800 Subject: [PATCH 1/4] Add Tab5 ST7121 display support --- src/M5GFX.cpp | 56 ++++++- .../v1/platforms/esp32p4/Panel_ST7121.hpp | 150 ++++++++++++++++++ 2 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index ea36616..39d715b 100644 --- a/src/M5GFX.cpp +++ b/src/M5GFX.cpp @@ -31,6 +31,7 @@ #include "lgfx/v1/platforms/esp32p4/Bus_DSI.hpp" #include "lgfx/v1/platforms/esp32p4/Panel_ILI9881C.hpp" +#include "lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp" #include "lgfx/v1/platforms/esp32p4/Panel_ST7123.hpp" #include "lgfx/v1/platforms/esp32p4/Touch_ST7123.hpp" @@ -2571,6 +2572,8 @@ The usage of each pin is as follows. i2c_write_register8_array(in_i2c_port, pi4io2_i2c_addr, reg_data_io2, 100000); lgfx::delay(10); i2c_write_register8_array(in_i2c_port, pi4io1_i2c_addr, reg_data_io1_2, 100000); + lgfx::pinMode(GPIO_NUM_23, lgfx::pin_mode_t::input); // TP INT + lgfx::delay(100); #if !CONFIG_SPIRAM ESP_LOGE(LIBRARY_NAME, "M5Tab5 need PSRAM enabled"); @@ -2585,26 +2588,49 @@ The usage of each pin is as follows. #endif { + bool hit_st7121 = false; + bool hit_st7123 = false; + bool read_st_touch_fw = false; + for (int i = 0; i < 3; ++i) { + uint8_t fw_version = 0; + uint8_t fw_reg[2] = { 0, 0 }; + if (lgfx::i2c::transactionWriteRead(in_i2c_port, Touch_ST7123::default_addr, fw_reg, sizeof(fw_reg), &fw_version, 1, 100000).has_value()) { + read_st_touch_fw = true; + ESP_LOGI(LIBRARY_NAME, "M5Tab5 ST touch FW version %02x", fw_version); + if (fw_version == 1) { + hit_st7121 = true; + break; + } + if (fw_version == 3) { + hit_st7123 = true; + break; + } + ESP_LOGW(LIBRARY_NAME, "M5Tab5 unknown ST touch FW version %02x", fw_version); + } + lgfx::delay(10); + } + if (!read_st_touch_fw) { + ESP_LOGW(LIBRARY_NAME, "M5Tab5 ST touch FW version read failed"); + } + auto bus_dsi = new Bus_DSI(); _bus_last.reset(bus_dsi); auto bus_cfg = bus_dsi->config(); bus_cfg.bus_id = 0; bus_cfg.lane_num = 2; - bus_cfg.lane_mbps = 1040; + bus_cfg.lane_mbps = hit_st7121 ? 900 : 1040; bus_cfg.ldo_chan_id = 3; bus_cfg.ldo_voltage_mv = 2500; bus_dsi->config(bus_cfg); if (bus_dsi->init()) { bool hit_ili9881 = false; - bool hit_st7123 = false; lgfx::delay(80); - for (int i = 0; i < 3; ++i) { + for (int i = 0; !hit_st7121 && !hit_st7123 && !hit_ili9881 && i < 3; ++i) { uint8_t id[3] = { 0, }; bus_dsi->readParams( 0xF4, id, 2 ); ESP_LOGD(LIBRARY_NAME, "ST ID %02x %02x", id[0], id[1]); if (id[0] == 0x71 && id[1] == 0x23) { - hit_st7123 = true; - break; + ESP_LOGI(LIBRARY_NAME, "M5Tab5 ST DSI ID matched 71 23"); } static constexpr uint8_t params_page1[] = { 0x98, 0x81, 0x01 }; bus_dsi->writeParams( 0xFF, params_page1, 3); @@ -2632,7 +2658,23 @@ The usage of each pin is as follows. det.vsync_pulse_width = 4; det.vsync_front_porch = 20; p->config_detail(det); + } else if (hit_st7121) { + ESP_LOGI(LIBRARY_NAME, "M5Tab5 detected ST7121 display"); + _touch_last.reset(new Touch_ST7123()); + auto p = new Panel_ST7121(); + _panel_last.reset(p); + auto det = p->config_detail(); + + det.dpi_freq_mhz = 70; + det.hsync_back_porch = 40; + det.hsync_pulse_width = 2; + det.hsync_front_porch = 40; + det.vsync_back_porch = 24; + det.vsync_pulse_width = 20; + det.vsync_front_porch = 200; + p->config_detail(det); } else if (hit_st7123) { + ESP_LOGI(LIBRARY_NAME, "M5Tab5 detected ST7123 display"); _touch_last.reset(new Touch_ST7123()); auto p = new Panel_ST7123(); _panel_last.reset(p); @@ -2649,6 +2691,10 @@ The usage of each pin is as follows. det.vsync_front_porch = 220; p->config_detail(det); } + if (_panel_last == nullptr) { + ESP_LOGE(LIBRARY_NAME, "M5Tab5 display panel was not detected"); + goto init_clear; + } { auto p = _panel_last.get(); auto cfg = p->config(); diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp b/src/lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp new file mode 100644 index 0000000..ca409d8 --- /dev/null +++ b/src/lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp @@ -0,0 +1,150 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include "Panel_DSI.hpp" +#if SOC_MIPI_DSI_SUPPORTED + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + struct Panel_ST7121 : public Panel_DSI + { + public: + protected: + + const uint8_t* getInitParams(size_t listno) const override + { + static constexpr uint8_t list0[] = + {//len(cmd+params), cmd, params + 4, 0x60, 0x71, 0x21, 0xA2, + 4, 0x60, 0x71, 0x21, 0xA3, + 4, 0x60, 0x71, 0x21, 0xA4, + 2, 0x78, 0x21, + 2, 0x79, 0xEF, + 2, 0xA4, 0x31, + 7, 0xB7, 0x00, 0x00, 0x5F, 0x5F, 0x44, 0x1A, + 8, 0xB0, 0x22, 0x6B, 0x11, 0x89, 0x25, 0x43, 0x43, + 3, 0xBF, 0xA7, 0xA7, + 3, 0xA5, 0xF0, 0x03, + 7, 0xD7, 0x10, 0x2C, 0x14, 0x2A, 0x80, 0x80, + 8, 0x90, 0x71, 0x23, 0x5A, 0x20, 0x24, 0x11, 0x21, + + 40, 0xA3, 0x80, 0x01, 0x8C, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x10, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, + 0x1E, 0x80, 0x10, 0xEF, 0x58, 0x00, 0x00, 0x00, 0xFF, + + 56, 0xA6, 0x0A, 0x00, 0x24, 0x71, 0x36, 0x00, 0x00, 0x00, 0x68, 0x68, + 0x91, 0xFF, 0x00, 0x24, 0x71, 0x37, 0x00, 0x00, 0x00, 0x68, + 0x68, 0x91, 0xFF, 0x00, 0x24, 0x71, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x68, 0x91, 0xFF, 0x00, 0x2C, 0x71, 0x00, 0x01, 0x00, + 0x00, 0x68, 0x68, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, + 0x06, 0x00, 0x00, 0x00, 0x00, + + 61, 0xA7, 0x1A, 0x1A, 0xC0, 0x64, 0x40, 0x04, 0x15, 0x40, 0x00, 0x40, + 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x26, + 0x37, 0x40, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, + 0x80, 0x64, 0x40, 0x8C, 0x9D, 0x40, 0x00, 0x00, 0x00, 0x68, + 0x68, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0xAE, 0xBF, 0x00, + 0x00, 0x20, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x79, + + 45, 0xAC, 0x1D, 0x18, 0x19, 0x1D, 0x18, 0x19, 0x04, 0x1C, 0x1D, 0x08, + 0x0A, 0x10, 0x12, 0x0C, 0x0E, 0x14, 0x16, 0x00, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x18, 0x19, 0x1D, 0x18, 0x19, 0x06, 0x1C, + 0x1D, 0x09, 0x0B, 0x11, 0x13, 0x0D, 0x0F, 0x15, 0x17, 0x02, + 0x1D, 0x1D, 0x1D, 0x1D, + + 26, 0xAD, 0x0C, 0x40, 0x46, 0x00, 0x07, 0x4B, 0x4B, 0xFF, 0xFF, 0xF0, + 0x40, 0x0E, 0x01, 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + + 8, 0xAE, 0xF0, 0xFF, 0x03, 0xF0, 0xFF, 0x03, 0x00, + 18, 0xB2, 0x15, 0x19, 0x05, 0x23, 0x49, 0x2D, 0x03, 0x2E, 0x5C, 0xD2, + 0xFF, 0x10, 0x60, 0xFD, 0x20, 0xC0, 0x00, + 15, 0xE8, 0x20, 0x60, 0x04, 0x8E, 0x8E, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, + 0x06, 0xFA, 0x26, 0x3E, + 3, 0x75, 0x03, 0x04, + + 43, 0xE7, 0x4B, 0x00, 0x00, 0xBE, 0x4B, 0x8C, 0x20, 0x1A, 0xF0, 0x7D, + 0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D, 0x14, 0xFF, 0x00, 0x32, + 0x30, 0x73, 0x00, 0x00, 0xC8, 0x6A, 0xFF, 0x5A, 0x64, 0x38, + 0x88, 0x15, 0xB1, 0x01, 0x01, 0x64, 0x01, 0x01, 0x7C, 0xFF, + 0x1A, 0x51, + + 3, 0xE1, 0x0C, 0x0C, + 4, 0xEA, 0x15, 0x00, 0x01, + 38, 0xC8, 0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1F, 0x01, 0x39, 0x3E, + 0x00, 0x78, 0x06, 0xE2, 0x02, 0x11, 0x33, 0x01, 0x7A, 0x0D, + 0x21, 0xC4, 0x0B, 0x19, 0x08, 0x32, 0xA0, 0x08, 0x1A, 0x0A, + 0xF3, 0x7F, 0x0E, 0xC5, 0xE8, 0x03, 0xFF, + 38, 0xC9, 0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1F, 0x01, 0x39, 0x3E, + 0x00, 0x78, 0x06, 0xE2, 0x02, 0x11, 0x33, 0x01, 0x7A, 0x0D, + 0x21, 0xC4, 0x0B, 0x19, 0x08, 0x32, 0xA0, 0x08, 0x1A, 0x0A, + 0xF3, 0x7F, 0x0E, 0xC5, 0xE8, 0x03, 0xFF, + 4, 0x60, 0x71, 0x21, 0x00, + 0, // end + }; + + static constexpr uint8_t list1[] = + {//len(cmd+params), cmd, params + 1, CMD_SLPOUT, + 0, // end + }; + + static constexpr uint8_t list2[] = + {//len(cmd+params), cmd, params + 1, CMD_DISPON, + 0, // end + }; + + static constexpr uint8_t list3[] = + {//len(cmd+params), cmd, params + 2, 0x35, 0x00, + 0, // end + }; + + switch (listno) + { + case 0: return list0; + case 1: return list1; + case 2: return list2; + case 3: return list3; + default: return nullptr; + } + } + + size_t getInitDelay(size_t listno) const override + { + switch (listno) + { + case 1: return 80; // after SLPOUT + case 2: return 800; // after DISPON + default: return 0; + } + } + }; + +//---------------------------------------------------------------------------- + } +} + +#endif From 8941e1c2c8295f39db86e2e98875913d6ccb056f Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 4 Jun 2026 15:34:21 +0900 Subject: [PATCH 2/4] tweak for PaperMono GDEQ0426T82 --- src/M5GFX.cpp | 26 +- src/lgfx/v1/panel/Panel_SSD1677.cpp | 959 ++++++++++++---------------- src/lgfx/v1/panel/Panel_SSD1677.hpp | 91 +-- 3 files changed, 469 insertions(+), 607 deletions(-) diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index 39d715b..e027f08 100644 --- a/src/M5GFX.cpp +++ b/src/M5GFX.cpp @@ -1786,7 +1786,7 @@ namespace m5gfx bus_cfg.spi_3wire = true; bus_cfg.spi_host = SPI2_HOST; - bus_cfg.freq_write = 20000000; + bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 10000000; bus_spi->config(bus_cfg); bus_spi->init(); @@ -1798,11 +1798,11 @@ namespace m5gfx cfg.pin_cs = GPIO_NUM_16; cfg.pin_rst = GPIO_NUM_NC; cfg.pin_busy = GPIO_NUM_18; - cfg.panel_width = 480; - cfg.panel_height = 800; + cfg.panel_width = 800; + cfg.panel_height = 480; cfg.offset_x = 0; cfg.offset_y = 0; - cfg.offset_rotation = 0; + cfg.offset_rotation = 3; cfg.readable = false; cfg.invert = false; cfg.bus_shared = false; @@ -3010,8 +3010,11 @@ The usage of each pin is as follows. case board_M5StackCoreInk: title = "M5StackCoreInk"; break; case board_M5Paper: title = "M5Paper"; break; case board_M5PaperS3: title = "M5PaperS3"; break; + case board_M5PaperColor: title = "M5PaperColor"; break; + case board_M5PaperMono: title = "M5PaperMono"; break; case board_M5Tough: title = "M5Tough"; break; case board_M5Station: title = "M5Station"; break; + case board_M5StopWatch: title = "M5StopWatch"; break; case board_M5AtomS3: title = "M5AtomS3"; break; case board_M5AtomS3R: title = "M5AtomS3R"; break; case board_M5Dial: title = "M5Dial"; break; @@ -3043,6 +3046,21 @@ The usage of each pin is as follows. r = 1; break; + case board_M5PaperColor: + w = 400; + h = 600; + pnl_cfg.offset_rotation = 0; + r = 1; + break; + + case board_M5PaperMono: + w = 800; + h = 480; + pnl_cfg.offset_rotation = 3; + p->setColorDepth(lgfx::color_depth_t::grayscale_8bit); + r = 1; + break; + case board_M5StackCoreInk: case board_M5AirQ: w = 200; diff --git a/src/lgfx/v1/panel/Panel_SSD1677.cpp b/src/lgfx/v1/panel/Panel_SSD1677.cpp index de5adec..508a054 100644 --- a/src/lgfx/v1/panel/Panel_SSD1677.cpp +++ b/src/lgfx/v1/panel/Panel_SSD1677.cpp @@ -21,6 +21,8 @@ Original Source: #include "lgfx/v1/misc/pixelcopy.hpp" #include "lgfx/v1/misc/colortype.hpp" +#include + #ifdef min #undef min #endif @@ -31,20 +33,115 @@ namespace lgfx { //---------------------------------------------------------------------------- - // static constexpr uint8_t Bayer[16] = { 8, 200, 40, 232, 72, 136, 104, 168, 56, 248, 24, 216, 120, 184, 88, 152 }; static constexpr int8_t Bayer[16] = { -30, 18, -22, 26, -14, 2, -6, 10, -18, 30, -26, 22, -2, 14, -10, 6 }; + // static constexpr int8_t Bayer[16] = { 0, }; + + // SSD1677 commands + static constexpr uint8_t CMD_DEEP_SLEEP = 0x10; + static constexpr uint8_t CMD_DATA_ENTRY = 0x11; + static constexpr uint8_t CMD_MASTER_ACTIVATION = 0x20; + static constexpr uint8_t CMD_DISP_UPDATE_CTRL1 = 0x21; + static constexpr uint8_t CMD_DISP_UPDATE_CTRL2 = 0x22; + static constexpr uint8_t CMD_WRITE_RAM_BW = 0x24; // current frame / LSB plane + static constexpr uint8_t CMD_WRITE_RAM_RED = 0x26; // previous frame / MSB plane + static constexpr uint8_t CMD_WRITE_TEMP = 0x1A; + static constexpr uint8_t CMD_WRITE_LUT = 0x32; + static constexpr uint8_t CMD_GATE_VOLT = 0x03; // VGH + static constexpr uint8_t CMD_SOURCE_VOLT = 0x04; // VSH1, VSH2, VSL + static constexpr uint8_t CMD_WRITE_VCOM = 0x2C; + static constexpr uint8_t CMD_SET_RAM_X = 0x44; + static constexpr uint8_t CMD_SET_RAM_Y = 0x45; + static constexpr uint8_t CMD_SET_RAM_X_CNT = 0x4E; + static constexpr uint8_t CMD_SET_RAM_Y_CNT = 0x4F; + + static constexpr uint8_t CTRL1_NORMAL = 0x00; + static constexpr uint8_t CTRL1_BYPASS_RED = 0x40; + + //-------------------------------------------------------------------------- + // LUTs (ported from community-sdk EInkDisplay, non-X3 / GDEQ0426T82). + // Layout: VS patterns (5 groups x 10 bytes) + TP/RP timing (10 groups x 5 bytes) + // + frame rate (5 bytes) = 105 bytes -> command 0x32. + // Then voltages [VGH, VSH1, VSH2, VSL, VCOM] (bytes 105..109) -> 0x03/0x04/0x2C. + //-------------------------------------------------------------------------- + + static constexpr uint8_t lut_fastest[110] = { + 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: 00 black + 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: 01 dark + 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: 10 light + 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: 11 white + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM + 0x09, 0x0C, 0x03, 0x03, 0x00, + 0x0F, 0x03, 0x07, 0x03, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, // frame rate + 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1, VSH2, VSL, VCOM + }; + + // Factory absolute LUTs. 2-bit pixel encoding: BW=bit0(LSB), RED=bit1(MSB). + // 00=black, 01=dark, 10=light, 11=white. (i.e. value v(0..3) = (MSB<<1)|LSB.) + static constexpr uint8_t lut_factory_fast[110] = { + 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: 00 black + 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: 01 dark + 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: 10 light + 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: 11 white + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM + 0x09, 0x0C, 0x03, 0x03, 0x00, + 0x0F, 0x03, 0x07, 0x03, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x44, 0x44, 0x44, 0x44, // frame rate (faster clock) + 0x17, 0x41, 0xA8, 0x32, 0x50, // VGH, VSH1, VSH2, VSL, VCOM(-2.0V) + }; + + static constexpr uint8_t lut_factory_quality[110] = { + 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: 00 black + 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: 01 dark + 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: 10 light + 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: 11 white + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM + 0x08, 0x0B, 0x02, 0x03, 0x00, + 0x0C, 0x02, 0x07, 0x02, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, + 0x22, 0x22, 0x22, 0x22, 0x22, // frame rate (slower clock) + 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1, VSH2, VSL, VCOM(-1.2V) + }; + + // Write a 110-byte LUT: 105 waveform bytes -> 0x32, voltages -> 0x03/0x04/0x2C. + static void send_lut(IBus* bus, const uint8_t* lut) + { + bus->writeCommand(CMD_WRITE_LUT, 8); + bus->writeBytes(lut, 105, true, false); + bus->writeCommand(CMD_GATE_VOLT, 8); + bus->writeData(lut[105], 8); // VGH + bus->writeCommand(CMD_SOURCE_VOLT, 8); + bus->writeData(lut[106], 8); // VSH1 + bus->writeData(lut[107], 8); // VSH2 + bus->writeData(lut[108], 8); // VSL + bus->writeCommand(CMD_WRITE_VCOM, 8); + bus->writeData(lut[109], 8); // VCOM + } - static constexpr uint8_t CMD_DEEP_SLEEP_MODE = 0x10; // スリープの設定。スリープからの復帰にはハードウェアリセットが必要 - static constexpr uint8_t CMD_MASTER_ACTIVATION = 0x20; // 画面の描画更新を実施する - static constexpr uint8_t CMD_DISPLAY_UPDATE_CONTROL_1 = 0x21; - static constexpr uint8_t CMD_DISPLAY_UPDATE_CONTROL_2 = 0x22; - static constexpr uint8_t CMD_WRITE_RAM_BW = 0x24; - static constexpr uint8_t CMD_WRITE_RAM_RED = 0x26; - - static constexpr uint8_t CMD_WRITE_LUT = 0x32; - static constexpr uint8_t CMD_GATE_VOLT = 0x03; - static constexpr uint8_t CMD_SOURCE_VOLT = 0x04; - static constexpr uint8_t CMD_WRITE_VCOM = 0x2C; + //-------------------------------------------------------------------------- Panel_SSD1677::Panel_SSD1677(void) { @@ -52,6 +149,11 @@ namespace lgfx _epd_mode = epd_mode_t::epd_quality; } + Panel_SSD1677::~Panel_SSD1677(void) + { + if (_prev_buf) { heap_free(_prev_buf); _prev_buf = nullptr; } + } + color_depth_t Panel_SSD1677::setColorDepth(color_depth_t depth) { (void)depth; @@ -60,16 +162,16 @@ namespace lgfx return color_depth_t::grayscale_8bit; } + uint32_t Panel_SSD1677::_get_plane_length(void) const + { + // EPD native: row indexed by Y(gate), packed along X(source). + // row_bytes = (panel_width+7)/8 ; rows = panel_height. + return (((_cfg.panel_width + 7) & ~7) >> 3) * _cfg.panel_height; + } + size_t Panel_SSD1677::_get_buffer_length(void) const { - // Buffer layout: - // LovyanGFX: panel_width=480 (X), panel_height=800 (Y) - // EPD RAM: RAM Y=480 (rows), RAM X=800 (bits, 100 bytes per row) - // LovyanGFX X (0-479) -> RAM Y (row index) - // LovyanGFX Y (0-799) -> RAM X (bit position) - // Buffer: panel_width rows * (panel_height+7)/8 bytes = 480 * 100 = 48000 bytes - auto buf_x1_len = ((_cfg.panel_height + 7) & ~7) * _cfg.panel_width >> 3; - return buf_x1_len * 2; + return _get_plane_length() * 2; // planeL + planeM } bool Panel_SSD1677::init(bool use_reset) @@ -80,11 +182,9 @@ namespace lgfx { return false; } - auto buf_len = _get_buffer_length(); - _buf_x1_len = buf_len >> 1; - memset(_buf, 0xFF, buf_len); + _buf_x1_len = _get_plane_length(); + memset(_buf, 0xFF, _get_buffer_length()); // white _after_wake(); - return true; } @@ -97,27 +197,43 @@ namespace lgfx command_list(cmds); } + // Full-screen RAM window + clear both RAM banks to white. + _set_ram_area(0, 0, _cfg.panel_width, _cfg.panel_height); + _wait_busy(); + _bus->writeCommand(0x46, 8); _bus->writeData(0xF7, 8); // auto write BW RAM + _wait_busy(); + _bus->writeCommand(0x47, 8); _bus->writeData(0xF7, 8); // auto write RED RAM + _wait_busy(); + + _screen_on = false; _last_epd_mode = (epd_mode_t)~0u; _initialize_seq = true; - _need_flip_draw = false; - _epd_frame_back = false; - - setInvert(_invert); setRotation(_rotation); _range_old.top = 0; _range_old.left = 0; - _range_old.right = _width - 1; - _range_old.bottom = _height - 1; - _range_mod.top = INT16_MAX; - _range_mod.left = INT16_MAX; - _range_mod.right = 0; + _range_old.right = _cfg.panel_width - 1; + _range_old.bottom = _cfg.panel_height - 1; + _range_mod.top = INT16_MAX; + _range_mod.left = INT16_MAX; + _range_mod.right = 0; _range_mod.bottom = 0; endWrite(); } + void Panel_SSD1677::_power_on(void) + { + if (_screen_on) { return; } + _bus->writeCommand(CMD_DISP_UPDATE_CTRL2, 8); + _bus->writeData(0xC0, 8); + _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); + _send_msec = millis(); + _wait_busy(); + _screen_on = true; + } + void Panel_SSD1677::waitDisplay(void) { _wait_busy(); @@ -128,74 +244,105 @@ namespace lgfx return _cfg.pin_busy >= 0 && gpio_in(_cfg.pin_busy); } + void Panel_SSD1677::_set_ram_area(int32_t x, int32_t y, int32_t w, int32_t h) + { + // Native coords: X = source (0..799), Y = gate (0..479). + // Gates are reversed on this panel -> reverse Y and use Y-decrement. + int32_t yrev = _cfg.panel_height - y - h; + + _bus->writeCommand(CMD_DATA_ENTRY, 8); + _bus->writeData(0x01, 8); // X increment, Y decrement + + _bus->writeCommand(CMD_SET_RAM_X, 8); + _bus->writeData(x & 0xFF, 8); + _bus->writeData((x >> 8) & 0xFF, 8); + _bus->writeData((x + w - 1) & 0xFF, 8); + _bus->writeData(((x + w - 1) >> 8) & 0xFF, 8); + + _bus->writeCommand(CMD_SET_RAM_Y, 8); + _bus->writeData((yrev + h - 1) & 0xFF, 8); + _bus->writeData(((yrev + h - 1) >> 8) & 0xFF, 8); + _bus->writeData(yrev & 0xFF, 8); + _bus->writeData((yrev >> 8) & 0xFF, 8); + + _bus->writeCommand(CMD_SET_RAM_X_CNT, 8); + _bus->writeData(x & 0xFF, 8); + _bus->writeData((x >> 8) & 0xFF, 8); + + _bus->writeCommand(CMD_SET_RAM_Y_CNT, 8); + _bus->writeData((yrev + h - 1) & 0xFF, 8); + _bus->writeData(((yrev + h - 1) >> 8) & 0xFF, 8); + } + + void Panel_SSD1677::_send_plane(uint32_t cmd, const uint8_t* plane, const range_rect_t& range, bool extra_invert) + { + int32_t xs = range.left & ~7; + int32_t xe = range.right | 7; + if (xe >= (int32_t)_cfg.panel_width) { xe = _cfg.panel_width - 1; } + int32_t ys = range.top; + int32_t ye = range.bottom; + if (ye >= (int32_t)_cfg.panel_height) { ye = _cfg.panel_height - 1; } + + _set_ram_area(xs, ys, xe - xs + 1, ye - ys + 1); + _wait_busy(); + _bus->writeCommand(cmd, 8); + + int32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; + int32_t xbytes = (xe - xs + 1) >> 3; + int32_t rows = ye - ys + 1; + const uint8_t* b = &plane[ys * row_bytes + (xs >> 3)]; + + bool inv = extra_invert ^ _invert ^ _cfg.invert; + if (inv) + { + uint8_t tmp[128]; + for (int32_t row = 0; row < rows; row++) + { + for (int32_t i = 0; i < xbytes; i++) { tmp[i] = ~b[i]; } + _bus->writeBytes(tmp, xbytes, true, false); + b += row_bytes; + } + } + else if (xbytes == row_bytes) + { + _bus->writeBytes(b, xbytes * rows, true, true); + } + else + { + for (int32_t row = 0; row < rows; row++) + { + _bus->writeBytes(b, xbytes, true, true); + b += row_bytes; + } + } + } + void Panel_SSD1677::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) { if (0 < w && 0 < h) { - _range_mod.left = std::min(_range_mod.left , x ); - _range_mod.right = std::max(_range_mod.right , x + w - 1); - _range_mod.top = std::min(_range_mod.top , y ); - _range_mod.bottom = std::max(_range_mod.bottom, y + h - 1); + uint_fast16_t xs = x, ys = y, xe = x + w - 1, ye = y + h - 1; + _rotate_pos(xs, ys, xe, ye); + _range_mod.left = std::min(_range_mod.left , std::min(xs, xe)); + _range_mod.right = std::max(_range_mod.right , std::max(xs, xe)); + _range_mod.top = std::min(_range_mod.top , std::min(ys, ye)); + _range_mod.bottom = std::max(_range_mod.bottom, std::max(ys, ye)); } if (_range_mod.empty()) { return; } - auto epd_mode = getEpdMode(); - bool need_flip_draw = _need_flip_draw || (epd_mode_t::epd_quality < epd_mode && epd_mode < epd_mode_t::epd_fast); - _need_flip_draw = false; - bool flg_mode_changed = (_last_epd_mode != epd_mode); + auto mode = getEpdMode(); + bool is_full = _initialize_seq || (_last_epd_mode != mode) + || (mode == epd_mode_t::epd_quality) || (mode == epd_mode_t::epd_text); - if (_initialize_seq || flg_mode_changed) + if (is_full) { - // CMD_DISPLAY_UPDATE_CONTROL_2 parameter - // 0b10000000 = Enable Clock signal - // 0b01000000 = Enable Analog - // 0b00100000 = Load temperature value - // 0b00010000 = Load LUT with DISPLAY Mode 1 - // 0b00011000 = Load LUT with DISPLAY Mode 2 - // 0b00000100 = Display with DISPLAY Mode 1 - // 0b00001100 = Display with DISPLAY Mode 2 - // 0b00000010 = Disable Analog - // 0b00000001 = Disable clock signal - // epd_quality高品質モードではフリッキング更新を行う _range_mod.left = 0; - _range_mod.right = _width - 1; _range_mod.top = 0; - _range_mod.bottom = _height - 1; - - if (_initialize_seq) { - _initialize_seq = false; - // リセット直後は起動シーケンス設定およびフレームバッファの転送を行う。ここではリフレッシュは行わない。 - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); - _bus->writeData(0xF8, 8); - _exec_transfer(CMD_WRITE_RAM_BW, _buf, _range_mod, true); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], _range_mod, true); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); - _send_msec = millis(); - } - - // epd_qualityの場合は反転描画は不要になる。 - // 他のモードに変更した直後は反転描画を行う。 - need_flip_draw = (epd_mode != epd_mode_t::epd_quality); - _epd_frame_switching = need_flip_draw; - if (!need_flip_draw) - { - if (_epd_frame_back) - { // フレームバッファ2番に送信される場合はモード変更前に一度描画更新を行う - _epd_frame_back = false; - _exec_transfer(CMD_WRITE_RAM_BW, _buf, _range_mod); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], _range_mod); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - _send_msec = millis(); - } - } - _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); // Display update seq opt - uint8_t refresh_param = (epd_mode == epd_mode_t::epd_quality) - ? 0x14 // DISPLAY Mode1 (flicking) - : 0x1C; // DISPLAY Mode2 (no flick) - _bus->writeData(refresh_param, 8); - _last_epd_mode = epd_mode; + _range_mod.right = _cfg.panel_width - 1; + _range_mod.bottom = _cfg.panel_height - 1; } + + // For fast diff, include the previously-changed region too. range_rect_t tr = _range_mod; if (tr.top > _range_old.top) { tr.top = _range_old.top; } if (tr.left > _range_old.left) { tr.left = _range_old.left; } @@ -203,41 +350,62 @@ namespace lgfx if (tr.bottom < _range_old.bottom) { tr.bottom = _range_old.bottom; } _range_old = _range_mod; - _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr, need_flip_draw); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, need_flip_draw); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update + startWrite(); + + // Base panel renders B/W: use planeM (MSB) as the monochrome bit (white if v>=2). + const uint8_t* img = &_buf[_buf_x1_len]; + + _send_plane(CMD_WRITE_RAM_BW, img, tr); + if (is_full) + { + _send_plane(CMD_WRITE_RAM_RED, img, tr); // full/half: same image to both + } + + // Refresh sequence (community-sdk refreshDisplay, B/W). + _wait_busy(); + _bus->writeCommand(CMD_DISP_UPDATE_CTRL1, 8); + _bus->writeData(is_full ? CTRL1_BYPASS_RED : CTRL1_NORMAL, 8); + + uint8_t dm = 0; + if (!_screen_on) { _screen_on = true; dm |= 0xC0; } + if (mode == epd_mode_t::epd_quality) { dm |= 0x34; } // FULL + else if (mode == epd_mode_t::epd_text) // HALF + { + _bus->writeCommand(CMD_WRITE_TEMP, 8); + _bus->writeData(0x5A, 8); + dm |= 0xD4; + } + else { dm |= 0x1C; } // FAST (built-in LUT) + + _bus->writeCommand(CMD_DISP_UPDATE_CTRL2, 8); + _bus->writeData(dm, 8); + _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); _send_msec = millis(); - if (need_flip_draw) - { // 反転リフレッシュを自前でやる場合 - _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - _send_msec = millis(); - } else { - if (_epd_frame_switching) { _epd_frame_back = !_epd_frame_back; } - else { _epd_frame_back = false; } + _wait_busy(); + + if (!is_full) + { + // Sync RED RAM with current frame so it serves as "previous" next time. + _send_plane(CMD_WRITE_RAM_RED, img, tr); } - _range_mod.top = INT16_MAX; - _range_mod.left = INT16_MAX; - _range_mod.right = 0; + _initialize_seq = false; + _last_epd_mode = mode; + _range_mod.top = INT16_MAX; + _range_mod.left = INT16_MAX; + _range_mod.right = 0; _range_mod.bottom = 0; + + endWrite(); } void Panel_SSD1677::setInvert(bool invert) { - if (_invert == invert && !_initialize_seq) { return; } _invert = invert; - startWrite(); - _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_1, 8); - _bus->writeData((invert ^ _cfg.invert) ? 0x88 : 0x00, 8); - _need_flip_draw = true; _range_mod.top = 0; _range_mod.left = 0; - _range_mod.right = _width - 1; - _range_mod.bottom = _height - 1; - endWrite(); + _range_mod.right = _cfg.panel_width - 1; + _range_mod.bottom = _cfg.panel_height - 1; } void Panel_SSD1677::setSleep(bool flg) @@ -246,14 +414,18 @@ namespace lgfx { startWrite(); _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); - _bus->writeData(0x03, 8); // Disable Analog , Disable clock signal - _bus->writeCommand(CMD_DEEP_SLEEP_MODE, 8); - _bus->writeData(0x03, 8); + _bus->writeCommand(CMD_DISP_UPDATE_CTRL2, 8); + _bus->writeData(0x03, 8); // analog off + clock off + _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); + _wait_busy(); + _bus->writeCommand(CMD_DEEP_SLEEP, 8); + _bus->writeData(0x01, 8); + _screen_on = false; endWrite(); } else { + // Deep-sleep wake requires a hardware reset (RST may be external -> rst_control). rst_control(false); delay(10); rst_control(true); @@ -266,10 +438,11 @@ namespace lgfx { startWrite(); _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); - _bus->writeData(flg ? 0x03 : 0xE0, 8); - _wait_busy(); + _bus->writeCommand(CMD_DISP_UPDATE_CTRL2, 8); + _bus->writeData(flg ? 0x03 : 0xC0, 8); _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); + _wait_busy(); + _screen_on = !flg; endWrite(); } @@ -277,19 +450,11 @@ namespace lgfx { uint_fast16_t xs = x, xe = x + w - 1; uint_fast16_t ys = y, ye = y + h - 1; - _xs = xs; - _ys = ys; - _xe = xe; - _ye = ye; + _xs = xs; _ys = ys; _xe = xe; _ye = ye; _update_transferred_rect(xs, ys, xe, ye); - grayscale_t color; - color.raw = rawcolor; int32_t value = rawcolor; - - // Buffer layout: LovyanGFX X (0-479) = row index, LovyanGFX Y (0-799) = bit position - // Each row is (panel_height+7)/8 = 100 bytes - int32_t row_bytes = ((_cfg.panel_height + 7) & ~7) >> 3; + int32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; y = ys; do @@ -298,25 +463,19 @@ namespace lgfx auto btbl = &Bayer[(y & 3) << 2]; do { - // Buffer index: x * row_bytes + y / 8, bit position: y % 8 - uint32_t byte_idx = x * row_bytes + (y >> 3); - uint8_t bit_mask = 0x80 >> (y & 7); + uint32_t byte_idx = y * row_bytes + (x >> 3); + uint8_t bit_mask = 0x80 >> (x & 7); int_fast8_t v = (value + btbl[x & 3]) >> 6; v = (v < 0) ? 0 : (v > 3 ? 3 : v); - { - if (v & 1) _buf[byte_idx] |= bit_mask; - else _buf[byte_idx] &= ~bit_mask; - } - { - if (v & 2) _buf[byte_idx + _buf_x1_len] |= bit_mask; - else _buf[byte_idx + _buf_x1_len] &= ~bit_mask; - } + if (v & 1) _buf[byte_idx] |= bit_mask; else _buf[byte_idx] &= ~bit_mask; + if (v & 2) _buf[byte_idx + _buf_x1_len] |= bit_mask; else _buf[byte_idx + _buf_x1_len] &= ~bit_mask; } while (++x <= xe); } while (++y <= ye); } void Panel_SSD1677::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) { + (void)use_dma; uint_fast16_t xs = x, xe = x + w - 1; uint_fast16_t ys = y, ye = y + h - 1; _update_transferred_rect(xs, ys, xe, ye); @@ -346,26 +505,20 @@ namespace lgfx void Panel_SSD1677::writePixels(pixelcopy_t* param, uint32_t length, bool use_dma) { + (void)use_dma; { - uint_fast16_t xs = _xs; - uint_fast16_t xe = _xe; - uint_fast16_t ys = _ys; - uint_fast16_t ye = _ye; + uint_fast16_t xs = _xs, xe = _xe, ys = _ys, ye = _ye; _update_transferred_rect(xs, ys, xe, ye); } - uint_fast16_t xs = _xs ; - uint_fast16_t ys = _ys ; - uint_fast16_t xe = _xe ; - uint_fast16_t ye = _ye ; - uint_fast16_t xpos = _xpos; - uint_fast16_t ypos = _ypos; + uint_fast16_t xs = _xs, ys = _ys, xe = _xe, ye = _ye; + uint_fast16_t xpos = _xpos, ypos = _ypos; static constexpr uint32_t buflen = 16; grayscale_t colors[buflen]; int bufpos = buflen; do { - if (bufpos == buflen) { + if (bufpos == (int)buflen) { param->fp_copy(colors, 0, std::min(length, buflen), param); bufpos = 0; } @@ -374,10 +527,7 @@ namespace lgfx if (++xpos > xe) { xpos = xs; - if (++ypos > ye) - { - ypos = ys; - } + if (++ypos > ye) { ypos = ys; } } } while (--length); _xpos = xpos; @@ -412,13 +562,9 @@ namespace lgfx if (delay_msec && delay_msec < timeout) { delay(delay_msec); } do { - if (millis() - start_time > timeout) { -// printf("TIMEOUT\n"); - return false; - } + if (millis() - start_time > timeout) { return false; } delay(1); } while (gpio_in(_cfg.pin_busy)); -// printf("time:%d\n", millis() - start_time); } return true; } @@ -426,448 +572,135 @@ namespace lgfx void Panel_SSD1677::_draw_pixel(uint_fast16_t x, uint_fast16_t y, uint32_t value) { _rotate_pos(x, y); - // Buffer layout: LovyanGFX X (0-479) = row index, LovyanGFX Y (0-799) = bit position - // row_bytes = (panel_height+7)/8 = 100 bytes per row - int32_t row_bytes = ((_cfg.panel_height + 7) & ~7) >> 3; - uint32_t byte_idx = x * row_bytes + (y >> 3); - uint8_t bit_mask = 0x80 >> (y & 7); - // bool flg = 256 <= value + Bayer[(x & 3) | (y & 3) << 2]; + int32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; + uint32_t byte_idx = y * row_bytes + (x >> 3); + uint8_t bit_mask = 0x80 >> (x & 7); int_fast8_t v = ((int32_t)value + (Bayer[(x & 3) + ((y & 3) << 2)])) >> 6; v = (v < 0) ? 0 : (v > 3 ? 3 : v); - { - if (v & 1) _buf[byte_idx] |= bit_mask; - else _buf[byte_idx] &= ~bit_mask; - } - { - if (v & 2) _buf[byte_idx + _buf_x1_len] |= bit_mask; - else _buf[byte_idx + _buf_x1_len] &= ~bit_mask; - } + if (v & 1) _buf[byte_idx] |= bit_mask; else _buf[byte_idx] &= ~bit_mask; + if (v & 2) _buf[byte_idx + _buf_x1_len] |= bit_mask; else _buf[byte_idx + _buf_x1_len] &= ~bit_mask; } uint8_t Panel_SSD1677::_read_pixel(uint_fast16_t x, uint_fast16_t y) { _rotate_pos(x, y); - // Buffer layout: LovyanGFX X (0-479) = row index, LovyanGFX Y (0-799) = bit position - int32_t row_bytes = ((_cfg.panel_height + 7) & ~7) >> 3; - uint32_t byte_idx = x * row_bytes + (y >> 3); - uint8_t bit_mask = 0x80 >> (y & 7); - uint_fast8_t result = (_buf[byte_idx] & bit_mask) ? 2 : 0; - result += (_buf[byte_idx + _buf_x1_len] & bit_mask) ? 1 : 0; + int32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; + uint32_t byte_idx = y * row_bytes + (x >> 3); + uint8_t bit_mask = 0x80 >> (x & 7); + uint_fast8_t result = (_buf[byte_idx] & bit_mask) ? 1 : 0; + result += (_buf[byte_idx + _buf_x1_len] & bit_mask) ? 2 : 0; return result; } - void Panel_SSD1677::_exec_transfer(uint32_t cmd, const uint8_t* buf, const range_rect_t& range, bool invert) - { - // LovyanGFX: panel_width=480 (X), panel_height=800 (Y) - // EPD RAM: X=800, Y=480 - // Coordinate mapping: LovyanGFX X -> RAM Y, LovyanGFX Y -> RAM X - // range.left/right = LovyanGFX X (0-479) -> RAM Y - // range.top/bottom = LovyanGFX Y (0-799) -> RAM X - // - // Buffer layout: row = LovyanGFX X (480 rows), column bits = LovyanGFX Y (100 bytes per row) - // row_bytes = (panel_height+7)/8 = 100 bytes - - // LovyanGFX Y needs byte alignment (maps to RAM X direction, stored as bits in buffer) - int32_t lgfx_xs = range.left; - int32_t lgfx_xe = range.right; - int32_t lgfx_ys = range.top & ~7; - int32_t lgfx_ye = (range.bottom & ~7) + 7; - - // Map to EPD RAM coordinates - int32_t ram_xs = lgfx_ys; // RAM X start = LovyanGFX Y start - int32_t ram_xe = lgfx_ye; // RAM X end = LovyanGFX Y end - int32_t ram_ys = lgfx_xs; // RAM Y start = LovyanGFX X start - int32_t ram_ye = lgfx_xe; // RAM Y end = LovyanGFX X end - - _wait_busy(); - - // 0x44: Set RAM X address start/end position (0-799, from LovyanGFX Y) - _bus->writeCommand(0x44, 8); - _bus->writeData(ram_xs & 0xFF, 8); - _bus->writeData((ram_xs >> 8) & 0xFF, 8); - _bus->writeData(ram_xe & 0xFF, 8); - _bus->writeData((ram_xe >> 8) & 0xFF, 8); - - // 0x45: Set RAM Y address start/end position (0-479, from LovyanGFX X) - _bus->writeCommand(0x45, 8); - _bus->writeData(ram_ys & 0xFF, 8); - _bus->writeData((ram_ys >> 8) & 0xFF, 8); - _bus->writeData(ram_ye & 0xFF, 8); - _bus->writeData((ram_ye >> 8) & 0xFF, 8); - - // 0x4E: Set RAM X address count to start - _bus->writeCommand(0x4E, 8); - _bus->writeData(ram_xs & 0xFF, 8); - _bus->writeData((ram_xs >> 8) & 0xFF, 8); - - // 0x4F: Set RAM Y address count to start - _bus->writeCommand(0x4F, 8); - _bus->writeData(ram_ys & 0xFF, 8); - _bus->writeData((ram_ys >> 8) & 0xFF, 8); - - _wait_busy(); - - _bus->writeCommand(cmd, 8); - - // Buffer layout: row = LovyanGFX X (480 rows), bytes per row = (panel_height+7)/8 = 100 - int32_t row_bytes = ((_cfg.panel_height + 7) & ~7) >> 3; - int32_t transfer_bytes = ((lgfx_ye - lgfx_ys) >> 3) + 1; // bytes to transfer per row (Y direction) - int32_t rows = lgfx_xe - lgfx_xs + 1; // number of rows (X direction) - - auto b = &buf[lgfx_xs * row_bytes + (lgfx_ys >> 3)]; - - if (invert) - { - for (int32_t row = 0; row < rows; row++) - { - for (int32_t i = 0; i < transfer_bytes; i++) - { - _bus->writeData(~b[i], 8); - } - b += row_bytes; - } - } - else - { - if (row_bytes == transfer_bytes) { - // Full width transfer - can send all at once - _bus->writeBytes(b, transfer_bytes * rows, true, true); - } - else - { - // Partial width transfer - send row by row - for (int32_t row = 0; row < rows; row++) - { - _bus->writeBytes(b, transfer_bytes, true, true); - b += row_bytes; - } - } - } - } - void Panel_SSD1677::_update_transferred_rect(uint_fast16_t &xs, uint_fast16_t &ys, uint_fast16_t &xe, uint_fast16_t &ye) { _rotate_pos(xs, ys, xe, ye); - // LovyanGFX Y direction (0-799) needs byte alignment for buffer access - int32_t y1 = ys & ~7; - int32_t y2 = (ye & ~7) + 7; - - // Clamp to valid range (panel_width=480, panel_height=800) - if (xe >= _cfg.panel_width) xe = _cfg.panel_width - 1; - if (y2 >= _cfg.panel_height) y2 = _cfg.panel_height - 1; + // X (source) direction is byte-packed -> align to 8. + int32_t x1 = xs & ~7; + int32_t x2 = (xe & ~7) + 7; + if (x2 >= (int32_t)_cfg.panel_width) { x2 = _cfg.panel_width - 1; } + if (ye >= _cfg.panel_height) { ye = _cfg.panel_height - 1; } - _range_mod.left = std::min(xs, _range_mod.left); - _range_mod.right = std::max(xe, _range_mod.right); - _range_mod.top = std::min(y1, _range_mod.top); - _range_mod.bottom = std::max(y2, _range_mod.bottom); + _range_mod.left = std::min(x1, _range_mod.left); + _range_mod.right = std::max(x2, _range_mod.right); + _range_mod.top = std::min(ys, _range_mod.top); + _range_mod.bottom = std::max(ye, _range_mod.bottom); } - void Panel_SSD1677::_set_lut(const lut_data_t* lut_data) - { - _wait_busy(); - _bus->writeCommand(CMD_WRITE_LUT, 8); - _bus->writeBytes(lut_data->lut, sizeof(lut_data_t::lut), true, false); - - _bus->writeCommand(CMD_GATE_VOLT, 8); - _bus->writeBytes(lut_data->gate, sizeof(lut_data_t::gate), true, false); + //========================================================================== + // Panel_SSD1677_4Gray + //========================================================================== - _bus->writeCommand(CMD_SOURCE_VOLT, 8); - _bus->writeBytes(lut_data->source, sizeof(lut_data_t::source), true, false); + bool Panel_SSD1677_4Gray::_ensure_prev_buf(void) + { + if (_prev_buf) { return true; } + size_t len = _get_plane_length() * 2; + _prev_buf = (uint8_t*)heap_alloc_psram(len); + if (!_prev_buf) { _prev_buf = (uint8_t*)heap_alloc(len); } + if (!_prev_buf) { return false; } + memset(_prev_buf, 0xFF, len); // white baseline + _prev_valid = false; + return true; + } - _bus->writeCommand(CMD_WRITE_VCOM, 8); - _bus->writeBytes(lut_data->vcom, sizeof(lut_data_t::vcom), true, false); + void Panel_SSD1677_4Gray::_store_prev(void) + { + if (!_prev_buf) { return; } + memcpy(_prev_buf, _buf, _get_plane_length() * 2); + _prev_valid = true; } void Panel_SSD1677_4Gray::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) { -#if 1 - static constexpr lut_data_t lut_gray4 = { - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xAA, 0xA0, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xA2, 0x22, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, - }, - { 0x17 }, - { 0x41, 0xA8, 0x32 }, - { 0x30 }, - }; - - static constexpr lut_data_t lut_gray4_rev = { - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x54, 0x54, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xA8, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFC, 0xFC, 0xFC, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, - }, - { 0x17 }, - { 0x41, 0xA8, 0x32 }, - { 0x30 }, - }; - - uint_fast16_t xs = x, xe = x + w - 1; - uint_fast16_t ys = y, ye = y + h - 1; - _rotate_pos(xs, ys, xe, ye); - if (0 < w && 0 < h) { - _range_mod.left = std::min(_range_mod.left , xs); - _range_mod.right = std::max(_range_mod.right , xe); - _range_mod.top = std::min(_range_mod.top , ys); - _range_mod.bottom = std::max(_range_mod.bottom, ye); + uint_fast16_t xs = x, ys = y, xe = x + w - 1, ye = y + h - 1; + _rotate_pos(xs, ys, xe, ye); + _range_mod.left = std::min(_range_mod.left , std::min(xs, xe)); + _range_mod.right = std::max(_range_mod.right , std::max(xs, xe)); + _range_mod.top = std::min(_range_mod.top , std::min(ys, ye)); + _range_mod.bottom = std::max(_range_mod.bottom, std::max(ys, ye)); } if (_range_mod.empty()) { return; } - auto epd_mode = getEpdMode(); - bool need_flip_draw = _need_flip_draw || (epd_mode_t::epd_quality < epd_mode && epd_mode < epd_mode_t::epd_fast); - _need_flip_draw = false; - bool flg_mode_changed = (_last_epd_mode != epd_mode); + auto mode = getEpdMode(); - if (true) //_initialize_seq || flg_mode_changed) - { - _range_mod.left = 0; - _range_mod.right = _cfg.panel_width - 1; - _range_mod.top = 0; - _range_mod.bottom = _cfg.panel_height - 1; + // 4-gray always refreshes the full screen. + range_rect_t full; + full.left = 0; full.top = 0; + full.right = _cfg.panel_width - 1; full.bottom = _cfg.panel_height - 1; - if (_initialize_seq) { - _initialize_seq = false; - // リセット直後は起動シーケンス設定およびフレームバッファの転送を行う。ここではリフレッシュは行わない。 - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); - _bus->writeData(0xF8, 8); - _exec_transfer(CMD_WRITE_RAM_BW, _buf, _range_mod, true); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], _range_mod, true); + const uint8_t* planeL = _buf; + const uint8_t* planeM = &_buf[_buf_x1_len]; - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); - _send_msec = millis(); - } - - // epd_qualityの場合は反転描画は不要になる。 - // 他のモードに変更した直後は反転描画を行う。 - need_flip_draw = (epd_mode != epd_mode_t::epd_quality); - _epd_frame_switching = need_flip_draw; - if (!need_flip_draw) - { - if (_epd_frame_back) - { // フレームバッファ2番に送信される場合はモード変更前に一度描画更新を行う - _epd_frame_back = false; - _exec_transfer(CMD_WRITE_RAM_BW, _buf, _range_mod); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], _range_mod); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - _send_msec = millis(); - } - } - _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); // Display update seq opt - uint8_t refresh_param = (epd_mode == epd_mode_t::epd_quality) - ? 0x14 // DISPLAY Mode1 (flicking) - : 0x1C; // DISPLAY Mode2 (no flick) - _bus->writeData(refresh_param, 8); - _last_epd_mode = epd_mode; - } - - range_rect_t tr = _range_mod; - if (tr.top > _range_old.top) { tr.top = _range_old.top; } - if (tr.left > _range_old.left) { tr.left = _range_old.left; } - if (tr.right < _range_old.right) { tr.right = _range_old.right; } - if (tr.bottom < _range_old.bottom) { tr.bottom = _range_old.bottom; } - _range_old = _range_mod; + startWrite(); - if (_epd_mode == epd_mode_t::epd_quality) + // Pick RAM source planes + LUT per mode. All use the factory absolute path + // (Display Mode 1, self-contained 0xC7). + // quality / text : 4-level, factory_quality (cleanest, slow) + // fast : 4-level, factory_fast + // fastest : 1-bit B/W. lut_fastest (differential) cannot drive + // full white<->black transitions, so fastest binarizes: + // feed planeM (v>=2 -> white) to BOTH RAMs, making the + // 2-bit value 00 (black) or 11 (white) only. + const uint8_t* bw_src = planeL; // 4-level: LSB -> BW, MSB -> RED + const uint8_t* red_src = planeM; + const uint8_t* lut = lut_factory_quality; + if (mode == epd_mode_t::epd_fastest) { - _set_lut(&lut_gray4); - _wait_busy(); - _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr, true); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, true); - - _bus->writeCommand(0x1A, 8); - _bus->writeData(0x5A, 8); - } - else + lut = lut_fastest; + } else + if (mode == epd_mode_t::epd_fast) { - _set_lut(&lut_gray4); - _wait_busy(); - _exec_transfer(CMD_WRITE_RAM_BW, &_buf[_buf_x1_len], tr, false); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, true); - + lut = lut_factory_fast; } - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update + // The factory LUT groups are reversed in brightness on this panel + // (HW group 00=white .. 11=black, opposite of the datasheet comment), so + // send the complement of both planes to get a correctly-oriented image. + _send_plane(CMD_WRITE_RAM_BW, bw_src, full, true); + _send_plane(CMD_WRITE_RAM_RED, red_src, full, true); + + send_lut(_bus, lut); + _wait_busy(); + _bus->writeCommand(CMD_DISP_UPDATE_CTRL1, 8); + _bus->writeData(CTRL1_NORMAL, 8); + _bus->writeCommand(CMD_DISP_UPDATE_CTRL2, 8); + _bus->writeData(0xC7, 8); // Mode 1, self-contained power cycle (powers down after) + _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); _send_msec = millis(); - if (need_flip_draw) - { // 反転リフレッシュを自前でやる場合 - } else { - if (_epd_frame_switching) { _epd_frame_back = !_epd_frame_back; } - else { _epd_frame_back = false; } - } + _wait_busy(); + _screen_on = false; - _range_mod.top = INT16_MAX; - _range_mod.left = INT16_MAX; - _range_mod.right = 0; + _initialize_seq = false; + _last_epd_mode = mode; + _range_mod.top = INT16_MAX; + _range_mod.left = INT16_MAX; + _range_mod.right = 0; _range_mod.bottom = 0; -#else - static constexpr lut_data_t lut_gray4 = { - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xAA, 0xA0, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xA2, 0x22, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, - }, - { 0x17 }, - { 0x41, 0xA8, 0x32 }, - { 0x30 }, - }; - - static constexpr lut_data_t lut_gray4_rev = { - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x54, 0x54, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xA8, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFC, 0xFC, 0xFC, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, - }, - { 0x17 }, - { 0x41, 0xA8, 0x32 }, - { 0x30 }, - }; - - if (0 < w && 0 < h) - { - _range_mod.left = std::min(_range_mod.left , x ); - _range_mod.right = std::max(_range_mod.right , x + w - 1); - _range_mod.top = std::min(_range_mod.top , y ); - _range_mod.bottom = std::max(_range_mod.bottom, y + h - 1); - } - if (_range_mod.empty()) { return; } - - range_rect_t tr = _range_mod; - if (tr.top > _range_old.top) { tr.top = _range_old.top; } - if (tr.left > _range_old.left) { tr.left = _range_old.left; } - if (tr.right < _range_old.right) { tr.right = _range_old.right; } - if (tr.bottom < _range_old.bottom) { tr.bottom = _range_old.bottom; } - _range_old = _range_mod; - - // _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr, need_flip_draw); - // _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, need_flip_draw); - - if (_epd_mode == epd_mode_t::epd_quality) - { - _set_lut(&lut_gray4); - _wait_busy(); - _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr, true); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, true); - - _bus->writeCommand(0x1A, 8); - _bus->writeData(0x5A, 8); - - _wait_busy(); - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); // Display update seq opt - // uint8_t refresh_param = 0xC7; - // uint8_t refresh_param = 0xC3; - // uint8_t refresh_param = 0xD7; - uint8_t refresh_param = 0x14; // Mode 1 (flicking) - _bus->writeData(refresh_param, 8); - } - else - { - - _exec_transfer(CMD_WRITE_RAM_BW, &_buf[_buf_x1_len], tr, true); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr, false); - - _set_lut(&lut_gray4_rev); - _wait_busy(); - - _bus->writeCommand(CMD_DISPLAY_UPDATE_CONTROL_2, 8); // Display update seq opt - uint8_t refresh_param = 0 - // | 0xC0 // ScreenOn - // | 0x20 // Load Temperature - | 0x10 // LUT Load - | 0x08 // Mode 2 - | 0x04 // Display Start - // | 0x03 // ScreenOff - ; - _bus->writeData(refresh_param, 8); - // _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - // _wait_busy(); -//*/ - } - - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - _send_msec = millis(); -/* - if (need_flip_draw) - { // 反転リフレッシュを自前でやる場合 - _exec_transfer(CMD_WRITE_RAM_BW, _buf, tr); - _exec_transfer(CMD_WRITE_RAM_RED, &_buf[_buf_x1_len], tr); - _bus->writeCommand(CMD_MASTER_ACTIVATION, 8); // Active Display update - _send_msec = millis(); - } else { - if (_epd_frame_switching) { _epd_frame_back = !_epd_frame_back; } - else { _epd_frame_back = false; } - } -//*/ - _range_mod.top = INT16_MAX; - _range_mod.left = INT16_MAX; - _range_mod.right = 0; - _range_mod.bottom = 0; -#endif + endWrite(); } //---------------------------------------------------------------------------- diff --git a/src/lgfx/v1/panel/Panel_SSD1677.hpp b/src/lgfx/v1/panel/Panel_SSD1677.hpp index be32b1a..c178849 100644 --- a/src/lgfx/v1/panel/Panel_SSD1677.hpp +++ b/src/lgfx/v1/panel/Panel_SSD1677.hpp @@ -26,9 +26,28 @@ namespace lgfx { //---------------------------------------------------------------------------- + /* + SSD1677 (GDEQ0426T82 / 800x480) E-Paper panel driver. + + Buffer / coordinate model (EPD native, see docs/Panel_SSD1677_rebuild_plan.md): + - Panel native orientation = landscape 800(X=source) x 480(Y=gate). + - 8 pixels per byte are packed along X (source) direction, matching the + controller's source-shift scan order. byte = 8 consecutive X pixels. + - Buffer rows are indexed by Y (gate). row_bytes = (panel_width+7)/8 = 100. + byte_idx = y * row_bytes + (x >> 3) ; bit = 0x80 >> (x & 7). + - Portrait usage is provided via setRotation (handled by _rotate_pos). + + Grayscale (4-level) model: + - _draw_pixel stores an abstract gray level v(0=black..3=white) split into + two bit planes: planeL = v&1 (-> BW RAM 0x24), planeM = v&2 (-> RED RAM 0x26). + - The hardware RAM encoding is generated at send time per epd_mode: + quality/text/fast : factory absolute LUT (planes sent as-is) + fastest : differential vs prev (lut_grayscale codes) + */ struct Panel_SSD1677 : public Panel_HasBuffer { Panel_SSD1677(void); + virtual ~Panel_SSD1677(void); bool init(bool use_reset) override; @@ -53,67 +72,52 @@ namespace lgfx protected: - static constexpr unsigned long _refresh_msec = 500; // Longer for 480x800 display - - struct lut_data_t - { - uint8_t lut[105]; - uint8_t gate[1]; - uint8_t source[3]; - uint8_t vcom[1]; - }; + // Longer busy ceiling for the 480x800 panel; full refresh ~3.8s. + static constexpr unsigned long _refresh_msec = 400; range_rect_t _range_old; unsigned long _send_msec = 0; epd_mode_t _last_epd_mode; - uint32_t _buf_x1_len; + uint32_t _buf_x1_len; // one plane length in bytes bool _initialize_seq; - bool _need_flip_draw; - bool _epd_frame_switching = false; - bool _epd_frame_back = false; + bool _screen_on = false; + + // Software "previous frame" planes (prevL, prevM). Allocated lazily and used + // only by the 4-gray differential (fastest) path; B/W and factory modes don't + // need it (B/W relies on the controller's RED RAM as the previous frame). + uint8_t* _prev_buf = nullptr; size_t _get_buffer_length(void) const override; + uint32_t _get_plane_length(void) const; bool _wait_busy(uint32_t timeout = 4096); void _draw_pixel(uint_fast16_t x, uint_fast16_t y, uint32_t value); uint8_t _read_pixel(uint_fast16_t x, uint_fast16_t y); void _update_transferred_rect(uint_fast16_t &xs, uint_fast16_t &ys, uint_fast16_t &xe, uint_fast16_t &ye); - void _exec_transfer(uint32_t cmd, const uint8_t* data, const range_rect_t& range, bool invert = false); - void _after_wake(void); - void _set_lut(const lut_data_t* lut_data); + // Set the controller RAM window (native coords; Y is gate, reversed in HW). + void _set_ram_area(int32_t x, int32_t y, int32_t w, int32_t h); + // Send one bit-plane region (native coords) to RAM command `cmd`. + void _send_plane(uint32_t cmd, const uint8_t* plane, const range_rect_t& range, bool invert = false); + + void _power_on(void); + void _after_wake(void); const uint8_t* getInitCommands(uint8_t listno) const override { - // SSD1677 initialization sequence - // LovyanGFX: panel_width=480, panel_height=800 (portrait) - // EPD RAM: X=800 (LovyanGFX Y), Y=480 (LovyanGFX X) - // 0x01 Driver output: 480 gates (EPD physical width) - // 0x44 RAM X: 0 to 799 (maps to LovyanGFX Y) - // 0x45 RAM Y: 0 to 479 (maps to LovyanGFX X) + // SSD1677 power-up sequence (community-sdk EInkDisplay, non-X3). + // panel native: 800(X=source) x 480(Y=gate). 0x01 sets 480 gates. + // RAM window / auto-clear / power-on are issued in _after_wake (computed). static constexpr uint8_t list0[] = { - 0x12, 0 + CMD_INIT_DELAY, 10, // SW Reset + 10 msec delay - 0x0C, 5, 0xAE, 0xC7, 0xC3, 0xC0, 0x80, // Booster soft start - 0x01, 3, (480-1) & 0xFF, ((480-1) >> 8) & 0xFF, 0x02, // Driver output control: 480 gates - 0x11, 1, 0x03, // Data entry mode: X+, Y+ - // 0x44, 4, 0x00, 0x00, (800-1) & 0xFF, ((800-1) >> 8) & 0xFF, // RAM X: 0 to 799 - // 0x45, 4, 0x00, 0x00, (480-1) & 0xFF, ((480-1) >> 8) & 0xFF, // RAM Y: 0 to 479 - // 0x4E, 2, 0x00, 0x00, // RAM X address count = 0 - // 0x4F, 2, 0x00, 0x00, // RAM Y address count = 0 - 0x3C, 1, 0x01, // BorderWaveform - 0x18, 1, 0x80, // Read built-in temperature sensor - 0x1A, 1, 0x5A, // 4 Gray - - 0x21, 1, 0x00, // 0x21: Display update control 1: normal update - 0x22, 1, 0xC0, - 0x20, 0, - + 0x12, 0 + CMD_INIT_DELAY, 10, // SW Reset + 10 msec + 0x18, 1, 0x80, // Temp sensor: internal + 0x0C, 5, 0xAE, 0xC7, 0xC3, 0xC0, 0x40, // Booster soft start + 0x01, 3, (480-1) & 0xFF, ((480-1) >> 8) & 0xFF, 0x02, // Driver output: 480 gates, SM=1/TB=0 + 0x3C, 1, 0x01, // Border waveform 0xFF, 0xFF, // end }; - switch (listno) { case 0: return list0; - // case 1: return list1; default: return nullptr; } } @@ -122,6 +126,13 @@ namespace lgfx struct Panel_SSD1677_4Gray : public Panel_SSD1677 { void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + protected: + bool _prev_valid = false; // _prev_buf holds a meaningful previous frame + epd_mode_t _last_gray_mode = (epd_mode_t)~0u; + + bool _ensure_prev_buf(void); + void _store_prev(void); // copy current _buf planes into _prev_buf }; //---------------------------------------------------------------------------- From b818cae5d9695dc16af107f575f7debf788812b4 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:53:25 +0900 Subject: [PATCH 3/4] tweak for esp-idf v5.0.x --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce592b8..e332635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,10 @@ file(GLOB SRCS set(COMPONENT_SRCS ${SRCS}) if (IDF_VERSION_MAJOR GREATER_EQUAL 5) - set(COMPONENT_REQUIRES nvs_flash efuse driver esp_timer esp_lcd esp_mm) + set(COMPONENT_REQUIRES nvs_flash efuse driver esp_timer esp_lcd) + if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.1") + list(APPEND COMPONENT_REQUIRES esp_mm) + endif() elseif ((IDF_VERSION_MAJOR EQUAL 4) AND (IDF_VERSION_MINOR GREATER 3) OR IDF_VERSION_MAJOR GREATER 4) set(COMPONENT_REQUIRES nvs_flash efuse esp_lcd) else() From f9c67a80191bd42a11ac0584cbd0b017f9fc4814 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:32:27 +0900 Subject: [PATCH 4/4] rising version 0.2.23 --- idf_component.yml | 2 +- library.json | 2 +- library.properties | 2 +- src/lgfx/v1/gitTagVersion.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/idf_component.yml b/idf_component.yml index bb53afb..874edf9 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -2,4 +2,4 @@ description: Graphics library for M5Stack series issues: https://github.com/m5stack/M5GFX/issues repository: https://github.com/m5stack/M5GFX.git url: https://github.com/m5stack/M5GFX.git -version: 0.2.22 +version: 0.2.23 diff --git a/library.json b/library.json index 22db7cb..ae0de4f 100644 --- a/library.json +++ b/library.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/m5stack/M5GFX.git" }, - "version": "0.2.22", + "version": "0.2.23", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5GFX.h" diff --git a/library.properties b/library.properties index 0777b72..81423fe 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5GFX -version=0.2.22 +version=0.2.23 author=M5Stack maintainer=M5Stack sentence=Library for M5Stack All Display diff --git a/src/lgfx/v1/gitTagVersion.h b/src/lgfx/v1/gitTagVersion.h index 76c682b..955eebf 100644 --- a/src/lgfx/v1/gitTagVersion.h +++ b/src/lgfx/v1/gitTagVersion.h @@ -1,4 +1,4 @@ #define LGFX_VERSION_MAJOR 1 #define LGFX_VERSION_MINOR 2 -#define LGFX_VERSION_PATCH 22 +#define LGFX_VERSION_PATCH 23 #define LOVYANGFX_VERSION F( LGFX_VERSION_MAJOR "." LGFX_VERSION_MINOR "." LGFX_VERSION_PATCH )