From e2c9d8f1e4850a202affd89c57b1377bb737d873 Mon Sep 17 00:00:00 2001 From: Matt Edholm Date: Fri, 15 May 2026 14:05:24 -0400 Subject: [PATCH] feat(13e6): cut panel power rail in deep sleep via PIN_PWR + GPIO hold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Waveshare board exposes PIN_PWR (GPIO 1) specifically so battery designs can gate the panel rail between refreshes. Before this commit PIN_PWR was driven HIGH at boot and never released, so the panel's boost converter kept its quiescent draw (50–500 µA) through every deep sleep. The e-ink particles are bistable so the displayed image persists without VDD; dropping the rail is a free win. Three pieces: • epd_sleep() drives PIN_PWR LOW after issuing the panel-internal DEEP_SLEEP command, then gpio_hold_en() latches the level so it survives the chip's RTC transition. • normal_operation_impl() calls gpio_deep_sleep_hold_en() just before esp_deep_sleep_start() so the per-pin hold extends through the deep sleep period itself (without this the holds release on the transition and the rail comes back up). • epd_setup_pins() calls gpio_hold_dis() at the very top to free PIN_PWR on wake before re-driving it HIGH; no-op on cold boot. Tests: 47/47 pass. Added test/mocks/driver/gpio.h with no-op stubs so the native test build links cleanly. Co-Authored-By: Claude Opus 4.7 --- src/operation.h | 8 ++++++++ src/panels/waveshare13e6/v1/epd_driver.cpp | 17 +++++++++++++++++ test/mocks/driver/gpio.h | 11 +++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/mocks/driver/gpio.h diff --git a/src/operation.h b/src/operation.h index 7104bf4..e193760 100644 --- a/src/operation.h +++ b/src/operation.h @@ -9,9 +9,11 @@ // The test build adds test/mocks to the include path via -iquote. #include "epd_mock.h" #include "esp_sleep.h" +#include "driver/gpio.h" #else #include "epd.h" #include +#include #include #endif @@ -354,6 +356,12 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre // false → normal_operation_impl runs → the next poll fetches a fresh // image, which doubles as a "force refresh" gesture. esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BTN_RESET, 0); + // Latch any gpio_hold_en pins through the deep sleep period. + // epd_sleep() cuts PIN_PWR LOW + holds it; without this call the + // hold releases on the RTC transition and the panel rail comes back + // up, losing the saving. Released per-pin in epd_setup_pins() on + // wake via gpio_hold_dis(). + gpio_deep_sleep_hold_en(); esp_deep_sleep_start(); } diff --git a/src/panels/waveshare13e6/v1/epd_driver.cpp b/src/panels/waveshare13e6/v1/epd_driver.cpp index 0e203e8..1a1af16 100644 --- a/src/panels/waveshare13e6/v1/epd_driver.cpp +++ b/src/panels/waveshare13e6/v1/epd_driver.cpp @@ -24,6 +24,7 @@ #include #include #include +#include static constexpr uint16_t W = 1200; static constexpr uint16_t H = 1600; @@ -103,6 +104,12 @@ static void panel_reset() { } void epd_setup_pins() { + // Release the PIN_PWR hold latched from the previous deep sleep so we + // can drive it again. No-op on cold boot (nothing was held). Paired + // with gpio_hold_en() in epd_sleep() and gpio_deep_sleep_hold_en() in + // operation.h. + gpio_hold_dis((gpio_num_t)PIN_PWR); + pinMode(PIN_PWR, OUTPUT); pinMode(PIN_RST, OUTPUT); pinMode(PIN_DC, OUTPUT); @@ -191,6 +198,16 @@ void epd_sleep() { SPI.transfer(0xA5); // sentinel cs_both(HIGH); s_initialized = false; + + // Cut the panel power rail. The Waveshare board exposes PIN_PWR + // specifically for battery operation — the e-ink image persists + // without VDD (particles are bistable), and dropping the rail kills + // the boost converter's quiescent draw (~50–500 µA depending on the + // load on the rail). Latch LOW so it survives deep sleep; paired with + // gpio_deep_sleep_hold_en() in operation.h just before the chip + // enters sleep. + digitalWrite(PIN_PWR, LOW); + gpio_hold_en((gpio_num_t)PIN_PWR); } // ── Draw helpers ─────────────────────────────────────────────────────────────── diff --git a/test/mocks/driver/gpio.h b/test/mocks/driver/gpio.h new file mode 100644 index 0000000..363d368 --- /dev/null +++ b/test/mocks/driver/gpio.h @@ -0,0 +1,11 @@ +#pragma once +// Native-test stubs for gpio_hold_* — operation.h calls +// gpio_deep_sleep_hold_en() before esp_deep_sleep_start() to latch +// PIN_PWR LOW through deep sleep. epd_driver.cpp (not built natively) +// also uses gpio_hold_en / gpio_hold_dis. Stubs let tests link. +#include "esp_sleep.h" // for gpio_num_t + +inline void gpio_hold_en(gpio_num_t) {} +inline void gpio_hold_dis(gpio_num_t) {} +inline void gpio_deep_sleep_hold_en() {} +inline void gpio_deep_sleep_hold_dis() {}