Working with the Waveshare 4.3inch ePaper Display

Hello everyone,

I wanted to share with you my experiences with the Waveshare 4.3'' ePaper display - and how to connect it to an ESP8266 Arduino. I had a simple project in mind that should do the following:

  1. Show the weather forecast for the next few hours
  2. Run on batteries for at least one week

There are only a few ePaper displays out there that are ready to be interfaced with an Arduino controller. The by far cheapest option with a medium sized display and good resolution is the Waveshare 4.3'' ePaper display. It has a 800x600 resolution (230dpi) and can display up to 4 colors: light gray, medium gray, black and white. I was able to get it for around 50€.

For the wifi enabled Arduino board, I went with the Adafruit Feather Huzzah with integrated ESP8266. This board is compatible with the Arduino IDE, has a wifi chip integrated and is well documented. I got it for around 15€.

Both the display and the Arduino board can be operated at 3V, so I simply purchased a 1400mAh lipo battery for 6€ to power both of them. I'll probably need to upgrade the battery, but that's the one I had available.

In total I paid around 75€ for the whole project. Here is how the three components look like when connected to each other: [u]http://imgur.com/a/60wYP[/u]

The wiring is pretty straight forward. I found an example on the Waveshare Wiki on how to connect to an Arduino Uno and adapted it to the Adafruit Huzzah:

3V GND RX TX D2
Red Black White Green Yellow

Remark: We are also connecting the RST Pin to Pin 16 on the Arduino in order to wake it from deep sleep. I found out about this on the Adafruit Product page.

As you might be able to tell, we are interfacing the display via UART. This created some problems down the road which I will cover at the end of this post.

Now on to the coding. Fortunately there is a library available for the UART serial communication with the display available here: [u]https://github.com/sabas1080/LibraryEPD[/u]. Among other functions you can put the display in sleep mode, set the drawing color, draw individual pixels, draw primitive shapes like lines and circles or display text with a font that is integrated on the display controller.

For my project, I didn't want to use simple text to show the weather forecast and the built in shapes weren't much use either. I wanted to display icons, nice fonts and even some graphs for rain probability and so on.

Because the Arduino board is not capable of rendering a full 800x600 4 bit image (remember, the display has 4 colors) I'm doing the rendering on my Webserver. If you are interested in this part I can share more information and even my code with you. For this post let's just say I am pulling weather information from forecast.io, display it in a HTML layout and render it to a png picture with PhantomJS.

PNG files are super small but also compressed. My Arduino board does not have the power to decompress a png so we'll need something simpler: the PBM format. You can look up the format on Wikipedia but for a black/white Image it's really simple. Each pixel is represented by one bit that can either be 0 or 1. 8 pixel get packed into one byte of data. That's 800 x 600 x 1bit / 8 = 60kbyte per image. If we wanted to use the four colors the display can handle this would be 4x the data, so 240kb. Nothing a good wifi couldn't handle but as it turns out more data than you can easily transfer over UART. We'll cover that in a moment.

In order to convert the png created by PhantomJS to 1bit PBM I use ImageMagick. I have a cronjob running on my server that pulls the weather information every 15 minutes and creates the PBM when finished.

The Arduino now only needs to connect to the internet every 30 minutes or so and download the PBM. Of course the Arduino does not have enough storage to save the image for later processing. Instead we are going to stream it from the internet byte by byte and create draw commands for the display while we do that.

We could do this one byte at a time, so 8 pixel on block, and make a draw_pixel command for each pixel that comes in. That however would be super slow. In order to tell the display to render one pixel at a given location, our library has to create a serial command that tells it to do so. This command is 13 bytes long for a single pixel draw command. Example:

"A5 00 0D 20 00 0A 00 0A CC 33 C3 3C 88" -> draw pixel at position 10 (x), 10 (y)

For a full black image we would have to call 800 x 600 draw_pixel commands, each taking 13byte traveling over serial from the Arduino to the display. That's a whopping 6mb of data. The UART communication of our Arduino and display run at 115200baud (bits per second) so around 14kb/s. At this speed it would take 440 seconds to get the image across.

Of course, our image won't be fully black. It's going to have plenty of whitespace as well. After resetting the screen to white, we only need to transmit black pixels. That reduces our data significantly but not enough. To really cut the data we are going to send draw_line commands instead of draw_pixel commands. The draw_line commands takes 17 bytes because it needs to specify 2 x/y coordinates. Even if we can batch two neighboring pixels like that, we'll save 9 Bytes. A whole 800px wide black line gets compressed to 17 Bytes instead of 10kb.

A checkerboard pattern would ruin our benefits, but in a real life scenario this is going to save our ass. The image you see above takes around 10 seconds to travel from the Arduino to the display. That is still a lot but since we're only updating the screen every 30 minutes it's not a big deal.

I have attached the whole Arduino code for this project to this post.

All in all I'm pretty happy with the result but there a few things I still need to do/find out:

  • Sometimes a few lines won't be displayed at all
  • Find out how long the battery will last
  • 3D Print a case to mount it on the wall
  • Find a way to use the four colors available to me

Especially the last point is giving me headaches. Setting the draw color takes 11 Bytes. It would be super inefficient to do that within my line compression. Multiple passes for each color would make more sense but since I am streaming the image from the internet I can't do that right now. I would have to store the image on a SD card and then read from that one time for each color.

Speaking of the SD card, the Waveshare display does have a SD card reader to store bmp images. Maybe I can find a way to share the SD card reader between the Arduino and the display so I can save the image from the internet to the SD card and then simply tell the display to show the image when it has finished downloading.

I hope this was a help to someone out there and if you do have any ideas of improvement I'd be happy to hear them.

ePaperWeather.ino (2.31 KB)

Nice project. But I wonder, could you not use some old Kindle to retrieve a page from your webserver every 30 minutes that would render the same? I am genuinely asking, I don't own a Kindle so I don't know what it's capable of in terms of web rendering and repurposing as a wifi display.

Yes I did check that option. There are quite a few projects out there describing how to turn a Kindle into a weather display. Unfortunately this involves jailbreaking the Kindle and getting special software to be able to run your own scripts on the Kindle. Both steps differ from which Kindle you get and the latest Kindles are mostly not supported. That's why I chose to go the DIY way where I have control over every aspect of the project.

As far as I read about Kindle and javascript, Kindle with at least firmware 3.x does support javascript so I guess simple setTimeout() should work. I don't need any special software on the Kindle, I just need it to display a web page which it should be able to do without jailbreaking. Having it's wifi wake up every 30 minutes is a different matter though, I guess without jailbreaking (and custom software) it would have to be connected to wifi all the time.

Hi Tombadil, thank you for sharing your code and experience.
I would be interested in learning your experience with the update time and behavior of the Waveshare epd.
My experience is with LinkSprite 4.3 (slow, >10s, but interesting) and Good Display, e.g. GDEW075T8 ( several seconds).

ZinggJM:
Hi Tombadil, thank you for sharing your code and experience.
I would be interested in learning your experience with the update time and behavior of the Waveshare epd.
My experience is with LinkSprite 4.3 (slow, >10s, but interesting) and Good Display, e.g. GDEW075T8 ( several seconds).

Great I am interested in the LinkSprite and the GDEW080T5 from Good Display too! The Waveshare has a very quick update time. To clear the screen the display takes 2 cycles of black/white flashing which takes 1 second. After that your image appears in under a second.

Which hardware do you use to control the GDEW075T8 and how do you interface it?

Thanks, so the Waveshare would be an option for me, if I need this higher resolution.
But the ESP8266 has only about 50k free ram, so I could only buffer a part easily.
(maybe use the spiffs filesystem for the ESP8266 flash).
I use a Wemos D1 mini (ESP8266), connected through a hand-woven interconnection board to the DESTM32-S2.
The DESTM32-S2 makes the connection to the flat cable connection of e.g. the GDEW075T8.
It also contains the charge pump circuit for the +- 15 or +- 22 V the display needs.
see e.g. https://www.aliexpress.com/store/product/7-5-inch-ePaper-with-adpater-board/1920096_32753335258.html

Yes you can't even buffer a 1bit image without an SD Card. The Waveshare does have 4mb of flash storage for images but I don't think you can access that from the connected MCU.

I just found out that the Waveshare also uses a GDE043A2 Panel from Good Display (GitHub - davidgfnet/wifi_display: E-ink wireless display). So you might be able to control it with the setup you already have.

Do you think you could create a pcb from the setup you have to drive the GDEW075T8?

There are quite some bits available with the 50kBytes free ram, its enough to buffer b/w for the GDEW075T8 with 640*384.
Have you noticed that the ESP8266 has a lot of free space in its flash memory, besides the space for programs?
This could be used for some paging mechanism to extend the ram buffer.
I did etch some pcbs 40 years ago, but its not worth the effort for the few displays I use.
My hand-woven interconnection corresponds to the wires on your picture.
The LinkSprite epd seems to contain the same GDE043A2 panel but driven by a Atmel Atmega32.
That's why the image data must be sent 11 times, because it can't buffer the whole data.

The Adafruit Huzzah Feather ESP8266 that I use has 4mb of Flash Memory. I think this is where the firmware lives and you can't access that space from your code.

When uploading an empty sketch I get the following information:

Sketch uses 221,947 bytes (21%) of program storage space. Maximum is 1,044,464 bytes.
Global variables use 31,520 bytes (38%) of dynamic memory, leaving 50,400 bytes for local variables. Maximum is 81,920 bytes.

I am currently talking to a German company that could produce a Controller for the Good Display EPDs. Don't know about pricing yet though.

take a look at the platform directory of Arduino for ESP8266. there you find the spiff filesystem.
you can find out about flash size:

void FlashInfo()
{
uint32_t realSize = ESP.getFlashChipRealSize();
uint32_t ideSize = ESP.getFlashChipSize();
FlashMode_t ideMode = ESP.getFlashChipMode();

Serial.printf("Flash real id: %08X\n", ESP.getFlashChipId());
Serial.printf("Flash real size: %u\n\n", realSize);

Serial.printf("Flash ide size: %u\n", ideSize);
Serial.printf("Flash ide speed: %u\n", ESP.getFlashChipSpeed());
Serial.printf("Flash ide mode: %s\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN"));

if (ideSize != realSize) {
Serial.println("Flash Chip configuration wrong!\n");
} else {
Serial.println("Flash Chip configuration ok.\n");
}
}

Flash real id:   001640EF
Flash real size: 4194304
Flash ide  size: 4194304
Flash ide speed: 40000000
Flash ide mode:  QIO
Flash Chip configuration ok.

However I still believe you can't write to the Flash Memory while your sketch is running. Maybe with a custom Firmware...

I think I found an example, but I am too lazy to download the required library right now.

/*
 * Example: storing JSON configuration file in flash file system
 *
 * Uses ArduinoJson library by Benoit Blanchon.
 * https://github.com/bblanchon/ArduinoJson
 *
 * Created Aug 10, 2015 by Ivan Grokhotkov.
 *
 * This example code is in the public domain.
 * https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/ConfigFile/ConfigFile.ino
 */

#include <ArduinoJson.h>
#include "FS.h"

bool loadConfig() {
  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  // Allocate a buffer to store contents of the file.
  std::unique_ptr<char[]> buf(new char[size]);

  // We don't use String here because ArduinoJson library requires the input
  // buffer to be mutable. If you don't use ArduinoJson, you may as well
  // use configFile.readString instead.
  configFile.readBytes(buf.get(), size);

  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(buf.get());

  if (!json.success()) {
    Serial.println("Failed to parse config file");
    return false;
  }

  const char* serverName = json["serverName"];
  const char* accessToken = json["accessToken"];

  // Real world application would store these values in some variables for
  // later use.

  Serial.print("Loaded serverName: ");
  Serial.println(serverName);
  Serial.print("Loaded accessToken: ");
  Serial.println(accessToken);
  return true;
}

bool saveConfig() {
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.createObject();
  json["serverName"] = "api.example.com";
  json["accessToken"] = "128du9as8du12eoue8da98h123ueh9h98";

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  }

  json.printTo(configFile);
  return true;
}

void setup() {
  Serial.begin(115200);
  Serial.println("");
  delay(1000);
  Serial.println("Mounting FS...");

  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount file system");
    return;
  }

  if (!saveConfig()) {
    Serial.println("Failed to save config");
  } else {
    Serial.println("Config saved");
  }

  if (!loadConfig()) {
    Serial.println("Failed to load config");
  } else {
    Serial.println("Config loaded");
  }
}

void loop() {
}

btw the Wemos D1 mini Pro costs only $5 and has extra size flash memory!

Thanks again for pointing me to the Waveshare epd. Its great, it contains a Good Display epd, and it is a complete STM32 system.
So it can be used in many different ways, some googling helps.

For Arduino use of importance is the availability of a library for this module. The one I found is:

what I immediately looked for is a method to draw a single pixel, and there is one:

void epd_draw_pixel(int x0, int y0)

so I may safely assume, that you can send a single pixel to draw to the display.

Therefore you can make this class a subclass of Adafruit_GFX by implementing the only required method:

void drawPixel(int16_t startX, int16_t startY, uint16_t color);

that is pure virtual in Adafruit_GFX

of course for efficiency other methods of Adafruit_GFX can be overridden as well, e.g. drawLine, as they are already implemented in this library.

So you do not need any buffer in your Arduino or ESP8266, the buffer used is on the Waveshare board.

ZinggJM:
Therefore you can make this class a subclass of Adafruit_GFX by implementing the only required method:

void drawPixel(int16_t startX, int16_t startY, uint16_t color);

Uh thanks for pointing that out! I'm not sure how fast this will be. From my experience without line compression you'd have to wait ages to get the data across to the screen. Might incorporate this somehow.

Meanwhile I have finished the design for the 3D printed case. I'll pick it up today, so no guarantee it will work if you print it yourself right now.

Waveshare Case Front
Waveshare Case Back

ZinggJM:
Hi Tombadil, thank you for sharing your code and experience.
I would be interested in learning your experience with the update time and behavior of the Waveshare epd.
My experience is with LinkSprite 4.3 (slow, >10s, but interesting) and Good Display, e.g. GDEW075T8 ( several seconds).

I just found this Article here that says you could use hard SPI instead of the Arduino soft SPI to improve display speed. Not sure how this would help the refresh speed of the display though.

I don't know if you are still looking at your forum contribution, but you might be interested in this topic that I just discovered:

http://forum.arduino.cc/index.php?topic=447212.0

Hi Tombadil,
thank you for your code and information. Have you managed to enhance the code for 4 color support?

Please, is possilbe to download the case for 3D print?

Thank you very much.

Best regards,
Petr

Hi,

Thank you for sharing this great project with us. Really I was trying to do this for a long time but I gave up unfortunatly :smiley: !

What im trying to do now is to use a GSM module in order to do the same thing! but after severel attempts I failed again.
So guys! does anyone have an idea how to do that ???

THANK YOU IN ADVANCE