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

Einführung

Im ersten Teil dieser Blogreihe habe ich gezeigt, wie wie man ein E-Paper-Display an einem ESP32 Mikrocontroller benutzt. Dabei sind einigen Nutzern bereits Unstimmigkeiten aufgefallen. Darauf werde ich in diesem Teil der Reihe zuerst eingehen. Dann werde ich zeigen, wie man eigene Bilder, Sensordaten oder Netzwerkdaten des WLAN-Moduls anzeigt. Außerdem werde ich statt des ESP32 auch den ESP8266 D1 Mini verwenden. Los geht's. 

Benötigte Hardware

Anzahl Bauteil Anmerkung
1 ESP32 NodeMCU oder ESP-32 Dev Kit C V4
1 ESP8266 D1 Mini
1 1,54" E-Paper Display
1 DHT11 Temperatursensor
PC mit Arduino IDE und Internetverbindung

Ein Tipp vorweg

Wenn Sie nicht jedes Mal den BOOT-Button am ESP32 betätigen wollen, um ein Programm hochzuladen, folgen Sie dieser Anleitung. Benötigt wird ein 10 µF Kondensator zwischen den Pins EN (+) und GND (-).

E-Paper Display V1 vs. V2

Wir erhielten einige Reaktionen auf den ersten Teil dieser Reihe. Meine damaligen Recherchen waren demnach leider unzureichend. Nun an dieser Stelle der Hinweis für Sie:

Die Quellcodes und Bilder in Teil 1 beziehen sich auf das Waveshare ePaper-Display mit 1,45" Größe und 200x200 Pixel Auflösung in schwarz/weiß in der (alten) Version 1.

An dieser Stelle einen herzlichen Dank an Eberhard Werminghoff für seine Unterstützung. Ich hoffe, dass nun alles so funktioniert, wie es soll.

Der Hersteller Waveshare weist auf seiner Webseite darauf hin, dass sich die Treiberboards des Displays V1 und V2 unterscheiden und daher der Treiber-Quellcode ausgetauscht werden muss. Außerdem muss die Version 1 mit einer Spannung von 3,3 V versorgt werden, die Version 2 verträgt bis 5 V. Näheres dazu in den beiden Datenblättern zur alten Version 1 und der neuen Version 2.

Ich werde hier kurz die Unterschiede beider Displays zeigen:

 

Hier sehen Sie die äußeren Unterschiede der neueren Version:

  • Der Stecker ist kleiner und sitzt weiter außen
  • Die Anzahl der Bauteile ist erweitert und sie sind anders angeordnet
  • Der Pin für die Versorgungsspannung heißt nun VCC, statt 3.3V
  • Es ist mit Rev2.1 benannt
  • Es ist ein V2 Aufkleber, statt PASS aufgeklebt
  • Die Interface Option BS ist seitenverkehrt
  • Auf dem Flachbandkabel ist ein Datum aufgedruckt. Das neuere Display ist von 2017:

Was die Pinnummern angeht, hatte ich sie so gewählt, dass ich sie auf dem Board nebeneinander aufstecken kann. Das schließt SCK und MOSI aus, die nicht geändert werden können. Sie können natürlich auch die vorgegebenen Pins aus den Beispielen verwenden. Achten Sie dann darauf, die Nummern in meinem Quelltext zu ändern.

Die ESP32- oder ESP8266-Boards gibt es in unterschiedlichen Varianten. Z.B. mit 30 statt 38, oder den D1 Mini ESP2866 mit noch weniger Pins. Bitte überprüfen Sie dazu die Angaben in den Datenblättern.

Kommen wir noch einmal zu den Bibliotheken. Ich nutze zum einen die Bibliothek EPD von Asuki Kono, die auch in der Arduino Refrerence angegeben wird. Informationen zur Verwendung und den Quellcode findet man auf Github. Sie kann für die alten Displays V1 mit einer Bildschirmgröße von 1,54" verwendet werden und bringt Beispiele für alle drei Modul-Varianten mit, für die je eine andere Headerdatei eingebunden werden muss:

Für mein Display hatte ich als Quelle das ShowRunningTime-Beispiel verwendet.

Hinweis: Achten Sie darauf, ob ihnen ein schwarz/weiß-Display, das schwarz/weiß/rote Display (B), oder das schwarz/weiß/gelbe Display (C) vorliegt. Dann ist noch darauf zu achten, ob sie das RAW-Panel (also das reine Display) nutzen, oder das Board mit SPI-Interface.

Leider wurde diese Bibliothek nicht aktualisiert und an die neuen Versionen der Displays angepasst. Der Hersteller Waveshare bietet den Quellcode für die neue Version auf Github an. Man kann nun leider den Code von Waveshare nicht so einfach in die EPD Bibliothek migrieren.

Ich habe die Waveshare-Quelle für das 1,54" schwarz/weiß-Display so angepasst, dass wir sie verwenden können, wie die EPD-Bibliothek. Sie können den Quellcode dazu herunterladen:

Download Library epd1in54_V2

Das funktioniert nicht mit den mehrfarbigen Displays. Dafür müsste man die anderen Quelldateien ebenfalls umschreiben.

Kurze Anleitung für die Installation:

  • öffnen Sie den Ordner \Sketchbooks\libraries\
  • wenn Sie den Speicherort nicht kennen, öffnen Sie die Arduino IDE, dort das Menü Datei -> Voreinstellungen
  • oben in dem Fenster finden Sie den Pfad unter Sketchbook-Speicherort
  • kopieren Sie den Ordner epd1in54_V2 in den Ordner libraries
  • starten Sie die Arduino IDE neu
  • Sie finden dann einen Beispiel-Sketch über das Menü Datei -> Beispiele -> Beispiele aus eigenen Bibliotheken -> epd1in54V2 -> epd1in54V2
  • im oberen Teil finden Sie Informationen zu den Pinbelegungen der verschiedenen Boards
  • kommentieren Sie die Zeile ein, die zu Ihrem Board passt
  • nach dem Upload auf den Mikrocontroller sollte die Waveshare Demo laufen

Sollten Sie ein Display anderer Größe oder mit mehr Farben verwenden, können Sie die aktuellen Bibliotheken von Waveshare von Github herunterladen. Achten Sie dabei auf die Pinbelegung, die in der jeweiligen Bibliothek vorgegeben ist. Für den Fall, dass Sie andere Pins nutzen möchten, öffnen Sie die Quellcode-Datei epdif.h und ändern folgende Zeilen:

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

Das ist dann zwar hardcodiert, passt dann aber trotzdem zu Ihren Bedürfnissen. Sollten Ihre Programmierkenntnisse darüber hinausgehen, können Sie den Konstruktor der Klasse epd ändern, so dass Sie die Pinnummern übergeben können.

Sie müssen außerdem in einigen Files den Speicherort der Datei pgmspace.h ändern. Der Unterschied besteht hier in der Nutzung des Boards (Board 100% kompatibel mit Arduino oder ESP32 bzw. ESP8266).

Hier ein Auszug aus der Datei font8.c vorher:

#include <avr/pgmspace.h>

und nachher:

#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD)
#include <avr/pgmspace.h>
#elif defined(ESP8266) || defined(ESP32)
#include <pgmspace.h>
#endif

Ich werde in diesem Beitrag meine erweiterte epd1in54_V2-Bibliothek für das 1,54"-Display V2 nutzen.

Als alternative Bibliothek hatte ich Ihnen die GxEPD2 von Jean-Marc Zingg gezeigt. Im GxEPD2_Example müssen Sie die entsprechende Zeile einkommentieren, die zu Ihrem Display passt. Dazu kommt, dass Sie auch den richtigen Prozessor (ESP8266, ESP32, STM32 oder Board kompatibel mit Arduino AVR) wählen müssen. Suchen Sie dafür folgende Zeilen:

#if defined (ESP8266)

oder

#if defined(ESP32)

oder

#if defined(ARDUINO_ARCH_STM32)

oder

#if defined(__AVR)
#if defined (ARDUINO_AVR_MEGA2560)

Die Zeilennummern ändern sich immer mal wieder, da an dem Projekt weiter gearbeitet wird. Daher gebe ich sie hier nicht an.

Unter den angegebenen defines finden Sie dann die passenden Deklarationen zu Ihrem Display. Für mein Display war das diese Zeile:

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

Diese hatte ich geändert in:

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

passend zu meiner Pinbelegung, die sie weiter unten auch noch einmal finden.

Hinweis: Am ESP32 Node MCU sollte man die Pins GPIO0, GPIO2 und GPIO12 nicht verwenden, da sonst der Upload nicht funktioniert. Infos dazu finden Sie hier.

An dieser Stelle sehen Sie bereits im Kommentar des Entwicklers den Hinweis "GDEP015OC1 no longer available". Es handelt sich hierbei um die alte Version des 1,54" Displays mit der Auflösung 200x200 Pixel. Für die aktuelle Version am ESP32 wählen Sie diese Zeile:

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

 am ESP8266 ist es diese:

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

Sie können auch die Pins aus den Beispielen übernehmen müssen dann aber die Verbindungen an der Hardware ändern. Bei genauerer Betrachtung sehen Sie auch, dass der Unterschied zwischen ESP32 und ESP8266 nur die Pinbelegung ist.

Verwenden Sie eines der Displays mit drei Farben, müssen Sie weiter unten nach solch einer Zeile suchen:

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

Sie müssen dafür dann diese Bibliothek mit einbinden, so wie es im GxEPD2_Example auch gemacht wird.

#include <GxEPD2_3C.h>

In meinem Quellcode brauchte ich das nicht. Der Entwickler gibt noch einen wichtigen Hinweis für die Benutzung des ESP8266, was für Sie im weiteren Verlauf dieses Beitrags wichtig sein könnte:

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

Übersetzt heißt das, dass der CS Pin Nummer 15 eventuell zu Bootproblemen führen kann und man dann auf einen anderen Pin ausweichen soll.

ESP32 und eigene Bilder anzeigen

Der Anschluss an den ESP32 NodeMCU Mikrocontroller sieht folgendermaßen aus:

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

SCK und MOSI sind wie gesagt durch den Mikrocontroller vorgegeben. Am ESP32 sind das Pin 18 für SCK und Pin 23 für MOSI. Schauen Sie in das Pinout Ihres Mikrocontrollers. Achten Sie dabei auf den Unterschied zwischen der GPIO-Nummer und der aufgedruckten Nummer auf dem Board. Busy, RST, DC und CS können frei gewählt werden (achten Sie dabei auf die Hinweise aus dem vorangegangenen Abschnitt).

 

Hinweis: Vermeiden Sie die Nutzung des GND-Pin neben dem 5V-Pin (auf dem Bild links unten). Weitere Informationen dazu finden Sie in der Pinout-Übersicht und im Datenblatt.

Ich gehe davon aus, dass die Bibliotheken EPD und GxEPD2 aus dem ersten Teil dieser Blogreihe installiert sind. Außerdem benötigen Sie meine alternative EPD-Bibliothek V2 für das neuere Display.

Bilder erstellen

Wir wollen nun ein eigenes Bild auf dem Display anzeigen. Ich habe im ersten Teil beschrieben, wie die Bilddaten in Form eines Arrays aus Zeichen in den Bildpuffer geladen werden. Wir müssen also die Daten des gewünschten Bildes extrahieren und in das Array kopieren. Zuerst suchen wir uns ein Bild. Ich nutze das AZ-Delivery Logo:

Da das Display ein quadratisches Format hat, sollte auch das Bild quadratisch sein. Um die Bilddaten zu extrahieren, wird von Waveshare das Tool image2lcd empfohlen. Ich habe über Github ein Online-Tool gefunden, mit dem das auch super funktioniert. Der Displayhersteller Waveshare zeigt auf seiner Webseite, wie man mit einem Grafikprogramm eigene Bilder wandeln kann.

Ich nutze hier das Online-Tool. In Schritt 1 wird die Bilddatei ausgewählt, in Schritt 2 können die Einstellungen so gelassen werden. In der Vorschau unter Schritt 3 sollte nun das Bild in schwarz/weiß erscheinen. In Schritt 4 stellen wir als Code output format "Arduino Code" ein. Der Identifier kann von Ihnen frei gewählt werden. So heißt dann später das Bilddatenarray. In unserem Fall ist das unerheblich, da wir nur den Inhalt benötigen. Das Array existiert schon und wir wollen es auch weiterhin verwenden. Den Draw mode stellen wir auf Horizontal - 1 bit per pixel und generieren den Code mit Klick auf den entsprechenden Button.

1.54" Display V1

Wir öffnen die Arduino IDE und darin aus der EPD-Bibliothek das Beispiel EPD1in54ShowRunningTime. Das wird unsere Vorlage. Wir ändern wieder Zeile 43 und 44:

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

In dem Beispiel werden erst Text und geometrische Symbole angezeigt, anschließend eine vollflächige Grafik und darauf dann die Zeitanzeige. Entweder kommentieren wir nun die nicht benötigten Zeilen aus, oder löschen sie einfach. Zeile 70 bis 116 ist der Teil, der den Text und die Symbole erzeugt und wird damit nicht gebraucht.

Die Zeitmessung in Zeile 122 wird ebenfalls nicht benötigt. Den Inhalt der loop()-Funktion entfernen wir genauso wie die komplette updateTime()-Funktion. Wir speichern das Projekt unter einem neuen Namen. Die imagedata.h und .cpp sollten automatisch mit gespeichert werden. Der Quellcode sieht verkürzt nun folgendermaßen aus:

#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

// Die Zeile fuer das gewuenschte Board einkommentieren
// ****************************************************
//
// ESP32:
EPD1in54 epd(33, 25, 26, 27);   // reset, dc, cs, busy

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


void setup() {
  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-Bild
  epd.setFrameMemory(IMAGE_DATA);
  epd.displayFrame();
}

void loop() {}

Wir kopieren uns nun aus dem Online-Tool den generierten Quellcode, der innerhalb der geschweiften Klammern steht.

In der Arduino IDE sollte im oberen Teil des Programmfensters der Reiter für die imagedata.h / .cpp zur Verfügung stehen:


Hinweis:
Die Bilddateien wurden in externe Dateien ausgelagert, da sie relativ viel Quelltext beinhalten. Diese Dateien müssen im gleichen Ordner liegen. Die .h-Datei wird mit Anführungszeichen inkludiert. Die dazugehörige .cpp wird dadurch bekannt gemacht. Deren Inhalt kann dann in unserem Programm verwendet werden. In diesem Fall in Form der Bilddaten-Arrays. Wenn sie das Projekt in der Arduino IDE öffnen, sollten dann die Reiter erscheinen, wie es im Bild oben zu sehen ist. Auf die gleiche Weise können Sie Funktionen bzw. Methoden oder Prozeduren auslagern und in anderen Programmen wiederverwenden.

Wir wählen die imagedata.cpp-Datei aus und fügen in das Array IMAGE_DATA zwischen den geschweiften Klammern unsere neuen Bilddaten aus dem Online-Tool ein.

Alternativ kann auch ein neues Array erstellt werden. Dafür wird der komplette zuvor generierte Quelltext von der Webseite kopiert (STRG + A und anschließend STRG + C) und in die imagedata.cpp am Ende eingefügt. So sieht das als gekürztes Beispiel aus:

// '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
};

Anschließend muss in der imagedata.h das Array deklariert werden. Dazu kopiert man einfach die Zeile, die dort bereits steht und ändert nur den Namen:

extern const unsigned char AZLogo[];

Wenn sie sich für das zusätzliche Bild entschieden haben, ergänzen wir folgende Zeilen am Ende der setup()-Funktion:

  delay(2000);

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

Wenn Sie das Programm kompilieren und auf den ESP32 laden, sollte auf dem Display das von Ihnen verwendete Bild erscheinen.

Das Logo, das ich benutzt habe, besteht nur aus zwei Farben und wenigen Details. Das ist auf einem zweifarbigen Display gut darstellbar. Möchten Sie jedoch Fotos mit mehr Details benutzen, müssen Sie eventuell einige Anpassungen durchführen. Meistens reicht es aus, den Kontrast und die Helligkeit zu verändern. Dafür kann man auf der oben genannten Webseite unter Punkt 2 den Wert für "Brightness / alpha threshold" verändern. Sollte das nicht reichen, muss man auf ein Grafikprogramm zurückgreifen.

ESP8266 D1 Mini

Die Pinbelegung des ESP8266 ist etwas anders, als am ESP32. Wir nutzen die GPIO-Nummern, im folgenden Bild türkis dargestellt:

 

Wir verbinden die Kabel vom D1 Mini zum EPD nach folgendem Schema (die Bezeichnung mit dem D daneben ist die aufgedruckte Beschriftung auf dem Mikrocontroller, die GPIO-Nummern sind die, die ich in der Arduino IDE verwende):

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 der Arduino IDE unter dem Menüpunkt "Werkzeuge" ändern wir das Board entweder in NodeMCU 1.0 (ESP-12E Module) oder Wemos D1 R1.

Nun kommentieren wir die passende Zeile im Quellcode ein:

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

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

Sie können auch einfach nur die Pinnummern ändern. Ich hab es hier etwas vereinfacht. Mehr als andere Pinnnummern benötigt man hier nicht.

Laden wir das Programm hoch, sollte wieder das von uns ausgesuchte Bild erscheinen.

Download des kompletten Programms: EPDEigenesBildESP32ESP8266_V1

Der Wechsel vom ESP32 zum ESP8266 funktioniert relativ unkompliziert. Hat man die richtigen Pins gefunden, angeschlossen und im Quellcode geändert, funktioniert das Display wie gehabt. Auch die anderen Beispiele aus Teil 1 kann man mit dem D1 Mini ausführen.

1.54" Display V2 am ESP32

Wir werden nun die neue Version des Displays anschließen und darauf die gleichen Bilder anzeigen. Die Pinbelegung bleibt die gleiche. Zuerst nutze ich die veränderte EPD-Bibliothek von Waveshare.

Im ersten Abschnitt dieses Beitrags habe ich Ihnen gezeigt, wie Sie das Beispielprogramm hochladen können. Dieses Beispiel nutzen wir als Quelle. Wir löschen hier auch die Variablen für die Zeitmessung und fast alle Zeilen aus dem setup(). Der Quellcode, der nun übrig bleibt, ist sehr übersichtlich:

#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

// Die Zeile fuer das gewuenschte Board einkommentieren
// ****************************************************
//
// 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 setup() {
  Serial.begin(115200);
  Serial.print(epd.HDirInit());
  epd.HDirInit();

  // Demo-Bild
  epd.Display(IMAGE_DATA);

  delay(2000);
 
  // Eigenes Bild
  epd.Display(AZLogo);
}

void loop() {}

Es ist der gleiche Ablauf wie im Programm für das alte Display.

1.54" Display V2 am ESP8266

Für den D1 Mini kommentieren wir nur entsprechend andere Zeile ein, in der wir das Display initialisieren:

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

Dann ändern wir natürlich in der Arduino IDE unter Werkzeuge noch das Board und laden das Programm hoch. Es wird wieder zuerst das Demo-Bild angezeigt und nach zwei Sekunden Pause das AZ-Logo.

Download des kompletten Programms: EPDEigenesBildESP32ESP8266_V2

Vergleich mit der GxEPD2-Bibliothek

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

// Die Zeile fuer das gewuenschte Board einkommentieren
// ****************************************************
//
// 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 setup() {
  display.init();
  display.setRotation(0);
  display.setFullWindow();

  // Demo-Bild
  display.fillScreen(GxEPD_BLACK);
  display.drawBitmap(0, 0, IMAGE_DATA, 200, 200, GxEPD_WHITE);
  while (display.nextPage());
 
  delay(2000);

  // Eigenes Bild
  display.fillScreen(GxEPD_BLACK);
  display.drawBitmap(0, 0, AZLogo, 200, 200, GxEPD_WHITE);
  while (display.nextPage()); 
}

void loop() {}

Die Bilddaten habe ich in separate Dateien ausgelagert, äquivalent zum vorhergehenden Beispiel mit der EPD-Bibliothek. Sie müssen hier die Zeile einkommentieren, die Ihrer Konfiguration entspricht. Display V1 am ESP32 oder ESP8266 bzw. Display V2 am ESP32 oder ESP8266.

Download des kompletten Programms: GxEPD2EigenesBildESP32ESP8266V1und_V2

Sensordaten anzeigen

Als Nächstes wollen wir echte Daten auf dem Display anzeigen. Ich wähle dafür den DHT11 Temperatursensor. Ich nutze weiterhin den D1 Mini und vorerst die EPD-Bbibliothek für das Display V1. Wir schließen den Sensor wie folgt an:

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

In der Bibliotheksverwaltung muss dann noch eine passende Bibliothek installiert werden. Ich nutze die "DHT sensor library for ESPx by beegee_tokyo". 

Für die Temperatur und Luftfeuchtigkeit deklarieren wir vier Variablen:

float temperatur = 0.0;
float temperatur_old = 0.0;
float humidity = 0.0;
float humidity_old = 0.0;

Es sind jeweils zwei, da wir später zwischen alten und neuen Daten vergleichen wollen. Das gibt uns die Möglichkeit, das Display nur dann zu aktualisieren, wenn sich die Werte verändert haben. Eine andere Möglichkeit wäre, eine Zeitspanne festzulegen, nach deren Takt der Sensor ausgelesen und das Display aktualisiert wird.

Die folgende Zeile erzeugt eine DHT-Objekt-Instanz:

DHTesp dht;

Im setup() muss nun noch der Temperatursensor initialisiert werden. Ich nutze einen DHT11 Sensor an Pin D0. In der oben gezeigten Grafik kann man sehen, dass die dazugehörige GPIO-Nummer die 16 ist. Diese wird hier eingetragen.

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

Mein Programmablauf sieht nun vor, das Display zu löschen, dann das AZ-Logo anzuzeigen. Anschließend soll wieder das Display gelöscht werden. Daraufhin wird dann ein Hintergrundbild angezeigt, dass ich mir erstellt habe. Danach werden die Werte für Luftfeuchtigkeit und Temperatur angezeigt. Sie werden aktualisiert, sobald sie sich ändern. Dabei soll nicht der gesamte Bildschirm aktualisiert werden, sondern nur die neuen Daten.

Das Bild habe ich mit Hilfe des Online-Tools in Bilddaten konvertiert, in die imagedata.cpp/.h eingefügt und als "Background" benannt:

imagedata.cpp (Auszug):

// '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
*/

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

/* FILE
END */

Die loop()-Funktion wird nun durch das Auslesen des Sensors ergänzt:

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

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

Abhängig davon, ob sich einer der Werte ändert, wird eine dazugehörige Funktion aufgerufen. Ich habe mich dafür entschieden, die updateTime()-Funktion aus dem EPD1in54ShowRunningTime-Beispiel als Vorlage zu verwenden und daraus zwei neue Funktionen für Temperatur und Luftfeuchtigkeit zu erstellen.

Es sollen beide Werte getrennt untersucht und auch getrennt auf dem Display aktualisiert werden. Je nachdem, welcher Wert sich verändert hat. Es wird dann nur noch ein kleines Fenster (partieller Bereich) im Display geändert, bzw. zwei für die jeweiligen Werte. Das Hintergrundbild bleibt stehen und wird nicht neu geschrieben. Das spart Energie und geht etwas schneller. Hier der Quellcode für das alte Display V1:

void updateTemp(float temperatur) {
  char temp_string[] = {'0', '0', '\0'};
  dtostrf(temperatur, 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();
}

Die Funktionen bekommen jeweils die Sensordaten übergeben. Es wird ein character-Array erzeugt, das mit 0 befüllt wird. Dann werden die übergebenen Sensordaten in das Array geschrieben. Dabei werden die Ziffern in char gewandelt.

Die Funktion dtostrf() zum Konvertieren kann auch Nachkommastellen in das Array schreiben. Ich habe mich dafür entschieden, Nachkommastellen wegzulassen, da der DHT11 im Gegensatz zu anderen Sensoren keine Nachkommastellen ausgibt (obwohl der Datentyp float das hergeben würde). Sollten Sie einen anderen Sensor nutzen, hätten Sie an der Stelle die Möglichkeit, Nachkommastellen anzuzeigen. Dafür ändern Sie den zweiten Parameter und geben damit die gesamte Länge des Wertes inklusive Komma an.

Als dritten Parameter tragen Sie die Anzahl der Nachkommastellen ein. In meinem Fall sind die Zahlen zweistellig und haben keine Nachkommastelle. Dafür stehen die 2 und die 0.

Im paint-Objekt werden die Größen der Fenster eingetragen, die aktualisiert werden sollen. Man muss dort ein wenig ausprobieren, bis alles an der richtigen Stelle sitzt. Etwas leichter wird das, wenn man die Farbe auf Schwarz setzt. Dann sieht man, wo sich das jeweilige Fenster befindet und kann es besser positionieren. Diese Bereiche werden dann einfarbig überschrieben. Anschließend wird der jeweilige String als Text erzeugt, in den Displaypuffer geschrieben und am Ende ausgegeben.

Die beiden Funktionen können natürlich auch zu einer zusammengefasst werden, um redundanten Code zu vermeiden und den Quelltext etwas schlanker zu machen. Darauf gehe ich an dieser Stelle aber nicht ein und lasse es so wie es ist.

Laden wir das Programm auf den D1-Mini, wird zuerst das Logo angezeigt und anschließend sollten dann die Sensorwerte erscheinen. Je nachdem, welcher der beiden Werte sich ändert, wird er auf dem Display aktualisiert.

Zwei Dinge kann man auf dem Bild sofort erkennen. Zum einen ist der D1-Mini ohne Stromversorgung und das Display zeigt trotzdem noch die letzten Werte an. Zum anderen ist es aktuell ziemlich warm.

Um das Programm mit dem ESP32 zu verwenden, müssen Sie wieder nur die Pinnummern ändern, oder einfach die entsprechende Zeile einkommentieren:

// Die Zeile fuer das gewuenschte Board einkommentieren
// ****************************************************
//
// ESP32:
EPD1in54 epd(33, 25, 26, 27);   // reset, dc, cs, busy
//
// ESP8266:
// EPD1in54 epd(5, 0, SS, 4);      // reset, dc, cs, busy

Eventuell müssen Sie den Eingangspin vom Temperatursensor im setup() ändern, wenn Ihr ESP32-Board keinen Pin mit der Nummer 16 besitzt.

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

Download des kompletten Programms: EPDESP32ESP8266V1DHT11

Das gleiche wollen wir nun mit dem neuen Display V2 umsetzen. Wir greifen wieder auf das epd1in54_V2-Beispiel zurück. Der Quellcode sieht relativ ähnlich aus, wie der für das alte Display. Die Funktionsnamen unterscheiden sich nur gering. Die Abläufe sind die gleichen.

Da wir die Temperaturdaten auf dem Display aktualisieren, sobald sie sich ändern, muss man einen partiellen Refresh durchführen. Dadurch wird an der Stelle, an der sich die Zahlen befinden, die Anzeige mit den neuen Werten erneuert. Das war auch in der alten Version so. In der neuen Version der Bibliothek muss man dafür das statische Hintergrundbild mit DisplayPartBaseImage() laden. Die Temperaturdaten werde mit DisplayPartFrame() angezeigt, statt mit DisplayFrame().

Testen Sie selbst das Programm:

Download des kompletten Programms: EPDESP32ESP8266V2DHT11

Zwischenfazit

Nach mehreren Tests musste ich feststellen, dass die Bibliothek für das neue Display leider nicht so funktioniert, wie die alte Version. Das führte in meinen Tests dazu, dass das Bild nur sehr blass zu sehen war. Erst nachdem die Temperatur mehrmals aktualisiert wurden, wurde das Hintergrundbild auch mehr und mehr gesättigter angezeigt.

Der äußere Bildschirmrand pulsiert leicht, auch wenn nichts aktualisiert wird. So richtig zufrieden bin ich damit also nicht. Ich habe auch keine weiteren Infos in den Wikis des Herstellers gefunden zu diesem Verhalten. Das betrifft allerdings nur das partielle Refresh des Display. Die normale Ausgabe von vollflächigen Inhalten sah normal aus. Eventuell nutze ich die Ausführungen der Funktionen nicht korrekt. Leider ist die gesamte Bibliothek nicht gut dokumentiert. Bis auf wenige Hinweise auf der Webseite von Waveshare findet man leider keine Ansätze.

Noch einmal mit GxEPD2

Es kristalliesiert sich so langsam heraus, dass es sinnvoll ist, diese Bibliothek zu verwenden. Sie ist sehr umfangreich, wird aktuell weiterentwickelt und an die Displays angepasst. Das vorangegangene Beispiel möchte ich nun noch mit dieser alternativen Bibliothek umsetzen.

Wir fügen dafür die Quellcodes aus den Programmen zusammen, die wir bereits geschrieben haben. Den Programmablauf für die Temperaturmessung holen wir uns aus den vorangegangenen Sketches, in denen wir die anderen Bibliotheken verwendet haben. Für die Ausgabe auf dem Display nutzen wir dann das Programm, das wir zuvor genutzt haben, um eigene Bilder anzuzeigen. Wir inkludieren folgende Bibliotheken:

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

Im nächsten Teil des Programms können Sie wieder wählen zwischen dem Display V1 oder V2, sowie den Prozessoren ESP32 oder ESP8266. Kommentieren Sie dafür die richtige Zeile ein. Achten Sie dann wieder auf die Verkabelung der Pins. Als Nächstes deklarieren wir wieder die Variablen für Temperatur und Luftfeuchtigkeit und eine DHTesp Objektinstanz.

float temperatur = 0.0;
float temperatur_old = 0.0;
float humidity = 0.0;
float humidity_old = 0.0;

DHTesp dht;

Im setup() initialisieren wir den Temperatursensor und anschließend das Display. Es wird dann zuerst das AZ-Logo angezeigt und nach einer kurzen Pause das Hintergrundbild, dass ich Ihnen bereits zuvor gezeigt hatte:

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

  // AZLogo
  display.fillScreen(GxEPD_BLACK);
  display.drawBitmap(0, 0, AZLogo, 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());

}

Die loop()-Funktion können wir komplett so übernehmen, wie sie ist. Ich habe nur die Aufrufe für die Ausgabe dupliziert. Es kam vor, dass die Werte nicht ordentlich angezeigt wurden. Das hier ist mein Workaround:

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

  if (temperatur != temperatur_old) {
    temperatur_old = temperatur;
    updateTemp(temperatur);
    updateTemp(temperatur);
  }

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

In den beiden Funktionen updateTemp() und updateHum() muss die Ausgabe auf dem Display ersetzt werden:

void updateTemp(float temperatur) {
  char temp_string[] = {'0', '0', '\0'};
  int16_t tbx, tby;
  uint16_t tbw, tbh;
 
  dtostrf(temperatur, 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());
}

Die Variablen tbx, tby, tbw und tbh werden mit den Werten gefüllt, die das partielle Fenster einnehmen wird. Die Abkürzung "tb" steht für "text boundary". Die Funktion dtostr() hatte ich Ihnen bereits vorgestellt.

(Es wird im GxEPD2_Example auch ein anderer Weg gezeigt. Dazu dient die Funktion helloValue().)

Die Variablen x und y sind der Eckpunkt unseres Ausgabefensters. Hier ist zu beachten, dass es nicht die linke obere Ecke ist, sondern die Grundlinie des Textes. Informationen dazu finden Sie im Handbuch zur Adafruit_GFX-Bibliothek im Abschnitt "Using GFX Fonts in Arduino Sketches".

Der Funktionsaufruf getTextBounds() ist ebenfalls aus dieser Bibliothek. Ihr wird der String übergeben, der Ausgegeben werden soll, sowie die Koordinaten, wo der String angezeigt werden soll. Die anderen vier Parameter sind die zuvor genannten text-boundary-Variablen. Sie dienen nicht als Übergabeparameter, sondern mit ihnen wird der Speicher bekannt gemacht, in den die Ergebnisse der Funktion hineingespeichert werden. Es sind quasi vier Rückgabewerte aus einer Funktion.

Die Nutzung der Werte kann etwas verwirrend sein. Das liegt daran, dass die Grundlinie und nicht die linke obere Ecke der Ankerpunkt des auszugebenden Textes ist. Das folgende Bild soll veranschaulichen, wie es gemeint ist:

Wir legen mit x und y fest, wo sich der Text befinden soll. Die Funktion getTextBounds() berechnet nun aus dem Text die Größe des zu ändernden Fensters, in dem der Text angezeigt wird. Die Werte tbx und tby stehen dann für die linke obere Ecke des berechneten Fensters. Für die Breite und Höhe dienen tbw und tbh.

Die nächsten Zeilen sind wahrscheinlich selbsterklärend. Es wird die Schriftart und anschließend die Farbe für den Text festgelegt (wir haben natürlich nur die Wahl zwischen Schwarz und Weiß). Mit setPartialWindow(tbx, tby, tbw, tbh) wird dann der Bereich festgelegt, der sich danach verändern wird. Hier stehen die tbx- und tby-Koordinaten für die linke obere Ecke und nicht für die Grundlinie.

Als Letztes wird dann das Fenster mit einer Farbe gefüllt und danach der Cursor, also der Textanfang, auf die x- und y-Koordinaten gesetzt. Hier ist es wieder die Grundlinie. Dann wird der Text geschrieben.

Die Schriftart muss als Headerdatei eingebunden werden (siehe weiter oben). Eine Übersicht über die enthaltenen Schriftarten finden Sie hier. Sollten Sie andere Schriftgrößen oder -arten benötigen, können Sie das mit diesem Online-Font-Konverter tun, der Ihnen fertige Headerdateien ausgibt. Diese müssen Sie in den Fonts-Ordner der Bibliothek kopieren. Wie Sie die Bibliothek finden, hatte ich Ihnen bereits erklärt. Suchen Sie im Ordner "libraries" nach dem Ordner GxEPD2 und dort nach dem Ordner fonts.

Eine Übersicht über alle Funktionen der AdafruitGFX finden Sie [hier](http://adafruit.github.io/Adafruit-GFX-Library/html/classadafruit__gf_x.html). Die GxEPD2-Bibliothek ist in Verbindung mit der GFX-Bibliothek ein sehr mächtiges Tool für die Anzeige auf Bildschirmen.

Wenn Sie das Programm auf den Mikrocontroller laden, sollte die gleiche Anzeige erscheinen, wie zuvor mit dem anderen Sketch.

Download des kompletten Programms: GxEPD2ESP32ESP8266V1undV2DHT11

Ich habe die beiden Funktionen für Temperatur und Luftfeuchtigkeit zusammengefasst und damit den Quellcode etwas verkürzt:

Download des kompletten Programms: GxEPD2ESP32ESP8266V1undV2DHT11_B

WLAN-Daten

Kommen wir nun zum letzten Punkt, den ich angekündigt hatte. Mit dem ESP32 und auch dem ESP8266 ist es möglich, sich mit einem WLAN-Netzwerk zu verbinden. Versuchen wir zuerst, Daten aus dem Netzwerk auf dem Serial Monitor anzuzeigen. Danach kümmern wir uns darum, dass diese Daten auf dem E-Paper-Display erscheinen.

Ich möchte mich in mein WLAN einloggen und dann meine IP, den Netzwerknamen (SSID), den verwendeten Kanal und die Signalstärke (in dBm) anzeigen lassen.

Beginnen wir wieder mit dem Display V1. Wir inkludieren die Bibliothek ESP8266WiFi.h bzw WiFi.h, abhängig davon, welches Board Sie verwenden. Im setup() initialisieren wir dann die Verbindung via WLAN:

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

void setup() {
    Serial.begin(115200);
    WiFi.begin("IHRE SSID", "IHR WLAN KEY"); // SSID, KEY
    Serial.print("Verbinde");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
      }
    Serial.println();
    Serial.print("Verbunden, IP-Adresse: ");
    Serial.println(WiFi.localIP());
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());
    Serial.print("Kanal: ");
    Serial.println(WiFi.channel());
    Serial.print("Signalstaerke: ");
    Serial.print(WiFi.RSSI());
    Serial.println(" dBm");}

void loop() {

}

Für SSID und KEY tragen Sie bitte Ihre eigenen Daten ein, bevor Sie das Programm auf Ihren Mikrocontroller laden. Wenn alles funktioniert, sollten Sie im Seriellen Monitor die IP-Adresse, SSID, Kanal und Signalstärke angezeigt bekommen.

Nun wollen wir diesen Code in das Programm einfügen, das wir für das E-Paper-Display verwendet haben. Wir entfernen das zweite Hintergrundbild und ersetzen die Klimadaten durch die WLAN-Daten. Die beiden Funktionen für Temperatur und Luftfeuchtigkeit können wir entfernen. 

Die loop() kann auch leer bleiben, da wir die Daten nur einmal abfragen müssen. Ich nutze weiterhin die EPD-Bibliothek. Aus dem Beispiel EPD1in54ShowRunningTime können wir den Quellcode für die Anzeige von Text aus dem setup() übernehmen. Für die Ausgabe des Textes benötigen wir für einige Daten ein Char Array als Zwischenspeicher. Das deklarieren wir global:

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

Im setup() sollten Sie nun die Initialisierung des WLAN-Netzwerkes eingefügt haben, wie ich es im vorangegangenen Beispiel gezeigt habe. Dann wird das Display initialisiert und das AZ-Logo ausgegeben. Anschließend lassen wir die gleichen Daten wie im Seriellen Monitor auf dem EPD anzeigen:

  paint.setRotate(ROTATE_0);
  paint.setWidth(200);
  paint.setHeight(18);
 
  paint.clear(COLORED);
  paint.drawStringAt(0, 2, "WIFI verbunden:", &Font16, UNCOLORED);
  epd.setFrameMemory(paint.getImage(), 0, 0, paint.getWidth(), paint.getHeight());  
  
  paint.clear(UNCOLORED);
  paint.drawStringAt(0, 2, "IP-Adresse:", &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, "Kanal:", &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, "Signalstaerke:", &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();

Die IP-Adresse kann mit .toString() als String zurückgegeben werden. Allerdings erwartet die Funktion drawStringAt() ein char Array. Also hängen wir noch .c_str() hinten dran. Dann funktioniert es. Die SSID wird schon als String zurückgegeben. Auch das konvertieren wir in ein char Array. Für den Kanal nutzen wir noch einmal die dtostrf()-Funktion, die bereits für die Temperaturdaten zum Einsatz kam. Dafür wird dann auch das zuvor deklarierte char Array, das wir als Zwischenspeicher angelegt haben, benötigt.

Ich habe den Quellcode nachträglich noch so umgeschrieben, dass man auf dem Display sehen kann, ob die WLAN-Verbindung hergestellt wurde, oder nicht. Für die Signalstärke habe ich eine weitere Möglichkeit implementiert, Zahlen und Text auszugeben. Da wir an den Wert noch die Maßeinheit dBm anhängen wollen, habe ich die String-Klasse und die Konvertierungsfunktion String() verwendet. Dann können wir auf einfache Weise zwei Strings verketten und übergeben das Resultat mit .c_str() an drawStringAt().

Wenn Sie das Programm auf den D1 Mini laden, sollten auf dem Display zuerst das AZ-Logo und anschließend die Daten Ihres WLAN-Netzwerkes erscheinen. Die gleichen Informationen werden auch im Seriellen Monitor angezeigt.

Download des kompletten Programms: EPDESP8266WifiData_V1

Man könnte nun die Ausgabe auch in die loop()-Funktion schreiben, falls sich die Signalstärke verändert und man diese Info immer aktuell angezeigt haben möchte. Den Quelltext für die Ausgabe der Textzeilen könnte man noch verkürzen, um redundanten Code zu vermeiden.

Der Vollständigkeithalber schreiben wir das Programm noch für das Display V2 um. Wir führen die gleichen Änderungen durch, wie wir das schon für das DHT11-Projekt getan haben. Wir ändern die Includes der Bibliotheken. Achten Sie auf die Anführungszeichen, statt eckigen Klammern:

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

Dann muss die Klasse umbedannt werden:

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

Die Initialisierung des Displays ändern wir ebenso, sowie das Löschen der Anzeige und die Ausgabe des AZ-Logos:

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

Bis auf die bereits angesprochenen Änderungen der Funktionsnamen, müssen wir auch die Positionierung ändern:

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

wird zu:

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

Alle y-Positionen haben nun Werte in 20er Schritten abwärts, statt aufwärts.

Hinweis: Mir ist aufgefallen, dass nichts über einem y-Wert von 180 angezeigt werden kann. Deswegen ist die komplette Anzeige um eine Zeile nach unten gerutscht. Wenn man den kompletten Bildschirm nutzen möchte, wird die letzte Zeile abgeschnitten.

Download des kompletten Programms: EPDESP8266WifiData_V2

Zu guter Letzt werden wir das jetzt noch für die GxEPD2-Bibliothek umschreiben. Wir ersetzen die Inlcudes:

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

Und entfernen alle Zeilen, die zu der epd-Klasse gehören.

Wir setzen die Initialisierungen für die Displayklassen ein:

// Die Zeile fuer das gewuenschte Board einkommentieren
// ****************************************************
//
// 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

Dann ersetzen wir im setup() die Initialisierung des Displays und übernehmen aus dem GxEPD-Programm für die Anzeige unseres eigenen Bildes die Ausgabe des AZ-Logos:

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

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

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

Die GxEPD2-Bibliothek bietet die Möglichkeit, Texte genauso auszugeben, wie Sie es vom Seriellen Monitor gewohnt sind. Genau wie in der EPD-Bibliothek kann zuerst der Bildpuffer geschrieben und dann komplett auf dem Display ausgegeben werden. Beim Aufruf der drawPaged()-Funktion wird eine weitere Funktion zusammen mit einem weiteren Parameter übergeben:

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

Schauen Sie sich dazu das GxEPD2_PagedDisplayUsingCallback-Beispiel genauer an.

Wir werden zwei partielle Bereiche anzeigen lassen. Dafür brauchen wir zwei Funktionen. Eine für die Überschrift, deren Text weiß und der Hintergrund schwarz ist:

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 verbunden:");
}

Die zweite Funktion wird alle WLAN-Daten ausgeben:

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-Adresse:");
  display.println(WiFi.localIP());
  display.println("SSID:");
  display.println(WiFi.SSID());
  display.println("Kanal:");
  display.println(WiFi.channel());
  display.println("Signalstaerke:");
  display.print(WiFi.RSSI());
  display.println(" dBm");
}

Die Funktion des Programms sollte nun die gleiche sein, wie mit der EPD-Bibliothek. Laden Sie das Programm auf das Board Ihrer Wahl. Vergessen Sie nicht, vorher dafür die richtige Zeile einzukommentieren.

Download des kompletten Programms: GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2

Fazit

Die EPD-Bibliothek ist nur für das ePaper-Display 1,54" in der Version 1 geeignet. Funktioniert damit allerdings sehr gut. Bei der Verwendung ist es relativ leicht, zwischen ESP32 und ESP8266 zu wechseln. Schließt man das Display mit der Version 2 an, sind die Resultate nicht mehr zu 100 Prozent zufriedenstellend. Es gibt dafür nur die Bibliothek direkt vom Hersteller, die noch angepasst werden muss.

Ich empfehle Ihnen daher, die GxEPD2-Bibliothek zu verwenden. Man braucht etwas Zeit, um sich durch die Beispiele zu arbeiten und zu verstehen, wie alles funktioniert. Man ist allerdings flexibel, was die Version des Displays und die Wahl des Mikrocontrollers angeht.

Es werden viele weitere Displays unterstützt. Die Bibliothek ist sehr schwergewichtig und braucht wesentlich länger zum Kompilieren. Es wird sich allerdings lohnen, da man damit viele Ideen umsetzen kann. Es lohnt sich, einen Blick in die vielen Beispiele zu werfen.

Ich hoffe, ich konnte Ihnen mit dieser Blogreihe bei der Umsetzung ähnlicher Projekte helfen, oder Ihre Kreativität anregen.

Andreas Wolter

für AZ-Delivery Blog

DisplaysEsp-32Projekte für anfängerSensoren

15 comments

Andreas Wolter

Andreas Wolter

@R.Parusel: ja das ist es in der Tat. Daher auch die Probleme, die aus dem ersten Teil dieser Blogreihe resultierten. Es ist wichtig zu wissen, welches Display das ist, wie viele Farben es unterstützt. Ich würde mich dann durch die Beispiele hangeln. Dort muss man eigentlich nur die passende Zeile für das Board einkommentieren und natürlich auch die Pinbelegung passend konfigurieren. Ich tippe bei Ihrem Board auf die V1.

Grüße,
Andreas Wolter
AZ-Delivery Blog

R.Parusel

R.Parusel

Hallo,
ich habe hier eine 1.54" ePaper mit einem Kontrollerboard V2 und einem Display von V1, auf dem Flachbandkabel steht 2015 08.01. Mit verlaub, alles sehr verwirrend.
Ich versuche schon einige Zeit es zum Laufen zu bekommen. Bisher ohne Erfolg.
Viele Grüsse

Gerd

Gerd

Vielen Dank für deinen ausführlichen Artikel!
Ich habe eine neuere Variante des waveshare 1,54“ BW Displays am esp8266 D1 mini. Aufkleber V2, Rev.2.1.
Mit der Deklaration GxEPD2_BW<GxEPD2_154_D67 funktionieren die partiellen Fenster nicht. Keine Anzeige im partiellen Fenster.
In diesen Fall ist GxEPD2_BW<GxEPD2_150_BW zu verwenden. Siehe https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865/3125
Um einen Sensor BME280 per i2c anschließen zu können, änderte ich die Pin’s am esp für RST und Busy des ePaper-Moduls. RST an D4 (GPIO 2) und Busy an D6 (GPIO 12).
display(GxEPD2_DRIVER_CLASS(/CS=15/ EPD_CS, /DC=D3/ 0, /RST=D4/ 2, /BUSY=D6/ 12));
Bei dieser Belegung ist ein Widerstand 4k7 von D8 gegen GND und ein Widerstand 1k von D4 an 3,3V anzuschließen.

Andreas Wolter

Andreas Wolter

@Carl Werner: Vielen Dank für den Hinweis. Wurde korrigiert.

Grüße,
Andreas Wolter
AZ-Delivery Blog

Carl Werner

Carl Werner

Die letzte Version des Skriptes (WifiData) ist wohl falsch verlinkt:
es erscheint nochmal die Version für die epd-Bibliothek, statt der für Gxepd2

Andreas Wolter

Andreas Wolter

@Georg: Vielen Dank. Nach meinem Kenntnisstand kann man die SPI-Schnittstelle teilen. Lediglich des CS Pin muss sich unterscheiden. Es gibt für die GxEPD2 Bibliothek ein Beispiel namens GxEPD2_MultiDisplay. Ich würde sagen, es ist sehr wahrscheinlich, dass es funktioniert. 100%ig sicher bin ich mir jedoch nicht.
Hier wurde diese Frage auch gestellt: https://forum.arduino.cc/t/two-pcd8544-on-wemos-d1-mini/484521/2
und mit Ja beantwortet.
Probieren Sie es aus und lassen uns wissen, ob es funktioniert hat.

Grüße,
Andreas Wolter
AZ-Delivery Blog

Joachim

Joachim

Toller Beitrag, herzlichen Dank zunächst! Ich erwäge, zwei Module an einem Board abzuschließen (vermutlich auch zwei größere, bspw. 4,3 Zoll). Das soll später per MQTT durch den home Assistant mit Daten gefüttert werden.
Meldet Frage: ist es grundsätzlich möglich, zwei epaper an ein Board anzuschließen und welches hätte ausreichend Anschlüsse? Das D1 dürfte wohl zu klein dafür sein…

Andreas Wolter

Andreas Wolter

@Dalwinder: There is no complete download of all sketches in a ZIP at this moment. If you click on all download links you will get all files.
It then depends on which microcontroller and display you are currently using.

im Moment gibt es keinen kompletten Download von allen Sketches in einer ZIP. Wenn Sie alle Download-Links anklicken, erhalten Sie alle Dateien.
Es hängt dann davon ab, welchen Mikrocontroller und welches Display Sie momentan verwenden.

Andreas Wolter
AZ-Delivery Blog

Dalwinder

Dalwinder

i want to see how e-paper display works. it is putting me close loop. some time this , some time that file is not included. i need a complete zip to download and see how does it looks like , please give me some way out . THANKS every body over there.

Markus Walsleben

Markus Walsleben

Hallo Herr Taz,
hier meine späte Antwort auf ihre Frage, die ich aber möglicherweise auch nicht richtig verstanden habe.
Die Library binde ich ich PIO-üblich über die Suche und dann “Add to Project” ein.
Dann habe ich in meinem Quelltext nur noch die Einbindung der passenden Variante (“#include ”) und dann definiere ich die Pins mittels
#define CS_PIN 26
#define DC_PIN 16
#define RST_PIN 22
#define BUSY_PIN 21
und der passenden Zeile
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(CS_PIN, DC_PIN, RST_PIN, BUSY_PIN)); // GDEH0154D67

Für die Initialisierung habe ich eine eigene Routine:
void initDisplay()
{
display.init(); // uses standard SPI pins, e.g. SCK, MISO, MOSI, SS
// * special handling for Waveshare ESP32 Driver board * //
// ********************************************************* //
SPI.end(); // release standard SPI pins, e.g. SCK, MISO, MOSI, SS
//SPI: void begin(int8_t sck=-1, int8_t miso=-1, int8_t mosi=-1, int8_t ss=-1);
SPI.begin(18, 12, 23, 15); // map and init SPI pins SCK, MISO, MOSI, SS
// * end of special handling for Waveshare ESP32 Driver board * //
// **************************************************************** //
display.setRotation(0);
display.setFullWindow();
display.fillScreen(FillColor);
display.nextPage();
}
Danach ist das Display mit den display-Routinen wie gewünscht zu nutzen.

@Andreas Wolter: Sorry für die Verzögerung, die Mail ist einfach zu weit nach unten gerutscht.

uwetaz

uwetaz

Hallo Hr. Walsleben,
vielleicht hätten Sie einen Tipp für die Portierung nach PlatformIO für mich.
Nachdem mit einem V2-Display das Beispiel GxEPD2_Example in der Arduino IDE bei mir läuft wollte ich das Ganze auch nach PlatformIO portieren. Ich arbeite seit längerem mit den ESPs damit, weiß aber nicht wie ich das mit den Files für die Display selection machen muss. Ich habe zwar im Unterordner .pio\libdeps\esp32dev\GxEPD2 das Beispiel wiedergefunden, aber es gibt die selection Files für jedes Beispiel. Wo muss denn nun ein File angelegt werden? Könnten Sie vielleicht Ihre Struktur hier schreiben?

Das wäre sehr nett!

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

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery