From 4ae3d22654ea66206609cbd691e5f05d279b4558 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:40:01 +0200 Subject: [PATCH] perf(oled): skip I2C flush when the frame is unchanged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SH1106 path always pushed the full 1 KB buffer over I2C every endFrame, even on static screens (clock, home) that don't change between updates. Hash the GFX buffer (FNV-1a, no external dep — the CRC32 lib is only wired into e-ink builds) and skip display.display() when it matches the last frame pushed. _force_redraw forces a flush on the first frame and after turnOn()/clear() so a post-wake or post-clear frame can't be wrongly skipped. Mirrors the existing e-ink CRC-skip. Builds green: WioTrackerL1_companion_solo_dual. Co-Authored-By: Claude Opus 4.8 --- src/helpers/ui/SH1106Display.cpp | 14 ++++++++++++++ src/helpers/ui/SH1106Display.h | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index 2adfa044..60f6b9e9 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -23,6 +23,7 @@ void SH1106Display::turnOn() display.oled_commandList(pre, 2); display.setContrast(_contrast); _isOn = true; + _force_redraw = true; // panel was off — guarantee the next endFrame() flushes } void SH1106Display::turnOff() @@ -35,6 +36,7 @@ void SH1106Display::clear() { display.clearDisplay(); display.display(); + _force_redraw = true; // next endFrame() must flush even if its CRC matches a pre-clear frame } void SH1106Display::startFrame(Color bkg) @@ -189,5 +191,17 @@ void SH1106Display::setBrightness(uint8_t level) void SH1106Display::endFrame() { + // Skip the I²C flush when the frame is byte-identical to the last one pushed. + // The most-shown screens (clock, home) are static between updates, so this + // cuts redundant display() traffic and a little power. FNV-1a over the 1 KB + // GFX buffer (~1k xor+mul, cheap); _force_redraw guarantees the first frame + // and the frame after wake/clear. + const uint8_t* buf = display.getBuffer(); + uint16_t n = (uint16_t)((width() * height()) / 8); + uint32_t h = 2166136261u; + for (uint16_t i = 0; i < n; i++) { h ^= buf[i]; h *= 16777619u; } + if (!_force_redraw && h == _last_frame_hash) return; + _force_redraw = false; + _last_frame_hash = h; display.display(); } diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index ef78a9a5..e8e54f1c 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -23,6 +23,12 @@ class SH1106Display : public DisplayDriver uint8_t _precharge; bool _use_lemon = false; int _text_sz; + // Frame-skip: endFrame() hashes the GFX buffer (FNV-1a, no external dep — the + // CRC32 lib is only wired into e-ink builds) and skips the I²C flush when it's + // byte-identical to the last one pushed. _force_redraw guarantees the first + // frame and the frame after turnOn()/clear() always flush. + uint32_t _last_frame_hash = 0; + bool _force_redraw = true; bool i2c_probe(TwoWire &wire, uint8_t addr); int16_t drawLemonChar(int16_t x, int16_t y, uint32_t cp);