Waveshare e-paper displays with SPI

Hello, I have been playing around with your library and so far I have to say that you have done an excellent job!

However in my project I want to display some large numbers on the screen. I am currently using one of the 24pt fonts included with the library and since it’s to small I also added the setTextSize(2) function which makes my text rougly the size I want. However when doubling the 24pt font it makes the text very blocky. I assume that the library doesn’t have a function to smooth the text so I guess I need a larger font. Do you know if there is a font available larger than 24 pt? Preferebly a 48pt freeSans font. Thanks!

I would like to answer without going to spend much time searching. But with the new forum my saved links don’t work anymore, or not exactly. So I only can tell you what to look for:

You can create your own fonts using the squix font converter.

You can use fonts from U8G2. GxEPD2 has an example for this.

Thank you. I actually managed to solve it myself. I don’t know if you have heard about it but I found an awesome online tool called truetype2gfx https://rop.nl/truetype2gfx/

What it does is lets you choose a font either FreeSans, FreeSerif or FreeMono (you can also upload your own). Then you set the font size you want and then it automatically creates a .h adafruit gfx font file that you can then download and use with your library.

Version 3.1.1 of library GxEPD is available, install with Library Manager.

  • added support for GDEM029T94 2.9" b/w, actual Waveshare 2.9" b/w
  • updated example GxEPD_WiFi_Example, for actual GitHub certificate

This panel has been reported to be used on the actual Waveshare 2.9" b/w board.
But I don't have this Waveshare board, so I can't check and test with.


BTW: forum, please accept that I update both of my main topics with this information!

Version 1.3.3 of library GxEPD2 is available, install with Library Manager.

  • added b/w differential refresh method to GDEW0213Z19
  • added b/w differential refresh method to GDEW029Z13
  • up to 100 b/w fast refreshes have been possible (resulting in slightly reddish background)
  • added experimental example GxEPD2x_MixedTest for proof of concept
  • GxEPD2x_MixedTest needs enough RAM for 2 GFXCanvas1 objects
  • general 3-color mixed refresh in GxEPD2 is in evaluation, for capable panels

I did not believe b/w fast partial update (differential refresh) would be possible for 3-color displays.

But the donator of these two panels, John Hawley, provided this link:

and the demo code he obtained from Good Display.

So I learned these e-paper displays can do new tricks, and I started to add them to GxEPD2.

Thanks John!


Hello Jean-Marc,

I had the same problem as @vanilla_chief and @allllo with my display. After accidentally destroying the screen of my old display, I ordered a new display and as mentioned it is "V2". By changing to the other line (GxEPD2_BW<GxEPD2_290_T94, MAX_HEIGHT(GxEPD2_290_T94)> display(GxEPD2_290_T94(/CS=53/ SS, /DC=/ 8, /RST=/ 9, /BUSY=/ 7)); // GDEM029T94) the display works, but my current problem is by normal updating the display. At the beginning, the display is quite dim, but recovers to a readable level after a few seconds. This was never the case with my old display, is there a tip how I can solve this weird behaviour?

I know that the change to GDEM029T94 from GDEH029A1 is only temporary:

Thanks in advance.


did you replace the Waveshare board, or the panel on the Waveshare board?

I know that the change to GDEM029T94 from GDEH029A1 is only temporary:

I don't know what you mean by this.
Ok, I know that any board from Waveshare will be changed over time!

I have exchanged the complete Waveshare board, only the plug included in the delivery is used again from my old display.

I was referring to the statement that there may be an adaptation for this type of display in the future.

I have no idea. My panel connected to the DESPI-C02 doesn't show this effect.
I don't have that Waveshare board.
It may be caused by the LDO or by the level converter, or by the "clever" reset circuit.

You could report the processor you use and the voltage you supply to the board on VCC,

It is connected directly via cable to the Joy-it MEGA 2560 (Atmega2560) and the recommended wiring, and it only happens with the new display.With the old display it worked fine with the same setup (power supply 5V), I wanted to exchange the displays 1 to 1 after the damage and that's how I came to this post.

It's strange that after a few updates the display reaches full strength, and after switching it off the contrast drops again slightly. But the initial state is actually not cool :confused:

Sometimes an improvement ("V2") doesn't seem to be better after all....


there may be a difference between the booster circuit of the board and the booster circuit in the panel specs.

If enough users recommend to Waveshare that they should provide me with a free sample, then one day I may be able to analyze these issues. But I am not willing to buy this board.

Or is it possible that there is a problem like you explained in this post:

At the very beginning, the display updates everything completely during init, after that I have the feeling that it is interrupted when building up the contrast.

Post diagnostic output, in a code window please.

There is no diagnostic output when I upload the code....

Everything works, only when the power source is connected for the first time I have the problem. Once the application is running, it is no longer a problem, but only at the beginning. Unfortunately, I can't explain this at the moment.

I have found out what the problem could be, or what has an influence:

 display.setPartialWindow(0, 0, display.width(), display.height());

I use this line to refresh the entire display, but without re-initiating it and flickering each time. As a result, the complete screen content changes quasi statically... As soon as the line is activated in the code (only with the new display, with the old one it worked perfectly as I said) the strength only comes with time after the reboot. If the line is commented out, the display flickers every time it changes, but the strength is there immediately... I would like to avoid this flickering, is there another way?

Update: I have found a workaround.Instead of putting the expression before the do .... while expression, I put it after all the commands and now it works almost completely the way I want. I don't know what this did in terms of source code, but it seems to work.... At least the display is there immediately!

With the incomplete and unstructured information you provide, I can't help you.

You could either provide your complete code, or better a minimal example that shows your effect.

Do you use hibernate()? Did you read the README.md remark on "clever" reset circuit?

Hi Jean-Marc,

I'm trying to use your library together with an eInk display to display gif files from an SD card (using the AnimatedGIF library - I increased the max image size buffer to 880 in the header fileThis text will be hidden). In my case I'm using a 7.5 inch b/w 880x528 from Waveshare with the e-Paper driver HAT and an ESP32 Adafruit HUZZAH32.

When reading images from an SD card, some are displayed as the full image, but most of the images are displayed only partially (1 out of 4 pages) and during the picture loop the connection to the SD card is lost and I have to fully restart the board (it does not crash though). A wake up from deep sleep will not be enough to reconnect the SD card.

This code will always work (reading the image and parsing the gif) as long as it is not painted using display.drawPixel in the GIFDraw function.
Meaning, when the display.drawPixel lines are removed, it will work, otherwise, it will cause the above described issue.

I assume this is a problem related to memory, do you have any ideas what could cause the issue.


#include <SPI.h>
#include <SD.h>

/* Animated GIF */
#include "src/AnimatedGIF/AnimatedGIF.h"
AnimatedGIF gif;

/* Enable or disable GxEPD2_GFX base class */
#define ENABLE_GxEPD2_GFX 1

#define DISPLAY_WIDTH 880
#define DISPLAY_HEIGHT 528

/* Display Dependencies */
#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <U8g2_for_Adafruit_GFX.h>


/* Connections for Adafruit Feather */
static const uint8_t EPD_BUSY = 32; // to EPD BUSY
static const uint8_t EPD_CS   = 15; // to EPD CS
static const uint8_t EPD_RST  = 27; // to EPD RST
static const uint8_t EPD_DC   = 33; // to EPD DC
static const uint8_t EPD_SCK  =  5; // to EPD CLK
static const uint8_t EPD_MISO = 19; // Master-In Slave-Out not used, as no data from display
static const uint8_t EPD_MOSI = 18; // to EPD DIN

/* Mapping of the ESP32 Driver Board */
GxEPD2_3C < GxEPD2_750c_Z90, GxEPD2_750c_Z90::HEIGHT / 4 > display(GxEPD2_750c_Z90(/*CS=D8*/ EPD_CS, /*DC=D3*/ EPD_DC, /*RST=D4*/ EPD_RST, /*BUSY=D2*/ EPD_BUSY)); // GDEH075Z90 880x528


/* Files */
File f;

/* Setup */
void setup()
  // Serial Port

  // Display

  // Variables
  int error = -1;

  // Prepare GIF

  // SD Card
  if (!SD.begin())
      Serial.println(F("Error: SD not initialization failed!"));

  Serial.print("Start Free Heap: ");

  if ( gif.open( "/movie/thumbnail013.gif" , GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw) )
    GIFINFO gi;
    Serial.printf("GIF opened (%dx%d)\n", gif.getCanvasWidth(), gif.getCanvasHeight());
    if (gif.getInfo(&gi)) {
      Serial.printf("Frames: %d\n", gi.iFrameCount);
      Serial.printf("Duration: %d ms\n", gi.iDuration);
      Serial.printf("Max delay: %d ms\n", gi.iMaxDelay);
      Serial.printf("Min delay: %d ms\n", gi.iMinDelay);
    Serial.print(F("GIF display Error: "));
    Serial.println( gif.getLastError() );

  // Build the View
  int page = 1;
    Serial.printf("Page: %d\n", page);

    while (gif.playFrame(true, NULL))
      if( gif.getLastError() > 0 )
        Serial.printf("Issue: %d\n", gif.getLastError() );

    Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap() );
  while (display.nextPage());

  /* Power off the display and put ESP32 into sleep mode */

  int delayTime = 60 * 60 * 24; // Deep sleep for a day;

  esp_sleep_enable_timer_wakeup( 120 * 1000000LL);
  Serial.printf("Deep-sleep: %d seconds\n", delayTime );

/* Program Loop */
void loop()

/* Open file from SD card */
void * GIFOpenFile(const char *fname, int32_t *pSize)
  f = SD.open(fname);
  // Serial.println("GIF: Open File...");
  if (f)
    *pSize = f.size();
    return (void *)&f;
  return NULL;
} /* GIFOpenFile() */

/* Close file */
void GIFCloseFile(void *pHandle)
  // Serial.println("GIF: Close File...");
  File *f = static_cast<File *>(pHandle);
  if (f != NULL)
} /* GIFCloseFile() */

/* Read a given GIF file */
int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
    int32_t iBytesRead;
    iBytesRead = iLen;
    File *f = static_cast<File *>(pFile->fHandle);
    // Note: If you read a file all the way to the last byte, seek() stops working
    if ((pFile->iSize - pFile->iPos) < iLen)
       iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
    if (iBytesRead <= 0)
       return 0;
    iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
    pFile->iPos = f->position();
    return iBytesRead;
} /* GIFReadFile() */

/* Seek a position in a given GIF file */
int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
  int i = micros();
  File *f = static_cast<File *>(pFile->fHandle);
  pFile->iPos = (int32_t)f->position();
  i = micros() - i;
  // Serial.printf("Seek time = %d us\n", i);
  return pFile->iPos;
} /* GIFSeekFile() */

// Draw a line of image directly on the display
void GIFDraw(GIFDRAW *pDraw)
    int x, y, iWidth;

    iWidth = pDraw->iWidth;
    if (iWidth + pDraw->iX > DISPLAY_WIDTH)
       iWidth = DISPLAY_WIDTH - pDraw->iX;

    y = pDraw->iY + pDraw->y; // current line
    if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1)

    String out = "";

    for (x=0; x<iWidth; x++)
      if( pDraw->pPixels[x] == 0)
        display.drawPixel(x, y, GxEPD_BLACK);
        display.drawPixel(x, y, GxEPD_WHITE);
} /* GIFDraw() */

This strongly suggests that memory has been overwritten, member variables of the display class.

Your example might work, if gif.reset(); works as it should, completely resets the gif state machine. But I don't expect this to be true.

Note that each iteration of the page loop needs to draw exactly the same. Needs to call drawPixel() with the same data. drawPixel() will just ignore calls that would draw to outside of the current page.

You could consider to let GIFDraw write a bitmap line directly to the controller memory using writeBitmap(), like the example GxEPD2_SD_Example.ino does.


Added: experience showed that on ESP32 the static RAM space check of the compiler is not reliable. Even if compiled ok, the program may hang because of insufficient RAM.
You could check with reduced page_height, e.g. divisor 8.

I would like to present additions in blue, or use other colors. But this seems missing in this forum SW. works! (manually).

This [color = red]reply[/color] is partly in [color = blue]colour[/color]

This reply is partly in colour

Thanks to @UKHeliBob

Thanks for the quick response!

I will look into the suggestions you had made and let you know if it works.

I'm trying to get the GxEPD2 library examples to work with a waveshare 2.66" display and so far I'm not having any luck. Is this display supported? The constructor I'm trying to use is

GxEPD2_BW<GxEPD2_260, GxEPD2_260::HEIGHT> display(GxEPD2_260(/*CS=D8*/ SS, /*DC=D3*/ 4, /*RST=D4*/ 2, /*BUSY=D2*/ 5)); // GDEW026T0

Here is the display: https://www.waveshare.com/2.66inch-e-paper-module.htm

No updates occur on the display when I use examples from this library. The same pinout configuration works with the waveshare example code. I'm using a wemos d1 esp8266.