Flugwiese

Außentemperatur-Meßstation mit Raspberry Pi Pico W und Messwerteanzeige im Web

Inhaltsverzeichnis
  1. Was die Meßstation kann
  2. Die Hardware
  3. Pinbelegung
  4. Der Temperaturfühler
  5. Alles Ortsabhängige steht auf der Karte
  6. Bedienung
  7. Aufzeichnung auf der SD-Karte
  8. Warum nicht jede Zeile sofort geschrieben wird
  9. Verlauf nach dem Neustart
  10. Die Messwerte auf der eigenen Webseite
  11. Die Stolperfallen — oder was man bei dieser Hardwarekonstellation beachten muss
  12. Die Display-Inversion fehlt fast überall
  13. Die ILI9488-Angabe führt in die Irre
  14. GP10, GP11 und GP12 liegen auf SPI1
  15. Chip-Select darf pro Byte umschalten
  16. Am Netzteil bleibt es dunkel, am Rechner läuft es
  17. Der Touch zieht den SPI-Takt herunter
  18. Die Beleuchtung verträgt keine schnelle PWM
  19. 85 Grad sind keine Messung
  20. Das Bild lässt sich drehen — der Touch nicht
  21. Der Trick zur Fehlersuche
  22. Warum Adafruit GFX und nicht TFT_eSPI
  23. Flackerfrei zeichnen ohne doppelten Bildpuffer
  24. Screendesign mit Claude Code
  25. Aufbau des Projekts
  26. Bekannte Einschränkungen
  27. Das Gehäuse
  28. Linksammlung

Angefangen hat es mit einem 3,5-Zoll-Touchdisplay, das ohne ein einziges Kabel direkt auf den Raspberry Pi Pico gesteckt wird. Das hatte zu einer Referenzimplementierung geführt, die ich hier vor kurzem veröffentlicht habe.
Nun ist daraus während meines Sommerurlaubes an drei Regentagen ein vollständiges Gerät geworden: Eine Meßstation mit großer Temperaturwertanzeige, 24-Stunden-Diagramm, Aufzeichnung der Daten auf SD-Karte, Uhrzeit über NTP und Übertragung der Messwerte an eine eigene Webseite.

Dieser Beitrag beschreibt die fertige Station in der Version 2.5 und auch die Stolperfallen auf dem Weg dorthin. Der Code ist auf echter Hardware erprobt und befindet sich bei mir auf mittlerweile mehreren Messgeräten im produktiven Einsatz. Der Link zum Download des gesamten Projektes befindet sich am Ende des Artikels.

Und wenn sich jetzt jemand wundert, warum es hier so eine tolle und ausführliche Projektbeschreibung gibt: Das Zauberwort heisst KI-Unterstützung – in meinem Falle Claude Code. Damit wurde nicht nur der Code für dieses Projekt erstellt sondern auch die gesamte Dokumentation. Was allerdings nicht erspart blieb, war die Überprüfung von Code und Dokumentation und die praktische Erprobung mittels der echten Hardware.
(Falls Interesse besteht, kann ich auch einmal mein Vorgehen und die Erfahrungen bei der SW-Entwicklung mit KI beschreiben – in diesem Falle bitte einen entsprechenden Kommentar hinterlassen)

Was die Meßstation kann

  • Messwertanzeige — Temperatur in großer Schrift, dazu Minimum und Maximum der letzten 24 Stunden
  • Diagramm — Verlauf der letzten 24 Stunden, Zeitachse mit vollen Stunden, Y-Achse mit mitwachsendem Raster
  • Status — Sensoren mit ROM-Adressen, Uhrzeit, WLAN, Kartenbelegung, Zustand des Uploads, Helligkeit
  • Dateiliste — was auf der SD-Karte liegt, mit Blättern
  • Aufzeichnung — je Kalendertag eine CSV-Datei mit echten Zeitstempeln
  • Uhrzeit über NTP — mit deutscher Sommerzeitregel, zyklisch nachgeführt
  • Verlauf übersteht den Neustart — beim Start wird der 24-Stunden-Verlauf aus den Tagesdateien wiederhergestellt
  • Webupload — alle 30 Minuten gehen aktueller Wert, Min/Max und der gesamte Verlauf an eine eigene PHP-Seite, die daraus dieselbe Kurve zeichnet wie das Gerät
  • Hintergrundbeleuchtung dimmbar — nach Uhrzeit oder, mit einem LDR, nach der Umgebungshelligkeit
  • Bild um 180 Grad drehbar — eine Zeile in einer Textdatei, für Montage über Kopf
  • Touch-Kalibrierung — beim ersten Start automatisch, im Flash abgelegt

Bemerkenswert daran ist weniger die Liste als das, was nicht im Quelltext steht: kein WLAN-Kennwort, keine Server-Adresse, nicht einmal die Beschriftung unter dem Messwert. Das alles kommt von der SD-Karte und kann dort mit wenig Aufwand individuell angepasst werden.


Die Hardware

Zum Einsatz kommt das Waveshare Pico-ResTouch-LCD-3.5, eine Aufsteckplatine für den Raspberry Pi Pico. Der Pico wird von unten eingesteckt, sämtliche Verbindungen laufen über die Stiftleisten. Löten oder verkabeln entfällt.

  • Auflösung 480 × 320 Pixel bei 3,5 Zoll Diagonale
  • Resistiver Touch über einen XPT2046-Controller
  • Anbindung über SPI1 — Display, Touch und SD-Karte teilen sich denselben Bus
  • microSD-Steckplatz auf der Rückseite, hier für Aufzeichnung und Konfiguration genutzt
  • Hintergrundbeleuchtung über einen eigenen GPIO, per PWM dimmbar

Als Rechner ist ein Pico W nötig, kein gewöhnlicher Pico: Ohne WLAN gibt es keine Uhrzeit über NTP, und ohne gültige Uhrzeit zeichnet das Gerät bewusst nichts auf. Eine Datei mit erfundenen Zeitstempeln wäre sinnlos.

Pinbelegung

Fest verdrahtet, nichts zu verkabeln:

SignalGPIOBemerkung
LCD_DCGP8Data/Command
LCD_CSGP9Chip Select Display
LCD_CLKGP10SPI1 SCK
LCD_MOSIGP11SPI1 TX
LCD_MISOGP12SPI1 RX
LCD_BLGP13Hintergrundbeleuchtung, per PWM gedimmt
LCD_RSTGP15Reset
TP_CSGP16Chip Select Touch
TP_IRQGP17Touch-Interrupt
SDGP18–GP22microSD, Chip Select auf GP22

Entscheidend: GP10/11/12 gehören zu SPI1, nicht SPI0. Dazu unten mehr.

Frei bleiben GP0–GP7, GP14 und GP26–GP28. Das Projekt belegt davon GP2 für den Temperatursensor und GP26 für den optionalen Lichtsensor.

Ein Zugangsproblem sei erwähnt: Das Display steckt auf dem Pico und belegt alle Stiftleisten, eigene Anschlussklemmen hat es nicht. Daher wird zum Anschluß des Temperatursensors ein wenig Lötarbeit nötig. Eine Alternative wären stapelbare Buchsenleisten auf dem Pico oder eine Zwischenplatine wie der Pico Omnibus von Pimoroni.

Der Temperaturfühler

Code
Pico 3V3(OUT) --+-- DS18B20 VDD  (rot)
                |
               4k7
                |
Pico GP2 -------+-- DS18B20 DQ   (gelb/weiß)

Pico GND ---------- DS18B20 GND  (schwarz)

Versorgung aus 3V3, nicht aus 5 V. Die Pico-GPIOs sind nicht 5-V-tolerant; der Pull-up legte sonst 5 V auf GP2.

Der 4,7-kΩ-Pull-up ist Pflicht, einmal für den ganzen Bus, nicht je Sensor. Bei Leitungen über etwa fünf Meter muss man diesen auf 3,3 kΩ verkleinern. Ohne Pull-up wird kein einziger Sensor gefunden — der mit Abstand häufigste Fehler, und er sieht aus wie ein defekter Sensor.

Hier ein Blick in das Gehäuse während der Entwicklung. Oben steckt noch das Mico-USB Kabel zum aufspielen des Code.
Die 5polige Buchse ist für den Temperatursensor, daneben die Rundbuchse für das 5V-Netzteil.

Hier noch die Pinbelegung der Rundbuchse:

Der erfoderliche 4k7 Pull-up Widerstand wurde an die Buchse gelötet, weil das deutlich weniger fummelig ist als wenn man diesen an das Pico Board lötet.

 


Alles Ortsabhängige steht auf der Karte

Vier Textdateien im Wurzelverzeichnis, nicht im Quelltext. Das hat zwei Gründe: Es steht kein Kennwort im Repository, und ein Netzwechsel oder eine andere Beschriftung braucht kein Neuflashen. Karte heraus, Datei ändern, Karte hinein.

Fehlt eine Datei, legt das Gerät beim Start eine kommentierte Vorlage an.

WLAN.TXT — nur 2,4 GHz. Fehlt die Datei, sucht das Gerät nach erreichbaren Netzen und gibt die Liste über die serielle Schnittstelle aus; das hilft bei der genauen Schreibweise der SSID.

Code
SSID=MeinNetz
PASSWORT=meinKennwort

SERVER.TXT — Ziel des Uploads. TITEL wandert mit den Werten zum Server und steht zugleich unter dem Messwert auf dem Gerät. Damit legt eine Textdatei fest, was das Gerät misst.

Code
URL=https://example.org/Temperatur/empfang.php
TOKEN=hier_das_gemeinsame_geheimnis
TITEL=Aussentemperatur

Die Adresse muss auf empfang.php selbst zeigen, ohne abschließenden Schrägstrich. Endet sie auf dem Verzeichnis, landet der POST bei der Anzeigeseite — die antwortet freundlich mit Code 200 und speichert nichts. Ein Fehler, der sich gut versteckt.

LICHT.TXT — Helligkeit nach Uhrzeit oder nach Umgebungslicht.

Code
TAG=100
NACHT=25
VON=22:00
BIS=06:30

SENSOR=0
DUNKEL=300
HELL=2800

SCREEN.TXT — Ausrichtung der Anzeige. 0 ist die Normallage, 180 dreht das Bild für Montage über Kopf. Der Touch dreht sich mit, ohne dass neu kalibriert werden muss. Das ist immer dann hilfreich, wenn man das Display irgendwo umgedreht einbauen muss, weil man sonst nicht mehr an die SD-Karte kommt.

Code
DREHUNG=0

Zeilen mit # werden in allen diesen TXT-Dateien überlesen, Leerzeichen um die Werte entfernt. Getrennt wird am ersten Gleichheitszeichen — ein Kennwort darf also selbst welche enthalten.


Bedienung

Vier Bildschirme, zwei Knöpfe. Diagramm, Status und Dateiliste aktualisieren sich bewusst nicht laufend: Sie zeigen den Stand zum Zeitpunkt des Aufrufs. Nach fünf Minuten ohne Tastendruck geht es zurück zur Messwertanzeige.

Links oben sitzt ein kleiner Statuspunkt: rot keine Verbindung, grün verbunden und ruhig, gelb Verkehr in den letzten Sekunden. Grün als Ruhezustand ist Absicht — dieses Gerät funkt nur zum Zeitabgleich und zum Upload. Wäre Grün an den Verkehr gekoppelt, zeigte der Punkt fast immer Gelb.


Aufzeichnung auf der SD-Karte

Je Kalendertag eine Datei, benannt nach dem Datum. Das sortiert sich in jedem Dateimanager von selbst richtig.

Code
Nr;Zeit;Sensor;Temperatur;Min24h;Max24h
1;20:15:03;1;18,4;17,9;24,3
2;20:17:03;1;18,3;17,9;24,3

Semikolon als Trenner, Komma als Dezimalzeichen — so öffnet sich die Datei in einer deutschen Excel-Installation ohne Nachbearbeitung.

Warum nicht jede Zeile sofort geschrieben wird

Eine Messzeile ist rund 30 Byte lang. Die Karte kennt aber keine 30 Byte: Sie löscht und beschreibt ganze Blöcke, und jedes Anhängen zieht neben dem Datenblock auch die Zuordnungstabelle und den Verzeichniseintrag nach. Aus einer Zeile werden mehrere Blockschreibvorgänge.

Bei zwei Minuten Messabstand wären das 720 Anhängevorgänge am Tag, über ein Jahr eine Viertelmillion — für acht Megabyte Nutzdaten. Karten mit ihrer begrenzten Zahl von Löschzyklen nehmen das übel, und jeder Vorgang hält außerdem den gemeinsamen SPI1 auf, an dem Anzeige und Touch hängen.

Die Zeilen sammeln sich deshalb im Arbeitsspeicher und gehen als Block auf die Karte:

einzelngesammelt
Schreibvorgänge je Tag72072
Verlust bei Stromausfallkeinerhöchstens 20 Minuten

Der Preis ist ehrlich zu benennen. Er ist verkraftbar, weil der Verlauf im Arbeitsspeicher bei einem Stromausfall ohnehin weg wäre und die Kurve danach eine Lücke zeigt statt eines falschen Wertes. Im Statusbildschirm steht hinter dem Dateinamen ein +n, solange Zeilen im Puffer liegen — ohne diese Angabe sähe eine Datei, die sich zwanzig Minuten lang nicht vergrößert, nach Ausfall aus.

Verlauf nach dem Neustart

Der 24-Stunden-Ringpuffer liegt im Arbeitsspeicher. Damit Gerät und Webseite nach einem Neustart nicht einen Tag lang einen zu engen Min/Max-Bereich zeigen, ohne dass man es ihnen ansieht, wird er beim Start aus den Tagesdateien von heute und gestern wiederhergestellt. Jeder Wert kommt in das Fach, das seinem Alter entspricht; Lücken durch Abschaltzeiten bleiben leer und werden von Min/Max und Diagramm übersprungen.


Die Messwerte auf der eigenen Webseite

Das Gerät sitzt hinter dem Router und ist von außen nicht erreichbar. Es meldet sich deshalb von sich aus beim Server, wie ein Browser — dafür braucht es keine Portfreigabe und keinen Dienst für die wechselnde IP-Adresse.

Drei PHP-Dateien liegen dem Projekt bei:

  • empfang.php nimmt den POST entgegen, prüft Token und Plausibilität und schreibt eine JSON-Datei
  • index.php zeigt Messwert, Min/Max und die Kurve als SVG
  • pruefung.php sagt bei der Einrichtung, woran es liegt — und wird danach gelöscht

Mitgeschickt wird bei jedem Upload der volle 24-Stunden-Verlauf, 96 Punkte zu je 15 Minuten. Der Server sammelt also nichts, sondern hält nur den zuletzt empfangenen Stand. Fällt ein Upload aus oder gehen die Serverdaten verloren, ist mit dem nächsten alles wieder da. Das ist die wesentlich robustere Bauform, und sie kostet nur rund ein Kilobyte je Übertragung.

Ein Detail, das Nerven gekostet hat: empfang.php prüft jeden Wert auf Plausibilität und setzt ihn auf null, wenn er außerhalb von −60 bis +80 °C liegt. Das ist richtig so — nur geschah es zunächst vollkommen stumm. Ein Sensor, der beim Kaltstart 85 °C lieferte (dazu gleich mehr), machte das 24-Stunden-Maximum unbrauchbar, es verschwand von der Seite, und nichts nannte den Grund. Die Prüfseite benennt verworfene Felder inzwischen ausdrücklich.

Übertragen wird über HTTPS mit setInsecure(): Die Verbindung ist verschlüsselt, das Zertifikat des Servers wird aber nicht geprüft. Ein fest hinterlegtes Zertifikat wäre sicherer, bräche aber alle 90 Tage, wenn Let’s Encrypt erneuert — für eine Temperaturanzeige ein schlechtes Geschäft.


Die Stolperfallen — oder was man bei dieser Hardwarekonstellation beachten muss

Diese Liste nennt die Stolperfallen bei der Umsetzung. Alle Punkte sind durch Tests bzw. Messung belegt, nicht aus Datenblättern abgeleitet.

Die Display-Inversion fehlt fast überall

Das Panel muss mit dem Kommando 0x21 (Display Inversion ON) initialisiert werden. Ohne dieses eine Kommando zeigt es jede Farbe als Komplement: Rot wird zu Cyan, Weiß zu Schwarz, Schwarz zu Weiß. Zahlreiche kursierende Treiber für dieses Board senden weder 0x21 noch 0x20 und überlassen die Sache dem Reset-Zustand — auf diesem Panel ist das schlicht falsch.

Wer an dieser Stelle versucht, das Problem durch Vertauschen der Farbkonstanten RED und BLUE zu beheben, kommt nicht weiter. Bei einer Inversion ist nicht die Kanalzuordnung falsch, sondern jeder einzelne Bitwert.

Die ILI9488-Angabe führt in die Irre

Waveshare nennt für dieses Board teilweise den Controller ILI9488. Ein ILI9488 kann am SPI-Bus grundsätzlich kein 16-Bit-Farbformat, er braucht 18 Bit mit drei Byte je Pixel. Das hier verbaute Panel verhält sich genau umgekehrt: Mit 0x3A = 0x55 (16 Bit) läuft es, mit 0x3A = 0x66 (18 Bit) bleibt das Bild schwarz.

Wer der Herstellerangabe folgt, bekommt kein Bild — und sucht den Fehler garantiert woanders. Die Chip-Kennung wurde nicht ausgelesen; das Verhalten spricht für einen ST7796-kompatiblen Controller.

GP10, GP11 und GP12 liegen auf SPI1

Nicht auf SPI0. Das klingt banal, ist aber die häufigste Ursache für ein totes Display bei Verwendung fertiger Bibliotheken.

Chip-Select darf pro Byte umschalten

Der Treiber zieht CS für jedes einzelne Byte auf LOW und wieder auf HIGH. Nach Datenblattlogik müsste das mehrbyteige Kommandos zerlegen und deren Parameter verwerfen. Auf diesem Controller tut es das nicht. Erwähnenswert, weil die naheliegende „Verbesserung“ — CS über die ganze Sequenz gedrückt halten — hier keinen Nutzen bringt und in der Erprobung Ärger gemacht hat.

Am Netzteil bleibt es dunkel, am Rechner läuft es

Ein Fehlerbild zum Verzweifeln: Nach dem Aufspielen über USB läuft alles, nach dem Einstecken am Netzteil bleibt der Schirm schwarz.

Die Ursache liegt vor der ersten Zeile Anwendungscode. Am gemeinsamen SPI1 hängen drei Bausteine mit eigenem Chip Select: Display (GP9), Touch (GP16) und SD-Karte (GP22). LOW bedeutet bei jedem von ihnen „ausgewählt“. Beim Kaltstart stehen die Pads des RP2040 auf Eingang mit Pull-down, also LOW — bis der jeweilige Treiber seinen Pin übernimmt, hören Touch und SD-Karte mit, während das Display seine Registersequenz bekommt, und antworten womöglich selbst auf den Bus. Nach einem Neustart aus der laufenden Firmware behalten die Pins dagegen ihr HIGH von vorher.

Die Behebung ist kurz und gehört an den Anfang von setup(), vor allem anderen:

C
pinMode(LCD_CS, OUTPUT);    digitalWrite(LCD_CS, HIGH);
pinMode(TOUCH_CS, OUTPUT);  digitalWrite(TOUCH_CS, HIGH);
pinMode(SD_CS_PIN, OUTPUT); digitalWrite(SD_CS_PIN, HIGH);
delay(250);                 // dem Panel Zeit geben, intern hochzufahren

Der Touch zieht den SPI-Takt herunter

Die XPT2046-Bibliothek stellt den Bus bei jedem Zugriff auf 2 MHz. Ohne eigene Transaktion je Zeichenvorgang bleibt das Display auf diesem Takt hängen — zehnfach verlangsamt, sichtbar als zeilenweiser Bildaufbau. Jede Zeichenoperation muss den Bus deshalb selbst auf ihre 20 MHz setzen.

Die Beleuchtung verträgt keine schnelle PWM

Am Anfang standen 20 kHz für die Hintergrundbeleuchtung, damit nichts fiept und auf Fotos keine Streifen entstehen. Am Gerät gemessen war das Display damit bei niedrigen Stufen praktisch dunkel: Die Treiberstufe hinter GP13 folgt der schnellen PWM nicht, sie sieht offenbar nur noch einen geglätteten Mittelwert. Bei 1 kHz ist dasselbe Tastverhältnis deutlich heller.

Dazu kommt die Kennlinie: Halbes Tastverhältnis sieht nicht halb so hell aus. Nach außen rechnet das Modul deshalb in wahrgenommenen Stufen 0–100 und biegt sie über ein Gamma von 2,0 auf das Tastverhältnis. Ohne das passiert unten fast nichts und oben springt es.

85 Grad sind keine Messung

Wer einen DS18B20 anschließt, sieht beim Einschalten mit einiger Wahrscheinlichkeit als ersten Wert 85,0 °C und danach plausible Temperaturen. Das ist keine Fehlmessung, sondern gar keine: 0x0550 im Temperaturregister, also genau +85,00 °C, ist der Zustand, mit dem der Sensor nach dem Anlegen der Versorgung dasteht. Er bedeutet „seit dem Einschalten wurde noch nicht gewandelt“.

Interessant ist, warum man ihn zu sehen bekommt, obwohl der Code auf das Ende der Wandlung wartet. Die DallasTemperature-Bibliothek kennt zwei Wege:

C
_dallas.setWaitForConversion(true);   // fragt den BUS ab
_dallas.requestTemperatures();
// ... oder ...
_dallas.setWaitForConversion(false);  // wir warten selbst
_dallas.requestTemperatures();
delay(800);                           // fragt die UHR ab

Der erste Weg fragt über den 1-Wire-Bus nach, ob die Wandlung fertig sei. Liegt die Leitung ohnehin hoch — schwacher Pull-up, lange Leitung, mehrere Sensoren am Strang —, meldet diese Abfrage sofort „fertig“, und gelesen wird ein Register, in dem noch nichts steht. Ein Zeitgeber lässt sich davon nicht täuschen.

Daher rührt das typische Muster, dass nur die allererste Messung falsch ist: Der laufende Betrieb wartet meist ohnehin über millis(), die einmalige Messung beim Start dagegen blockierend.

Naheliegend wäre, 85,0 einfach herauszufiltern. Das ist die falsche Abkürzung — für den DS18B20 ist es ein zulässiger Messwert, und ein Sensor, der dauerhaft in seinen Einschaltzustand zurückfällt, ist defekt oder schlecht versorgt. Das gehört sichtbar gemacht, nicht weggerechnet.

Und der Wert wirkt länger nach, als man denkt: Er wandert als 24-Stunden-Maximum in die Tagesdatei, wird beim nächsten Start von dort wieder in den Ringpuffer geladen und prägt die Anzeige bis zu einem vollen Tag.

Das Bild lässt sich drehen — der Touch nicht

Für die Montage über Kopf genügt ein einziger Registerwert. In MADCTL (0x36) kippen die Bits MY und MX das Bild um 180 Grad, alles andere — vor allem das BGR-Bit — bleibt stehen. Aus 0xE8 wird 0x28. Breite und Höhe ändern sich nicht, es bleibt Querformat 480 × 320, und keine einzige Zeichenkoordinate muss angefasst werden.

Die Falle sitzt woanders: Der XPT2046 dreht sich nicht mit. Er liefert unabhängig von der Bildlage dieselben Rohwerte. Wer nur MADCTL umstellt, hat ein richtig stehendes Bild und einen Touch, der punktsymmetrisch zur Bildmitte danebengreift — ein Fehlerbild, das man sich lange ansehen kann, ohne darauf zu kommen.

Die Umrechnung muss die Lage also kennen und beide Achsen spiegeln:

C
if (kalibriertGedreht != jetztGedreht) {
    x = (LCD_WIDTH  - 1) - x;
    y = (LCD_HEIGHT - 1) - y;
}

Wer die Lage zusätzlich im Kalibrierdatensatz ablegt, muss nach dem Umstellen nicht neu kalibrieren — und wer das gleich beim ersten Entwurf einplant, spart sich später eine Formatänderung.

Zweiter Punkt zur Reihenfolge: Die Einstellung muss gelesen sein, bevor der erste Text erscheint und bevor kalibriert wird. Sonst stehen Startbildschirm und Kalibrierkreuze auf dem Kopf, und die Kalibrierung entsteht in der falschen Lage.


Der Trick zur Fehlersuche

Wer ein Display mit unklarem Verhalten vor sich hat, sollte nicht mit bunten Farbbalken anfangen. Acht Balken nebeneinander sind mehrdeutig: Eine ungewohnte Reihenfolge kann von falschen Farben kommen — oder von einem um 180 Grad gedrehten Bild. Genau daran ist die Fehlersuche hier zweimal vorbeigelaufen.

Eindeutig wird es mit zwei Zeilen:

C
LCD_Clear(RED);                     // eine Farbe über den ganzen Schirm
LCD_FillRect(0, 0, 80, 50, WHITE);  // Marke am Koordinatenursprung

Daraus ergeben sich zwei Fragen ohne Interpretationsspielraum:

  • Welche Farbe hat der Hintergrund? Cyan statt Rot bedeutet fehlende Inversion. Blau statt Rot bedeutet vertauschte Rot- und Blaukanäle. Gelb bedeutet beides zusammen.
  • In welcher Ecke sitzt die weiße Marke? Sitzt sie nicht oben links, stimmt die Ausrichtung nicht.

Eine einfarbige Fläche hat keine Reihenfolge, die sich durch eine Drehung umkehren ließe. Das ist der ganze Trick — und er hätte einige Stunden gespart.

Dasselbe Prinzip zieht sich durch das ganze Projekt: Ein Gerät, dessen Zustand man nicht ablesen kann, ist nicht zu reparieren. Der Statusbildschirm zeigt deshalb nicht nur Messwerte, sondern auch die IP-Adresse, den Rohwert des Lichtsensors, die Kartenbelegung und den letzten Ausgang des Uploads samt Fehlernummer. Jede dieser Zeilen ist entstanden, nachdem ihr Fehlen einmal eine Stunde gekostet hatte.


Warum Adafruit GFX und nicht TFT_eSPI

TFT_eSPI ist die verbreitetere Wahl, deutlich schneller und bringt Sprites und geglättete Schriften mit. Die Entscheidung fiel trotzdem auf Adafruit GFX, und zwar aus einem pragmatischen Grund: TFT_eSPI lieferte auf diesem Aufbau auch nach Korrektur mehrerer belegbarer Konfigurationsfehler kein Bild. Die Ursache blieb offen.

Adafruit GFX verlangt von einer abgeleiteten Klasse lediglich eine drawPixel()-Funktion und baut alles Weitere darauf auf. Damit ließ sich die Bibliothek auf den bereits funktionierenden eigenen Treiber aufsetzen, statt eine fremde Initialisierung zu übernehmen. Eine laufende Grundlage ist mehr wert als offene Fehlersuche.

Wer TFT_eSPI dennoch einsetzen möchte: In kursierenden Setup-Dateien steht häufig

C
#define RP2040_SPI1_PORT

Dieses Symbol wertet TFT_eSPI nirgends aus. Die Portwahl hängt allein an TFT_SPI_PORT und fällt ohne diese Zeile auf SPI0 zurück. Die Bibliothek spricht dann die falsche SPI-Hardware an, während die Pins auf SPI1 liegen — Ergebnis ist ein schwarzer Schirm bei scheinbar korrekter Konfiguration. Richtig ist #define TFT_SPI_PORT 1.


Flackerfrei zeichnen ohne doppelten Bildpuffer

Der naheliegende Weg scheidet aus: 480 × 320 × 2 Byte sind 307 kB, der RP2040 hat 264 kB RAM. Es gehen also zwei andere Verfahren.

Kleine, häufige Änderungen werden in einem GFXcanvas1 aufgebaut und in einem Zug übertragen — ein Adressfenster, ein Datenstrom. Die große Zahl wird so nie gelöscht und neu gemalt, sondern einmal überschrieben.

Beim Seitenwechsel wird nur der Inhaltsbereich geleert und der Knopfstreifen stehen gelassen:

C
void clearContent() {
  tft.fillRect(0, 0, LCD_WIDTH, BTN_Y, COL_BG);   // statt fillScreen()
}

Neu gezeichnet werden dann nur die Knöpfe, deren Beschriftung sich geändert hat. Beim Blättern in der Dateiliste ändert sich gar nichts. Das ist wirksamer, als die Knöpfe nur früher zu zeichnen: Ein fillScreen() löscht sie, und was gelöscht und identisch neu gezeichnet wird, sieht man als Flackern.


Screendesign mit Claude Code

Das Screen Design für dieses Gerät wurde übrigens mit der Hand gezeichnet, dann als Bild an Claude Code übergeben und den Rest hat die KI erledigt. Als Beispiel sei hier hier Hauptbildschirm beispielhaft betrachtet. Es wurde die folgende Handskizze erstellt:

Claude Code wurde die Aufgabe gegeben, den Hauptbildschirm für die Messwertamnzeige nach dieser Vorgabe zu erstellen- Und hier ist das Ergebnis:

Auch für die Webansicht hat sich Claude Code dann so ziemlich genau an diese Vorgabe gehalten:

Also statt einen Bildschirm lang und aufwändig zu beschreiben, einfach eine Handskizze erstellen und diese dann an Claude Code übermitteln mit der Bitte, den Bildschirm entsprechend umzusetzen.


Aufbau des Projekts

Code
picopi_tft.ino     Anwendung: vier Bildschirme, Bedienung
      │
      ├── PicoTFT.*       Brücke Adafruit_GFX -> LCD_Driver
      │        │
      │   LCD_Driver.*    SPI1, Registersequenz, Adressfenster, Blocktransfer
      │
      ├── TouchCal.*      Touch-Kalibrierung, im Flash abgelegt
      ├── TempSensors.*   DS18B20, nicht blockierend
      ├── TempHistory.*   Ringpuffer über 24 h
      ├── DataLogger.*    CSV auf SD-Karte, Tagesdateien
      ├── Netzwerk.*      WLAN des Pico W, Zugangsdaten von der Karte
      ├── Zeit.*          NTP mit deutscher Sommerzeitregel
      ├── Webupload.*     Messwerte an die eigene Webseite
      ├── Helligkeit.*    Hintergrundbeleuchtung per PWM
      └── Bildschirm.*    Ausrichtung der Anzeige

Alle Module folgen derselben Bauform: begin() beim Start, ein nicht blockierendes update() im Hauptlauf, Einstellungen aus einer Textdatei auf der Karte. Nichts hält die Anzeige an.

Treiber, GFX-Brücke und Touch-Kalibrierung sind von der Anwendung getrennt. Wer nur das Display zum Laufen bringen will, braucht die oberen drei Zeilen und kann den Rest liegen lassen — dafür gibt es das Projekt auch als abgespecktes Referenzpaket.


Bekannte Einschränkungen

  • Kein DMA, keine Sprites, keine geglätteten Schriften. Ein kompletter Bildaufbau dauert spürbar; für die statische Oberfläche hier und teilweise aktualisierten Diagramme ist das aber kein Thema.
  • Angezeigt und aufgezeichnet wird nur der erste gefundene Sensor, obwohl bis zu acht am Bus erkannt werden.
  • Ohne WLAN keine Uhrzeit, ohne Uhrzeit keine Aufzeichnung.
  • Der Verlauf wird beim Start nur aus heutiger und gestriger Tagesdatei wiederhergestellt. Längere Abschaltzeiten bleiben als Lücke sichtbar.
  • Beim Webupload wird das Serverzertifikat nicht geprüft – der Aufwand war mir einfach zu groß.
  • Die Controller-Kennung wurde nicht ausgelesen; ST7796 ist eine Ableitung aus dem Verhalten des Displays.

Das Gehäuse

Das Gehäuse für die Meßstation ist selbst in FUSION konstruiert und auf dem Bambulab X1C mit PLA gedruckt. Die Druckdaten einschließlich der Konstruktionsdaten als STEP-Datei sind auf makerworld.com zu finden. Es ist modular gestaltet und kann daher sehr einfach auch an andere Displays bzw. Microcontroller angepasst werden.

Eine Besonderheit des Gehäuses ist die Möglichkeit, den Abstand zwischen Frontplatte und dem Touch-Display durch verschieben des Platinenträgers feinfühlig justieren zu können. Das hat sich als nörtig erwiesen, da die Displays bzw. deren Befestigungsschrauben doch immer wieder Längentoleranzen aufweisen. Dieses Problem kann so umgangen werden. Die Verschraubung erfolgt mit selbtschneidenen Schrauben der Größe 3×6


Linksammlung

Hinweise zum Nachbau: Benötigt werden die Arduino IDE 2.x und das Board-Paket „Raspberry Pi Pico/RP2040/RP2350″ von Earle Philhower (getestet 6.0.0), Board-Einstellung „Raspberry Pi Pico W“. Das offizielle „Arduino Mbed OS RP2040″ ist nicht gemeint und funktioniert hier auch nicht — der Sketch nutzt unter anderem SPI1.setSCK(), das es nur im Philhower-Core gibt.

An Bibliotheken kommen dazu: Adafruit GFX, Adafruit BusIO, XPT2046_Touchscreen, OneWire und DallasTemperature. Die SD-Bibliothek bringt das Board-Paket mit.

Das Projekt steht unter MIT-Lizenz und darf frei verwendet, geändert und weitergegeben werden, auch kommerziell.

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert