Waveshare e-paper displays with SPI

@ZinggJM,

I am using WaveShare 2.9 E-paper, GxEPD2 on Arduino Mega board.

I am having some problem on "exactly" positioning the Y axis for set partial window.

Currently, I set the rotation to 1 and I want to set the partial window at X=0 and Y=34 however it always depict at Y=32. I have tried to adjust the value of Y from 33 to 39 but it still displays at Y=32 but when the value of Y=40 then it displays correctly to Y=40.

One thing I observe 32 and 40 are divisible by 8, does it mean that in using setPartialWindow I can only set a value of Y with a number divisible by 8 in setRotation(1) orientation?

I hope I communicated well in addressing my concern.

Thank you.

@EthanWeise,

you are welcome to ask this question. The answer is easy: see line 203 of GxEPD2_BW.h:

    // setPartialWindow, use parameters according to actual rotation.
    // x and w should be multiple of 8, for rotation 0 or 2,
    // y and h should be multiple of 8, for rotation 1 or 3,
    // else window is increased as needed,
    // this is an addressing limitation of the e-paper controllers
    void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
    {
      _pw_x = gx_uint16_min(x, width());
      _pw_y = gx_uint16_min(y, height());
      _pw_w = gx_uint16_min(w, width() - _pw_x);
      _pw_h = gx_uint16_min(h, height() - _pw_y);
      _rotate(_pw_x, _pw_y, _pw_w, _pw_h);
      _using_partial_mode = true;
      // make _pw_x, _pw_w multiple of 8
      _pw_w += _pw_x % 8;
      if (_pw_w % 8 > 0) _pw_w += 8 - _pw_w % 8;
      _pw_x -= _pw_x % 8;
    }

Jean-Marc

@Jean-Marc got it. Thanks.

Another thing I am using grid icons by including GxEPD library on top of GxEPD2 library on my project(GxEPD2 on Arduino Mega with WaveShare 2.9). The grid icons are showing in the screen however it is mirrored. Like for example arrow-left will depict on the screen as arrow-right and grid icon pencil printed on the screen like "" where it should be displayed like this "/"

Dear Jean Marc

I am the guy who is still working with the https://www.waveshare.com/7.5inch-hd-e-paper-b.htm. Maybe you remember me. I have sent you the display this summer.

It is all working fine. Now i was just experimenting a bit with inverting the colors (dark mode). Then i recognized: The less i paint with red, the less red it gets.

Here are two images for comparison:

A: The Red is brighter
![](http://vision11.ch/share/temp/IMG_5118 2.JPG)

B: Logo Strava is barely readable

Are you aware of this effect. Do you have an idea how to always get a shiny red also on a black background?

Thank you for your answer.
Lukas

Hallo Lukas,

yes, of course I remember you! Thank you again for the e-paper panel and driver board.

No, I am not aware of this effect. But I use 3-color e-paper displays only for tests, currently.

I assume you use the Waveshare ESP8266 driver board (I just looked at your previous posts).
Maybe the effect is related to the driver board. The 3.3V VCC to the panel may be marginal during refresh.
The big panels take more current during refresh, and are more susceptible to low VCC.
You could try to measure this voltage during refresh. Maybe a big capacitor on 3.3V could help.
Or you could short the protection diode on the 5V supply to have more margin for the LDO.
Or use an external 3.3V supply, maybe for a test.

The waveform tables used for waveform generation for refresh are taken by the controller from controller OTP memory. And the few configuration parameters for initialization are from the Good Display demo code.
Therefore the GxEPD2 code has no influence on this.
I would have expected the thin red lines on black background to be more of a problem than the thicker text.

Did you watch the refresh behavior? The final phase for producing the red parts is interesting and critical:
in repeated phases of longer lower voltage pulses the red particles are moved towards the front, but the black particles also move. A shorter pulse of higher opposite voltage pulls the black particles back. It looks like this pulling back doesn't work well enough in your issue.
Unfortunately I am short of time to verify this behavior.

Thank you for the pictures. Your frame looks good!

Best regards

Jean-Marc

Hi Jean Marc

Unfortunately your answer is too technical for me. But thank you anyway. Indeed the voltage was very low because my LiPo was drained. But also with USB Connected, i have the same effect:

Here is Video: EINK Test - YouTube

When i tested some examples in the beginning with more Red it was looking good. It is just going bad, when i reduce the amount of red. But never mind. I like my new darkmode without red color the most. :slight_smile:

Btw: Check the Digitec Frontpage. I wrote a small article about this project. I will later post a Link to an English Version.

Lukas

Hi Lukas

Congratulations and thank you for your report on digitec.ch!
I think its worth to take a look at even for readers that don't understand German.

I tried to analyze your issue with red on black background. I didn't see anything first with font FreeMonoBold9pt7b.
Looked like pure black, even more black than the black of b/w e-paper displays. But with strong light the red text could be seen. With font FreeMonoBold18pt7b the text becomes visible. The addition of a red rectangle doesn't improve it on my display, though.
I measured 3.29V on the 3.3V pin, powered from USB. I have not yet tried with lower voltages.

// GxEPD2_Red_on_Black.ino by Jean-Marc Zingg

// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX
//#include <GFX.h>

#define ENABLE_GxEPD2_GFX 0
#include <GxEPD2_BW.h> // including both doesn't hurt
#include <GxEPD2_3C.h> // including both doesn't hurt
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>

// copy constructor for your e-paper from GxEPD2_Example.ino, and for AVR needed #defines
#define MAX_DISPLAY_BUFFER_SIZE 800 // 
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
//GxEPD2_BW<GxEPD2_154, MAX_HEIGHT(GxEPD2_154)> display(GxEPD2_154(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // GDEP015OC1 no longer available
//GxEPD2_BW<GxEPD2_154_D67, MAX_HEIGHT(GxEPD2_154_D67)> display(GxEPD2_154_D67(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // GDEH0154D67

// e.g. for Wemos D1 mini:
//GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(/*CS=D8*/ SS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); // GDEH0154D67
//GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4));
// ***** for mapping of Waveshare e-Paper ESP8266 Driver Board *****
GxEPD2_3C < GxEPD2_750c_Z90, GxEPD2_750c_Z90::HEIGHT / 4 > display(GxEPD2_750c_Z90(/*CS=15*/ SS, /*DC=4*/ 4, /*RST=2*/ 2, /*BUSY=5*/ 5)); // GDEH075Z90 880x528

void setup()
{
  display.init(115200);
  helloWorld();
  display.hibernate();
}

const char HelloWorld[] = "Hello World!";

void helloWorld()
{
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  //display.setFont(&FreeMonoBold18pt7b);
  //display.setTextColor(GxEPD_BLACK);
  display.setTextColor(GxEPD_RED);
  //display.setTextColor(GxEPD_WHITE);
  int16_t tbx, tby; uint16_t tbw, tbh;
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  // center bounding box by transposition of origin:
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  display.setFullWindow();
  display.firstPage();
  do
  {
    //display.fillScreen(GxEPD_WHITE);
    display.fillScreen(GxEPD_BLACK);
    display.setCursor(x, y);
    display.print(HelloWorld);
    const uint16_t rsize = 100; // to experiment with, 20 is too low
    //display.fillRect((display.width() - rsize) / 2, display.height() * 3 / 4 - rsize / 2, rsize, rsize, GxEPD_RED);
  }
  while (display.nextPage());
}

void loop() {};

I think the waveform used by this display is not suitable for red on black background.
But changing the waveform tables on 3-color displays is not easy.

Jean-Marc

Thank you! Maybe for that Use-Case it should be a possible to punch out a white color as backdrop first on the black background and then fill it with red. But i agree, the contrast between red and black is very low. Also on regular Screens.

Lukas

Hello E-Ink Fans

I just made a small recap of my project. You can read the full article here:

A big thank you to Jean Marc. He helped me a lot along with this little project.

Lukas

(deleted)

@tomzanna,

I don't know. But I would not be surprised if it doesn't work.

Don't you get any warnings from the compiler? If you raise the warning level in preferences?

It may be a problem with the sequence of constructor calls. Or the calculation of the size of instances.

The template classes of GxEPD2 use a default copy constructor call of the driver class.
This to allow to have a one line constructor statement of the display instance.

Do you create the instance ("make an object") on stack or in the heap?

Jean-Marc

(deleted)

@tomzanna,

thank you for the feedback and for using code windows in your first posts!

Your second version is the preferred one, as the compiler can check if the fat object fits in RAM.

However, the ESP32 is a bit special; the fattest object possible in heap space is bigger than the fattest in static memory (bss section). The linker complains long before the total RAM is used.
The 96k limit of the bss section is unfortunate, but my complaint (issue raised for ESP32 package) was not accepted.

Jean-Marc

@luki_v11, Hello Lukas,

I happened to connect the GDEH075Z90 panel through the DESPI-C2 connection module to a Wemos D1 mini, to measure the VCOM value at the test point. I did run the GxEPD2_Red_on_Black.ino example for this.

With the DESPI-C2 the red text on black background is visible, but still a bit dark.

I then noticed in the panel specs GDEH075Z90R.pdf on page 9 that the RESE value should be 3 ohms.

If I set the RESE switch on the Waveshare Driver board to position A I also get visible red on black.

With the DESPI-C2 both positions work and give the same result. But the correct position for this board is 3.

The reason may be that the driver board uses an inductance of 68uH instead of 10uH as in the booster reference circuit.

I tried to find out the meaning of the 6.46V value on the sticker on the back of the panel.
This can't be a VCOM value, as on other panels. And it is a bit low for the reduced voltage for driving red, I think.

I tried to find out if the waveform tables can be read from the controllers OTP, but there is no command for this for that controller.

Jean-Marc

Hi @ZinggJM
I recently bought the following

800×480, 7.5inch E-Ink raw display BW
https://www.waveshare.com/7.5inch-e-paper.htm

Universal e-Paper Raw Panel Driver Board, ESP32 WiFi / Bluetooth Wireless

I use the example GxEPD2_WS_ESP32_Driver in your GxEPD2
it run it successfully.

then I try to write a simple clock program, display HH:MM every minute.
I try search some example about esp32 deep sleep with ePaper partial update, seems none.
I search the keyword
esp_deep_sleep_start GxEPD2 setPartialWindow

the problem I have is, after the esp32 wakes up, it always do a full refresh, rather than the partial refresh I wish to happen.
could you help?

below code is just some sample I wrote to test the partial update with esp32 deep sleep.

#define ENABLE_GxEPD2_GFX 0
#include <GxEPD2_BW.h>

GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(GxEPD2_750_T7(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25)); // GDEW075T7 800x480
int bootCount = 0;

void setup()
{
  display.init(115200); // uses standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5)
  
  // *** special handling for Waveshare ESP32 Driver board *** //
  // ********************************************************* //
  SPI.end(); // release standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5)
  //SPI: void begin(int8_t sck=-1, int8_t miso=-1, int8_t mosi=-1, int8_t ss=-1);
  SPI.begin(13, 12, 14, 15); // map and init SPI pins SCK(13), MISO(12), MOSI(14), SS(15)
  // *** end of special handling for Waveshare ESP32 Driver board *** //
  // **************************
  
  if (bootCount == 0)
  {
    display.setFullWindow();
    display.fillScreen(GxEPD_WHITE);
  }

  display.setPartialWindow(0, 0, 150, 150);

  bootCount++;
  int x = random(0, 100);
  int y = random(0, 100);
  
  display.firstPage();
  do
  {
    display.fillRect(x, y, 50, 50, GxEPD_BLACK);
  }
  while (display.nextPage());
  
  delay(10);
  display.powerOff();
  delay(10);
   
  esp_sleep_enable_timer_wakeup(10000000); // 10 seconds
  esp_deep_sleep_start(); 
}

void loop() {};

@david_ng,

thank you for using a code window in your first post!

The initial refresh on a panel that supports differential refresh needs to be a full refresh after power on of the panel.

You can tell GxEPD2 if it is the initial init call by an optional parameter of the init() method. See GxEPD2_BW.h line 137:

    // init method with additional parameters:
    // initial false for re-init after processor deep sleep wake up, if display power supply was kept
    // this can be used to avoid the repeated initial full refresh on displays with fast partial update
    // NOTE: garbage will result on fast partial update displays, if initial full update is omitted after power loss
    // reset_duration = 20 is default; a value of 2 may help with "clever" reset circuit of newer boards from Waveshare 
    // pulldown_rst_mode true for alternate RST handling to avoid feeding 5V through RST pin
    void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)

I have to admit that I have never tested this, but it should work.

On ESP32 you can find out the reset reason. This should make it easy to find out if it is a normal reset or a deep sleep wake-up.

Jean-Marc

Thank you @ZinggJM
it is working, I hope below code will help some newbie like me who looking for
ESP32 Deep Sleep eInk Partial Update with GxEPD2

The serial debug log said it is came from deep sleep.
but from the usb power monitor, while ePaper actively changing, it used 0.32w (4.220v 0.6708a). while it is in deep sleep, it used 0.10w (4.9152v 0.0212a)
maybe something is lost in the hardware, like 5v to 3.3v?
not a hardware guy, anyone know which board has the lowest power consumption during deep sleep?

#define ENABLE_GxEPD2_GFX 0
#include <GxEPD2_BW.h>
#include <rom/rtc.h>

// Waveshare 7.5 inch 800*480 v2 Black White
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(GxEPD2_750_T7(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25)); // GDEW075T7 800x480

RTC_DATA_ATTR int bootCount = 0;
#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */

int TIME_TO_SLEEP = 10;
int box_width = 50;


void setup()
{
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  
  if (bootCount == 0)
  {
    display.init(115200); // uses standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5)
  }
  else
  {
    display.init(115200, false); // uses standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5)
  }

  
  // ********************************************************* //
  // *** special handling for Waveshare ESP32 Driver board *** //
  SPI.end(); // release standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5)
  //SPI: void begin(int8_t sck=-1, int8_t miso=-1, int8_t mosi=-1, int8_t ss=-1);
  SPI.begin(13, 12, 14, 15); // map and init SPI pins SCK(13), MISO(12), MOSI(14), SS(15)
  // *** end of special handling for Waveshare ESP32 Driver board *** //
  // **************************
  
  
  display.setPartialWindow(0, 0, display.width(), display.height());
  display.fillScreen(GxEPD_WHITE);
  

  int x = random(0, display.width() - box_width);
  int y = random(0, display.height() - box_width);
  
  
  display.firstPage();
  do
  {
    display.fillRect(x, y, box_width, box_width, GxEPD_BLACK);
  }
  while (display.nextPage());
  

  // do not use   display.hibernate()   , as previous screen shadow will show after wake up.
  display.powerOff();

  
  //Increment boot number and print it every reboot
  Serial.println("Boot number: " + String(++bootCount));

  
  //Print the wakeup reason for ESP32
  verbose_print_reset_reason(rtc_get_reset_reason(0));
  print_wakeup_reason();
  

  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("set to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");
  Serial.flush();
  esp_deep_sleep_start();
}

void loop() {};

void verbose_print_reset_reason(RESET_REASON reason)
{
  switch ( reason)
  {
    case 1  : Serial.println ("Vbat power on reset");break;
    case 3  : Serial.println ("Software reset digital core");break;
    case 4  : Serial.println ("Legacy watch dog reset digital core");break;
    case 5  : Serial.println ("Deep Sleep reset digital core");break;
    case 6  : Serial.println ("Reset by SLC module, reset digital core");break;
    case 7  : Serial.println ("Timer Group0 Watch dog reset digital core");break;
    case 8  : Serial.println ("Timer Group1 Watch dog reset digital core");break;
    case 9  : Serial.println ("RTC Watch dog Reset digital core");break;
    case 10 : Serial.println ("Instrusion tested to reset CPU");break;
    case 11 : Serial.println ("Time Group reset CPU");break;
    case 12 : Serial.println ("Software reset CPU");break;
    case 13 : Serial.println ("RTC Watch dog Reset CPU");break;
    case 14 : Serial.println ("for APP CPU, reseted by PRO CPU");break;
    case 15 : Serial.println ("Reset when the vdd voltage is not stable");break;
    case 16 : Serial.println ("RTC Watch dog reset digital core and rtc module");break;
    default : Serial.println ("NO_MEAN");
  }
}

void print_wakeup_reason()
{
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

Hi @ZinggJM
I read somewhere, that you have the Waveshare 7 color ePaper display
https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F)[](https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F))
and it did not support Partial Update.

I just bought one native display without the driver board.

I tested the
Waveshare ESP32 driver board + Waveshare 7.5 800*480 ePaper display BW
support Partial Update in post #2156

So I am wondering if Waveshare ESP32 driver board + Waveshare 7 color ePaper native display without driver board
will support Partial Update in GxEPD2 or not?
any hit on this?

@david_ng,

No, GxEPD2 can't support partial update on the 5.65" 7-color display.

The controller of this display is unknown, and the only available document on the controller commands is:
https://www.waveshare.com/w/upload/7/7a/5.65inch_e-Paper_(F)_Sepecification.pdf

There is no "set window address" or "set partial window" command.

If anybody knows more about the controller of this panel and its specs, please tell me.

Jean-Marc

Hi @ZinggJM

I just got a "TTGO T5 v2.3 2.13 20190107", it include a 2.13 inch e-ink display, I test it with GxEPD2 example by uncomment the following ESP32 line

GxEPD2_BW<GxEPD2_213_B73, GxEPD2_213_B73::HEIGHT> display(GxEPD2_213_B73(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEH0213B73

comment out the below IF line in function  drawBitmaps128x296(), so it could show photo.
// if (display.epd2.panel == GxEPD2::GDEH029A1)

everything works.

I then disconnected the 2.13 inch e-ink cable, and plugin my Waveshare 800×480, 7.5inch E-Ink raw display BW
https://www.waveshare.com/7.5inch-e-paper.htm

so I un-comment the

GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(GxEPD2_750_T7(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEW075T7 800x480

at first, it does not work.
but later, I found out the display cable was loose.
the TTGO T5 display connector is not the same one appear in Waveshare ESP32 driver board.
TTGO T5 v2.3 dislay connector is way more harder to plugin, and Waveshare ESP32 driver board is very easy to plugin.
after the connector is fixed, everything works.

I then uncomment

GxEPD2_7C < GxEPD2_565c, GxEPD2_565c::HEIGHT / 2 > display(GxEPD2_565c(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // Waveshare 5.65" 7-color

and plugin my Waveshare 5.65 inch 7 color ePaper display, and run the example, it works too, I see the 7 color appear on the screen.
https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F)

Thank you for your awesomeness.

The reason I like this TTGO T5 v2.3 is, it is small, include a 2.13 inch small ePaper display, external wifi antenna has 3.7v external battery connector, sd card slot which we could store photo in it.