todd-herbert / heltec-eink-modules

Third-party Arduino Library for Heltec E-Ink displays
18 stars 4 forks source link

Partial refresh inbetween deep sleeps #11

Closed allllo20 closed 6 months ago

allllo20 commented 6 months ago

First of all, thank you so much for this project! It turned e-waste into something usable..

I have a specific problem that I tried in various ways to approach but none seem to work. I'm trying to code a clock with an esp32 that updates the time on the e-paper display after every minute and then goes to deep sleep for the rest of the time. The idea is to do a full update every full hour and the rest of the time should be partial (or fast-)updates.

The GxEPD2 library allows to skip the initial refresh of the display when initializing. I can't find anything similar here. I basically tried the "fast_mode" example but added a deep sleep command like this:

void setup() {
    display.setTextSize(2);
    display.fastmodeOn();

    display.setCursor(0, f.bottom() - 30);
    display.println("Fastmode:");
    display.println("On");
    display.update();

    display.setTextColor(WHITE);
    display.setWindow( f.left(), f.top(), f.width(), f.height() - 35 ); // Don't overwrite the bottom 35px

    for (int demo = 0; demo <= 5; demo++) { // Count up to 5
      display.drawXBitmap(ICON_L, ICON_T, hourglasses[demo % 3], hourglass_1_width, hourglass_1_height, BLACK);
      display.fillRect(0, 0, 30, 30, BLACK);
      display.setCursor(10, 10);
      display.print(demo);
      display.update();
    }
    display.fastmodeOff();

    esp_sleep_enable_timer_wakeup(2*1000000ULL);
    esp_deep_sleep_start();
}
todd-herbert commented 6 months ago

~~Ah yes, I know what you mean. The fact that the screen clears after a reboot was originally a deliberate design decision. I always figured a bunch of people would reboot with an existing image on the screen, and then end up with it smearing together with the first fast-mode update.~~

I hadn't actually considered a situation where the device is waking from deep-sleep, but it's definitely an important use case. It would be nice to disable the clear-at-boot behavior if the library can detect that a device is waking from sleep, but this might be a bit fiddly to implement across all the different platforms. I will probably look into though.

For now, it shouldn't be hard to add an optional argument to bypass the clear-at-boot- behavior. I should be able to get this out in the next few hours.

todd-herbert commented 6 months ago

Hmm, okay. I've had a look at the library code. Apparently only devices with limited RAM, such as Arduino Uno, actually enforce a full-refresh clear at boot. With ESP32, no refresh takes place, but the library does wipe the memory.

I will get an update out shortly, which allows bypass of this initial memory wipe.


If I remember, this memory wipe is done to clear the white-noise pattern which fills the display memory if power is removed for >10 seconds. This memory loss is problematic for fast-mode, due to what goes on behind-the-scenes.

For a normal refresh, no problem: the display just blinks and flashes until all the pixels are wrangled into the right spot. For fast-mode though, the display hardware needs to keep track of two sets of data: the new image for update, and the existing image on screen. When the update happens, the display only moves pixels which have changed between the old and new image. It is really a "differential update".

If the display loses power for an extended period (>10 seconds), the memory will be lost, and the display will believe that the "existing image" is just a white-noise pattern. It will be unable to move the correct pixels for a fast-mode update.

I mention this because I see you are trying to minimize power usage with esp_deep_sleep_start(). I believe the displays have quite a low current-draw at standby, however this library does not put them into a true "deep-sleep" state. The reason is that Heltec did not break out the display's reset pin, so the only way to exit the "deep-sleep" state is to completely remove power from the display.

The library does have code to support this, however it is not currently suitable for your use-case. The custom power switch feature is designed to remove all power from the display. It was not written with the intention of using the display's "deep-sleep" method. Because extended power-removal causes display memory loss, it is not possible to use fast-mode immediately after customPowerOn().

What I can look into is writing separate methods to allow the same transistor set-up to use deep-sleep, and then a short power-down (>4s, <10s) to escape the deep-sleep mode. This might allow fast-mode directly after deep-sleep.

Let me know if you think this last option might be helpful, and I can prioritize exploring.

todd-herbert commented 6 months ago

What I can look into is writing separate methods to allow the same transistor set-up to use deep-sleep, and then a short power-down (>4s, <10s) to escape the deep-sleep mode. This might allow fast-mode directly after deep-sleep.

I experimented with this today, but there was no appreciable power-saving. Without a physical RST pin, I don't think these Heltec displays can really make use of their deep-sleep function (different from the microcontroller's deep sleep).

todd-herbert commented 6 months ago

Using fast-mode with ESP32 deep-sleep is now supported as of v4.2.0. You can install this right now by downloading the .zip release from github, or in the next few hours via the Arduino Library Manager.

You should pass the argument fastmodeOn(false) to bypass the initial memory clearing.

Note that the code in the 1st post would also need a call to clearMemory() at the start of each for loop. By design, update() does not erase your working image, allowing you to add to it by drawing BLACK, or erase by drawing WHITE. Calling clearMemory() will return the working image to the background color, before drawing the new and different hourglass graphic.

If you are not already doing so, I would suggest calling clear() at first boot, to make sure both the display and the memory are blank.

void setup() {
    if (esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_TIMER)
        display.clear();

    display.setTextSize(2);
    display.fastmodeOn(false);  // false to disable memory-init

    display.setCursor(0, f.bottom() - 30);
    display.println("Fastmode:");
    display.println("On");
    display.update();

    display.setTextColor(WHITE);
    display.setWindow( f.left(), f.top(), f.width(), f.height() - 35 ); // Don't overwrite the bottom 35px

    for (int demo = 0; demo <= 5; demo++) { // Count up to 5
      display.clearMemory();    // Start with a blank image
      display.drawXBitmap(ICON_L, ICON_T, hourglasses[demo % 3], hourglass_1_width, hourglass_1_height, BLACK);
      display.fillRect(0, 0, 30, 30, BLACK);
      display.setCursor(10, 10);
      display.print(demo);
      display.update();
    }
    display.fastmodeOff();

    esp_sleep_enable_timer_wakeup(15*1000000ULL);
    esp_deep_sleep_start();
}

Don't overdo the fast-mode or the display will get a nasty afterimage. Once an hour might not be enough for a normal refresh; you might need to experiment. If you do overdo it and need to rescue the display, running a bunch of normal refreshes of random images seems to help.

Thanks for taking the time to point out the issue. Let me know how you get on!

allllo20 commented 5 months ago

@todd-herbert hey there again! I wasn't able to check back until today after the issue, but man.. thank you so much for the extensive explanation and blazing fast fix! Everything's working now as expected and I can't thank you enough. Is there a way I can spend you a coffee via PayPal or anything?

todd-herbert commented 5 months ago

Is there a way I can spend you a coffee via PayPal or anything?

No need, but thank you! Just glad to see you get the hardware working.