Write image in pieces from TCP stream ESP8266 x GxEPD2

Introduction

After a long break I try again on an e-paper project with ESP8266, Arduino and the GxEPD2. Last time the project failed because of the energy hunger of my solution. Currently I try to write a bitmap provided by my server in parts to the display. To avoid a long activity of the WiFi module, the image should reach the display without writing page by page. This process took more than 20 seconds in the past.

Vision/Problem

The basic idea of the new solution is relatively simple: from the TCP stream of the server, one line of the image should always be buffered and then written to the display. Afterwards the image is displayed. However, my understanding of the GxEPD2 does not seem to be sufficient. The display flickers and then is only white to see.

Beyond my current code, I have no ideas to address the problem. Any tips on a possible solution would really help me.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <GxEPD2_BW.h>

#include "ops.h"
#include "wifi.h"

// GDEW075T7 800x480 GxEPD 2 Implementation
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(
    GxEPD2_750_T7(/*CS=D8*/ 5, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 15));

static char ssid[] = "*";
static char psk[] = "*";

static IPAddress host(192, 168, 137, 1);
static uint16_t port = 4200;

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.printf("\nConnecting to ssid: %s, psk: %s\n", ssid, psk);
  WiFi.begin(ssid, psk);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.printf("Status %i\n", WiFi.status());
  }
  Serial.println("Connection established!");
}

void loop() {
  WiFiClient wc;
  if (!wc.connect(host, port)) {
    Serial.println("Failed to connect");
    delay(60000);
    ESP.restart();
  }
  FetchRequest req{};
  FetchResponse res{
      .error = 0xff,
  };
  if (!fetch(wc, req, res)) {
    Serial.println("Failed to fetch");
    delay(60000);
    ESP.restart();
  }
  display.init();
  display.setRotation(res.rotation);
  for (int16_t y = 0; y < display.height(); y++) {
    if (!wc.available()) {
      Serial.println("Connection closed prematurely");
      delay(60000);
      ESP.restart();
    }
    uint16_t w = display.width();
    uint8_t line[w / 8];
    wc.readBytes(line, sizeof(line));
    display.drawImagePart(line, 0, y,  // x_part, y_part: position in the part
                           w, 1,        // w_bitmap, h_bitmap: size of the part
                           0, y,        // x, y: position on the display
                           w, 1);       // w, h: size on the display
  }
  display.display();
  delay(60000);
  ESP.restart();
}

@plaenkler, hi again!

Let me suggest a different approach. Load your BMP file to ESP8266 flash, then turn off WiFi, then draw your BMP file from flash to display.
This would be a combination of the examples GxEPD2_Spiffs_Loader.ino and GxEPD2_Spiffs_Example.ino.
SPIFFS is the name used on ESP32. On ESP8266 it is known as LittleFS.

I think about adding such an example to GxEPD2, as it is useful or needed for big e-paper displays and 4-color or 7-color e-paper displays.
-jz-

1 Like

A good idea to reduce power consumption. However, this approach comes with disadvantages. Suppose I want to update the image every hour, how long will the flash survive? I'm worried about this approach simply because I don't know enough about it. That's why I thought the above approach would be more attractive to pursue, but it's not possible?

I don't know how many times you can write the ESP8266 flash. I would check the docs.
https://www.espressif.com/en/support/documents/technical-documents

I didn't look at the details of your code, as I would use the other approach.
Instead of display.drawImagePart you could use display.writeImagePart to write to the controller RAM, and call refresh at the end.

An other option would be to add SRAM (a SPI RAM chip) and use a filesystem on it.

Thanks for the good advice. I have decided to give both ideas a chance, but I am again faced with a conundrum. I have used writeImagePart(...) and refresh() according to your instruction, but the display remains white except for a black line at one edge.

platform.ini:

[env:test]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps = 
	mbed-yasuyuki/EEPROM@0.0.0+sha.25e5bc71e65f
	adafruit/Adafruit GFX Library@1.10.13
	adafruit/Adafruit BusIO@1.14.1
	adafruit/RTClib@2.1.1
	zinggjm/GxEPD2@1.4.9
	Wire
	SPI
monitor_filters = esp8266_exception_decoder
test_speed = 115200
monitor_speed = 115200
upload_speed = 460800
build_type = debug

Log:

Connecting to ssid: *, psk: *
Status 7
Status 7
Status 7
Status 3
Connection established!
Busy Timeout!

Code:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <GxEPD2_BW.h>

#include "ops.h"
#include "wifi.h"

// GDEW075T7 800x480 GxEPD 2 Implementation
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(
    GxEPD2_750_T7(/*CS=D8*/ 5, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 15));

static char ssid[] = "*";
static char psk[] = "*";

static IPAddress host(192, 168, 137, 1);
static uint16_t port = 4200;

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.printf("\nConnecting to ssid: %s, psk: %s\n", ssid, psk);
  WiFi.begin(ssid, psk);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.printf("Status %i\n", WiFi.status());
  }
  Serial.println("Connection established!");
}

void loop() {
  WiFiClient wc;
  if (!wc.connect(host, port)) {
    Serial.println("Failed to connect");
    delay(60000);
    ESP.restart();
  }
  FetchRequest req{};
  FetchResponse res{
      .error = 0xff,
  };
  if (!fetch(wc, req, res)) {
    Serial.println("Failed to fetch");
    delay(60000);
    ESP.restart();
  }
  display.init();
  display.setRotation(res.rotation);
  const int16_t dh = display.height();
  const int16_t dw = display.width();
  for (int16_t y = 0; y < dh; y++) {
    if (!wc.available()) {
      Serial.println("Connection closed prematurely");
      break;
    }
    uint8_t line[dw / 8];
    wc.readBytes(line, sizeof(line));
    display.writeImagePart(line, 0, y, dw, 1, 0, y, dw, 1);
  }
  wc.stop();
  display.refresh();
  delay(60000);
  ESP.restart();
}

Schematics:

I didn't look at the details, so I didn't notice this error.

Your code seems to fetch line per line, and intends to write each line to the controller.
This is exactly what is done in GxEPD2_WiFi_Example.ino.
See e.g. line 692:

          display.writeImage(output_row_mono_buffer, output_row_color_buffer, x, yrow, w, 1);

Signature of the method used is:

virtual void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;

You could also use the monochrome one:

virtual void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;

If you prefer to use:

    virtual void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
                                int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) = 0;

x_part and y_part would need to be zero, as your line "part" starts at the origin.

BTW: does your upstream code convert the BMP format to raw monochrome bitmap format?
We don't know what this line delivers:

1 Like

BTW: does your upstream code convert the BMP format to raw monochrome bitmap format?
We don't know what this line delivers:

Yes, the image is delivered in raw monochrome bitmap format. The server transmits (800*480)/8 = 48000 bytes. Each bit corresponds to a pixel.

Ok, then this is the optimum solution. You can switch off WiFi before refresh for minimum power use.

I have started the attempt but run into new problems😂. When I write to the display in the process, the WiFi client reports after a few iterations that no more bytes are available. If I comment out writeScreenBuffer/writeImagePart all bytes can be read from the connection without problems.

Log:

Connecting to ssid: *, psk: *
fpm close 3
mode : sta(bc:dd:c2:7a:cf:8b)
add if0
Status 7
Status 7
scandone
state: 0 -> 2 (b0)
Status 7
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 1
cnt
connected with *, channel 11
dhcp client start...
ip:192.168.137.47,mask:255.255.255.0,gw:192.168.137.1
Status 3
Connection established!
Free heap: 2632
KeepAlive: 1
Init display done Rotation: 0
pm_err,flash_tmp alloc fail
Busy Timeout!
Wrote line 0
Wrote line 1
Wrote line 2
Wrote line 3
Wrote line 4
Wrote line 5
Wrote line 6
Wrote line 7
Wrote line 8
Wrote line 9
Wrote line 10
Wrote line 11
Wrote line 12
Wrote line 13
Wrote line 14
Wrote line 15
Wrote line 16
Connection closed prematurely
Busy Timeout!

Code:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <GxEPD2_BW.h>

#include "ops.h"

// GDEW075T7 800x480 GxEPD 2 Implementation
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(
    GxEPD2_750_T7(/*CS=D8*/ 5, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 15));

static char ssid[] = "*";
static char psk[] = "*";

static IPAddress host(192, 168, 137, 1);
static uint16_t port = 4200;

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  delay(10);
  Serial.printf("\nConnecting to ssid: %s, psk: %s\n", ssid, psk);
  WiFi.begin(ssid, psk);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.printf("Status %i\n", WiFi.status());
  }
  Serial.println("Connection established!");
}

void loop() {
  Serial.printf("Free heap: %i\n", ESP.getFreeHeap());
  WiFiClient wc;
  if (!wc.connect(host, port)) {
    Serial.println("Failed to connect");
    delay(60000);
    ESP.restart();
  }
  wc.keepAlive(1, 1, 3);
  Serial.printf("KeepAlive: %i\n", wc.isKeepAliveEnabled());
  FetchRequest req{};
  FetchResponse res{
      .error = 0xff,
  };
  if (!fetch(wc, req, res)) {
    Serial.println("Failed to fetch");
    delay(60000);
    ESP.restart();
  }
  display.init();
  display.writeScreenBuffer();
  display.setRotation(res.rotation);
  Serial.printf("Init display done Rotation: %i\n", res.rotation);
  const int16_t dh = display.height();
  const int16_t dw = display.width();
  uint8_t line[dw / 8] = {0x00};
  for (int16_t y = 0; y < dh; y++) {
    if (!wc.available()) {
      Serial.println("Connection closed prematurely");
      break;
    }
    wc.readBytes(line, sizeof(line));
    display.writeImagePart(line, 0, 0, dw, 1, 0, y, dw, 1);
    Serial.printf("Wrote line %i\n", y);
  }
  wc.abort();
  display.refresh();
  delay(60000);
  ESP.restart();
}

I don't understand how this would compile for ESP8266. It doesn't have enough RAM for full buffered display for this panel.

You would need to tell which board you select to compile for.

Where does this come from?

expect to fail then.

I don't have time for further analysis.

That's what I thought, but how do I work with the HEIGHT/2 definition without writing it page by page? Does that work?


bool fetch(WiFiClient &c, FetchRequest &request, FetchResponse &response) {
  // Send data
  request.flag = 0x03;
  byte wbuffer[53];
  memcpy(&wbuffer, &request, sizeof(request));
  c.write(wbuffer, sizeof(wbuffer));
  // Receive preflight data
  byte rbuffer[6] = {0xff};
  c.readBytes(rbuffer, sizeof(rbuffer));
  if (rbuffer[0] != 0) {
    response.error = rbuffer[0];
    return false;
  }
  memcpy(&response, &rbuffer, sizeof(rbuffer));
  return true;
}

The request to the server that transmits edge parameters such as rotation before the image.

I already suspected all this. Thank you first of all you have already more than diligently supported. If you could answer my first question in this reply I think we can retire the thread. Tank you and best regards Simon

Your code doesn't use the graphics buffer, it writes directly to the controller memory.

Still don't know the content of that file.

That was the solution! I only now understood that the buffer is then no longer needed!
It works i am so happy! :tada: :tada:
I've also been able to reduce RAM usage to almost nothing!

The file only contains the one function and the “proto” definitions of the request and response.

Log:

Connecting to ssid: *, psk: *
fpm close 3
mode : sta(bc:dd:c2:7a:cf:8b)
add if0
Status 7
Status 7
scandone
state: 0 -> 2 (b0)
Status 7
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 1
cnt
connected with workstation, channel 11
dhcp client start...
ip:192.168.137.54,mask:255.255.255.0,gw:192.168.137.1
Status 3
Connection established!
Free heap: 50496
KeepAlive: 1
pm open,type:2 0
Busy Timeout!
Init display done Rotation: 0
Wrote line 0
...
Wrote line 479
Busy Timeout!
Busy Timeout!
Refreshed display

Code:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <GxEPD2_BW.h>

#include "ops.h"

// GDEW075T7 800x480 GxEPD 2 Implementation
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT/480> display(
    GxEPD2_750_T7(/*CS=D8*/ 5, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 15));

static char ssid[] = "*";
static char psk[] = "*";

static IPAddress host(192, 168, 137, 1);
static uint16_t port = 4200;

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  delay(10);
  Serial.printf("\nConnecting to ssid: %s, psk: %s\n", ssid, psk);
  WiFi.begin(ssid, psk);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.printf("Status %i\n", WiFi.status());
  }
  Serial.println("Connection established!");
}

void loop() {
  Serial.printf("Free heap: %i\n", ESP.getFreeHeap());
  WiFiClient wc;
  if (!wc.connect(host, port)) {
    Serial.println("Failed to connect");
    delay(60000);
    ESP.restart();
  }
  wc.keepAlive(1, 1, 3);
  Serial.printf("KeepAlive: %i\n", wc.isKeepAliveEnabled());
  FetchRequest req{};
  FetchResponse res{
      .error = 0xff,
  };
  if (!fetch(wc, req, res)) {
    Serial.println("Failed to fetch");
    delay(60000);
    ESP.restart();
  }
  display.init();
  display.writeScreenBuffer();
  display.setRotation(res.rotation);
  Serial.printf("Init display done Rotation: %i\n", res.rotation);
  const int16_t dh = display.height();
  const int16_t dw = display.width();
  uint8_t line[dw / 8] = {0x00};
  for (int16_t y = 0; y < dh; y++) {
    if (!wc.available()) {
      Serial.println("Connection closed prematurely");
      break;
    }
    wc.readBytes(line, sizeof(line));
    display.writeImagePart(line, 0, 0, dw, 1, 0, y, dw, 1);
    Serial.printf("Wrote line %i\n", y);
  }
  wc.abort();
  display.refresh();
  Serial.println("Refreshed display");
  Serial.flush();
  delay(10000);
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.