OLED-Display zur Anzeige von Werten des Sensors DHT22

OLED-Display mit Treiber-Chip SSD1306

Die monochromen SSD1306-OLED-Displays sind eine relativ günstige und stromsparende Möglichkeit, Mikrocontroller-Projekte mit einer kleinen Anzeige auszustatten. Sie verwenden den Treiberchip SSD1306 und werden per I2C-Schnittstelle gesteuert. Die Versorgungsspannung liegt zwischen 3,3 und 5 Volt, sodass sie mit Arduinos, ESP32, ESP8266 und anderen Mikrocontrollern funktionieren. Verschiedene Bibliotheken bieten neben der Darstellung von Text in mehreren Größen und Schriftarten auch Funktionen zum Zeichnen geometrischer Figuren, Bitmap-Grafiken und kleiner Animationen.

Varianten

OLEDs mit 128x32 Pixeln (0,91 Zoll) und 128x64 Pixeln (0,96 Zoll Diagonale)
OLEDs mit 128×32 Pixeln (0,91″) und 128×64 Pixeln (0,96″ Diagonale)

Die Displays gibt es in verschiedenen Varianten:

  • 128 * 64 Pixel; Diagonale 0,96 Zoll; Farben weiß, blau, gelb oder zweifarbig gelb-blau[1]Bei der zweifarbigen Variante ist das obere Viertel (ein 16 Pixel hoher Streifen) gelb, der Rest blau.
  • 128 * 32 Pixel; 0,91 Zoll; weiß, blau oder gelb
  • 64 * 48 Pixel; 0,66 Zoll; weiß
  • 72 * 40 Pixel; 0,42 Zoll; weiß

Am weitesten verbreitet sind die blauen und weißen Displays mit I2C-Anschluss und 128 Pixeln in der Breite, die man ab etwa 6-7 € in D bekommt. Auf die bezieht sich dieser Beitrag. Bei Bestellung in China findet man auch die gelbe Variante und die kleineren Displays; alle oft unter 1,50 €. [2]Wenn man z.B. bei aliexpress.com ein wenig sucht, findet man die Displays bei einigen Händlern wahlweise mit I2C- oder SPI-Anschluss. Falls man eine etwas größere Anzeige benötigt, gibt es … Continue reading

Die nutzbare Bildschirmfläche beim 0,96″-Display ist etwa 22 * 11 mm2 groß, dazu kommen die Ränder der Scheibe von ca. 2 mm auf jeder Seite – die Glasfläche ist also etwa 27 mm breit und 15 mm hoch. Die Platine hat oben und unten Ränder für Anschlüsse und Befestigungsbohrungen, sodass sich eine quadratische Größe von ca. 27 * 27 bis 27,5 * 27,5 mm2 ergibt. Eine Variante hat nur ca. 1 mm breite Ränder der Glasscheibe und ist mit 25 * 26 mm2etwas kleiner.

I2C-Schnittstelle

OLED-Display mit I2C-Anschluss: Pin-Beschriftung
OLED-Display mit I2C-Anschluss: Pin-Beschriftung

Die Displays haben vier Anschlüsse:

  • GND (Masse)
  • VCC (Versorgungsspannung; auch als »VDD« beschriftet): 3,3 – 5 Volt
  • SDA (Daten)
  • SCL (Takt; auch als »SCK« beschriftet)

Die Standard-Pins zum Anschließen der I2C-Geräte an gängige Mikrocontrollern sind folgende:

  • ESP32 (DevKitC) – SDA: Pin 21, SCL: Pin 22
  • Arduino Nano oder Uno – SDA: A4, SCL: A5
  • ESP8266 (NodeMCU) – SDA: D2 (GPIO 4), SCL: D1 (GPIO 5)
Pin-Varianten bei OLEDs mit 128x32 Pixeln (0,91 Zoll) und 128x64 Pixeln (0,96 Zoll Diagonale)
Pin-Varianten bei OLEDs mit 128×32 Pixeln (0,91″) und 128×64 Pixeln (0,96″ Diagonale)

Achtung!Im Bild sieht man, dass die Anordnung der Pins bei den Displays unterschiedlich ist: Beim kleinen Display (128 * 32 Pixel) und einem der größeren ist die Reihenfolge »GND, VDD, SCK, SDA«, beim anderen der größeren Displays (Mitte) dagegen »VCC, GND, SCL, SDA« – d.h. Masse und Versorgungsspannung sind getauscht. Nicht unwesentlich beim Aufbau der Schaltung oder wenn man ein Display gegen ein anderes austauscht – es gilt die Beschriftung auf dem Gerät, das man in den Fingern hält, nicht die Reihenfolge in Diagrammen oder auf Fotos, die man als Vorlage nutzt (beim Fritzing-Bauteil[3]Ich habe in den Diagrammen das Bauteil aus dem Fritzing-Forum verwendet. ist die Reihenfolge »GND, VDD, SCK, SDA«). Beide Versionen der Pin-Anordnung findet man bei den 128*64-Pixel-Displays häufig im Handel.

Mit einem Widerstand kodierte I2C-Adresse (OLED-Display Typ SSD1306)
Mit einem Widerstand kodierte I2C-Adresse (OLED-Display Typ SSD1306)

Meine Displays haben alle die I2C-Adresse 0x3C;[4]Bei zwei Displays ist auf der Rückseite der Platine die falsche Adressangabe »0x78« aufgedruckt; durch Versetzen des Widerstands änderbar in »0x7A«. Die Angabe ist Unfug, … Continue reading bei den größeren Displays (128*64 Pixel) kann diese auf 0x3D geändert werden. Hierzu muss auf der Rückseite ein SMD-Widerstand (im Bild rot markiert) ausgelötet und an der benachbarten Position wieder eingelötet werden.
So eine Änderung der Adresse kann erforderlich sein, wenn man mehrere Displays an einem I2C-Interface des Mikrocontrollers betreiben will. Alternativen beschreibt der Beitrag zur I2C-Schnittstelle. Mit einem ESP32 kann man problemlos zwei Displays mit gleicher Adresse steuern, da er zwei I2C-Schnittstellen besitzt – ein Beispiel findet sich weiter unten. Falls man sich nicht sicher ist, welche Adresse ein Display (oder ein anderes I2C-Gerät) hat, kann man diese mit einem I2C-Scanner ermitteln.

Bibliotheken für den SSD1306

Zwei verbreitete Bibliotheken zur Steuerung der OLED-Displays sind

Ich habe mich erstmal für die Adafruit SSD1306-Bibliothek entschieden, einfach weil ich zuerst darauf gestoßen bin. u8g2 scheint mir vom Funktionsumfang aber nicht schlechter. Das Projekt unterstützt neben dem SSD1306 eine große Anzahl von Treiber-Chips für Monochrom-Displays – z.B. auch die häufig zu findenden 32*8 Pixel großen MAX7219-LED-Matrix-Displays – und hat in dem auf der Projektseite verlinkten Wiki eine hervorragende Dokumentation. Ein kleines Beispiel zur Nutzung zeigt der Abschnitt zum Display-Shield für den D1 mini unten in diesem Beitrag.

Beide Bibliotheken können über die Bibliotheksverwaltung der Arduino-IDE installiert werden – z.B. per Suche nach »ssd1306« oder direkt »u8g2«. Um die Adafruit-Bibliothek nutzen zu können, muss zusätzlich die Adafruit GFX Library installiert werden, die Basisfunktionen zum Zeichnen von Grafiken bereitstellt. (Eine Anleitung zur Arduino-Bibliotheksverwaltung gibt es im Beitrag »Arduino-IDE: Bibliotheken verwalten«.)

Beispielprogramme werden bei der Installation der Bibliotheken in der Arduino-IDE mitkopiert – man findet sie in der IDE im Datei-Menü: Datei → (Beispiele aus eigenen Bibliotheken) Adafruit SSD1306 → ssd1306_128x64_i2c bzw. im libraries-Verzeichnis der Arduino-Installation (bei einer Windows-Standard-Installation im Ordner C:\Users\<benutzername>\Documents\Arduino\libraries\Adafruit_SSD1306\examples\). Dort werden alle möglichen Funktionen an Beispielen gezeigt, u.a. das Zeichnen von geometrischen Objekten wie Linien, Kreisen oder Rechtecken, Text mit verschiedenen Schriftarten, Scrollen von Text, Invertieren der Anzeige und das Darstellen einer Bitmap-Grafik oder einer Animation.

Wie man z.B. aus einem Firmenlogo eine passende Schwarz-Weiß-Bitmap erzeugt, erwähnt die (knappe) Adafruit-Dokumentation nicht. Eine gute Erklärung ist im Beitrag ESP32 OLED Display with Arduino IDE auf randomnerdtutorials.com zu finden (ziemlich weit nach unten blättern; Abschnitt »Display Bitmap Images in the OLED«). Aus der Dokumentation zur u8g2-Library stammt der Link zum sandhansblog (für Anwender von Gimp; ebenfalls englisch); eine deutschsprachige Anleitung unter Benutzung des Online-Tools image2cpp gibt es auf polluxlabs.net.

Programm: Sensorwerte ausgeben

Mir geht es erst einmal nur darum, Text darzustellen, und zwar Messwerte des Temperatur- und Luftfeuchtigkeits-Sensors DHT11. Die Schaltung ist mit einen Arduino-Nano-Klon aufgebaut – das folgende Programm sollte aber auch auf einem ESP32 laufen. Man muss nur das Display mit den jeweiligen Standard-Pins des Controllers für SDA und SCL verbinden und den Pin, an dem man den Sensor anschließt, passend im Programmkopf eintragen:

  1. #define DHTPIN 4     // Pin für Sensor-DATA

Das ganze Programm sieht so aus:

  1. /* Messung von Sensorwerten: DHT11
  2.  * (Temperatur und rel. Luftfeuchtigkeit)
  3.  *
  4.  * Anzeige der Messwerte auf OLED-Display
  5.  * Typ SSD1306 128*32 Pixel
  6.  *
  7.  * 2020-11-15 Heiko (unsinnsbasis.de)
  8.  */
  9. // Bibliotheken für den DHT11/22-Sensor
  10. #include <Adafruit_Sensor.h>
  11. #include <DHT.h>
  12.  
  13. #define DHTPIN 4     // Pin für Sensor-DATA
  14. // Sensor-Typ: DHT11, DHT21 (AM2301) oder DHT22 (AM2302)
  15. #define DHTTYPE DHT11
  16.  
  17. DHT dht(DHTPIN, DHTTYPE);  // Datenstruktur für den Sensor
  18.  
  19. // Bibliotheken für das Display
  20. #include <SPI.h>
  21. #include <Wire.h>
  22. #include <Adafruit_GFX.h>
  23. #include <Adafruit_SSD1306.h>
  24.  
  25. // I2C-Adresse des Displays (0x3C oder 0x3D)
  26. #define DISPLAY_I2C_ADDRESS 0x3C
  27. // Auflösung des SSD1306-OLED-Displays
  28. #define DISPLAY_WIDTH 128  // Breite in Pixeln
  29. #define DISPLAY_HEIGHT 32  // Höhe in Pixeln
  30.  
  31. // Datenstruktur für das Display
  32. // - Verbindung per I2C (Standard-Pins SCL, SDA)
  33. // - Display hat keinen Reset-Pin (-1)
  34. Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
  35.  
  36. // Bitrate für die Datenübertragung zum seriellen Monitor
  37. // (ESP: z.B. 115200, Arduino: zwingend 9600)
  38. #define BITRATE 9600  // Arduino: 9600
  39.  
  40. float temp, hum;  // Variablen für Temperatur und Luftfeuchtigkeit
  41.  
  42. void setup() {
  43.   // Übertragungsrate zum seriellen Monitor setzen
  44.   Serial.begin(BITRATE);
  45.  
  46.   dht.begin();  // Sensor initialisieren
  47.  
  48.   // Display initialisieren
  49.   // im Fehlerfall Meldung ausgeben und Programm nicht
  50.   // fortsetzen (leere Dauerschleife))
  51.   if(!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
  52.     Serial.println("SSD1306 nicht gefunden");
  53.     for(;;);
  54.   }
  55. }
  56.  
  57. void loop() {
  58.   temp = dht.readTemperature();
  59.   hum = dht.readHumidity();
  60.  
  61.   display.clearDisplay();  // Display(puffer) löschen
  62.   display.setTextSize(1);  // kleine Schriftgröße (Höhe 8px)
  63.   display.setTextColor(WHITE);  // helle Schrift, dunkler Grund)
  64.   display.setCursor(0, 0);  // links oben anfangen
  65.  
  66.   if (isnan(temp)) {    
  67.     Serial.println("Fehler beim Auslesen des DHT-Sensors.");
  68.   } else {
  69.     display.print("Temperatur:   ");
  70.     display.print(temp, 1);
  71.     display.print("\xF7");  // Grad-Symbol ° in Codepage 437
  72.     display.println("C");
  73.   }
  74.  
  75.   // gleiches Vorgehen für die Luftfeuchtigkeit
  76.   if (isnan(hum)) {
  77.     Serial.println("Fehler beim Auslesen des DHT-Sensors.");
  78.   } else {
  79.     display.print("rel. Feuchte: ");
  80.     display.print(hum, 1);
  81.     display.println("%");
  82.   }
  83.   // alle bisher nur in den Puffer geschriebenen Daten anzeigen
  84.   display.display();
  85.   delay(5000);  // vor der nächsten Messung etwas warten
  86. }

Quellcode auf Github

Messwerte des DHT 11 auf einem SSD1306-OLED anzeigen
Messwerte des DHT 11 auf einem SSD1306-OLED anzeigen

Auf dem Foto sind nicht alle Steckbrücken zu erkennen; die sieht man dann im Fritzing-Diagramm. Dort ist auch ein 4,7 kΩ-Widerstand zwischen dem Datenpin des Sensors und der Spannungsversorgung eingezeichnet. Dieser wird nur benötigt, wenn man den Sensor „solo” verwendet. Bei einem Modul wie auf dem Foto ist der Widerstand bereits vorhanden und entfällt deshalb beim Aufbau auf dem Breadboard.
Wichtig beim Nachbauen: Die Anordnung der Pins (Masse und Spannung) kann bei verschiedenen Displays und Sensor-Modulen unterschiedlich sein! Maßgeblich ist die Beschriftung auf dem Gerät, nicht die Position im Diagramm.

OLED (SSD1306) und Sensor DHT11 am Arduino Nano (Diagramm)
OLED (SSD1306) und Sensor DHT11 am Arduino Nano (Diagramm)

Die wesentlichen Abschnitte im Programm sind:

  1. // Bibliotheken für das Display
  2. #include <SPI.h>
  3. #include <Wire.h>
  4. #include <Adafruit_GFX.h>
  5. #include <Adafruit_SSD1306.h>

Hier werden die Programm-Bibliotheken eingebunden. Im Prinzip genügt die unterste Zeile, da die Adafruit_SSD1306.h die anderen drei sowieso einbindet.

  1. // I2C-Adresse des Displays (0x3C oder 0x3D)
  2. #define DISPLAY_I2C_ADDRESS 0x3C
  3. // Auflösung des SSD1306-OLED-Displays
  4. #define DISPLAY_WIDTH 128  // Breite in Pixeln
  5. #define DISPLAY_HEIGHT 32  // Höhe in Pixeln
  6.  
  7. // Datenstruktur für das Display
  8. // - Verbindung per I2C (Standard-Pins SCL, SDA)
  9. // - Display hat keinen Reset-Pin (-1)
  10. Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);

Es werden Konstanten für die Display-Eigenschaften (I2C-Adresse, Breite und Höhe) definiert und ein Objekt mit dem Namen display angelegt. Da die Standard-I2C-Pins verwendet werden, wird auch das Standard-Objekt Wire für die Schnittstelle genutzt. Der letzte Parameter -1 bedeutet, dass das Display keinen Reset-Pin hat (sonst ist das der Controller-Pin, mit dem der Reset-Pin verbunden ist).

  1. display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS);

… initialisiert das display-Objekt; hier wird die oben definierte I2C-Adresse benötigt. Im Fehlerfall wird der boolsche Wert false zurückgegeben, bei Erfolg true. Im Beispiel wird er direkt verwendet, man kann ihn aber z.B. mit returncode = display.begin(...); auch speichern und dann auswerten.

  1.   display.clearDisplay();  // Display(puffer) löschen
  2.   display.setTextSize(1);  // kleine Schriftgröße (Höhe 8px)
  3.   display.setTextColor(WHITE);  // helle Schrift, dunkler Grund)
  4.   display.setCursor(0, 0);  // links oben anfangen

Der Block bereitet die Ausgabe zum Display vor. Der bisherige Inhalt des Display-Puffers wird gelöscht. Die folgende Text-Ausgabe nutzt die Standardschrift in Größe 1, hell auf dunklem Grund wird als Farbe gesetzt (die Angaben SSD1306_WHITE oder WHITE sind gleichbedeutend) und beginnt in der linken oberen Display-Ecke. Maßgeblich für die Methode setCursor() ist die linke obere Ecke des Zeichens – diese wird dann an die angegebene Position gesetzt. Die Schriftgröße basiert auf der Standardgröße eines Zeichens von 6*8 Pixeln. Der Zeichensatz ist ein Monospace-Font, alle Zeichen sind also gleich groß. (Wie man Zeichensätze mit variabler Breite nutzt, kann man im oben erwähnten Bespielprogramm der Bibliothek sehen.) Folgende Schriftgrößen sind möglich:

  • 1 – Zeichengröße 6*8 Pixel; 21 Zeichen pro Zeile; 8 Zeilen auf einem 128*64-Display
  • 2 – Größe 12*16, 10 Zeichen pro Zeile in 4 Zeilen
  • 3 – Größe 18*24, 7 Zeichen pro Zeile in 2,5 Zeilen
  • 4 – Größe 24*32, 5 Zeichen pro Zeile in 2 Zeilen
  • 8 – Größe 48*64, 2 Zeichen in 1 Zeile

Man kann beim Anzeigen von Text auch verschiedene Schriftgrößen mischen, indem man vor jeder print()-Ausgabe mit setTextSize() die gewünschte Größe einstellt. (Eine beispielhafte Ausgabe zeigt das Bild oberhalb des Beitrags.) Gibt man mehr Zeichen aus, als in eine Zeile passen, wird die Ausgabe in der nächsten Zeile fortgesetzt. Überzählige Zeilen werden ignoriert, wobei je nach Schriftgröße von der letzten Zeile mglw. nur die oberen Pixel angezeigt werden.

  1.     display.print("Temperatur:   ");

print() gibt einen Text aus, println() mit anschließendem Zeilenvorschub.

  1.   display.display();

Die Ausgabemethoden wie print() schreiben erstmal nur Daten in den Display-Puffer. Erst mit der Methode display() werden diese Inhalte tatsächlich angezeigt.

Programm: Zeichentabelle der Codepage 437

Nicht ganz wie erwartet ist die Ausgabe von Sonderzeichen (z.B. deutsche Umlaute oder im Programm das Grad-Zeichen):

  1.     display.print("\xF7");  // Grad-Symbol ° in Codepage 437

Der Zeichensatz verwendet die Codepage 437. In Zeiten von UTF-8 (Unicode) ist der Umgang mit Codepages ungewohnt; wer früher unter (MS-)DOS-Betriebssystemen gearbeitet hat, wird sich erinnern. Sonst hilft ein Blick in die Wikipedia; dort gibt es auch eine Tabelle der darstellbaren Zeichen.

Die kann man mit folgendem Programm selbst erzeugen:

  1. /* SSD1306 OLED-Display 128*64 Pixel
  2.  *  
  3.  *  Anzeige der darstellbaren Zeichen
  4.  *  
  5.  * 2020-11-15 Heiko (unsinnsbasis.de)
  6.  */
  7. // Bibliothek für das Display
  8. // bindet auch <Adafruit_GFX.h>, <SPI.h>, <Wire.h> ein
  9. #include <Adafruit_SSD1306.h>
  10.  
  11. // I2C-Adresse des Displays (0x3C oder 0x3D)
  12. #define DISPLAY_I2C_ADDRESS 0x3C
  13. // Auflösung des SSD1306-OLED-Displays
  14. #define DISPLAY_WIDTH 128  // Breite in Pixeln
  15. #define DISPLAY_HEIGHT 64  // Höhe in Pixeln
  16.  
  17. // Datenstruktur für das Display
  18. // - Verbindung per I2C (Standard-Pins SCL, SDA)
  19. // - Display hat keinen Reset-Pin
  20. Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
  21.  
  22. // Bitrate für die Datenübertragung zum seriellen Monitor
  23. // (ESP: z.B. 115200, Arduino: zwingend 9600)
  24. #define BITRATE 115200  // Arduino: 9600
  25.  
  26. int16_t i, j;
  27.  
  28. void setup() {
  29.   // Übertragungsrate zum seriellen Monitor setzen
  30.   Serial.begin(BITRATE);
  31.  
  32.   // Display initialisieren
  33.   // im Fehlerfall Meldung ausgeben und Programm nicht
  34.   // fortsetzen (leere Dauerschleife))
  35.   if (!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
  36.     Serial.println("SSD1306 nicht gefunden");
  37.     for (;;) ;
  38.   }
  39. }
  40.  
  41. void loop() {
  42.   display.clearDisplay();  // Display(buffer) löschen
  43.   display.setTextSize(1);  // kleine Schriftgröße (Höhe 8px)
  44.   display.setTextColor(SSD1306_WHITE);  // helle Schrift, dunkler Grund)
  45.   display.setCursor(0, 0);  // links oben anfangen
  46.   display.cp437(true);      // Zeichensatz der Codepage 437 verwenden
  47.   // erste Hälfte der Codepage (Zeichen 0-127)
  48.   for (i=0; i<8; i++) {
  49.     if (i == 0) {
  50.       display.print("00 ");
  51.     } else {
  52.       display.print(i<<4, HEX);
  53.       display.write(" ");
  54.     }
  55.     for (j=0; j<16; j++) {
  56.       if (i*16+j == '\n')
  57.         display.write(" ");
  58.       else
  59.         display.write(i*16+j);
  60.     }
  61.     display.write("\n");  // neue Zeile
  62.   }
  63.   display.display();
  64.   delay(5000);
  65.   display.clearDisplay();
  66.   display.setCursor(0, 0);
  67.   // zweite Hälfte der Codepage (Zeichen 128-255)
  68.   for (i=8; i<16; i++) {
  69.     display.print(i<<4, HEX);
  70.     display.write(" ");
  71.     for (j=0; j<16; j++) {
  72.       display.write(i*16+j);
  73.     }
  74.     display.write("\n");  // neue Zeile
  75.   }
  76.   display.display();
  77.   delay(5000);
  78. }

Quellcode auf Github

OLED-Display SSD1306: darstellbare Zeichen der Codepage 437
OLED-Display SSD1306: darstellbare Zeichen der Codepage 437

Aus der Tabelle kann man sich den Code eines Sonderzeichens heraussuchen. (Aus einem mir nicht erfindlichen Grund werden die Zeichen ab xB0 eine Position nach links verschoben. Das Grad-Symbol wird also z.B. mit dem Code xF7 erzeugt und nicht – wie zu erwarten – mit xF8. Vielleicht finde ich noch die Ursache; bis dahin nehme ich den pragmatischen Weg und verwende bei Bedarf die „falschen” Zeichencodes. Also nicht wundern.) 😉

  1.       display.write(i*16+j);

Die Methode write() gibt Zeichen byteweise aus. Ein Zahlenwert n wird hierbei als Zeichen mit der Position n im Zeichensatz interpretiert. write(65) gibt nicht die Ziffern der Zahl 65 aus, sondern das Zeichen an Position 65 (hexadezimal x41), also den Buchstaben »A«.
Wird eine Zeichenkette (ein einzelnes oder eine Folge von Zeichen, in doppelte Hochkommata eingeschlossen), an write() übergeben, wird diese Zeichen für Zeichen ausgegeben – die Methode führt zum gleichen Ergebnis wie print().

Ändern der Helligkeit des OLED-Displays

Die Displayhelligkeit kann nur sehr bedingt durch eine Kontraständerung beeinflusst werden.[5]Siehe Adafruit Support Forum (engl.) und Arduino Forum (dt.) Für den Kontrast sind Werte zwischen 0 und 255 möglich, mehr als 4 oder 5 Stufen sind kaum sinnvoll (z.B. 0, 80, 160, 240 wie im folgendem Codeschnipsel). Um den Kontrast zu ändern, wird der Kommandocode SSD1306_SETCONTRAST, gefolgt vom Kontrastwert, an das Display-Objekt geschickt. SSD1306_SETCONTRAST ist in der Header-Datei Adafruit_SSD1306.h als 0x81 definiert.

  // Kontrast in vier Stufen ändern
  for (uint8_t conlvl=0; conlvl<4; conlvl++) {
    display.ssd1306_command(SSD1306_SETCONTRAST);  // 0x81
    display.ssd1306_command(conlvl*80);
    delay(1000);
  }

Diese Zeilen kann man testhalber in ein Programm einfügen, nachdem mit der display()-Methode ein Inhalt dargestellt wurde. Dann wird in vier Stufen von jeweils einer Sekunde Dauer der Kontrast bzw. die Helligkeit erhöht. Ein erneuter Aufruf der display()-Methode ist dabei nicht nötig – der Effekt der Änderung tritt sofort ein; der Inhalt des Anzeigepuffers bleibt unverändert. Die Unterschiede sind zwar sichtbar, aber relativ klein. Ein Kontrastwert von 0 bedeutet bei weitem kein dunkles Display; ein Aus- oder Einblendeffekt ist nicht möglich.

Einen ähnlichen Effekt bewirkt die Methode dim(), die als Argument true oder false erwartet:

  display.dim(true);   // setzt Kontrast auf 0
  display.dim(false);  // setzt Kontrast auf Standardwert (nicht Maximum)

Als Standardwert ist in der verwendeten Bibliotheksversion für den Kontrast 0x8F definiert.[6]Datei Adafruit_SSD1306.cpp Zeile 555: contrast = 0x8F;

Programm: Zwei OLED-Displays am ESP32

Mit der Standard-I2C-Adresse ist es nicht möglich, zwei Displays an einer I2C-Schnittstelle zu betreiben, denn die Geräte können nicht unterschieden werden.[7]Man kann schon mehrere Displays an eine I2C-Schnittstelle anschließen. Da sie wegen der gleichen Adresse nicht unabhängig voneinander adressiert werden können, sollten sie aber baugleich sein. Auf … Continue reading Da der ESP32 zwei I2C-Schnittstellen hat (jedenfalls die Standard-Boards vom Typ DevKitC), kann man aber an jede ein Display anschließen, ohne dass man – wie oben beschrieben – die Adresse hardwareseitig ändern muss.

  1. /* SSD1306 OLED-Display 128*64 Pixel
  2.  * und
  3.  * SD1306 OLED-Display 128*32 Pixel
  4.  * gleichzeitig ansprechen
  5.  *
  6.  * 2020-11-15 Heiko (unsinnsbasis.de)
  7.  */
  8. // Bibliothek für das Display
  9. // bindet auch <Adafruit_GFX.h>, <SPI.h>, <Wire.h> ein
  10. #include <Adafruit_SSD1306.h>
  11.  
  12. // großes Display am Standard-I2C-Interface
  13. // (SDA Pin 21, SCL Pin 22)
  14. // I2C-Adresse des Displays (0x3C oder 0x3D)
  15. #define DISPLAY_1_I2C_ADDRESS 0x3C
  16. // Auflösung des SSD1306-OLED-Displays
  17. #define DISPLAY_1_WIDTH 128  // Breite in Pixeln
  18. #define DISPLAY_1_HEIGHT 64  // Höhe in Pixeln
  19.  
  20. // kleines Display am zweiten I2C-Interface
  21. // (SDA Pin 17, SCL Pin 16)
  22. #define I2C_2_SDA 17
  23. #define I2C_2_SCL 16
  24. // I2C-Adresse des Displays (0x3C oder 0x3D)
  25. #define DISPLAY_2_I2C_ADDRESS 0x3C
  26. // Auflösung des SSD1306-OLED-Displays
  27. #define DISPLAY_2_WIDTH 128  // Breite in Pixeln
  28. #define DISPLAY_2_HEIGHT 32  // Höhe in Pixeln
  29.  
  30. // je I2C-Kanal ein Interface definieren
  31. TwoWire I2C_1 = TwoWire(0);
  32. TwoWire I2C_2 = TwoWire(1);
  33.  
  34. // Datenstrukturen für die Displays
  35. // (-1 -> Display hat keinen Reset-Pin)
  36. Adafruit_SSD1306 display1(DISPLAY_1_WIDTH, DISPLAY_1_HEIGHT, &I2C_1, -1);
  37. Adafruit_SSD1306 display2(DISPLAY_2_WIDTH, DISPLAY_2_HEIGHT, &I2C_2, -1);
  38.  
  39. // Bitrate für die Datenübertragung zum seriellen Monitor
  40. // (ESP: z.B. 115200, Arduino: zwingend 9600)
  41. #define BITRATE 115200  // Arduino: 9600
  42.  
  43. void setup() {
  44.   bool status1, status2;
  45.  
  46.   // Übertragungsrate zum seriellen Monitor setzen
  47.   Serial.begin(BITRATE);
  48.   Serial.println("Test mit 2 OLED-Displays");
  49.  
  50.   // beide I2C-Interfaces nutzen
  51.   I2C_1.begin();  // Standard-Interface
  52.   I2C_2.begin(I2C_2_SDA, I2C_2_SCL);  // 2. Interface
  53.  
  54.   // Displays initialisieren
  55.   // im Fehlerfall Meldung ausgeben und Programm nicht
  56.   // fortsetzen (leere Dauerschleife))
  57.   status1 = display1.begin(SSD1306_SWITCHCAPVCC, DISPLAY_1_I2C_ADDRESS);
  58.   status2 = display2.begin(SSD1306_SWITCHCAPVCC, DISPLAY_2_I2C_ADDRESS);
  59.   if (!(status1 & status2)) {
  60.     Serial.println("Fehler beim Initialisieren der Displays");
  61.     Serial.print("Status Display 1: ");
  62.     Serial.print(status1);
  63.     Serial.print(" - Status Display 2: ");
  64.     Serial.print(status2);
  65.     Serial.println(" (1=OK)");
  66.     for (;;) ;
  67.   }
  68.  
  69.   display1.clearDisplay();
  70.   display1.setTextSize(2);  // große Schrift
  71.   display1.setTextColor(SSD1306_WHITE); // helle Schrift auf dunklem Grund
  72.   display1.setCursor(0, 0);
  73.   display1.print("  Grosse\nBuchstaben\n  in vier\n  Zeilen");
  74.   display1.display();
  75.  
  76.   display2.clearDisplay();
  77.   display2.setTextSize(1);  // kleine bzw. Standard-Schrift
  78.   display2.setTextColor(SSD1306_WHITE); // helle Schrift auf dunklem Grund
  79.   display2.setCursor(0, 0);
  80.   display2.println("Mit dem Unsinn lebt");
  81.   display2.println("es sich leichter als");
  82.   display2.println("mit der Sinnlosigkeit");
  83.   display2.print("(Ernst Reinhardt)");
  84.   display2.display();
  85. }
  86.  
  87. void loop() {
  88. }

Quellcode auf Github

ESP32 mit zwei OLED-Displays (Aufbau)
ESP32 mit zwei OLED-Displays (Aufbau)

Statt einer legt man in diesem Fall zwei I2C-Schnittstellen an. Man kann auch die vordefinierten Standardobjekte Wire und Wire1 aus der ESP32-Wire-Bibliothek nutzen, die genauso definiert sind. [8]Bei einer Standard-Windows-Installation: Datei C:\Users\<benutzername>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\Wire\src\Wire.cpp; die Definitionen von Wire und … Continue reading

  1. TwoWire I2C_1 = TwoWire(0);
  2. TwoWire I2C_2 = TwoWire(1);

Für die beiden Displays definiert man zwei Objekte display1 und display2, wobei jedes seine eigene Schnittstelle nutzt, …

  1. Adafruit_SSD1306 display1(DISPLAY_1_WIDTH, DISPLAY_1_HEIGHT, &I2C_1, -1);
  2. Adafruit_SSD1306 display2(DISPLAY_2_WIDTH, DISPLAY_2_HEIGHT, &I2C_2, -1);
ESP32 mit zwei OLED-Displays (Diagramm)
ESP32 mit zwei OLED-Displays (Diagramm)

… und initialisiert die beiden Schnittstellen:

  1.   I2C_1.begin();  // Standard-Interface
  2.   I2C_2.begin(I2C_2_SDA, I2C_2_SCL);  // 2. Interface

Für die erste werden hier keine Parameter übergeben, d.h. es werden die Standards (beim ESP32 die Pins SCL 22, SDA 21) benutzt. Das zweite Interface nutzt die weiter oben im Programm definierten Konstanten (SCL 16, SDA 17 – in diesem Fall identisch mit den Standardwerten für die zweite Schnittstelle).

Anschließend kann man jedes Display mit den verschiedenen Methoden unabhängig vom anderen steuern.

0,66″ Display-Shield für den D1 mini

Pixelei bei Nutzung der Adafruit-Bibliotheken
Pixelei bei Nutzung der Adafruit-Bibliotheken

Für den D1 mini gibt es diverse Shields zum Aufstecken, u.a. eines mit einem kleinen OLED-Display. Es hat eine Diagonale von 0,66 Zoll, stellt 64 * 48 Pixel dar und wird ebenfalls vom Treiber-Chip SSD1306 gesteuert.[9]Ich besitze die „alte”, einfache Version des Shields; es gibt eine neuere, die zusätzlich zum Display auf der Unterseite zwei Buttons hat – einen rechts und einen links. Die … Continue reading

Mit den oben für die „großen” Displays verwendeten Bibliotheken von Adafruit habe ich bei der Programmierung des Display-Shields keinen Erfolg gehabt. Das folgende Programm läuft zwar mit einem 128×64 Pixel großen OLED-Display am ESP32 problemlos und zeigt dort in zwei Zeilen in kleiner Schrift den Text „Hallo Welt!” an. Wie das Bild zeigt, bringt die Kombination aus D1 mini mit OLED-Shield und der Bibliotheken Adafruit_SSD1306 und Adafruit_GFX aber nur Pixelmüll auf das Display: Die rechte Hälfte wird nicht geändert (der bisherige Inhalt des Displaypuffers bleibt erhalten, nach einem Neustart also zufällige Pixel), die linke Hälfte wird zwar gelöscht, aber es werden keine sinnvolllen Daten angezeigt.[10]Installiert sind die Anfang Juni 2021 aktuellen Versionen: Adafruit_SSD1306 2.4.5 und Adafruit_GFX 1.10.10. Auf Basis einer älteren Version (2.3.x) der Adafruit_SSD1306-Bibliothek gibt es eine an … Continue reading

  1. /* Test des OLED-Displays für den D1 mini
  2.  * mit 64*48 Pixeln
  3.  *
  4.  * Version 1: Adafruit-Bibliotheken
  5.  * -> FUNKTIONIERT NICHT
  6.  * 
  7.  * 2021-06-05 Heiko (unsinnsbasis.de)
  8.  */
  9.  
  10. // Bibliotheken für das Display
  11. #include <SPI.h>
  12. #include <Wire.h>
  13. #include <Adafruit_GFX.h>
  14. #include <Adafruit_SSD1306.h>
  15.  
  16. // I2C-Adresse des Displays (0x3C oder 0x3D)
  17. #define DISPLAY_I2C_ADDRESS 0x3C
  18. // Auflösung des SSD1306-OLED-Displays
  19. #define DISPLAY_WIDTH  64 // 128  // Breite in Pixeln
  20. #define DISPLAY_HEIGHT 48 // 64   // Höhe in Pixeln
  21.  
  22. // Datenstruktur für das Display
  23. // - Verbindung per I2C (Standard-Pins SCL, SDA)
  24. // - Display hat keinen Reset-Pin (-1)
  25. Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
  26.  
  27. void setup() {
  28.   // Übertragungsrate zum seriellen Monitor setzen
  29.   Serial.begin(115200);
  30.   delay(500);
  31.  
  32.   // Display initialisieren; im Fehlerfall Programm anhalten
  33.   if(!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
  34.     Serial.println("SSD1306 nicht gefunden");
  35.     for(;;);  // leere Dauerschleife
  36.   }
  37. }
  38.  
  39. void loop() {
  40.   display.clearDisplay();  // Display(puffer) löschen
  41.   display.setTextSize(1);  // kleine Schriftgröße (Höhe 8px)
  42.   display.setTextColor(WHITE);  // helle Schrift, dunkler Grund)
  43.   display.setCursor(0, 0);  // links oben anfangen
  44.   display.print("Hallo");
  45.   display.setCursor(10, 20);
  46.   display.print("Welt!");
  47.   display.display();
  48.   delay(2000);  // etwas warten
  49.   display.clearDisplay();  // dann Display kurz löschen
  50.   display.display();
  51.   delay(500);
  52. }

Auf dem D1 mini führt das Programm dazu, dass das Display erst nach einem Kaltstart (kurz die Spannungsversorgung unterbrechen; USB-Kabel abziehen) wieder korrekt funktioniert. Auch einige andere Programme, die ich im Netz gefunden habe, haben ähnliche Effekte oder sorgen dafür, dass der ESP8266 abstürzt bzw. einen Reset nach dem anderen ausführt, obwohl ich davon ausgehe, dass sie bei den jeweiligen Autoren funktioniert haben. Das mag einer anderen Kombination aus Hard- und Software geschuldet sein (ich besitze bspw. günstige Varianten bzw. Nachbauten der Original D1 minis und Shields von Wemos). Bei mir vertragen sich das Display und die Adafruit-Bibliotheken jedenfalls nicht.

Textanzeige bei Nutzung der u8g2-Bibliothek
Textanzeige bei Nutzung der u8g2-Bibliothek

Wie oben genannt, gibt es als Alternative zu den Adafruit-Bibliotheken die Bibliothek u8g2. Diese hat mit meiner Hardware auf Anhieb funktioniert.

Sie verwendet bei Text ein etwas anderes Ausgabemodell als Adafruit: Die Position (0,0) auf dem Display bezieht sich auf die Unterkante der Zeichen – damit ein 8 Pixel hoher Text ab der linken oberen Displayecke komplett angezeigt wird, muss man also mindestens an Position (0,8) beginnen. (Bei Adafruit bezieht sich die Angabe auf die Oberkante – (0,0) bewirkt, dass die Darstellung links oben beginnt).

Die Funktionen sind in einem Wiki ausführlich dokumentiert. Außerdem werden viele Beispielprogramme zur Bibliothek mitinstalliert.

Ein dem obigen Beispiel entsprechendes Programm sieht mit der u8g2-Lib in etwa so aus:

  1. /* Test des OLED-Displays für den D1 mini
  2.  * mit 64*48 Pixeln
  3.  *
  4.  * Version 2: u8g2
  5.  * 
  6.  * 2021-06-05 Heiko (unsinnsbasis.de)
  7.  */
  8. // Bibliothek U8G2 für das Display
  9. #include <Arduino.h>
  10. #include <U8g2lib.h>
  11. #include <Wire.h>
  12.  
  13. // Datenstruktur für das Display anlegen:
  14. // 64 x 48 Pixel 0,66 Zoll (Shield für D1 mini)
  15. U8G2_SSD1306_64X48_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
  16.  
  17. void setup() {
  18.   // Display initialisieren (liefert immer "true" zurück;
  19.   // keine Fehlerbehandlung möglich / nötig)
  20.   u8g2.begin();
  21. }
  22.  
  23. void loop() {
  24.   u8g2.clearBuffer();
  25.   // Zeichensatz mit 8 Pixel Höhe auswählen
  26.   u8g2.setFont(u8g2_font_ncenB08_tr);
  27.   u8g2.drawStr(0,8,"Hallo");
  28.   u8g2.drawStr(10,28,"Welt!");
  29.   u8g2.sendBuffer();
  30.   delay(2000);  // etwas warten
  31.   u8g2.clearBuffer();  // dann Display kurz löschen
  32.   u8g2.sendBuffer();
  33.   delay(500);
  34. }

Änderungen:
05.06.2021 Ergänzung des Abschnitts für das 0,66-Zoll-Display

Fußnoten

Fußnoten
1 Bei der zweifarbigen Variante ist das obere Viertel (ein 16 Pixel hoher Streifen) gelb, der Rest blau.
2 Wenn man z.B. bei aliexpress.com ein wenig sucht, findet man die Displays bei einigen Händlern wahlweise mit I2C- oder SPI-Anschluss. Falls man eine etwas größere Anzeige benötigt, gibt es Displays mit 1,3″ Bildschirmdiagonale, SPI- oder I2C-Schnittstelle und dem Treiberbaustein SSH1106 (ebenfalls mit 128*64 Pixeln Auflösung) für etwas mehr Geld.
3 Ich habe in den Diagrammen das Bauteil aus dem Fritzing-Forum verwendet.
4 Bei zwei Displays ist auf der Rückseite der Platine die falsche Adressangabe »0x78« aufgedruckt; durch Versetzen des Widerstands änderbar in »0x7A«. Die Angabe ist Unfug, auch diese Anzeigen haben die I2C-Adresse 0x3C.
5 Siehe Adafruit Support Forum (engl.) und Arduino Forum (dt.)
6 Datei Adafruit_SSD1306.cpp Zeile 555: contrast = 0x8F;
7 Man kann schon mehrere Displays an eine I2C-Schnittstelle anschließen. Da sie wegen der gleichen Adresse nicht unabhängig voneinander adressiert werden können, sollten sie aber baugleich sein. Auf allen wird dann der gleiche Inhalt angezeigt.
8 Bei einer Standard-Windows-Installation: Datei C:\Users\<benutzername>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\Wire\src\Wire.cpp; die Definitionen von Wire und Wire1 sind ganz am Ende der Datei zu finden.
TwoWire Wire = TwoWire(0);
TwoWire Wire1 = TwoWire(1);

9 Ich besitze die „alte”, einfache Version des Shields; es gibt eine neuere, die zusätzlich zum Display auf der Unterseite zwei Buttons hat – einen rechts und einen links. Die Displayfarbe ist weiß.
10 Installiert sind die Anfang Juni 2021 aktuellen Versionen: Adafruit_SSD1306 2.4.5 und Adafruit_GFX 1.10.10.
Auf Basis einer älteren Version (2.3.x) der Adafruit_SSD1306-Bibliothek gibt es eine an das kleine OLED angepasste Bibliothek auf github.com/mcauser/Adafruit_SSD1306. Auch diese hat mit meiner Hardware nicht funktioniert. Diese Variante hat zusätzlich den Nachteil, dass sie die Original-Bibliothek von Adafruit überschreibt – man muss die also sichern und später wiederherstellen oder sich mit einer portablen Installation der Arduino-IDE eine zusätzliche Entwicklungsumgebung schaffen.
Es gibt auf Github auch eine – bisher nicht beantwortete – Anfrage (sog. issue) vom Februar 2021, die Unterstützung für das 64×48-Pixel-Display in die offizielle Bibliothek zu integrieren.
Da es aber mit der u8g2-Library eine einfach zu nutzende und funktionierende Alternative gibt, habe ich den Weg mit Adafruit für das 0,66-Zoll-Display nicht weiter verfolgt.

 

[Quellenangabe zum Bild „Achtung-Zeichen”: Clker-Free-Vector-Images auf Pixabay); Pixabay License: »Free for commercial use. No attribution required«]

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.