E-Paper Display am ESP32 und ESP8266 - [Teil 2]

Introduction

In the first part of this blog series I showed how to use an e-paper display on an ESP32 micro controller. Some users have already noticed inconsistencies. I'll go into that first in this part of the series. Then I will show you how to display your own images, sensor data or network data from the WiFi module. I will also use the ESP8266 D1 Mini instead of the ESP32. Here we go.

Hardware 

Number Component Annotation
1 ESP32 NodeMCU or ESP-32 Dev Kit C V4
1 ESP8266 D1 Mini
1 1.54 "e-paper display
1 DHT11 temperature sensor
PC with Arduino IDE and Internet connection

One tip in advance

If you don't want to press the BOOT button on the ESP32 every time to upload a program, follow the steps of these instructions. A 10 µF capacitor is required between the pins EN (+) and GND (-).

E-paper display V1 vs. V2

We received some reactions to the first part of this series. My research at that time was therefore unfortunately insufficient. Now at this point the hint for you:

The source codes and images in part 1 refer to the Waveshare ePaper display with 1.45 "size and 200x200 pixel resolution in black and white in the (old) version 1.

At this point, many thanks to Eberhard Werminghoff for his support. I hope that everything now works as it should.

The manufacturer Waveshare points out on its website that the driver boards of the display V1 and V2 differ and therefore the driver source code must be exchanged. In addition, version 1 must be supplied with a voltage of 3.3 V, version 2 can withstand up to 5 V. For more information, see the two data sheets for old version 1 and the new one version 2.

I will briefly show the differences between the two displays here:


Here you can see the external differences of the newer version:

  • The connector is smaller and sits further out
  • The number of components is expanded and they are arranged differently
  • The pin for the supply voltage is now called VCC instead of 3.3V
  • It's named Rev2.1
  • It is a V2 sticker instead of a PASS sticker
  • The interface option BS is reversed
  • A date is printed on the ribbon cable. The newer display is from 2017:

As for the pin numbers, I chose them so that I can stick the pins next to each other on the board. This excludes SCK and MOSI, which cannot be changed. You can of course also use the pins given in the examples. Then be sure to change the numbers in my source code.

The ESP32 or ESP8266 boards are available in different versions. E.g. with 30 instead of 38, or the D1 Mini ESP2866 with even fewer pins. Please check the information in the data sheets.

Let's come back to the libraries. On the one hand, I use the library EPD by Asuki Kono who is also specified in the Arduino Reference. Information on usage and the source code can be found on Github. It can be used for the old V1 displays with a screen size of 1.54" and includes examples for all three module variants, for each of which a different header file must be integrated:

I had used that as a source for my display ShowRunningTime- Example.

Note: Make sure you have the black and white display, the black / white / red Display (B), or that black / white / yellow Display (C) present. Then you have to pay attention to whether you are using the RAW panel (i.e. the pure display) or the board with SPI interface.

Unfortunately, this library has not been updated and adapted to the new versions of the displays. The manufacturer Waveshare offers the source code for the new version on Github, but the Waveshare code cannot be easily migrated to the EPD library.

I have adapted the waveshare source for the 1.54 "black and white display so that we can use it like the EPD library. You can download the source code for it:

Download Library epd1in54_V2

That doesn't work with the multicolored displays. For this you would have to rewrite the other source files as well.

Brief instructions for installation:

  • open the folder \ Sketchbooks \ libraries \
  • if you don't know the storage location, open the Arduino IDE, there the menu File -> Preferences
  • At the top of the window you will find the path under Sketchbook Location
  • copy the folder epd1in54_V2 to the folder libraries
  • restart the Arduino IDE
  • You can then find an example sketch via the menu File -> Examples -> Examples from your own libraries -> epd1in54V2 -> epd1in54V2
  • in the upper part you will find information about the pin assignments of the various boards
  • comment out the line that goes with your board
  • After the upload to the microcontroller, the Waveshare demo should run

If you use a display of a different size or with more colors, you can download the current Waveshare libraries from Github. Pay attention to the pin assignment that is specified in the respective library. In case you want to use other pins, open the source code file epdif.h and change the following lines:

// Pin definition
#define RST_PIN 8
#define DC_PIN 9
#define CS_PIN 10
#define BUSY_PIN 7

This is then hardcoded, but then still fits your needs. If your programming skills go beyond that, you can change the constructor of the class epd so that you can pass the pin numbers.

You also have to change the location of the pgmspace.h file in some files. The difference here is the use of the board (board 100% compatible with Arduino or ESP32 or ESP8266).

Here is an excerpt from the file font8.c previously:

#include

and then:

#if defined (__ AVR__) || defined (ARDUINO_ARCH_SAMD)
#include
#elif defined (ESP8266) || defined (ESP32)
#include
#endif

I will use my extended epd1in54_V2 library for the 1.54" display V2 in this post.

As an alternative library I had shown you the GxEPD2 by Jean-Marc Zingg. In the GxEPD2_Example you have to comment the appropriate line which fits to your display. In addition, you also have to choose the right processor (ESP8266, ESP32, STM32 or board compatible with Arduino AVR). To do this, look for the following lines:

#if defined (ESP8266)

or

#if defined (ESP32)

or

#if defined (ARDUINO_ARCH_STM32)

or

#if defined (__ AVR)
#if defined (ARDUINO_AVR_MEGA2560)

The line numbers keep changing as the project continues. Therefore, I am not including them here.

You will then find the appropriate declarations for your display under the specified defines. For my display it was this line:

GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (/ * CS = 5 * / SS, / * DC = * / 17, / * RST = * / 16, / * BUSY = * / 4)); // GDEP015OC1 no longer available

I changed this to:

GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (/ * CS = * /26, / * DC = * / 25, / * RST = * / 33, / * BUSY = * / 27)); // GDEP015OC1 no longer available

matching my pin assignment, which you will find again below.

Note: On the ESP32 Node MCU you should not use the pins GPIO0, GPIO2 and GPIO12, otherwise the upload will not work. You can find information on this here.

At this point you will already see the note in the developer's comment "GDEP015OC1 no longer available". This is the old version of the 1.54" display with a resolution of 200x200 pixels. For the current version on the ESP32, select this line:

GxEPD2_BW <GxEPD2_154_D67, GxEPD2_154_D67 :: HEIGHT> display (GxEPD2_154_D67 (/ * CS = D5 * / SS, / * DC = * / 17, / * RST = * / 16, / * BUSY = * / 4)); // GDEH0154D67

on the ESP8266 it is this:

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

You can also use the pins from the examples, but then you have to change the connections on the hardware. On closer inspection, you can also see that the only difference between the ESP32 and ESP8266 is the pin assignment.

If you are using one of the three-color displays, you will need to look for such a line below:

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

You then have to include this library, as it is in the GxEPD2_Example is also made.

#include

I didn't need that in my source code. The developer gives another important note for the use of the ESP8266, which could be important for you in the further course of this article:

NOTE for ESP8266: Using SS (GPIO15) for CS may cause boot mode problems, use different pin in case.

Translated, this means that CS pin number 15 may lead to boot problems and you should then switch to another pin.

Show ESP32 and own pictures

The connection to the ESP32 NodeMCU microcontroller looks like this:

EPD pins ESP32 GPIO
Busy 27
RST 33
DC 25
CS 26
CLK SCK = 18
DIN MOSI = 23
GND GND
3.3V 3.3V

As I said, SCK and MOSI are specified by the microcontroller. These are pins on the ESP32 18 for SCK and Pin 23 for MOSI. Look at the pinout of your microcontroller. Pay attention to the difference between the GPIO number and the number printed on the board. Busy, RST, DC and CS can be freely selected (pay attention to the information from the previous section).


Note: Avoid using the GND pin next to the 5V pin (on the bottom left of the picture). Further information can be found at the Pinout overview and in the data sheet.

I'm assuming the libraries EPD and GxEPD2 from the first part of this blog series are installed. You will also need my alternative EPD library V2 for the newer display.

Create images

We now want to show our own picture on the display. In the first part I described how the image data is loaded into the image buffer in the form of an array of characters. So we need to extract the data of the desired image and copy it into the array. First we look for a picture. I use the AZ-Delivery logo:

Since the display is square, the image should also be square. To extract the image data, Waveshare recommends the use of the tool the image2lcd. I found an Online tool on Github that works great. The display manufacturer Waveshare shows on its website how you can convert your own images with a graphics program.

I use the online tool here. In step 1 the image file is selected, in step 2 the settings can be left as they are. In the preview under step 3, the image should now appear in black and white. In step 4 we set "Arduino Code" as the code output format. You can freely choose the identifier. This is what the image data array will be called later. In our case, this is irrelevant as we only need the content. The array already exists and we want to keep using it. We set the draw mode to horizontal - 1 bit per pixel and generate the code by clicking on the corresponding button.

1.54 "display V1

We open the Arduino IDE and in it the example from the EPD library EPD1in54ShowRunningTime. This will be our template. We change lines 43 and 44 again:

// EPD1in54 epd; // default reset: 8, dc: 9, cs: 10, busy: 7
EPD1in54 epd (33, 25, 26, 27); // reset, dc, cs, busy

In the example, text and geometric symbols are displayed first, followed by a full-surface graphic and then the time display. We either comment out the unnecessary lines or simply delete them. Line 70 to 116 is the part that creates the text and symbols and is therefore not needed.

The time measurement in line 122 is also not required. The content of the loop ()- We remove the function as well as the complete one updateTime ()-Function. We'll save the project under a new name. The imagedata.h and .cpp should be saved automatically. The source code now looks shortened as follows:

#include <SPI.h>
#include <EPD1in54.h>
#include <EPDPaint.h>
#include "imagedata.h"

#define COLORED 0
#define UNCOLORED 1

unsigned char image[1024];
EPDPaint paint (image, 0, 0);    // width should be the multiple of 8

// Comment in the line for the desired board
// ****************************************************
//
// ESP32:
EPD1in54 epd (33, 25, 26, 27);   // reset, dc, cs, busy

// ESP8266:
// EPD1in54 epd (5, 0, SS, 4); // reset, dc, cs, busy


void set up() {
Serial.begin (115200);
  if (epd.init (lutFullUpdate)! = 0) {
Serial.print ("e-Paper init failed");
    return;
  }

epd.clearFrameMemory (0xFF);   // bit set = white, bit reset = black
epd.displayFrame ();
epd.clearFrameMemory (0xFF);   // bit set = white, bit reset = black
epd.displayFrame ();
 
paint.setRotate (ROTATE_0);

  // demo picture
epd.setFrameMemory (IMAGE_DATA);
epd.displayFrame ();
}

void loop () {}

We will now copy the generated source code from the online tool, which is within the curly brackets.

In the Arduino IDE, the tab for the imagedata.h / .cpp should be available:


Note: The image files were moved to external files because they contain a relatively large amount of source text. These files must be in the same folder. The .h file is enclosed in quotation marks. The associated .cpp is thereby made known. Their content can then be used in our program. In this case in the form of image data arrays. When you open the project in the Arduino IDE, the tabs should appear as shown in the picture above. In the same way, you can outsource functions, methods or procedures and reuse them in other programs.

We choose that imagedata.cpp-File and insert our new image data from the online tool to the array IMAGE_DATA between the curly brackets.

Alternatively, a new array can be created. To do this, the entire previously generated source text is copied from the website (CTRL + A and then CTRL + C) and inserted in the imagedata.cpp at the end. This is what it looks like as a shortened example:

// 'AZ logo', 200x200px
const unsigned char AZLogo [] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  ...
  ...
  ...
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

Then in the imagedata.h the array must be declared. To do this, simply copy the line that is already there and just change the name:

external const unsigned char AZLogo [];

If you have decided on the additional picture, we add the following lines to the end of the setup () function:

delay (2000);

epd.clearFrameMemory (0xFF);   // bit set = white, bit reset = black
epd.displayFrame ();
epd.clearFrameMemory (0xFF);   // bit set = white, bit reset = black
epd.displayFrame ();
 
  // Own picture
epd.setFrameMemory (AZLogo);
epd.displayFrame ();

When you compile the program and load it onto the ESP32, the image you are using should appear on the display.

The logo I used consists of just two colors and a few details. This is easy to see on a two-tone display. However, if you want to use photos with more detail, you may need to make some adjustments. Most of the time it is enough to change the contrast and brightness. For this you can change the value for "Brightness / alpha threshold" on the above website under point 2. If that is not enough, you have to use a graphics program.

ESP8266 D1 Mini

The pin assignment of the ESP8266 is slightly different than on the ESP32. We use the GPIO numbers, shown in turquoise in the following picture:

We connect the cables from the D1 Mini to the EPD according to the following scheme (the designation with the D next to it is the label printed on the microcontroller, the GPIO numbers are the ones I use in the Arduino IDE):

EPD pins ESP2866 Node MCU D1 Mini GPIO
Busy D2 (GPIO 4)
RST D1 (GPIO 5)
DC D3 (GPIO 0)
CS D8 (GPIO15, SS)
CLK D5 (SCK)
DIN D7 (MOSI)
GND GND
3.3V 3.3V

In the Arduino IDE under the menu item "Tools" we change the board to either NodeMCU 1.0 (ESP-12E module) or Wemos D1 R1.

Now we comment in the appropriate line in the source code:

// ESP32:
// EPD1in54 epd (33, 25, 26, 27); // reset, dc, cs, busy

// ESP8266:
EPD1in54 epd (5, 0, SS, 4);      // reset, dc, cs, busy

You can also just change the pin numbers. I've simplified it a bit here. You don't need more than other pin numbers here.

If we upload the program, the image we selected should appear again.

Download the complete program: EPDEigenesBildESP32ESP8266_V1

Switching from the ESP32 to the ESP8266 is relatively straightforward. Once the correct pins have been found, connected and changed in the source code, the display works as usual. The other examples from Part 1 can also be carried out with the D1 Mini.

1.54 "display V2 on the ESP32

We will now connect the new version of the display and show the same images on it. The pin assignment remains the same. First I use the modified EPD library from Waveshare.

In the first section of this post, I showed you how to upload the sample program. We use this example as a source. We also delete the variables for timing and almost all lines from the setup(). The source code that remains is very clear:

#include <SPI.h>
#include "epd1in54_V2.h"
#include "epdpaint.h"
#include "imagedata.h"

#define COLORED 0
#define UNCOLORED 1

unsigned char image[1024];
Paint paint (image, 0, 0);    // width should be the multiple of 8

// Comment in the line for the desired board
// ****************************************************
//
// ESP32:
Epd epd (33, 25, 26, 27);     // my Pins ESP32 (Reset, DC, CS, Busy)
//
// ESP8266:
// epd epd (5, 0, SS, 4); // my pins ESP8266 (Reset, DC, CS, Busy)

void set up() {
Serial.begin (115200);
Serial.print (epd.HDirInit ());
epd.HDirInit ();

  // demo picture
epd.Display (IMAGE_DATA);

delay (2000);
 
  // Own picture
epd.Display (AZLogo);
}

void loop () {}

It is the same process as in the program for the old display.

1.54 "display V2 on the ESP8266

For the D1 Mini we only comment in another line in which we initialize the display:

Epd epd (5, 0, SS, 4);        // my pins ESP8266 (D1, D3, D8, D2)

Then of course we change the board in the Arduino IDE under Tools and upload the program. The demo image is displayed again first and, after a two second pause, the AZ logo.

Download the complete program: EPDEigenesBildESP32ESP8266_V2

Comparison with the GxEPD2 library

#include <GxEPD2_BW.h>
#include "imagedata.h"

// Comment in the line for the desired board
// ****************************************************
//
// 1.54 "200x200 V1 (old):
// -----------------------
// ESP32:
// GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (26, 25, 33, 27)); // CS, DC, RST, Busy // GDEP015OC1 no longer available
// ESP8266:
// GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (SS, 0, 5, 4)); // CS, DC, RST, BUSY // GDEP015OC1 no longer available
//
// 1.54 "200x200 V2 (new):
// -----------------------
// ESP32:
// GxEPD2_BW <GxEPD2_154_D67, GxEPD2_154_D67 :: HEIGHT> display (GxEPD2_154_D67 (26, 25, 33, 27)); // CS, DC, RST, Busy // GDEH0154D67
// ESP8266:
// GxEPD2_BW <GxEPD2_154_D67, GxEPD2_154_D67 :: HEIGHT> display (GxEPD2_154_D67 (SS, 0, 5, 4)); // CS, DC, RST, Busy // GDEH0154D67

void set up() {
display.init ();
display.setRotation (0);
display.setFullWindow ();

  // demo picture
display.fillScreen (GxEPD_BLACK);
display.drawBitmap (0, 0, IMAGE_DATA, 200, 200, GxEPD_WHITE);
  while (display.nextPage ());
 
delay (2000);

  // Own picture
display.fillScreen (GxEPD_BLACK);
display.drawBitmap (0, 0, AZ logo, 200, 200, GxEPD_WHITE);
  while (display.nextPage ());
}

void loop () {}

I have moved the image data to separate files, equivalent to the previous example with the EPD library. You have to comment out the line that corresponds to your configuration. Display V1 on the ESP32 or ESP8266 or display V2 on the ESP32 or ESP8266.

Download the complete program: GxEPD2EigenesBildESP32ESP8266V1und_V2

Show sensor data

Next, let's see real data on the display. I choose for that a DHT11 Temperature sensor. I continue to use the D1 Mini and for the time being the EPD library for that Display V1. We connect the sensor as follows:

DHT11 pins ESP2866 D1 Mini GPIO
S (DATA) D0 (GPIO 16)
GND GND
VCC 5V

A suitable library must then be installed in the library management. I use the "DHT sensor library for ESPx by beegee_tokyo". 

We declare four variables for the temperature and humidity:

float temperature = 0.0;
float temperature_old = 0.0;
float humidity = 0.0;
float humidity_old = 0.0;

There are two at a time, because we want to compare between old and new data later. This gives us the option of only updating the display when the values ​​have changed. Another possibility would be to define a period of time after which the sensor is read out and the display is updated.

The following line creates a DHT object instance:

DHTesp dht;

in the setup() the temperature sensor must now be initialized. I am using a DHT11 sensor on pin D0. In the graphic above you can see that the corresponding GPIO number is 16. This is entered here.

dht.setup (16, DHTesp :: DHT11);

My program sequence is now to delete the display, then show the AZ logo. Then the display is to be deleted again. Then a background image is displayed that I have created. After that, the values for humidity and temperature are displayed. They are updated as soon as they change. Not the whole screen should be updated, but only the new data.

I converted the picture with the help of the Online tools , then inserted  the image data into the imagedata.cpp/.h and named it as "Background":

imagedata.cpp (excerpt):

// 'Background', 200x200px
const unsigned char Background [] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ...

imagedata.h:

/**
 *  @filename: Imagedata.h
 *  @brief: Head file for imagedata.cpp
*/

external const unsigned char Background [];
external const unsigned char AZLogo [];

/ * FILE
END */

The loop ()-Function is now supplemented by reading out the sensor:

void loop () {
temperature = dht.getTemperature ();
humidity = dht.getHumidity ();
 
  if (temperature! = temperature_old) {
temperature_old = temperature;
updateTemp (temperature);
  }

  if (humidity! = humidity_old) {
humidity_old = humidity;
updateHum (humidity);
  }
}

A corresponding function is called depending on whether one of the values ​​changes. I chose the updateTime ()Function from the EPD1in54ShowRunningTime- Use the example as a template and use it to create two new functions for temperature and humidity.

Both values ​​should be examined separately and updated separately on the display. Depending on which value has changed. Only one small window (partial area) is then changed in the display, or two for the respective values. The background image remains and is not rewritten. That saves energy and is a little faster. Here is the source code for the old one Display V1:

void updateTemp (float temperature) {
  char temp_string [] = {'0', '0', '\0'};
dtostrf (temperature, 2, 0, temp_string);
 
paint.setWidth (30);
paint.setHeight (21);
 
paint.clear (UNCOLORED);
paint.drawStringAt (2, 2, temp_string, & Font24, COLORED);
epd.setFrameMemory (paint.getImage (), 30, 159, paint.getWidth (), paint.getHeight ());
epd.displayFrame ();
epd.setFrameMemory (paint.getImage (), 30, 159, paint.getWidth (), paint.getHeight ());
epd.displayFrame ();
}

void updateHum (float humidity) {
  char temp_string [] = {'0', '0', '\0'};
dtostrf (humidity, 2, 0, temp_string);
 
paint.setWidth (30);
paint.setHeight (21);
 
paint.clear (UNCOLORED);
paint.drawStringAt (2, 2, temp_string, & Font24, COLORED);
epd.setFrameMemory (paint.getImage (), 30, 93, paint.getWidth (), paint.getHeight ());
epd.displayFrame ();
epd.setFrameMemory (paint.getImage (), 30, 93, paint.getWidth (), paint.getHeight ());
epd.displayFrame ();
}

The functions receive the sensor data. A character array is generated that is filled with 0. Then the transferred sensor data are written into the array. The digits are converted to char.

The function dtostrf () can also write decimal places in the array for conversion. I decided to leave out decimal places because, unlike other sensors, the DHT11 does not output any decimal places (although the data type float would allow it). If you use a different sensor, you have the option of displaying decimal places here. To do this, change the second parameter and enter the entire length of the value including the comma.

Enter the number of decimal places as the third parameter. In my case, the numbers are two-digit and have no decimal places. This is what the 2 and 0 stand for.

in the paintObject, the sizes of the windows to be updated are entered. You have to try a little bit there until everything is in the right place. It becomes a little easier if you set the color to black. Then you can see where the respective window is and can position it better. These areas are then overwritten in one color. Then the respective string is generated as text, written to the display buffer and output at the end.

The two functions can of course also be combined into one in order to avoid redundant code and to make the source code a bit leaner. But I will not go into that at this point and leave it as it is.

If we load the program onto the D1-Mini, the logo is displayed first and then the sensor values ​​should appear. Depending on which of the two values ​​changes, it is updated on the display.

Two things can be seen immediately in the picture. On the one hand, the D1-Mini has no power supply and the display still shows the last values. On the other hand, it is currently quite warm.

To use the program with the ESP32, all you have to do is change the pin numbers again, or simply comment on the corresponding line:

// Comment in the line for the desired board
// ****************************************************
//
// ESP32:
EPD1in54 epd (33, 25, 26, 27);   // reset, dc, cs, busy
//
// ESP8266:
// EPD1in54 epd (5, 0, SS, 4); // reset, dc, cs, busy

You may need to use the input pin from the temperature sensor in the set up() change if your ESP32 board does not have a pin with the number 16.

dht.setup (16, DHTesp :: DHT11); // Pin D0 / GPIO16

Download the complete program: EPDESP32ESP8266V1DHT11

Next, we want to implement the same thing with the new one Display V2. We're going back to that epd1in54_V2-Example back. The source code looks relatively similar to the one for the old display. The function names differ only slightly. The processes are the same.

Since we update the temperature data on the display as soon as it changes, you have to perform a partial refresh. This will refresh the display with the new values ​​at the point where the numbers are. It was the same in the old version. In the new version of the library you have to include the static background image with DisplayPartBaseImage (). The temperature data will be displayed with DisplayPartFrame () instead of with DisplayFrame ().

Test the program yourself:

Download the complete program: EPDESP32ESP8266V2DHT11

Interim conclusion

After several tests I had to find out that the library for the new display unfortunately doesn't work like the old version. In my tests, this meant that the image was only very faint. Only after the temperature had been updated several times did the background image also appear more and more saturated.

The outer edge of the screen pulsates slightly even when nothing is updated. So I'm not really satisfied with it. I haven't found any further information on this behavior in the manufacturer's wikis. However, this only affects the partial refresh of the display. The normal output of full-surface content looked normal. It is possible that I am not using the functions correctly. Unfortunately, the entire library is not well documented. Apart from a few references on the Waveshare website, there are unfortunately no hints.

Once again with GxEPD2

I made up my mind that it makes sense to use this library. It is very extensive, is currently being further developed and adapted to the displays. I would now like to implement the previous example with this alternative library.

To do this, we put the source codes together from the programs that we have already written. We get the program sequence for the temperature measurement from the previous sketches in which we used the other libraries. For output on the display, we then use the program that we previously used to display our own images. We include the following libraries:

#include <GxEPD2_BW.h>
#include <Fonts/FreeSans12pt7b.h>
#include "imagedata.h"
#include "DHTesp.h"

In the next part of the program you can again choose between the display V1 or V2, as well as the processors ESP32 or ESP8266. Comment on the correct line for this. Then again pay attention to the wiring of the pins. Next we declare the variables for temperature and humidity and a DHTesp object instance again.

float temperature = 0.0;
float temperature_old = 0.0;
float humidity = 0.0;
float humidity_old = 0.0;

DHTesp dht;

in the setup() we initialize the temperature sensor and then the display. The AZ logo is then displayed first and, after a short pause, the background image that I had already shown before:

void set up() {
// Serial.begin (115200);
dht.setup (16, DHTesp :: DHT11); // Pin D0 / GPIO16
display.init ();
display.setRotation (0);
display.setFullWindow ();

  // AZ logo
display.fillScreen (GxEPD_BLACK);
display.drawBitmap (0, 0, AZ logo, 200, 200, GxEPD_WHITE);
  while (display.nextPage ());
 
delay (2000);

  // Background
display.fillScreen (GxEPD_BLACK);
display.drawBitmap (0, 0, Background, 200, 200, GxEPD_WHITE);
  while (display.nextPage ());

}

The loop ()- We can take over the function completely as it is. I just duplicated the calls for the output. It happened that the values ​​were not displayed properly. This is my workaround:

void loop () {
temperature = dht.getTemperature ();
humidity = dht.getHumidity ();

  if (temperature! = temperature_old) {
temperature_old = temperature;
updateTemp (temperature);
updateTemp (temperature);
  }

  if (humidity! = humidity_old) {
humidity_old = humidity;
updateHum (humidity);
updateHum (humidity);
  }
}

In the two functions updateTemp () and updateHum () the output on the display must be replaced:

void updateTemp (float temperature) {
  char temp_string [] = {'0', '0', '\0'};
  int16_t tbx, tby;
  uint16_t tbw, tbh;
 
dtostrf (temperature, 2, 0, temp_string);

  uint16_t x = 30;
  uint16_t y = 178;
display.getTextBounds (temp_string, x, y, & tbx, & tby, & tbw, & tbh);
display.setFont (& FreeSans12pt7b);
display.setTextColor (GxEPD_BLACK);
display.setPartialWindow (tbx, tby, tbw, tbh);
display.firstPage ();
  do {
display.fillScreen (GxEPD_WHITE);
display.setCursor (x, y);
display.print (temp_string);
  } while (display.nextPage ());
}

void updateHum (float humidity) {
  char temp_string [] = {'0', '0', '\0'};
  int16_t tbx, tby;
  uint16_t tbw, tbh;
 
dtostrf (humidity, 2, 0, temp_string);

  uint16_t x = 30;
  uint16_t y = 112;
display.getTextBounds (temp_string, x, y, & tbx, & tby, & tbw, & tbh);
display.setFont (& FreeSans12pt7b);
display.setTextColor (GxEPD_BLACK);
display.setPartialWindow (tbx, tby, tbw, tbh);
display.firstPage ();
  do {
display.fillScreen (GxEPD_WHITE);
display.setCursor (x, y);
display.print (temp_string);
  } while (display.nextPage ());
}

The variables tbx, tby, tbw and tbh are filled with the values ​​that the partial window will occupy. The abbreviation "tb" stands for "text boundary". The function dtostr () was already introduced above.

(Another way is shown in GxEPD2_Example. The helloValue () function is used for this.)

The variables x and y are the corner point of our output window. It should be noted here that it is not the upper left corner, but the baseline of the text. Information on this can be found in the manual for Adafruit_GFX library in the "Using GFX Fonts in Arduino Sketches" section.

The function call getTextBounds () is also from this library. It is given the string to be output, as well as the coordinates where the string is to be displayed. The other four parameters are the aforementioned text-boundary variables. They are not used as transfer parameters, but rather the memory in which the results of the function are saved is made known. There are basically four return values ​​from one function.

Using the values ​​can be a bit confusing. This is because the baseline and not the upper left corner is the anchor point of the text to be output. The following picture is meant to illustrate how it is meant:

We take part x and y where the text should be. The function getTextBounds () now calculates from the text the size of the window to be changed in which the text is displayed. The values tbx and tby then stand for the upper left corner of the calculated window. Serve for the width and height tbw and tbh.

The next few lines are probably self-explanatory. The font and then the color for the text are determined (of course we only have the choice between black and white). With setPartialWindow (tbx, tby, tbw, tbh) the area is then determined that will change afterwards. Here the tbx and tby coordinates stand for the upper left corner and not for the baseline.

Finally, the window is filled with a color and then the cursor, i.e. the beginning of the text, is set to the x and y coordinates. Here it's the baseline again. Then the text is written.

The font must be included as a header file (see above). You will find an overview of the fonts included here. If you need other font sizes or types, you can do so with this one Online font converter do, which gives you finished header files. You have to copy these into the fonts folder of the library. I already explained how to find the library. Look for the folder in the libraries folder GxEPD2 and there after the folder fonts.

An overview of all the functions of the AdafruitGFX can be found [here] (http://adafruit.github.io/Adafruit-GFX-Library/html/classadafruit__Gf_x.html). The GxEPD2 library, in conjunction with the GFX library, is a very powerful tool for display on screens.

When you load the program onto the micro controller, the same display should appear as before with the other sketch.

Download the complete program: GxEPD2ESP32ESP8266V1undV2DHT11

I have combined the two functions for temperature and humidity and thus shortened the source code a bit:

Download the complete program: GxEPD2ESP32ESP8266V1undV2DHT11_B

WiFi data

Now we come to the last point I announced. With the ESP32 and also the ESP8266 it is possible to connect to a WLAN network. First, let's try to display data from the network on the Serial Monitor. Then we make sure that this data appears on the e-paper display.

I would like to log into my WLAN and then display my IP, the network name (SSID), the channel used and the signal strength (in dBm).

Let's start with that again Display V1. We include the library ESP8266WiFi.h or WiFi.h, depending on which board you are using. in the set up() We then initialize the connection via WLAN:

#if defined (ESP8266)
#include
#elif defined (ESP32)
#include
#endif

void set up() {
Serial.begin (115200);
WiFi.begin ("YOUR SSID", "YOUR WLAN KEY"); // SSID, KEY
Serial.print ("Connect");
    while (WiFi.status ()! = WL_CONNECTED) {
delay (500);
Serial.print (".");
      }
Serial.println ();
Serial.print ("Connected, IP address:");
Serial.println (WiFi.localIP ());
Serial.print ("SSID:");
Serial.println (WiFi.SSID ());
Serial.print ("Channel: ");
Serial.println (WiFi.channel ());
Serial.print ("Signal strength:");
Serial.print (WiFi.RSSI ());
Serial.println ("dBm");}

void loop () {

}

For SSID and KEY please enter your own data before you load the program onto your micro controller. If everything works, you should see the IP address, SSID, channel and signal strength displayed in the serial monitor.

Now let's add this code to the program we used for the e-paper display. We remove the second background image and replace the climate data with the WiFi data. We can remove the two functions for temperature and humidity.

The loop () can also remain empty, as we only have to query the data once. I continue to use the EPD library. From the example EPD1in54ShowRunningTime we can get the source code for displaying text from the set up() take over. For the output of the text we need a char array as a buffer for some data. We declare that globally:

char tempString [] = {'0', '0', '0', '\0'};

in the setup() you should now have inserted the initialization of the WLAN network, as I showed in the previous example. Then the display is initialized and the AZ logo is output. Then we display the same data as in the serial monitor on the EPD:

paint.setRotate (ROTATE_0);
paint.setWidth (200);
paint.setHeight (18);
 
paint.clear (COLORED);
paint.drawStringAt (0, 2, "WIFI connected:", & Font16, UNCOLORED);
epd.setFrameMemory (paint.getImage (), 0, 0, paint.getWidth (), paint.getHeight ());
  
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, "IP address:", & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 20, paint.getWidth (), paint.getHeight ());
 
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, WiFi.localIP (). ToString (). C_str (), & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 40, paint.getWidth (), paint.getHeight ());
 
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, "SSID:", & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 60, paint.getWidth (), paint.getHeight ());
 
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, WiFi.SSID (). C_str (), & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 80, paint.getWidth (), paint.getHeight ());
 
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, "Channel:", & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 100, paint.getWidth (), paint.getHeight ());
 
dtostrf (WiFi.channel (), 2, 0, tempString);
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, tempString, & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 120, paint.getWidth (), paint.getHeight ());
 
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, "Signal strength:", & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 140, paint.getWidth (), paint.getHeight ());
 
String signalString = String (WiFi.RSSI ()) + "dBm";
paint.clear (UNCOLORED);
paint.drawStringAt (0, 2, signalString.c_str (), & Font16, COLORED);
epd.setFrameMemory (paint.getImage (), 0, 160, paint.getWidth (), paint.getHeight ());
 
epd.displayFrame ();

The IP address can be returned as a string with .toString (). However, the function expects drawStringAt () a char array. So we have to add .c_str () at the end. Then it works. The SSID is already returned as a string. We also convert that into a char array. For the canal we use the again dtostrf ()-Function that was already used for the temperature data. For this, the previously declared char array, which we created as a buffer, is required.

I subsequently rewrote the source code so that you can see on the display whether the WiFi connection was established or not. For the signal strength, I have implemented another option to output numbers and text. Since we want to append the unit of measure dBm to the value, I have used the string class and the conversion function String (). Then we can easily concatenate two strings and transfer the result with .c_str () at drawStringAt ().

When you load the program onto the D1 Mini, the AZ logo should first appear on the display and then the data of your WLAN network. The same information is also displayed in the serial monitor.

Download the complete program: EPDESP8266WifiData_V1

You could now also export the output to the loop ()-Write a function if the signal strength changes and you want this information to be displayed up to date. The source code for the output of the text lines could be shortened to avoid redundant code.

For the sake of completeness, we are still writing the program for the Display V2 around. We're making the same changes as we did for the DHT11 project. We change the includes of the libraries. Pay attention to the quotation marks instead of square brackets:

#include "epd1in54_V2.h"
#include "epdpaint.h"

Then the class has to be renamed:

// ESP8266:
Epd epd (5, 0, SS, 4); // reset, dc, cs, busy

We change the initialization of the display as well as the deletion of the display and the output of the AZ logo:

epd.HDirInit ();
epd.Clear ();
epd.Display (AZLogo);
delay (2000);
epd.Clear ();

Except for the changes to the function names already mentioned, we also have to change the positioning:

paint.DrawStringAt (0, 2, "WIFI connected:", & Font16, UNCOLORED);
epd.SetFrameMemory (paint.GetImage (), 0, 0, paint.GetWidth (), paint.GetHeight ());

becomes:

paint.DrawStringAt (0, 2, "WIFI connected:", & Font16, UNCOLORED);
epd.SetFrameMemory (paint.GetImage (), 0, 180, paint.GetWidth (), paint.GetHeight ());

All y positions now have values ​​in steps of 20 downwards instead of upwards.

Note: I noticed that nothing can be displayed above a y-value of 180. That's why the entire display has slid down one line. If you want to use the entire screen, the last line is cut off.

Download the complete program: EPDESP8266WifiData_V2

Last but not least, we'll do a rewrite of the GxEPD2 library. We replace the Includes:

#include <GxEPD2_BW.h>
#include <Fonts/FreeSans9pt7b.h>
#include "imagedata.h"

And remove all lines that belong to the epd-Class.

We use the initialization for the display classes:

// Comment in the line for the desired board
// ****************************************************
//
// 1.54 "200x200 V1 (old):
// -----------------------
// ESP32:
// GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (26, 25, 33, 27)); // CS, DC, RST, Busy // GDEP015OC1 no longer available
// ESP8266:
// GxEPD2_BW <GxEPD2_154, GxEPD2_154 :: HEIGHT> display (GxEPD2_154 (SS, 0, 5, 4)); // CS, DC, RST, BUSY // GDEP015OC1 no longer available
//
// 1.54 "200x200 V2 (new):
// -----------------------
// ESP32:
GxEPD2_BW <GxEPD2_154_D67, GxEPD2_154_D67 :: HEIGHT> display (GxEPD2_154_D67 (26, 25, 33, 27)); // CS, DC, RST, Busy // GDEH0154D67
// ESP8266:
// GxEPD2_BW <GxEPD2_154_D67, GxEPD2_154_D67 :: HEIGHT> display (GxEPD2_154_D67 (SS, 0, 5, 4)); // CS, DC, RST, Busy // GDEH0154D67

Then we replace im set up() the initialization of the display and take over the output of the AZ logo from the GxEPD program for the display of our own image:

display.init ();
display.setRotation (0);
display.setFullWindow ();

// AZ logo
display.fillScreen (GxEPD_BLACK);
display.drawBitmap (0, 0, AZ logo, 200, 200, GxEPD_WHITE);
while (display.nextPage ());

delay (2000);
display.fillScreen (GxEPD_WHITE);
while (display.nextPage ());

The GxEPD2 library offers the option of outputting texts just as you are used to from the serial monitor. Just like in the EPD library, the image buffer can be written first and then output completely on the display. When calling the drawPaged ()Function is passed another function together with another parameter:

display.drawPaged (headline, 0);
display.drawPaged (WifiDataPaged, 0);

Take a look at this GxEPD2_PagedDisplayUsingCallback-Example in more detail.

We are going to display two partial areas. For that we need two functions. One for the heading with white text and black background:

void Headline (const void*) {
  int16_t tbx, tby; uint16_t tbw, tbh;
display.setFont (& FreeSans9pt7b);
display.getTextBounds ("TestText", 0, 0, & tbx, & tby, & tbw, & tbh);
 
display.setTextColor (GxEPD_WHITE);
display.setPartialWindow (0, 0, display.width (), tbh + 3);
display.fillScreen (GxEPD_BLACK);
display.setCursor (0, tbh + 1);
display.println ("WIFI connected:");
}

The second function will output all WiFi data:

void WifiDataPaged (const void*) {
  int16_t tbx, tby; uint16_t tbw, tbh;
display.setFont (& FreeSans9pt7b);
display.getTextBounds ("TestText", 0, 0, & tbx, & tby, & tbw, & tbh);
 
display.setTextColor (GxEPD_BLACK);
display.setPartialWindow (0, tbh + 3, display.width (), display.height () - tbh + 3);
display.fillScreen (GxEPD_WHITE);
display.setCursor (0, (tbh * 2) + 5);
display.println ("IP address:");
display.println (WiFi.localIP ());
display.println ("SSID:");
display.println (WiFi.SSID ());
display.println ("Channel:");
display.println (WiFi.channel ());
display.println ("Signal strength:");
display.print (WiFi.RSSI ());
display.println ("dBm");
}

The function of the program should now be the same as with the EPD library. Load the program onto the board of your choice. Don't forget to comment in the correct line beforehand.

Download the complete program: GxEPD2ESP32ESP8266WifiDataV1undV2

Conclusion

The EPD library is only suitable for the 1.54 "ePaper display in version 1. It works very well with it. When using it, it is relatively easy to switch between ESP32 and ESP8266. If you connect the display version 2 the results are no longer 100 percent satisfactory, only the library directly from the manufacturer is available  and has to be adapted.

I therefore recommend that you use the GxEPD2 library. It takes some time to work your way through the examples and understand how it all works. However, you are flexible in terms of the version of the display and the choice of micro controller.

Many other displays are supported. The library is very heavy and takes a lot longer to compile. It will be worthwhile, however, because you can implement many ideas with it. It is worth taking a look at the many examples.

I hope I was able to help you with the implementation of similar projects with this blog series, or to stimulate your creativity.


Andreas Wolter

for AZ-Delivery Blog

DisplaysEsp-32Projekte für anfängerSensoren

4 comments

Christian Seel

Christian Seel

Vielen Dank für das Update zu “epd1in54V2”, ich dachte zunächst schon, das Display sei defekt! Um den Code zum Laufen zu bringen, war allerdings in epdif.h Zeile 34 arduino.h in Arduino.h zu ändern, in Datei epdif.h Zeile 32 spi.h in SPI.h. Vermutlich ist die Windows-IDE bei der Behandlung von Groß- und Kleinschreibung großzügiger als die Linux-Version.

Matthias Kasper

Matthias Kasper

Hallo,
habe dafür ein Gehäuse für den 3D-Druck erstellt. Es muss allerdings ein Board ohne Stiftleisten genutzt und die Leitungen angelötet werden.
Hier geht’s zur Druckvorlage → https://www.thingiverse.com/thing:4738646
Das Gehäuse besteht aus verschiedenen Unter und Oberteilen, die miteinander kombiniert werden können.
Viele Grüße
Matthias

csierra67

csierra67

Great tutorial, many thanks !
With these additonal explanations I have been able to put my display at work and try all of the proposed sketches.
For the information of other users, I own a b/w/r display and the driver that works best is
EPD2_154_Z90c
May save you some trials.

Markus Walsleben

Markus Walsleben

Ich freue mich sehr, dass die Artikelreihe fortgeführt wird. Ja, mit der EPD-Library hatte ich (wohl insb. wegen meines v2-Displays) so meine Probleme und bin deshalb gleich zur GxEPD2 gewechselt.
Hier möchte ich nun meine Erfahrungen teilen und Anmerkungen zum Artikel anbringen.
1. Das betrifft jetzt weder das Display noch eine der Libraries, sondern den Tipp mit dem Kondensator am ESP32. Die gleichen Probleme hatte ich anfangs auch mit der Arduino-IDE. Und diese Probleme waren komischerweise direkt nach dem Wechsel auf PlatformIO weg. Ich kann das nicht erklären, bin aber mit der aktuellen Situation zufrieden. Die Arduino-IDE ist ja auch um viele Längen langsamer, insbesondere beim kompilieren. Mit der GxEPD2 und PlatformIO fällt nicht auf, dass diese größer ist und länger benötigt.

2. Jetzt zum Display als solches. Ein E-Paper-Display wird ja vermutlich gerade in Hinblick auf den Stromsparenden Einsatz verwendet. Also Fälle, in denen der ESP nur zyklisch etwas erledigt, also Werte holt und/oder schickt und dann anzeigt und dann für eine Zeitlang wieder ruht. Dafür ist ja gerade der ESP32 mit dem Deep Sleep sehr gut geeignet. Hier sind dann auch die EPD zusammen mit der GxEPD2-Library sehr gut für vorbereitet, denn in dieser Library gibt es die Funktionen .powerOff() und .hibernate(). Diese schicken die Elektronik des Display schlafen und sorgen so für 1) noch weniger Stromverbrauch und 2) wird so verhindert, dass die beim “Schlafengehen” des ESP noch fließenden Spannungen den Inhalt des Display hässlich verändern.
Mit aktiviertem .hibernate() kann ich das komplette System (ESP+EPD) vom Strom nehmen und das Display zeigt über viele Stunden (ich habe keine Grenze gefunden, vielleicht ja unendlich?) das zuletzt ausgegebene Bild an.

3. Wer den ESP für längere Zeit schlafen legt, wird möglicherweise darüber stolpern, dass der ESP schon nach ~45 Minuten aufwacht. Das liegt am scheinbar oft verwendeten Beispiel zu liegen:

#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 7200
RTC_DATA_ATTR int bootCount = 0;

void setup(){
Serial.begin(115200);
delay(200);
++bootCount;
Serial.println("Boot number: " + String(bootCount));
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");

Serial.println(“Going to sleep now”); esp_deep_sleep_start();

}
void loop(){
}

Die Lösung liegt darin, die Zeile in
esp_sleep_enable_timer_wakeup((uint64_t)(TIME_TO_SLEEP) * uS_TO_S_FACTOR);
zu ändern. Den Tipp fand ich nach langem Suchen hier: https://github.com/espressif/arduino-esp32/issues/796#issuecomment-347516325

Wer seinen ESP also nur für kurze Zeit schlafen legt, wird also damit keine Probleme bekommen. Es kann aber nicht schaden, das immer so zu programmieren.

Leave a comment

All comments are moderated before being published