In diesem Tutorial lernst du, wie du einen Monatskalender auf einem E-Paper-Display mit einem ESP32 implementierst. Das Display zeigt auch die aktuelle Uhrzeit an und synchronisiert die Zeit mit einem Internet-Zeitserver (SNTP-Server), sodass Zeit und Kalender immer genau sind. Kein manuelles Einstellen von Zeit und Datum mehr.
Benötigte Teile
Ich verwende den ESP32 lite als Mikroprozessor, da er günstig ist und eine Batterieschnittstelle zum Laden hat, was es ermöglicht, den Kalender mit einer LiPo-Batterie zu betreiben. Jeder andere ESP32 oder ESP8266 mit ausreichend Speicher funktioniert ebenfalls, aber am besten nimmst du einen mit Batterieschnittstelle. Ein Arduino sollte auch funktionieren, habe ich aber nicht ausprobiert.

4,2″ E-Paper Display

ESP32 lite

USB-Datenkabel

Dupont-Kabelset

Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
E-Paper Display
Das in diesem Projekt verwendete E-Paper-Display ist ein 4,2″ Modul mit 400×300 Pixel Auflösung, 4 Graustufen, einer partiellen Aktualisierungszeit von 0,4 Sekunden und einem eingebetteten Controller mit SPI-Schnittstelle.

Beachte, dass das Modul auf der Rückseite einen kleinen Jumper-Pad/Schalter hat, um von 4-Draht-SPI auf 3-Draht-SPI umzuschalten. Wir verwenden hier den Standard 4-Draht-SPI, daher musst du nichts ändern.

Das Display-Modul läuft mit 3,3V oder 5V, hat einen sehr niedrigen Schlafstrom von 0,01µA und verbraucht beim Aktualisieren nur etwa 26,4mW. Für mehr Informationen zu E-Paper-Displays allgemein, schau dir die folgenden zwei Tutorials an: Interfacing Arduino To An E-ink Display und Weather Station on e-Paper Display.
Anschluss und Test des E-Paper
Zuerst verbinden wir das E-Paper und testen seine Funktion. Das folgende Bild zeigt die komplette Verkabelung für Stromversorgung und SPI.

Unten eine Tabelle mit allen Verbindungen zur Übersicht. Beachte, dass du das Display mit 3,3V oder 5V versorgen kannst, aber der ESP32-lite hat nur einen 3,3V-Ausgang und die SPI-Datenleitungen müssen 3,3V sein!
| E-Paper Display | ESP32 lite |
|---|---|
| CS/SS | 5 |
| SCL/SCK | 18 |
| SDA/DIN/MOSI | 23 |
| BUSY | 15 |
| RES/RST | 2 |
| DC | 0 |
| VCC | 3,3V |
| GND | G |
Du kannst ein Breadboard verwenden, um alles zu verdrahten. Ich habe das Display allerdings direkt mit Dupont-Kabeln an den ESP32 angeschlossen. Das Bild unten zeigt, wie mein Setup aussah.

GxEPD2 Bibliothek installieren
Bevor wir auf dem E-Paper-Display zeichnen oder schreiben können, müssen wir zwei Bibliotheken installieren. Die Adafruit_GFX Grafikbibliothek, die eine gemeinsame Sammlung von Grafikprimitiven (Text, Punkte, Linien, Kreise usw.) bereitstellt. Und die GxEPD2 Bibliothek, die die Grafiktreiber-Software für das E-Paper-Display liefert.
Installiere die Bibliotheken einfach wie gewohnt. Nach der Installation sollten sie im Library Manager wie folgt erscheinen.

Testcode
Nachdem du die Bibliothek installiert hast, führe den folgenden Testcode aus, um sicherzustellen, dass alles funktioniert.
#include "GxEPD2_BW.h"
//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));
void setup() {
epd.init(115200, true, 50, false);
epd.setRotation(1);
epd.setTextColor(GxEPD_BLACK);
epd.setTextSize(2);
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
epd.setCursor(90, 190);
epd.print("Makerguides");
epd.display();
epd.hibernate();
}
void loop() {}
Der Textcode gibt den Text „Makerguides“ aus und nach einem kurzen Flackern des Displays (Vollaktualisierung) solltest du ihn wie unten gezeigt auf deinem Display sehen:

Wenn nicht, stimmt etwas nicht. Wahrscheinlich ist das Display nicht korrekt verkabelt oder der falsche Display-Treiber gewählt. Die kritische Codezeile ist diese, in der wir den 4,2″ Display-Treiber angeben:
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));
Die Readme GxEPD2 Bibliothek listet alle unterstützten Displays auf, und du findest die Details in den Header-Dateien, z.B. GxEPD2.h. Finde den Display-Treiber, der zu deinem Display passt. Das kann etwas Ausprobieren erfordern.
Code für einen Kalender auf E-Paper
In diesem Abschnitt schreiben wir den Code für unseren Kalender. Der Screenshot unten zeigt, wie er aussehen wird:

Der aktuelle Tag und die Uhrzeit werden oben angezeigt. Darunter zeigt das Display den aktuellen Monat, das Jahr und die Tage des Monats mit dem markierten aktuellen Tag.
Unten ist der Code für diesen Kalender. Schau ihn dir zuerst kurz an, um einen Überblick zu bekommen, dann gehen wir ins Detail:
#include <WiFi.h>
#include <time.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
const char* ssid = "SSID";
const char* password = "PWD";
const char* ntpServer = "pool.ntp.org";
const char* timezone = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* dayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
char* dayLongNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* monthNames[] = {
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November", "December"
};
const int shifts[] = {
3, 0, 0, 1, 0, 0, 0, 0, 0, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1
};
//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));
bool isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int daysInMonth(int year, int month) {
int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month-1];
}
// Return day of the week (0=Sunday, 1=Monday, ..., 6=Saturday)
int dayOfWeek(int year, int month, int day) {
tm timeinfo = {};
timeinfo.tm_year = year - 1900;
timeinfo.tm_mon = month - 1;
timeinfo.tm_mday = day;
mktime(&timeinfo);
return timeinfo.tm_wday;
}
void drawAt(int x, int y, const char* text, int shift, bool mark) {
int16_t xb, yb;
uint16_t wb, hb;
epd.getTextBounds(text, 0, 0, &xb, &yb, &wb, &hb);
epd.setCursor(x - wb - shift, y);
epd.setTextColor(mark ? GxEPD_WHITE : GxEPD_BLACK);
if (mark) {
epd.fillRect(x - wb - shift - 3, y - hb - 3, wb + 8, hb + 8, GxEPD_BLACK);
}
epd.print(text);
}
void drawCalendar(int year, int month, int day) {
char buf[6];
int dy = 25;
int dx = 56;
int xoff = 10;
int yoff = 100;
// Print name of month and the year
epd.setPartialWindow(0, yoff - 20, 400, 300 - (yoff - 20));
epd.fillRect(6, yoff - 20, 400 - 12, 28, GxEPD_BLACK);
epd.setFont(&FreeSansBold12pt7b);
epd.setTextColor(GxEPD_WHITE);
epd.setCursor(xoff, yoff);
epd.printf("%s %d", monthNames[month - 1], year);
// print names of week days
epd.setTextColor(GxEPD_BLACK);
epd.setFont(&FreeSansBold9pt7b);
xoff += 30;
for (int i = 0; i < 7; i++) {
int x = xoff + i * dx;
int y = yoff + 35;
drawAt(x, y, dayNames[i], 0, false);
}
// Print days of the month
int days = daysInMonth(year, month);
int firstDay = dayOfWeek(year, month, 1);
int x = xoff;
int y = yoff + 65;
for (int i = 0; i < firstDay; i++) {
x += dx; // shift pos of first day
}
epd.setFont(&FreeSans9pt7b);
for (int d = 1; d <= days; d++) {
sprintf(buf, "%d", d);
drawAt(x, y, buf, shifts[d - 1], d == day);
x += dx;
if ((firstDay + d) % 7 == 0) {
y += dy;
x = xoff;
}
}
}
void updateTime(const void* pv) {
struct tm t;
getLocalTime(&t);
epd.setTextColor(GxEPD_BLACK);
epd.setFont(&FreeSansBold24pt7b);
epd.setCursor(10, 60);
epd.setPartialWindow(0, 0, 400, 80);
epd.printf("%s %02d:%02d", dayLongNames[t.tm_wday], t.tm_hour, t.tm_min);
}
void updateCalendar(const void* pv) {
struct tm t;
getLocalTime(&t);
drawCalendar(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
}
bool isNewDay() {
struct tm t;
getLocalTime(&t);
return t.tm_hour == 0 && t.tm_min == 0;
}
void syncTime() {
WiFi.begin(ssid, password);
for (int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) {
delay(100);
}
if (WiFi.status() == WL_CONNECTED)
configTzTime(timezone, ntpServer);
}
void clearScreen() {
epd.setFullWindow();
epd.fillScreen(GxEPD_WHITE);
epd.display();
}
void initEPD() {
epd.init(115200, true, 50, false);
epd.setRotation(0);
}
void setup() {
Serial.begin(115200);
syncTime();
initEPD();
clearScreen();
epd.drawPaged(updateCalendar, 0);
epd.hibernate();
}
void loop() {
if (isNewDay()) {
syncTime();
clearScreen();
epd.drawPaged(updateCalendar, 0);
}
epd.drawPaged(updateTime, 0);
epd.hibernate();
delay(60 * 1000); // 60 seconds
}
Bibliotheken
Der Code beginnt mit dem Einbinden der notwendigen Bibliotheken für WiFi-Verbindung, Zeitmanagement und E-Paper-Display-Funktionalität.
#include <WiFi.h> #include <time.h> #include <GxEPD2_BW.h>
Schriftarten
Danach binden wir die verwendeten Schriftarten ein. Du kannst andere wählen, solange sie die gleiche Größe haben (9pt, 12pt, 24pt), sollte das Kalenderlayout funktionieren. Eine Liste verfügbarer Schriftarten findest du hier.
#include <Fonts/FreeSans9pt7b.h> #include <Fonts/FreeSansBold9pt7b.h> #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSansBold24pt7b.h>
Konstanten
Als nächstes definieren wir einige Konstanten für WiFi-Zugangsdaten, NTP-Server, Zeitzone, Arrays für Tages- und Monatsnamen sowie Layout-Korrekturen.
const char* ssid = "SSID";
const char* password = "PWD";
const char* ntpServer = "pool.ntp.org";
const char* timezone = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* dayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
char* dayLongNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* monthNames[] = {
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November", "December"
};
const int shifts[] = {
3, 0, 0, 1, 0, 0, 0, 0, 0, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1
};
Du musst die SSID und PASSWORD Konstanten für dein WiFi ersetzen und wahrscheinlich auch die TIMEZONE für deine Zeitzone.
Die Zeitzonenspezifikation „AEST-10AEDT,M10.1.0,M4.1.0/3“ ist für Australien und entspricht der Australian Eastern Standard Time (AEST) mit Sommerzeit-Anpassungen. Für andere Zeitzonendefinitionen schau dir das Posix Timezones Database an. Kopiere einfach den dort angegebenen String und ändere die TIMEZONE Konstante entsprechend.
Verschiebungen
Die Konstanten für die Namen der Tage und Monate sind klar, aber die shifts Konstante braucht eine Erklärung. Die Tagesnamen und die Tage des Monats im Kalender werden rechtsbündig gezeichnet. Ich benutze die Funktion getTextBounds() dafür. Allerdings sind die von dieser Funktion berechneten Textgrenzen nicht ganz genau, und die Rechtsbündigkeit daher auch nicht perfekt. Siehe unten:

Die shifts Konstanten verschieben die Rechtsbündigkeit um die angegebene Anzahl Pixel für einen bestimmten Tag, z.B. shifts[0] verschiebt Tag 1 um drei Pixel nach rechts. Das Bild oben vergleicht die Ausrichtung der Tagesspalten ohne (links) und mit (rechts) der Verschiebungskorrektur. Du siehst, dass die Tageszahlen links nicht perfekt rechtsbündig sind, was beim Betrachten des gesamten Kalenders störend wirkt. Die Verschiebungskonstanten beheben das.
Display-Konfiguration
Die folgende Codezeile erstellt das Display-Objekt und verbindet es mit den Pin-Verbindungen für CS, SCL, SDA, BUSY, RES und DC.
//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0 GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));
Wenn du ein anderes E-Paper-Display verwendest, musst du dort eventuell einen anderen Treiber wählen. Die Readme GxEPD2 Bibliothek listet alle unterstützten Displays auf, und du findest die Details in den Header-Dateien, z.B. GxEPD2.h und GxEPD2_display_selection_new_style.h.
Datumsfunktionen
Als nächstes kommen einige Datumsfunktionen. Die isLeapYear() gibt true zurück, wenn das angegebene year ein Schaltjahr ist.
bool isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
Die daysInMonth() Funktion gibt die Anzahl der Tage in einem Monat zurück, wobei Schaltjahre im Februar berücksichtigt werden.
int daysInMonth(int year, int month) {
int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month - 1];
}
Und die dayOfWeek() Funktion berechnet den Wochentagsindex für ein gegebenes Datum. Sie gibt Werte von 0 (Sonntag) bis 6 (Samstag) zurück.
int dayOfWeek(int year, int month, int day) {
tm timeinfo = {};
timeinfo.tm_year = year - 1900;
timeinfo.tm_mon = month - 1;
timeinfo.tm_mday = day;
mktime(&timeinfo);
return timeinfo.tm_wday;
}
Zeichenfunktionen
drawAt Funktion
Die drawAt() Funktion ist verantwortlich für das Zeichnen von Text auf dem E-Paper-Display an angegebenen Koordinaten, mit optionaler Markierung.
void drawAt(int x, int y, const char* text, int shift, bool mark) {
int16_t xb, yb;
uint16_t wb, hb;
epd.getTextBounds(text, 0, 0, &xb, &yb, &wb, &hb);
epd.setCursor(x - wb - shift, y);
epd.setTextColor(mark ? GxEPD_WHITE : GxEPD_BLACK);
if (mark) {
epd.fillRect(x - wb - shift - 3, y - hb - 3, wb + 8, hb + 8, GxEPD_BLACK);
}
epd.print(text);
}
Die Markierung wird verwendet, um den aktuellen Tag hervorzuheben, indem er mit schwarzem Hintergrund unterlegt und der Text weiß geschrieben wird. Unten ein Beispiel, wie das für Tag 6 aussieht:

Beachte den shift Parameter, der zur Korrektur der Rechtsbündigkeit der Tage dient, wie oben beschrieben. Wir ermitteln die Textgrenzen durch Aufruf von getTextBounds(), verschieben dann den Text nach rechts, indem wir die Breite wb des Textes von der Zeichenposition x abziehen und zusätzlich die shift Korrektur abziehen.
drawCalendar Funktion
Die drawCalendar() Funktion zeichnet den gesamten Kalender für einen angegebenen Tag, Monat und Jahr.
void drawCalendar(int year, int month, int day) {
...
// Print name of month and the year
epd.setPartialWindow(0, yoff - 20, 400, 300 - (yoff - 20));
epd.fillRect(6, yoff - 20, 400 - 12, 28, GxEPD_BLACK);
epd.setFont(&FreeSansBold12pt7b);
epd.setTextColor(GxEPD_WHITE);
epd.setCursor(xoff, yoff);
epd.printf("%s %d", monthNames[month - 1], year);
// Print names of week days
epd.setTextColor(GxEPD_BLACK);
epd.setFont(&FreeSansBold9pt7b);
xoff += 30;
for (int i = 0; i < 7; i++) {
int x = xoff + i * dx;
int y = yoff + 35;
drawAt(x, y, dayNames[i], 0, false);
}
// Print days of the month
int days = daysInMonth(year, month);
int firstDay = dayOfWeek(year, month, 1);
int x = xoff;
int y = yoff + 65;
for (int i = 0; i < firstDay; i++) {
x += dx; // shift pos of first day
}
epd.setFont(&FreeSans9pt7b);
for (int d = 1; d <= days; d++) {
sprintf(buf, "%d", d);
drawAt(x, y, buf, shifts[d - 1], d == day);
x += dx;
if ((firstDay + d) % 7 == 0) {
y += dy;
x = xoff;
}
}
}
Die Kalenderansicht ist im Wesentlichen in vier Teile gegliedert. Der erste Teil (1) oben zeigt den Namen des aktuellen Tages und die aktuelle Uhrzeit. Der zweite Teil (2) zeigt den aktuellen Monat und das Jahr. Teil (3) zeigt die Namen der Wochentage. Und Teil (4) zeigt die Tage des Monats.

updateTime Funktion
Die oben gezeigte drawCalendar() Funktion zeichnet die Teile 2 bis 4, während die updateTime() Funktion unten Teil 1 zeichnet. Sie holt die aktuelle Ortszeit und zeigt sie an.
void updateTime(const void* pv) {
struct tm t;
getLocalTime(&t);
epd.setTextColor(GxEPD_BLACK);
epd.setFont(&FreeSansBold24pt7b);
epd.setCursor(10, 60);
epd.setPartialWindow(0, 0, 400, 100);
epd.printf("%s %02d:%02d", dayLongNames[t.tm_wday], t.tm_hour, t.tm_min);
}
updateCalendar Funktion
Ähnlich holt die updateCalendar() Funktion das aktuelle Datum und ruft dann drawCalendar() auf, um den angezeigten Kalender zu aktualisieren.
void updateCalendar(const void* pv) {
struct tm t;
getLocalTime(&t);
drawCalendar(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
}
Beachte, dass beide Update-Funktionen, updateTime() und updateCalendar() eine partielle Aktualisierung durchführen, die schneller ist und Flackern des Displays vermeidet. Wenn du mehr über die partielle Aktualisierung erfahren möchtest, schau dir das Partial Refresh of e-Paper Display Tutorial an.
Zeitsynchronisation
Die syncTime() Funktion stellt eine WiFi-Verbindung her und synchronisiert dann die interne Uhr des ESP32 mit dem SNTP-Server „pool.ntp.org“ durch Aufruf von configTzTime().
void syncTime() {
WiFi.begin(ssid, password);
for (int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) {
delay(100);
}
if (WiFi.status() == WL_CONNECTED)
configTzTime(timezone, ntpServer);
}
Du kannst auch andere oder mehrere SNTP-Server angeben. Sieh dir das How to synchronize ESP32 clock with SNTP server Tutorial für mehr Informationen an.
Beachte, dass die syncTime() Funktion nur 10 Mal versucht, sich mit dem WiFi zu verbinden, und dann aufgibt. Das hat den Vorteil, dass der Kalender weiterläuft, auch wenn kein WiFi verfügbar ist. Sonst würde der Code hier blockieren, bis WiFi verfügbar ist.
Setup Funktion
In der setup() Funktion initialisieren wir die serielle Kommunikation, synchronisieren die Zeit, initialisieren das E-Paper-Display, löschen den Bildschirm und zeichnen den Kalender.
void setup() {
Serial.begin(115200);
syncTime();
initEPD();
clearScreen();
epd.drawPaged(updateCalendar, 0);
epd.hibernate();
}
Das bedeutet, dass bei jedem Reset des ESP32 die Zeit synchronisiert wird und du dich nicht um das manuelle Einstellen von Zeit und Datum kümmern musst.
Loop Funktion
Die loop() Funktion prüft, ob ein neuer Tag begonnen hat, synchronisiert die Zeit, wenn ja, löscht den Bildschirm und aktualisiert den angezeigten Kalender und die Uhrzeit.
void loop() {
if (isNewDay()) {
syncTime();
clearScreen();
epd.drawPaged(updateCalendar, 0);
}
epd.drawPaged(updateTime, 0);
epd.hibernate();
delay(60 * 1000); // 60 seconds
}
Der Kalender wird nur einmal täglich neu gezeichnet/aktualisiert, während die Uhrzeit alle 60 Sekunden aktualisiert wird. Alle diese Updates sind partielle Aktualisierungen, die schnell und flackerfrei sind.
Allerdings führt die clearScreen() Funktion, die einmal täglich beim Kalender-Refresh aufgerufen wird, eine Vollaktualisierung durch. Das verhindert ein „Einbrennen“ von Nachbildern durch die partielle Aktualisierung. Das Waveshare Manual Tutorial enthält mehr Informationen dazu.
Beachte, dass die tägliche Zeitsynchronisation Sommerzeit-Änderungen berücksichtigt, aber es wird einige Stunden geben, in denen die Uhrzeit nicht korrekt ist. Du kannst das vermeiden, indem du etwa alle 30 Minuten synchronisierst. Sieh dir das Digital Clock on e-Paper Display Tutorial für Beispielcode an.
Fazit
In diesem Tutorial hast du gelernt, wie man einen Monatskalender auf einem E-Paper-Display mit einem ESP32 implementiert.
E-Paper-Displays verbrauchen sehr wenig Strom und sind daher ideal für batteriebetriebene Projekte. Du könntest den ESP32 mit dem Kalender auf einer LiPo-Batterie betreiben, aber in diesem Fall wäre es besser, den ESP32 zwischen den Display-Updates in den Deep-Sleep-Modus zu versetzen. Ich habe dir in diesem Tutorial nicht gezeigt, wie das geht, aber das Digital Clock on e-Paper Display Tutorial enthält Beispielcode dafür.
Außerdem wäre es für einen batteriebetriebenen Kalender schön, den Ladezustand der LiPo-Batterie zu überwachen und ein kleines Batteriesymbol anzuzeigen. Schau dir das Monitor Battery Levels with a MAX1704X Tutorial an, wenn du mehr über Batteriemonitoring lernen möchtest.
Neben dem Batterieladezustand könntest du auch die Umgebungstemperatur oder Wetterdaten anzeigen. Sieh dir das Weather Station on e-Paper Display Tutorial für mehr dazu an.
Wenn du Kommentare hast, hinterlasse sie gerne im Kommentarbereich.
Viel Spaß beim Tüfteln ; )

