128x160 Pixel 1.8" TFT ST7735 Farb-Display am Nano betreiben

Ich habe mich auf der Suche nach einem weiteren grafischen Display-Typen gemacht, das kein allzu großes Loch in den Geldbeutel reißt.

Es sollte von der Anzeigefläche größer sein als das OLED-Display (128x64 px, 0.96", monochrome). Das hat zwar eine schöne, gestochen scharfe Ausgabe (eben weil OLED) ohne Hintergrundbeleuchtung, die Schrift ist aber irgendwie winzig. Es sei denn, man benutzt große Schriften, die sehen dann zwar toll aus, aber auf das Display passt dann nicht viel.

Es schwebte mir eher etwas in der Größe des Nokia 5110 Displays (84x48 px, 1.5", monochrome) vor. Das Nokia 5110 Display ist super für den Außeneinsatz, denn es hat ein transreflektives Display, das heißt, dass das Nokia 5110 Display ohne Hintergrundbeleuchtung funktioniert. Dann kann die Sonne auf den reflektierenden Hintergrund fallen und so den Kontrast erzeugen. für mein kommendes GPS-Empfänger-Projekt wird das erste Wahl sein. Aber für normale Indoor-Anwendungen ist die Größe zwar schön, aber die Auflösung ein bisschen gering.

Gefunden habe ich dann ein 1.8" großes TFT-Display mit 160x128 Pixeln Auflösung in Farbe und Hintergrundbeleuchtung mit ST7735-Controller für ca. 4 Euro aus China (inkl. Versand, Preis Stand Januar 2022) und das gleich mal bestellt, um es zu testen.


Die Features gibt der Händler wie folgt an:

Meine zwei Lieblings-Displays mit dem neuen einmal im Vergleich:

SSD1306 OLEDNokia 5110 LCDST7735 128x160 TFT
Auflösung:128x64 (=8192 Pixel, 2:1 Format)84x48 (=4032 Pixel, 7:4 Format)160x128 (=20480 Pixel, 5:4 Format)
Farben:s/ws/w262'144
Hintergrund-Beleuchtung:nicht nötigabschaltbar (dann in Sonne noch gut zu lesen)abschaltbar (dann nichts mehr zu lesen, nur zum Strom sparen)
Ablesbarkeit Indoor:perfektgut (mit Hintergrundbeleuchtung)gut (mit Hintergrundbeleuchtung)
Ablesbarkeit Outdoor bei Sonnenschein:mäßigsehr gut (ohne Hintergrundbeleuchtung)mäßig (mit Hintergrundbeleuchtung)
Diagonale:0.96" (24.384 mm)1.5" (38.1 mm)1.8" (45.72 mm)
Maße Modul:28 x 27.2 x 5 mm (ohne Header)45.3 x 45.0 x 6.4 mm (ohne Header)56 x 36 x 6 mm (ohne Header)
Maße Anzeigefläche:21.4 x 11.4 mm (243.96 mm2)30.4 x 21.4 mm (650.56 mm2)35.0 x 28.0 mm (980 mm2)
dpi hor.:15270.1892.89
dpi vert.:142.656.97116.11
dots per mm233.586.220.9
Spannungsbereich:3.0V...5V3.3V...5V3.0V...5V

Das neue ST7735-Display hört sich von den Daten bezüglich der Pixel- und damit Schriftgröße nach gesundem Mittelmaß an. Das Nokia-Display ist von der Schriftgröße okay, aber allzuviel bekommt man damit nicht an Text auf den Schirm. Beim OLED bekommt man zwar mehr auf den Screen, aber das ist dann winzig.

Anschluss am Arduino Nano

Und die Pin-Belegung der Screen-Platine wie nachfolgend. Die Header-Steckerleiste war bei mir übrigens schon fertig eingelötet und mit den langen Pins nach unten zeigend. Das entspricht dem, wie ich es auch anlöten würde für das Breadboard. Allerdings möchte man manchmal auch Höhe sparen und die Kabel direkt anlöten. Darum wäre ein nicht schon angelöteter Header allgemein gesehen praktischer. Aber das variiert auch von Lieferant zu Lieferant, wie man das bekommt.


TFT PinBedeutungAnschluss am Arduino an Pin
GNDGroundGND (schwarz)
VCCSpannungsversorgung 3.3...5V5V (rot)
SCLSPI ClockD13 (orange)
SDASPI DataD11 (braun)
RESController ResetD8 (grau)
DCCommand/DataD9 (blau)
CSChipselect for LCDD10 (gelb)
LED BLBacklightGND = off / 3.3V/5V = on. Hierüber kann man die Hintergrundbeleuchtung über einen Arduino-Pin ausschalten und wieder einschalten, falls benötigt



Der ST7735 Controller

Angesteuert wird das TFT-Display über einen ST7735-Controller, für den ich mir natürlich gleich das entsprechende Data Sheet besorgt habe. Denn schließlich muss ich den ST7735 ansteuern, um etwas auf das Display zu zaubern. Naja, oder ich benutze eine fertige Library; so diese denn all meine Wünsche erfüllt.

Hier in Kürze die wichtigsten Features des ST7735 Controllers: Das sind so die Eckdaten. Das komplette Data Sheet von Sitronix ist 167 Seiten lang und beschreibt Aufbau, Protokoll etc. bis ins Detail.

Nachdem hier ein Spannungsversorgungsbereich von 3.0 bis 5.0 Volt angegeben ist, kann ich mir auch sicher sein, dass der Controller bzw. das Display bei den 5 Volt auf den Datenleitungen des Arduino Nano nicht kaputt gehen wird. Schön außerdem zu wissen, dass das Display mit 3.3V klarkommen wird, wie es der STM32, die ESP8266 / ESP32 und der Raspberry Pi Pico liefern würden und ich es auch mit diesen Mikrocontrollern benutzen werden kann.

Eine passende Arduino Library finden

Ich hoffe aber darauf, das Data Sheet nicht komplett durchlesen zu müssen und schon eine fertige Library zu finden. Und schaue mich gleich mal im Bibliotheksverwalter um. Dafür gebe ich "ST7735" im Suchfeld ein.

Die in der Arduino IDE eingebaute TFT-Library



Beispielprogramme und die API-Dokumentation ist unter https://www.arduino.cc/en/Reference/TFTLibrary zu finden. Die Library stammt ursprünglich von adafruit und ist jetzt in die Arduino-IDE eingebaut.

Das macht den Gebrauch der TFT-Library natürlich einfach und ich kann davon ausgehen, dass sie ausgiebig getestet wurde, denn sonst hätte sie ja nicht als Standard Einzug in die IDE gehalten, oder? Wir müssen nur die Library einbinden und ein paar Pins definieren, die Pins für SPI sind ja beim Arduino vordefiniert auf D11 (MISO), D12 (MOSI) und D13 (SPI Clock): #include <SPI.h> #include <TFT.h> #define CS 10 #define DC 9 #define RESET 8 Dann braucht es nur noch ein Befehle, um das Display zu initialisiern: TFT tft = TFT(CS, DC, RESET); tft.begin(); die Hintergrundfarbe und die Schriftfarben sowie die Schriftgröße zu setzen und mit text() etwas auf den Bildschirm auszugeben: tft.background(0, 0, 0); tft.setTextSize(2); tft.stroke(255, 0, 0); tft.text("rot", 0, 0); tft.stroke(0, 255, 0); tft.text("gruen", 0, 20); tft.stroke(0, 0, 255); tft.text("blau", 0, 40); Im Einschaltzustand ist der Hintergrund weiß, die Hintergrund-LEDs leuchten also voll durch. Das ändert sich aber schnell, sobald wir den background()-Befehl abgesetzt haben und den Hintergrund auf schwarz ändern. Die Schriftgröße habe ich auf 2 gesetzt. Damit ist jedes Pixel, das vorher 1x1 px groß war, jetzt 2x2 px groß. Das macht die Schrift größer, aber auch klobiger. Aber ich will ja auch die Farben erkennen können.


Das Ergebnis sieht dann so aus, wie rechts gezeigt (draufklicken für ein vergrößertes Bild). Aber da stimmt doch etwas nicht mit den Farben. Rot ist blau und blau ist rot. Nur grün stimmt. Seltsam.
stroke

Description: Called before drawing an object on screen, it sets the color of lines and borders around shapes.

stroke() expects 8-bit values for each of the red, green, and blue channels, but the screen does not display with this fidelity. The red and blue values are scaled to 5-bit color (32 discrete steps), and the green is 6-bit color (64 discrete steps).

Syntax: screen.stroke(red, green, blue);

Parameters:
red : int 0-255
green : int 0-255
blue : int 0-255
Doch. Die Reihenfolge soll laut Syntax Rot, Grün, Blau sein, RGB, wie man das so von der Reihenfolge gewohnt ist. Funktioniert aber nicht richtig. Man muss die Reihenfolge Blau, Grün, Rot benutzen, damit die Farben stimmen. Naja, nicht weiter schlimm, muss man halt nur wissen, und dann die Farben vertauschen.

Und dann wär da noch was anderes: Die Schrift fängt immer schon vor dem Bildschirm links an und oben scheint auch etwas abgeschnitten und die letzten zwei Display-Zeilen zeigen bunten Blödsinn an. Das hat aber keiner bestellt. Was soll das? Also noch einmal in die API geschaut:
text

Description: Write text to the screen at the given coordinates.

Syntax: screen.text(text, xPos, yPos);

...

// write text to the screen in the top left corner
screen.text("Testing!", 0, 0);
"Top left corner". Ecke links oben ist 0 Komma 0. Hab ich so angegeben. Aber trotzdem schreibt er den Mist zu weit links. Also versuchen wir mal, das zurecht zu rücken mit
tft.background(0, 0, 0); tft.setTextSize(2); int xoff=2; int yoff=3; tft.stroke(0, 0, 255); tft.text("rot", xoff, yoff); tft.stroke(0, 255, 0); tft.text("gruen", xoff, 20+yoff); tft.stroke(255, 0, 0); tft.text("blau", xoff, 40+yoff); Okay, jetzt stimmt die Positionierung, aber unten sind immer noch die 2 Pixel-Zeilen mit kunterbuntem Unsinn. Wie kommt die Library eigentlich an die Screen-Abmessungen von 160 x 128? Hat die die vom Controller zurückgeliefert bekommen und liefert der die virtuellen 162 x 132 zurück? Das sieht irgendwie unschön aus und das möchte ich weg bekommen.

Wollen wir das doch mal überprüfen. Wir können ja die Bildschirmgrößen mit tft.width() und tft.height() abfragen.


int xoff=2; int yoff=3; int lihi=20; tft.stroke(0, 0, 255); tft.text("rot", xoff, yoff); tft.stroke(0, 255, 0); tft.text("gruen", xoff, lihi*1+yoff); tft.stroke(255, 0, 0); tft.text("blau", xoff, lihi*2+yoff); tft.stroke(255,255,255); snprintf(tmp, sizeof(tmp), "width: %d", tft.width()); tft.text(tmp, xoff, lihi*3+yoff); snprintf(tmp, sizeof(tmp), "height: %d", tft.height()); tft.text(tmp, xoff, lihi*4+yoff); Nein, die Library und der ST7735 haben sich richtig auf 160 x 128 Pixel verständigt. Woher rührt dann der Pixel-Matsch an der Unterseite. Und wie werde ich ihn los?

Versuchen wir doch einfach, vorher ein schwarzes Rechteck in den richtigen Dimensionen zu zeichnen. Das geht ja ganz einfach mit
rect

Description: Draws a rectangle to the TFT screen. rect() takes 4 arguments, the first two are the top left corner of the shape, the last two are the width and height of the shape.

Syntax: screen.rect(xStart, yStart, width, height);

int xoff=1; int yoff=3; int lihi=20; tft.stroke(0, 0, 0); tft.fill(0, 0, 0); tft.rect(0, 0, 162, 132); am Anfang sollte das doch erledigen. Es wird ein großes schwarzes Rechteck gezeichnet.

Dummerweise funktioniert das auch das nicht. Sieht genau so aus wie ohne das Rechteck. Ich habe das Rechteck auch noch in anderen Farben gezeichnet, um genau zu sehen, was passiert. Und die beiden unteren Zeilen hat das jedesmal ausgelassen, egal wie groß ich das Rechteck auch gewählt habe. Ganz schön viel Herumprobiererei und Work Arounds für eine Standard Library, finde ich.


Erst nach weiterem Herumprobieren finde ich einen Workaround, der funktioniert: Das mehrfache Löschen des Screens in unterschiedlichen Bildschirmorientierungen.

tft.begin(); tft.setRotation(3); // 0/2 = Hochformat, 1/3=Querformat (*90 Grad) tft.background(0, 0, 0); tft.setRotation(0); tft.background(0, 0, 0); tft.setRotation(1); tft.background(0, 0, 0); Am Anfang führt endlich zum gewünschten Ergebnis. Und ich bermerke auch gleich, dass die X- und Y-Offsets jedes mal anders sind, wenn sich die Bildschirmorientierung ändert. Na, was für ein Spaß. Ich glaube, die Library wird nicht mein Liebling.

Aber die Portrait-Orientierung, also hochkant, will ich natürlich trotzdem noch probieren. So steckt das Display ja schließlich bei mir im Board und so kann ich es ablesen, ohne mir den Kopf zu verrenken.


Mit den durch Probieren gewonnenen Erfahrung ist dann der folgende Code entstanden, der zu dem gewünschten Ergebnis führt. TFT tft = TFT(CS, DC, RESET); tft.begin(); tft.setRotation(3); // 0/2 = Hochformat, 1/3=Querformat (*90 Grad) tft.background(0, 0, 0); tft.setRotation(0); tft.background(0, 0, 0); int xoff=3; int yoff=1; int lihi=20; tft.setTextSize(1); tft.stroke(0, 255, 255); // blau, grün, rot tft.text("Dies ist ein gelber", xoff, yoff); tft.setTextSize(1); tft.stroke(0, 255, 255); // blau, grün, rot tft.text("Text in Groesse 1", xoff, lihi*.5+yoff); tft.setTextSize(2); tft.stroke(255, 255, 255); // blau, grün, rot tft.text("Groesse 2", xoff, lihi*1+yoff); tft.setTextSize(3); tft.stroke(255, 255, 255); // blau, grün, rot tft.text("Gr. 3", xoff, lihi*2+yoff); tft.setTextSize(4); tft.stroke(255, 255, 255); // blau, grün, rot tft.text("Gr. 4", xoff, lihi*3.5+yoff); tft.setTextSize(5); tft.stroke(255, 255, 255); // blau, grün, rot tft.text("Gr.5", xoff, lihi*5.5+yoff); Der Text ist gut abzulesen. Schriftgröße 1 ist schon fast wieder ein bisschen klein, aber noch aus normaler Entfernung (80 cm) lesbar. Am besten ist es natürlich wenn man senkrecht auf das Display schaut, aber auch von der Seite her betrachtet kann man alles noch lesen und die Farben werden beibehalten, nur die Helligkeit nimmt ab.

Naja, okay, damit könnte ich leben. Aber eigentlich will ich auch ein Bild auf dem TFT anzeigen, wenn ich schon ein Farb-Display habe. Zumindest ein Logo.

Die Library kann ein File von dem normalerweise in dem adafruit-Shield enthaltenen SD-Kartenleser lesen und anzeigen. Ich würde das Bild aber gerne im Speicher definieren und daraus in das ST7735-RAM verschieben und dann anzeigen lassen. Ein Bild würde mir ja reichen. Ich will doch nicht immer einen SD-Kartenleser aufs Breadboard (oder ins Gerät) tun müssen, nur um ein Logo anzuzeigen.

Obwohl, vielleicht überschätze ich da auch den Arduino. Der hat ja nur ca 30 KB Flash-Speicher, bleiben bei einem kleinen Programm vielleicht noch 20 KBytes für das Logo. Rechnen wir mal: 160 mal 128 Pixel mal 18 Bits macht 368'640 Bits oder 46'080 Bytes. Tja, ich habe aber keine 46 KB, sondern nur 20. Witzig, das Display hat mehr Speicher als der Arduino.

Bildanzeige kann nur über einen externen Speicher gehen wie ein extra EEPROM oder einen SD-Kartenleser. Zumindest auf dem Arduino. Mit einem ESP8266 oder einem Raspberry Pi Pico hätte ich wohl genug Speicherplatz dafür.

Bliebe für den Arduino nur, die Grafik komprimiert zu speichern und ggf. auf Farben zu verzichten oder durch Zeichenbefehle darzustellen. Davon kennt die Library ja point(), line(), rect() und circle(). Nicht gerade viel. Keine Bezier-Kurven, keine Ellipsen; alles nicht so ergiebig.

Dann vielleicht doch ein kleineres schwarz/weiß-Logo, dass ich mit beliebiger Farbe an einer beliebigen X/Y-Position einblenden kann.

Aber mal schauen, was die anderen Libraries so können und ob die einfacher zu benutzen sind.

Ucglib by oliver V 1.5.2



Ich fange mal mit dem Hello World-Beispiel an. Ich muss erst einmal nicht viel anpassen, sogar die Pins sind die gleichen, die ich mir ausgesucht hatte, so dass ich hier nur die richtige Zeile auskommentieren muss:
#include <SPI.h> #include "Ucglib.h" Ucglib_ST7735_18x128x160_HWSPI tft(/*cd=*/ 9, /*cs=*/ 10, /*reset=*/ 8); void setup(void) { delay(1000); tft.begin(UCG_FONT_MODE_TRANSPARENT); //tft.begin(UCG_FONT_MODE_SOLID); tft.clearScreen(); // ucg.setRotate180(); tft.setFont(ucg_font_ncenR12_tr); tft.setColor(255, 255, 255); //tft.setColor(0, 255, 0); tft.setColor(1, 255, 0,0); tft.setPrintPos(0,25); tft.print("Hello World!"); } Lassen wir den Sketch einfach mal so laufen und schauen, was bei heraus kommt.

Okay, der Text steht auf dem Kopf. Und da ist schon wieder dieses komische Phänomen am Rand. Das scheint wohl dann doch an meinem spezifischen Display zu hängen. Sehr seltsam. Ich schaue mal, ob ich das zurecht gerückt bekomme.

char tmp[256]; int xoff=3; int yoff=0; int lihi=20; delay(1000); tft.begin(UCG_FONT_MODE_TRANSPARENT); //ucg.begin(UCG_FONT_MODE_SOLID); tft.clearScreen(); tft.setRotate270(); tft.clearScreen(); tft.setRotate90(); tft.clearScreen(); tft.setRotate180(); tft.clearScreen(); tft.setFontPosTop(); // pos gibt obere linke Ecke für Text an tft.setFont(ucg_font_ncenR12_tr); tft.setColor(255, 255, 0); // rot, grün, blau tft.setPrintPos(xoff,yoff); tft.print("ncenR12 in gelb"); tft.setFont(ucg_font_5x8_tf); tft.setColor(255, 255, 255); // rot, grün, blau tft.setPrintPos(xoff,yoff+15); tft.print("Mini-Font in weiss"); tft.setFont(ucg_font_9x15_tf); tft.setColor(0, 255, 0); // rot, grün, blau tft.setPrintPos(xoff,yoff+25); tft.print("Midi in gruen"); tft.setFont(ucg_font_9x15_67_75); tft.setColor(0, 255, 0); // rot, grün, blau tft.setPrintPos(xoff,yoff+40); snprintf (tmp, sizeof(tmp), "%c%c%c%c", 129, 130, 131, 132); tft.print(tmp); tft.setColor(255, 255, 0); // rot, grün, blau tft.setPrintPos(xoff+4*9,yoff+40); snprintf (tmp, sizeof(tmp), "%c%c", 133, 134); tft.print(tmp); tft.setColor(255, 0, 0); // rot, grün, blau tft.setPrintPos(xoff+6*9,yoff+40); snprintf (tmp, sizeof(tmp), "%c%c", 135, 136); tft.print(tmp); tft.setFont(ucg_font_logisoso16_hr); tft.setColor(255, 255, 255); // rot, grün, blau tft.setPrintPos(xoff,yoff+60); tft.print("angenehm"); tft.setFont(ucg_font_logisoso24_hr); tft.setColor(255, 255, 255); // rot, grün, blau tft.setPrintPos(xoff,yoff+80); tft.print("gross!"); Super, bei dieser Library kann man mit den Fonts aus dem Vollen schöpfen. Viele vordefinierte Schriftarten, die man benutzen kann Allerdings erhöhen gerade die größeren den Speicherverbrauch erheblich. Aber normalerweise braucht man ja auch nur eine oder zwei.

Die Liste der Schriftarten findet man unter https://github.com/olikraus/ucglib/wiki/fontsize und die Gesamtdokumentation unter https://github.com/olikraus/ucglib/wiki/reference.

Dort erfährt man auch, dass es hier viel mehr Funktionen zum Zeichnen gibt: drawBox, drawCircle, drawDisc, drawFrame, drawGlyph, drawGradientBox, drawGradientLine, drawHLine, drawLine, drawPixel, drawRBox, drawRFrame, drawString, drawTetragon, drawTriangle und drawVLine. Damit lässt sich schon eine Menge angestellen. Die mitgelieferten Beispiele demonstrieren da einiges. Die Dokumentation ist gut mit Beispielen bebildert, so dass man schnell findet, was man braucht. Ich empfehle einen Blick hinein, um sich ein Bild über den Funktionsumfang zu machen.

Diese Bibliothek gefällt mir sehr viel besser als die TFT-Library, die schon in der Arduino IDE eingebaut ist. Die vielen Fonts sind der Knaller.


Schließlich habe ich auch noch mein Logo auf den Screen gebracht. Rechts mal zwei Fotos, einmal mit schwarzem und einmal mit weißem Hintergrund.

Allgemein finde ich, sieht der schwarze Hintergrund besser aus, weil man da nicht so die Displayränder sieht, die ja auch schwarz sind. Außerdem wird man bei Dunkelheit nicht so geblendet.

Das Logo sieht schwarz auf weiß allerdings wieder besser aus. Aber ich musste es ein klein wenig beschneiden, damit noch ein kleiner weißen Rand drum herum bleibt und damit das schwarz nicht in den Displayrand übergeht.

Abzulesen ist beides gut. Allerdings muss in beiden Fällen die Hintergrundbeleuchtung an sein, sonst ist gar nichts zu lesen, außer vielleiccht, man strahlt mit einer Lampe direkt aufs Display, dann kann man etwas erahnen. Das Display ist halt dafür gemacht, von hinten bestrahlt zu werden.

Fazit

Das 128x160 1.8" TFT-Farb-Display ist günstig, nicht zu klein und man kann es gut ablesen. Damit ist es meine Nummer eins für Indoor, wenn man etwas mehr oder etwas in Farbe dargestellt werden soll.

Wenn ich das nächste mal bei einem anderen Lieferanten bestelle, bleibt abzuwarten, ob dieser bunte Pixelrand dann auch noch auftritt oder ob das ein Fehler in den Libraries ist. Die Farbreihenfolge bei der ucglib war ja wieder richtig mit rot, grün, blau.

Die ucglib von Oli Kraus finde ich um Längen besser als die eingebaute, auf adafruit basierende. Mit den verfügbaren Fonts hat man eine gute Auswahl für gut aussehende Schriften in vielen Größen. Und genügend Zeichenbefehle, falls man etwas grafisch machen will.

Auch die drawPixel()-Funktion ist flott genug, eine Bitmap darzustellen. Allerdings darf die nicht zu groß sein, denn der Arduino-Speicher ist begrenzt.