Eine ESP-Wetterstation       

von Günther Zöppel                       
 
                      Elektronik-Labor  Bastelecke  Projekte  Mikrocontoller                        




Ich besitze noch eine ältere  mechanische Wetterstation, die langsam ihre Grenznutzungsdauer erreicht ;-)  Deshalb machte ich mir Gedanken, wie man eine solche auf elektronische Art realisieren könnte. Die Anzeige der mechanischen Station beschränkt sich auf die wichtigsten Daten : Uhrzeit, Luftdruck, Luftfeuchte  und Temperatur. Das müsste doch mit elektronischen Mitteln machbar sein?  Herausgekommen ist folgende Schaltung:



Für die Anzeige standen 3 Displays GC9A01A zur Verfügung, die kreisrund und farbig sind und mit 240 Pixeln Diagonale ausreichend Auflösung bieten. Als Steuerelemente wurden  2 ESP-Microcontroller eingesetzt, ein ESP8266 D1 mini für die Uhrzeit (die vom heimischen WLAN sekundengenau vom NTP-Server abgeholt wird) und ein ESP32-WROOM-32, der die Steuerung der weiteren zwei Displays und Auswertung der Sensoren (DHT22 für Feuchte und Temperatur sowie BMP180 für den Luftdruck) übernimmt. Die Stromversorgung übernimmt ein elektronischer Netztrafo HLK-10M05B, der preiswert erhältlich ist und die Netzspannung von 230V galvanisch getrennt direkt auf stabilisierte 5 V= /  max. 2A umsetzt.






 
Ich habe zwei Sketche für die Controller geschrieben, die sehr trivial gehalten sind und mit den Hinweisen  im Text problemlos auf eigene Verhältnisse anpassbar sind. Mittels der Arduino IDE 1.8.19 können diese über USB auf den jeweils zuständigen Controller geflasht werden. Die im Sketch includierten Bibliotheken sind vor der Kompilierung zu installieren.

Zu beachten ist die Anzeige des Luftdrucks, der hier mit seinem absoluten Wert – gemessen am Ort des Sensors (BMP180) - dargestellt wird. Der offizielle Wetterbericht gibt den Luftdruck immer bezogen auf Meeresspiegelhöhe (0 m) an, daher ist der tatsächliche Druck in höheren Lagen geringer. Wer möchte, kann diesen im Sketch für den jeweiligen Ort umrechnen lassen.

Ebenso ist eine gewisse Abweichung bei der Temperatur zu verzeichnen, da der DHT 22 die chipinterne Temperatur misst und diese durch Eigenerwärmung höher als tatsächlich ausfällt. Man sollte die Aktualisierungsrate der Messwertabfrage (im Sketch anpassbar) gering halten (ca. 1 min), denn bei häufigerer Abfrage erwärmt sich der Chip stärker – evtl. ist zur Korrektur ein Referenzthermometer heranzuziehen und die Differenz zum tatsächlichen Wert im Sketch einzurechnen.  Damit Luftfeuchte und -temperatur korrekt angezeigt werden, sollte das Gehäuse keinesfalls völlig geschlossen sein und auf Höhe des Sensors einen entsprechenden stets freizuhaltenden Lüftungsdurchbruch erhalten.





Zur Programmierung der Displays GC9A01A ist evtl. folgende Skizze hilfreich, welche die Adressierung der Pixel etwas klarer macht und im Sketch entsprechend angepasst werden kann.



Die Uhr könnte bei Bedarf auch getrennt aufgebaut werden, um eine Tagesuhr zur Verfügung zu haben.

Fazit:
Sollte meine mechanische Wetterstation demnächst den Geist aufgeben, steht hiermit ein elektronisches Äquivalent zur Verfügung.

G.Zöppel
Juli2025

Temp_Druck_Feuchte
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <DHT.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085.h>

// PIN-Konfiguration für das erste Display
#define TFT1_CS 15
#define TFT1_DC 2
#define TFT1_RST 4

// PIN-Konfiguration für das zweite Display
#define TFT2_CS 26
#define TFT2_DC 25
#define TFT2_RST 27

// PIN-Konfiguration für den DHT22
#define DHTPIN 13 // Pin, an dem der DHT22 angeschlossen ist
#define DHTTYPE DHT22

// Initialisierung der Displays
Adafruit_GC9A01A tft1(TFT1_CS, TFT1_DC, TFT1_RST);
Adafruit_GC9A01A tft2(TFT2_CS, TFT2_DC, TFT2_RST);

// Initialisierung des DHT22-Sensors
DHT dht(DHTPIN, DHTTYPE);

// Initialisierung des BMP180-Sensors
Adafruit_BMP085 bmp;

// Variable für die Breite des gelben Kreises
int circleWidth = 5; // Wählbare Breite des Kreises in Pixel

void setup() {
// Serielle Kommunikation starten
Serial.begin(115200);
Serial.println("Starte Programm...");

// Displays initialisieren
tft1.begin();
tft1.fillScreen(GC9A01A_BLACK);

tft2.begin();
tft2.fillScreen(GC9A01A_BLACK);

// Gelben Kreis und roten Strich auf beiden Displays zeichnen
drawStaticElements(tft1);
drawStaticElements(tft2);

// DHT22 initialisieren
dht.begin();

// BMP180 initialisieren
if (!bmp.begin()) {
Serial.println("Fehler: BMP180 nicht gefunden!");
while (1); // Stoppt den Sketch, wenn der Sensor nicht gefunden wird
}
}

void loop() {
// DHT-Werte auf Display 1 aktualisieren
updateDisplay1();

// BMP180-Werte auf Display 2 aktualisieren
updateDisplay2();

delay(2000);
}

void drawStaticElements(Adafruit_GC9A01A &display) {
int centerX = display.width() / 2;
int centerY = display.height() / 2;
int radius = (display.width() < display.height() ? display.width() : display.height()) / 2 - circleWidth;

// Gelben Kreis zeichnen
for (int i = 0; i < circleWidth; i++) {
display.drawCircle(centerX, centerY, radius - i, GC9A01A_YELLOW);
}

// Roten Strich zeichnen
display.drawLine(centerX - radius, centerY, centerX + radius, centerY, GC9A01A_RED);
}

void updateDisplay1() {
// Messung der Luftfeuchtigkeit und Temperatur
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();

if (isnan(humidity) || isnan(temperature)) {
Serial.println("Fehler beim Lesen der DHT22-Daten!");
return;
}

// Bildschirmbereiche für Werte löschen
int centerX = tft1.width() / 2;
int centerY = tft1.height() / 2;
int radius = (tft1.width() < tft1.height() ? tft1.width() : tft1.height()) / 2 - circleWidth;
tft1.fillRect(centerX - radius, centerY - 80, radius * 2, 160, GC9A01A_BLACK);

drawStaticElements(tft1);

// Luftfeuchtigkeit anzeigen
tft1.setTextColor(GC9A01A_CYAN, GC9A01A_BLACK);
tft1.setTextSize(2);
tft1.setCursor(centerX - (10 * 5) - 8, centerY - 70);
tft1.print("Luftfeuchte");

tft1.setCursor(centerX - (10 * 3), centerY - 40);
tft1.print(humidity, 1);
tft1.print(" %");

// Temperatur anzeigen
tft1.setCursor(centerX - (10 * 5) - 6, centerY + 25);
tft1.print("Temperatur");

tft1.setCursor(centerX - (10 * 3) - 6, centerY + 60);
tft1.print(temperature, 1);

tft1.setTextSize(1);
tft1.setCursor(centerX + 23, centerY + 53);
tft1.print("o");

tft1.setTextSize(2);
tft1.setCursor(centerX + 30, centerY + 60);
tft1.print("C");
}

void updateDisplay2() {
// Messung des Luftdrucks
float pressure = bmp.readPressure() / 100.0; // Konvertierung in hPa

int centerX = tft2.width() / 2;
int centerY = tft2.height() / 2;
int radius = (tft2.width() < tft2.height() ? tft2.width() : tft2.height()) / 2 - circleWidth;
tft2.fillRect(centerX - radius, centerY - 80, radius * 2, 160, GC9A01A_BLACK);

drawStaticElements(tft2);

// Luftdruck anzeigen
tft2.setTextColor(GC9A01A_CYAN, GC9A01A_BLACK);
tft2.setTextSize(2);
tft2.setCursor(centerX - (10 * 5) - 8, centerY - 70);
tft2.print("Luftdruck");

tft2.setCursor(centerX - (10 * 6), centerY - 40);
tft2.print(pressure, 1);
tft2.print(" hPa");

// Text "Zoeppel" und "Electronic" anzeigen
tft2.setCursor(centerX - (10 * 4), centerY + 25);
tft2.print("Zoeppel");

tft2.setCursor(centerX - (10 * 5), centerY + 60);
tft2.print("Electronic");

Serial.print("Luftdruck: ");
Serial.print(pressure);
Serial.println(" hPa");
}

Wetterstation_Uhr
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Display-Pins
#define TFT_CS D8 // Chip Select
#define TFT_DC D3 // Data/Command
#define TFT_RST D4 // Reset

// Display-Objekt
Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_RST);

// WLAN-Zugangsdaten
const char* ssid = "Mein_WLAN";
const char* password = "Mein_WLAN_Passwort";

// NTP-Client
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7200, 60000); // Zeitzone: MEZ 3600, MESZ 7200

// Vorherige Werte zum Vermeiden von unnötigem Blinken
int prevHours = -1, prevMinutes = -1, prevSeconds = -1;
char prevDate[20] = "";
char prevDay[10] = "";

void setup() {
Serial.begin(115200);

// Display initialisieren
tft.begin();
tft.setRotation(0);
tft.fillScreen(GC9A01A_BLACK);

// WLAN verbinden
Serial.print("Verbinde mit WLAN...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWLAN verbunden!");

// NTP-Client starten
timeClient.begin();
Serial.println("NTP-Client gestartet.");

// Gelben Rand und roten Strich zeichnen
drawCircleAndLine();
}

void loop() {
timeClient.update();

int hours = timeClient.getHours();
int minutes = timeClient.getMinutes();
int seconds = timeClient.getSeconds();

// Datum und Wochentag berechnen
time_t epochTime = timeClient.getEpochTime();
struct tm *tm = gmtime(&epochTime);

// Wochentage auf Deutsch
const char* germanDays[] = {
"Sonntag", "Montag", "Dienstag", "Mittwoch",
"Donnerstag", "Freitag", "Samstag"
};
const char* currentDay = germanDays[tm->tm_wday];

// Datum formatieren
char currentDate[20];
sprintf(currentDate, "%02d.%02d.%04d", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900);

// Zeit in der oberen Hälfte anzeigen
if (hours != prevHours || minutes != prevMinutes) {
displayTime(hours, minutes, seconds, true);
prevHours = hours;
prevMinutes = minutes;
} else {
displayTime(hours, minutes, seconds, false);
}

// Datum und Wochentag nur bei Änderung aktualisieren
if (strcmp(currentDay, prevDay) != 0 || strcmp(currentDate, prevDate) != 0) {
displayDate(currentDay, currentDate);
strcpy(prevDay, currentDay);
strcpy(prevDate, currentDate);
}
}

void drawCircleAndLine() {
// Gelber Kreis am Rand
tft.drawCircle(120, 120, 119, GC9A01A_YELLOW);
tft.drawCircle(120, 120, 118, GC9A01A_YELLOW);
tft.drawCircle(120, 120, 117, GC9A01A_YELLOW);
tft.drawCircle(120, 120, 116, GC9A01A_YELLOW);

// Roter waagerechter Strich durch die Mitte
tft.drawLine(5, 120, 235, 120, GC9A01A_RED);
}

void displayTime(int hours, int minutes, int seconds, bool updateMain) {
// Stunden und Minuten als HH:MM formatieren
char timeMainBuffer[6];
sprintf(timeMainBuffer, "%02d:%02d", hours, minutes);

// Sekunden als SS formatieren
char timeSecBuffer[3];
sprintf(timeSecBuffer, "%02d", seconds);

// Hintergrund der oberen Hälfte nur bei Änderung bereinigen
if (updateMain) {
tft.fillRect(40, 40, 160, 42, GC9A01A_BLACK); // Hauptzeit-Bereich
}

// Nur aktualisieren, wenn die Sekunden sich geändert haben
if (seconds != prevSeconds) {
tft.fillRect(95, 85, 70, 30, GC9A01A_BLACK); // Sekunden-Bereich

tft.setTextColor(GC9A01A_CYAN);
tft.setTextSize(3);
tft.setCursor(110, 90);
tft.print(timeSecBuffer);

prevSeconds = seconds; // Neue Sekunde speichern
}

// Hauptzeit (HH:MM) zentriert anzeigen
if (updateMain) {
tft.setTextColor(GC9A01A_CYAN);
tft.setTextSize(4);
tft.setCursor(65, 50);
tft.print(timeMainBuffer);
}
}

void displayDate(const char* day, const char* date) {
// Hintergrund der unteren Hälfte reinigen
tft.fillRect(40, 125, 160, 70, GC9A01A_BLACK);

// Wochentag anzeigen
tft.setTextColor(GC9A01A_CYAN);
tft.setTextSize(2);
tft.setCursor(75, 140); // Näher zur roten Linie
tft.print(day);

// Datum anzeigen
tft.setCursor(60, 170); // Direkt unter dem Wochentag
tft.print(date);
}

Elektronik-Labor  Bastelecke  Projekte  Mikrocontoller