Skip redundant panel refreshes and fetch the image before the config check
The panel driver now splits writing a frame into its SPI buffer (epd_write_frame(), which also computes a CRC32 as it streams) from actually triggering the physical refresh (epd_turn_on_display()). frame_client.c compares the new CRC against the last one that was actually refreshed (persisted in NVS) and skips the refresh entirely when they match -- e.g. a reboot redisplaying the same photo before the server's refresh interval elapsed no longer causes a visible flash for no visual change. Also reorders the per-wake fetch cycle: the image fetch (15s timeout) now goes before the config check (3s timeout), instead of after. The config check's tighter timeout was intermittently tripping on connection-setup latency that's common on the first request after waking from a long deep sleep (e.g. stale ARP); putting the more tolerant request first absorbs that latency, and the config check then rides the connection it already warmed up.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "freertos/task.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_rom_crc.h"
|
||||
|
||||
#include "epd7in3e.h"
|
||||
|
||||
@@ -95,7 +96,7 @@ static void epd_reset(void)
|
||||
|
||||
/* Power on, "second setting" registers, refresh, power off -- mirrors
|
||||
* EPD_7IN3E_TurnOnDisplay() in the reference driver. */
|
||||
static esp_err_t epd_turn_on_display(void)
|
||||
esp_err_t epd_turn_on_display(void)
|
||||
{
|
||||
EPD_CHECK(epd_send_command(0x04)); // POWER_ON
|
||||
epd_wait_busy();
|
||||
@@ -204,7 +205,7 @@ esp_err_t epd_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
||||
esp_err_t epd_write_frame(epd_read_fn_t read_fn, void *ctx, uint32_t *out_crc32)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(read_fn != NULL, ESP_ERR_INVALID_ARG, TAG, "read_fn required");
|
||||
|
||||
@@ -220,6 +221,7 @@ esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
||||
* reentrant, but this driver only ever runs from one task at a time. */
|
||||
static uint8_t chunk[EPD_SPI_CHUNK_SIZE];
|
||||
size_t total = 0;
|
||||
uint32_t crc = 0;
|
||||
size_t n;
|
||||
esp_err_t err = ESP_OK;
|
||||
while ((n = read_fn(chunk, sizeof(chunk), ctx)) > 0) {
|
||||
@@ -227,6 +229,7 @@ esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
||||
if (err != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
crc = esp_rom_crc32_le(crc, chunk, n);
|
||||
total += n;
|
||||
}
|
||||
|
||||
@@ -235,18 +238,31 @@ esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
||||
|
||||
if (total != EPD_FRAME_BYTES) {
|
||||
/* Whatever was received has already been clocked into the panel's
|
||||
* internal RAM over SPI, but epd_turn_on_display() (the actual
|
||||
* physical refresh trigger) hasn't been called yet -- returning
|
||||
* here instead leaves the visible screen exactly as it was, rather
|
||||
* than refreshing onto a mostly-garbage buffer. Confirmed on
|
||||
* hardware: a misdirected fetch that returned a ~10KB error page
|
||||
* instead of a 192,000-byte frame still triggered a refresh before
|
||||
* this check existed, painting garbage over a previously-good image. */
|
||||
* internal RAM over SPI, but the physical refresh trigger hasn't
|
||||
* been called -- returning here instead leaves the visible screen
|
||||
* exactly as it was, rather than refreshing onto a mostly-garbage
|
||||
* buffer. Confirmed on hardware: a misdirected fetch that returned
|
||||
* a ~10KB error page instead of a 192,000-byte frame still
|
||||
* triggered a refresh before this check existed, painting garbage
|
||||
* over a previously-good image. */
|
||||
ESP_LOGE(TAG, "Stream supplied %u bytes, expected %u -- aborting refresh",
|
||||
(unsigned)total, (unsigned)EPD_FRAME_BYTES);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (out_crc32 != NULL) {
|
||||
*out_crc32 = crc;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
||||
{
|
||||
esp_err_t err = epd_write_frame(read_fn, ctx, NULL);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return epd_turn_on_display();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,29 @@ typedef size_t (*epd_read_fn_t)(uint8_t *chunk, size_t chunk_size, void *ctx);
|
||||
*/
|
||||
esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx);
|
||||
|
||||
/**
|
||||
* Like epd_display_stream(), but writes the frame into the panel's
|
||||
* internal buffer over SPI WITHOUT triggering the physical refresh (the
|
||||
* visible flash/flicker, which also takes 15-30+ seconds) -- call
|
||||
* epd_turn_on_display() separately to make it visible. Returns
|
||||
* ESP_ERR_INVALID_SIZE if read_fn didn't supply exactly EPD_FRAME_BYTES,
|
||||
* same as epd_display_stream(); either way nothing is refreshed, so the
|
||||
* visible screen is left untouched on error.
|
||||
*
|
||||
* If out_crc32 is non-NULL, it's set to a CRC32 of the bytes written --
|
||||
* lets a caller compare against the last-displayed frame's CRC and skip
|
||||
* the refresh entirely when nothing actually changed (e.g. redisplaying
|
||||
* the same photo after a reboot).
|
||||
*/
|
||||
esp_err_t epd_write_frame(epd_read_fn_t read_fn, void *ctx, uint32_t *out_crc32);
|
||||
|
||||
/**
|
||||
* Triggers the panel's physical refresh cycle (power on, refresh, power
|
||||
* off) -- the visible flash/flicker sequence, 15-30+ seconds. Call after
|
||||
* epd_write_frame() to make the written buffer visible.
|
||||
*/
|
||||
esp_err_t epd_turn_on_display(void);
|
||||
|
||||
/** Convenience wrapper around epd_display_stream() for an in-memory frame buffer. */
|
||||
esp_err_t epd_display_buffer(const uint8_t *frame, size_t len);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user