GxEPD2: support for 10" four-color GDEM102F91

Hello there,

I recently bought this eink screen thinking i would use my knowledge with the inkplate 10 to tinker with it, but it looks harder than i though ... (i am not a C dev)

But lucky me, i discovered that there is a fantastic library that cover all the hard work !
Except, bummer, this particular display is not yet supported...

So i looked around and it seems the best way to ask for support is through this forum.
Maybe @ZinggJM the creator of the lib will see this message !!

Thank a lot!

I have to add that i tried using the lib with a similar screen config : GDEY116F51 ( same colors, same resolution ) but there is nothing happening... the screen does not even update...

I would have tried the same for a beginning. Same controller is the most important "same".

10.2-inch large ePaper four color E Ink screen 960x640, GDEM102F91_GooDisplay

  • Driver IC: SSD2677

Please report all details: processor used, connection module used, wiring used, pin-to-pin, RESE switch used if DESPI-C02. Power supply may also be an issue with these big displays.
-jz-

And take a look at diagnostic output from GxEPD2 in Serial Monitor. And post it in a code window.

Welcome to the forum! btw.

Thank you!

I'm using the demo kit provided by GoodDisplay: ESP32-L + DESPI-C02 + USB powered
RESE used is 0.47
Not sure what you mean by pin-to-pin

The system is working with the default code example provided by GD: https://www.good-display.com/companyfile/1427.html

About your HelloWorld example:

  • i uncomment line 23 & 113 in display_selection_new_style.h
  • tried both line 47/48 of HelloWorld.ino with the same result ...

The following is the Serial Monitor debug

Busy Timeout!
_InitDisplay reset : 25001103
_InitDisplay reset : 21954998
_PowerOn : 935000
Busy Timeout!
_InitDisplay reset : 25001103
ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:4604
ho 0 tail 12 room 4
load:0x40078000,len:15488
load:0x40080400,len:4
load:0x40080404,len:3180
entry 0x400805b8
Busy Timeout!

Uncomment line 216:

//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13)); // Good Display ESP32 Development Kit ESP32-L

RESE should be 3 Ohms for SSD controller. Take a look at the reference circuit in the panel specs.

Pin-to-pin is answered by the board you use, but needs to correspond to the constructor parameters.

GP Brasilia F1 has just got red-flagged, so I have time to answer.
-jz-

Thanks gentlemen, it is WORKING !! the line 216 did the trick !

3 ohms or 0.47 seems to be working equivalently... i can't find reference in the docs provided by GD so i gonna stick with 3 ohms

I will test the others GxEPD2 examples and will let you know.

Have a good evening!

You can find it on page 21 of GDEM102F91.pdf.

In other specs docs it is called reference circuit.

Good you got it working!
-jz-

Well, Gx_EPD2_HelloWorld and Gx_EPD2_Example are working seamlessly :+1:

Some trouble with Gx_EPD2_WiFi_Example, i had to add these lines:

#if defined (ESP32)
#include <WiFi.h>
#endif

and modify this line, because WiFi.getAutoConnect() does not exist in WiFi.h ...

if (!WiFi.getAutoReconnect() || ( WiFi.getMode() != WIFI_STA) || ((WiFi.SSID() != ssid) && String(ssid) != "........"))
  {

but not sure it is relevant !

Many thanks for this very usefull library ! I could not imagine how i would make this work without this ^^

Last question: does image display functions are limited to .bmp format ? what about .jpg or .png ?

Thx,
Simon

Thank you for reporting this issue!

I had tested with GxEPD2_WiFi_Example about a year ago last time.
There has been a major update of the ESP32 package in the meantime.
WiFiClient.h used to include WiFi.h, I think.
But no longer: WiFiClient.h is just:

#pragma once
#include "NetworkClient.h"
typedef NetworkClient WiFiClient;

and getAutoReconnect() had been marked as deprecated, but still existed.

I have given up doing a sort of regression tests for new releases; there are just too many test cases. But as GxEPD2_WiFi_Example is one of the important examples, I should at least do a compile test.

Yes, GxEPD2 only supports files in BMP format.
The purpose is to show how partial windows can be used to efficiently write line per line to the controller buffer. The examples are just examples; they can serve as a base for any format users want to implement.

You can search for examples of other formats in TFT libraries, e.g. Bodmer/TFT_eSPI or moononournation/Arduino_GFX
-jz-

Hello again,

Still having a problem with your _WiFi_Example, the image is not fully rendered... there is a vertical black box to the right of the screen...

Could not figuring why...
The screen is OK, picture looking goods with other methods
I'm using showBitmapFrom_HTTP()

Edit: Oh also there is a white horizonal bar at the top !

Thank you for reporting this issue!
I had tested with GxEPD2_WiFi_Example about a year ago last time.

I didn't have time to test this example with 4-color displays when I added support for these to GxEPD2. And currently I also have no time for this. I will take a look at it later.
Support for 4-color rendering is missing in this example. Why 3 colors don't work on this display needs be analyzed.

You could try if it works with method showBitmapFrom_HTTP_Buffered().

Please post your BMP file, in a ZIP-file. Else I would need to go searching for a picture with these dimensions.
-jz-

Same result with showBitmapFrom_HTTP_Buffered... a big black box on the right of the screen.

Here is the BMP file:
screenshot.zip (66,0 Ko)

Thank you. I will take a look at it when time and fun with my hobby have returned to me.

static const uint16_t input_buffer_pixels = 800; // may affect performance

Maybe the logic behind this is not correct. You could try with 960.

Hello again !

I have managed to display a full image with all 4 colors !!!
I used a mix of your WiFi example and the example provided by GoodDisplay

I put the code bellow for reference.
Thanks for your help!

#include <SPI.h>
//EPD
#include "Display_EPD_W21_spi.h"
#include "Display_EPD_W21.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include "HTTPClient.h"  

#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */

const char* ssid     = "*******";
const char* password = "*******";
const int httpPort  = 80;
const int httpsPort = 443;
const int sleepTime = 60; // min
const int wifiTimeout = 30; // seconds
const int serverTimeout = 60; // seconds

const int SCREEN_WIDTH = 960;
const int SCREEN_HEIGHT = 640;

void setup() {

  Serial.begin(115200);
  Serial.println();
  Serial.println("=== WiFi TEST ===");

   pinMode(A14, INPUT);  //BUSY
   pinMode(A15, OUTPUT); //RES 
   pinMode(A16, OUTPUT); //DC   
   pinMode(A17, OUTPUT); //CS   
   //SPI
   SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0)); 
   SPI.begin ();  

   if (!WiFi.getAutoReconnect() || ( WiFi.getMode() != WIFI_STA) || ((WiFi.SSID() != ssid) && String(ssid) != "........"))
    {
      Serial.println();
      Serial.print("WiFi.getAutoReconnect() = ");
      Serial.println(WiFi.getAutoReconnect());
      Serial.print("WiFi.SSID() = ");
      Serial.println(WiFi.SSID());
      WiFi.mode(WIFI_STA); // switch off AP
      Serial.print("Connecting to ");
      Serial.println(ssid);
      WiFi.begin(ssid, password);
}
 

  int ConnectTimeout = 60; // 30 seconds
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
    Serial.print(WiFi.status());
    if (--ConnectTimeout <= 0)
    {
      Serial.println();
      Serial.println("WiFi connect timeout");
      return;
    }
  }
  Serial.println();
  Serial.println("WiFi connected");

  // Print the IP address
  Serial.println(WiFi.localIP());

  setClock();

  // init screen
  EPD_init();

   int16_t x = 0;
   int16_t y = 0;
   showBitmapFrom_HTTP("example.com", "/images/", "image.bmp", x, y, true);

   Serial.println("hibernate...");
   delay(2000); // wait, so that all other components can calm down
   esp_sleep_enable_timer_wakeup(sleepTime * 60 * uS_TO_S_FACTOR);
   esp_deep_sleep_start();
}
//Tips//
/*
1.Flickering is normal when EPD is performing a full screen update to clear ghosting from the previous image so to ensure better clarity and legibility for the new image.
2.There will be no flicker when EPD performs a partial refresh.
3.Please make sue that EPD enters sleep mode when refresh is completed and always leave the sleep mode command. Otherwise, this may result in a reduced lifespan of EPD.
4.Please refrain from inserting EPD to the FPC socket or unplugging it when the MCU is being powered to prevent potential damage.)
5.Re-initialization is required for every full screen update.
6.When porting the program, set the BUSY pin to input mode and other pins to output mode.
*/
void loop() {
    
    while(1); // The program stops here         
   
}

static const uint16_t input_buffer_pixels = 960; // may affect performance

static const uint16_t max_row_width = 1872; // for up to 7.8" display 1872x1404
static const uint16_t max_palette_pixels = 256; // for depth <= 8

uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24

void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color)
{
  WiFiClient client;
  bool connection_ok = false;
  bool valid = false; // valid format to be handled
  bool flip = false; // bitmap is stored bottom-to-top
  uint32_t startTime = millis();
  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
  Serial.print("connecting to "); Serial.println(host);
  if (!client.connect(host, httpPort))
  {
    Serial.println("connection failed");
    return;
  }
  Serial.print("requesting URL: ");
  Serial.println(String("http://") + host + path + filename);
  client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: GxEPD2_WiFi_Example\r\n" +
               "Connection: close\r\n\r\n");
  Serial.println("request sent");
  while (client.connected())
  {
    String line = client.readStringUntil('\n');
    if (!connection_ok)
    {
      connection_ok = line.startsWith("HTTP/1.1 200 OK");
      if (connection_ok) Serial.println(line);
      //if (!connection_ok) Serial.println(line);
    }
    if (!connection_ok) Serial.println(line);
    //Serial.println(line);
    if (line == "\r")
    {
      Serial.println("headers received");
      break;
    }
  }
  if (!connection_ok) return;
  // Parse BMP header
  if (read16(client) == 0x4D42) // BMP signature
  {
    uint32_t fileSize = read32(client);
    uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused
    uint32_t imageOffset = read32(client); // Start of image data
    uint32_t headerSize = read32(client);
    uint32_t width  = read32(client);
    int32_t height = (int32_t) read32(client);
    uint16_t planes = read16(client);
    uint16_t depth = read16(client); // bits per pixel
    uint32_t format = read32(client);
    uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far
    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
    {
      Serial.print("File size: "); Serial.println(fileSize);
      Serial.print("Image Offset: "); Serial.println(imageOffset);
      Serial.print("Header size: "); Serial.println(headerSize);
      Serial.print("Bit Depth: "); Serial.println(depth);
      Serial.print("Image size: ");
      Serial.print(width);
      Serial.print('x');
      Serial.println(height);
      // BMP rows are padded (if needed) to 4-byte boundary
      uint32_t rowSize = (width * depth / 8 + 3) & ~3;
      if (depth < 8) rowSize = ((width * depth + 8 - depth) / 8 + 3) & ~3;
      if (height < 0)
      {
        height = -height;
        flip = false;
      }
      uint16_t w = width;
      uint16_t h = height;
     
      if ((x + w - 1) >= SCREEN_WIDTH)  w = SCREEN_WIDTH  - x;
      if ((y + h - 1) >= SCREEN_HEIGHT) h = SCREEN_HEIGHT - y;
      if (w <= max_row_width) // handle with direct drawing
      {
        valid = true;
        uint8_t bitmask = 0xFF;
        uint8_t bitshift = 8 - depth;
        uint16_t red, green, blue;

        unsigned char color1,color2,color3,color4,data;
        unsigned char pixel;
        bool whitish = false;
        bool reddish = false;
        bool blackish = false;
        bool yellowish = false;


        //display.clearScreen();
        EPD_W21_WriteCMD(0x10);


        uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
        //Serial.print("skip "); Serial.println(rowPosition - bytes_read);
        bytes_read += skip(client, rowPosition - bytes_read);
        for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
        {
          if (!connection_ok || !(client.connected() || client.available())) {
            //Serial.println("break");
            break;
          }
          delay(1); // yield() to avoid WDT
          uint32_t in_remain = rowSize;
          uint32_t in_idx = 0;
          uint32_t in_bytes = 0;
          uint8_t in_byte = 0; // for depth <= 8
          uint8_t in_bits = 0; // for depth <= 8
          uint8_t out_byte = 0xFF; // white (for w%8!=0 border)
          uint8_t out_color_byte = 0xFF; // white (for w%8!=0 border)
          uint32_t out_idx = 0;
          uint8_t buff[SCREEN_WIDTH];
          uint32_t pixel_idx = 0;
          for (uint16_t col = 0; col < w; col++) // for each pixel
          {
            yield();
            if (!connection_ok || !(client.connected() || client.available())) {
              //Serial.println("break");
              break;
            }
            // Time to read more pixel data?
            if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
            {
              uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
              uint32_t got = read8n(client, input_buffer, get);
              while ((got < get) && connection_ok)
              {
                //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read);
                uint32_t gotmore = read8n(client, input_buffer + got, get - got);
                got += gotmore;
                connection_ok = gotmore > 0;
              }
              in_bytes = got;
              in_remain -= got;
              bytes_read += got;
            }
            if (!connection_ok)
            {
              Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!");
              break;
            }

            blue = input_buffer[in_idx++];
            green = input_buffer[in_idx++];
            red = input_buffer[in_idx++];
            whitish = ((red > 0xAA) && (green > 0xAA) && (blue > 0xAA)); // whitish
            reddish = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish
            yellowish = ((red > 0xAA) && (green > 0xAA) && (blue < 0x20)); // yellowish
            //blackish = ((red < 0x20) && (green < 0x20) && (blue < 0x20)); // blackish

            if (whitish)
            {
              pixel = WHITE;
            }
            else if(yellowish)
            {
              pixel = YELLOW;
            }
            else if (reddish)
            {
              pixel = RED;
            }
            else {
              pixel = BLACK;
            }

            buff[pixel_idx] = pixel;
            pixel_idx++;
          } // end col

          const int length = sizeof(buff);
          for(size_t i = 0; i < length; i+=4) {
            color1 = buff[i]<<6;
            color2 = buff[i+1]<<4;
            color3 = buff[i+2]<<2;
            color4 = buff[i+3];
            data = color1|color2|color3|color4;    
            EPD_W21_WriteDATA(data); 
          }
          
        } // end row
        Serial.print("downloaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
        EPD_refresh();
      }
      Serial.print("bytes read "); Serial.println(bytes_read);
    }
  }
  client.stop();
  if (!valid)
  {
    Serial.println("bitmap format not handled.");
  }
}


uint16_t read16(WiFiClient& client)
{
  // BMP data is stored little-endian, same as Arduino.
  uint16_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read(); // MSB
  return result;
}

uint32_t read32(WiFiClient& client)
{
  // BMP data is stored little-endian, same as Arduino.
  uint32_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read();
  ((uint8_t *)&result)[2] = client.read();
  ((uint8_t *)&result)[3] = client.read(); // MSB
  return result;
}

uint32_t skip(WiFiClient& client, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while ((client.connected() || client.available()) && (remain > 0))
  {
    if (client.available())
    {
      client.read();
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}

uint32_t read8n(WiFiClient& client, uint8_t* buffer, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while ((client.connected() || client.available()) && (remain > 0))
  {
    if (client.available())
    {
      int16_t v = client.read();
      *buffer++ = uint8_t(v);
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}

// Set time via NTP, as required for x.509 validation
void setClock()
{
  configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");

  Serial.print("Waiting for NTP time sync: ");
  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2)
  {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println("");
  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);
  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));
}

//////////////////////////////////END//////////////////////////////////////////////////

Congratulations!

The logic was correct. But there is or was a missing buffer index reset.

              in_bytes = got;
              in_remain -= got;
              bytes_read += got;
              in_idx = 0;

-jz-