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
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
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)
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.
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
- Adafruit SSD1306 auf github.com/adafruit/Adafruit_SSD1306
- u8g2 auf github.com/olikraus/u8g2/wiki (Nachfolger der u8glib)
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:
#define DHTPIN 4 // Pin für Sensor-DATA
Das ganze Programm sieht so aus:
/* Messung von Sensorwerten: DHT11
* (Temperatur und rel. Luftfeuchtigkeit)
*
* Anzeige der Messwerte auf OLED-Display
* Typ SSD1306 128*32 Pixel
*
* 2020-11-15 Heiko (unsinnsbasis.de)
*/
// Bibliotheken für den DHT11/22-Sensor
#include <Adafruit_Sensor.h>
#include <DHT.h>
#define DHTPIN 4 // Pin für Sensor-DATA
// Sensor-Typ: DHT11, DHT21 (AM2301) oder DHT22 (AM2302)
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE); // Datenstruktur für den Sensor
// Bibliotheken für das Display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_WIDTH 128 // Breite in Pixeln
#define DISPLAY_HEIGHT 32 // Höhe in Pixeln
// Datenstruktur für das Display
// - Verbindung per I2C (Standard-Pins SCL, SDA)
// - Display hat keinen Reset-Pin (-1)
Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
// Bitrate für die Datenübertragung zum seriellen Monitor
// (ESP: z.B. 115200, Arduino: zwingend 9600)
#define BITRATE 9600 // Arduino: 9600
float temp, hum; // Variablen für Temperatur und Luftfeuchtigkeit
void setup() {
// Übertragungsrate zum seriellen Monitor setzen
Serial.begin(BITRATE);
dht.begin(); // Sensor initialisieren
// Display initialisieren
// im Fehlerfall Meldung ausgeben und Programm nicht
// fortsetzen (leere Dauerschleife))
if(!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
Serial.println("SSD1306 nicht gefunden");
for(;;);
}
}
void loop() {
temp = dht.readTemperature();
hum = dht.readHumidity();
display.clearDisplay(); // Display(puffer) löschen
display.setTextSize(1); // kleine Schriftgröße (Höhe 8px)
display.setTextColor(WHITE); // helle Schrift, dunkler Grund)
display.setCursor(0, 0); // links oben anfangen
if (isnan(temp)) {
Serial.println("Fehler beim Auslesen des DHT-Sensors.");
} else {
display.print("Temperatur: ");
display.print(temp, 1);
display.print("\xF7"); // Grad-Symbol ° in Codepage 437
display.println("C");
}
// gleiches Vorgehen für die Luftfeuchtigkeit
if (isnan(hum)) {
Serial.println("Fehler beim Auslesen des DHT-Sensors.");
} else {
display.print("rel. Feuchte: ");
display.print(hum, 1);
display.println("%");
}
// alle bisher nur in den Puffer geschriebenen Daten anzeigen
display.display();
delay(5000); // vor der nächsten Messung etwas warten
}
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.
Die wesentlichen Abschnitte im Programm sind:
// Bibliotheken für das Display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#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.
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_WIDTH 128 // Breite in Pixeln
#define DISPLAY_HEIGHT 32 // Höhe in Pixeln
// Datenstruktur für das Display
// - Verbindung per I2C (Standard-Pins SCL, SDA)
// - Display hat keinen Reset-Pin (-1)
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).
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.
display.clearDisplay(); // Display(puffer) löschen
display.setTextSize(1); // kleine Schriftgröße (Höhe 8px)
display.setTextColor(WHITE); // helle Schrift, dunkler Grund)
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.
display.print("Temperatur: ");
print()
gibt einen Text aus, println()
mit anschließendem Zeilenvorschub.
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):
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:
/* SSD1306 OLED-Display 128*64 Pixel
*
* Anzeige der darstellbaren Zeichen
*
* 2020-11-15 Heiko (unsinnsbasis.de)
*/
// Bibliothek für das Display
// bindet auch <Adafruit_GFX.h>, <SPI.h>, <Wire.h> ein
#include <Adafruit_SSD1306.h>
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_WIDTH 128 // Breite in Pixeln
#define DISPLAY_HEIGHT 64 // Höhe in Pixeln
// Datenstruktur für das Display
// - Verbindung per I2C (Standard-Pins SCL, SDA)
// - Display hat keinen Reset-Pin
Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
// Bitrate für die Datenübertragung zum seriellen Monitor
// (ESP: z.B. 115200, Arduino: zwingend 9600)
#define BITRATE 115200 // Arduino: 9600
int16_t i, j;
void setup() {
// Übertragungsrate zum seriellen Monitor setzen
Serial.begin(BITRATE);
// Display initialisieren
// im Fehlerfall Meldung ausgeben und Programm nicht
// fortsetzen (leere Dauerschleife))
if (!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
Serial.println("SSD1306 nicht gefunden");
for (;;) ;
}
}
void loop() {
display.clearDisplay(); // Display(buffer) löschen
display.setTextSize(1); // kleine Schriftgröße (Höhe 8px)
display.setTextColor(SSD1306_WHITE); // helle Schrift, dunkler Grund)
display.setCursor(0, 0); // links oben anfangen
display.cp437(true); // Zeichensatz der Codepage 437 verwenden
// erste Hälfte der Codepage (Zeichen 0-127)
for (i=0; i<8; i++) {
if (i == 0) {
display.print("00 ");
} else {
display.print(i<<4, HEX);
display.write(" ");
}
for (j=0; j<16; j++) {
if (i*16+j == '\n')
display.write(" ");
else
display.write(i*16+j);
}
display.write("\n"); // neue Zeile
}
display.display();
delay(5000);
display.clearDisplay();
display.setCursor(0, 0);
// zweite Hälfte der Codepage (Zeichen 128-255)
for (i=8; i<16; i++) {
display.print(i<<4, HEX);
display.write(" ");
for (j=0; j<16; j++) {
display.write(i*16+j);
}
display.write("\n"); // neue Zeile
}
display.display();
delay(5000);
}
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.) 😉
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.
/* SSD1306 OLED-Display 128*64 Pixel
* und
* SD1306 OLED-Display 128*32 Pixel
* gleichzeitig ansprechen
*
* 2020-11-15 Heiko (unsinnsbasis.de)
*/
// Bibliothek für das Display
// bindet auch <Adafruit_GFX.h>, <SPI.h>, <Wire.h> ein
#include <Adafruit_SSD1306.h>
// großes Display am Standard-I2C-Interface
// (SDA Pin 21, SCL Pin 22)
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_1_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_1_WIDTH 128 // Breite in Pixeln
#define DISPLAY_1_HEIGHT 64 // Höhe in Pixeln
// kleines Display am zweiten I2C-Interface
// (SDA Pin 17, SCL Pin 16)
#define I2C_2_SDA 17
#define I2C_2_SCL 16
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_2_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_2_WIDTH 128 // Breite in Pixeln
#define DISPLAY_2_HEIGHT 32 // Höhe in Pixeln
// je I2C-Kanal ein Interface definieren
TwoWire I2C_1 = TwoWire(0);
TwoWire I2C_2 = TwoWire(1);
// Datenstrukturen für die Displays
// (-1 -> Display hat keinen Reset-Pin)
Adafruit_SSD1306 display1(DISPLAY_1_WIDTH, DISPLAY_1_HEIGHT, &I2C_1, -1);
Adafruit_SSD1306 display2(DISPLAY_2_WIDTH, DISPLAY_2_HEIGHT, &I2C_2, -1);
// Bitrate für die Datenübertragung zum seriellen Monitor
// (ESP: z.B. 115200, Arduino: zwingend 9600)
#define BITRATE 115200 // Arduino: 9600
void setup() {
bool status1, status2;
// Übertragungsrate zum seriellen Monitor setzen
Serial.begin(BITRATE);
Serial.println("Test mit 2 OLED-Displays");
// beide I2C-Interfaces nutzen
I2C_1.begin(); // Standard-Interface
I2C_2.begin(I2C_2_SDA, I2C_2_SCL); // 2. Interface
// Displays initialisieren
// im Fehlerfall Meldung ausgeben und Programm nicht
// fortsetzen (leere Dauerschleife))
status1 = display1.begin(SSD1306_SWITCHCAPVCC, DISPLAY_1_I2C_ADDRESS);
status2 = display2.begin(SSD1306_SWITCHCAPVCC, DISPLAY_2_I2C_ADDRESS);
if (!(status1 & status2)) {
Serial.println("Fehler beim Initialisieren der Displays");
Serial.print("Status Display 1: ");
Serial.print(status1);
Serial.print(" - Status Display 2: ");
Serial.print(status2);
Serial.println(" (1=OK)");
for (;;) ;
}
display1.clearDisplay();
display1.setTextSize(2); // große Schrift
display1.setTextColor(SSD1306_WHITE); // helle Schrift auf dunklem Grund
display1.setCursor(0, 0);
display1.print(" Grosse\nBuchstaben\n in vier\n Zeilen");
display1.display();
display2.clearDisplay();
display2.setTextSize(1); // kleine bzw. Standard-Schrift
display2.setTextColor(SSD1306_WHITE); // helle Schrift auf dunklem Grund
display2.setCursor(0, 0);
display2.println("Mit dem Unsinn lebt");
display2.println("es sich leichter als");
display2.println("mit der Sinnlosigkeit");
display2.print("(Ernst Reinhardt)");
display2.display();
}
void loop() {
}
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
TwoWire I2C_1 = TwoWire(0);
TwoWire I2C_2 = TwoWire(1);
Für die beiden Displays definiert man zwei Objekte display1
und display2
, wobei jedes seine eigene Schnittstelle nutzt, …
Adafruit_SSD1306 display1(DISPLAY_1_WIDTH, DISPLAY_1_HEIGHT, &I2C_1, -1);
Adafruit_SSD1306 display2(DISPLAY_2_WIDTH, DISPLAY_2_HEIGHT, &I2C_2, -1);
… und initialisiert die beiden Schnittstellen:
I2C_1.begin(); // Standard-Interface
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
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
/* Test des OLED-Displays für den D1 mini
* mit 64*48 Pixeln
*
* Version 1: Adafruit-Bibliotheken
* -> FUNKTIONIERT NICHT
*
* 2021-06-05 Heiko (unsinnsbasis.de)
*/
// Bibliotheken für das Display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// I2C-Adresse des Displays (0x3C oder 0x3D)
#define DISPLAY_I2C_ADDRESS 0x3C
// Auflösung des SSD1306-OLED-Displays
#define DISPLAY_WIDTH 64 // 128 // Breite in Pixeln
#define DISPLAY_HEIGHT 48 // 64 // Höhe in Pixeln
// Datenstruktur für das Display
// - Verbindung per I2C (Standard-Pins SCL, SDA)
// - Display hat keinen Reset-Pin (-1)
Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
void setup() {
// Übertragungsrate zum seriellen Monitor setzen
Serial.begin(115200);
delay(500);
// Display initialisieren; im Fehlerfall Programm anhalten
if(!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
Serial.println("SSD1306 nicht gefunden");
for(;;); // leere Dauerschleife
}
}
void loop() {
display.clearDisplay(); // Display(puffer) löschen
display.setTextSize(1); // kleine Schriftgröße (Höhe 8px)
display.setTextColor(WHITE); // helle Schrift, dunkler Grund)
display.setCursor(0, 0); // links oben anfangen
display.print("Hallo");
display.setCursor(10, 20);
display.print("Welt!");
display.display();
delay(2000); // etwas warten
display.clearDisplay(); // dann Display kurz löschen
display.display();
delay(500);
}
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.
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:
/* Test des OLED-Displays für den D1 mini
* mit 64*48 Pixeln
*
* Version 2: u8g2
*
* 2021-06-05 Heiko (unsinnsbasis.de)
*/
// Bibliothek U8G2 für das Display
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
// Datenstruktur für das Display anlegen:
// 64 x 48 Pixel 0,66 Zoll (Shield für D1 mini)
U8G2_SSD1306_64X48_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup() {
// Display initialisieren (liefert immer "true" zurück;
// keine Fehlerbehandlung möglich / nötig)
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
// Zeichensatz mit 8 Pixel Höhe auswählen
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0,8,"Hallo");
u8g2.drawStr(10,28,"Welt!");
u8g2.sendBuffer();
delay(2000); // etwas warten
u8g2.clearBuffer(); // dann Display kurz löschen
u8g2.sendBuffer();
delay(500);
}
Änderungen:
05.06.2021 Ergänzung des Abschnitts für das 0,66-Zoll-Display
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«]
Themenverwandte Beiträge
Mehrstellige 7-Segment-Anzeigen direkt per Schieberegister zum Anzeigen von Daten zu nutzen, bedeute...
S/MIME ist ein Standard zum Signieren und Verschlüsseln von Mails. Es basiert auf der Verwendung von...
Auf Basis des Chips DS3231 werden günstige und sehr genaue RTC-Module (real time clock) zum Einsatz ...
LittleFS ist ein Dateisystem für den Flash-Speicher verschiedener Mikrocontroller. Auf den ESP-Contr...
Hallo Heiko,
Danke für deinen gelungenen übersichtlichen Beitrag, ich bin über Google darauf gestoßen, als ich dasselbe Problem mit °C Zeichen hatte.
Zu dem ständigen Reset mit den D1 mini habe ich folgende Erfahrung (bin aber Anfänger auf dem Gebiet Mikrokontroller):
Die Beispiele von Adafruit benötigen auch die SPI.h Library, was die U8g2lib.h Beispiele nicht machen.
Sobald nun einer der Standard GPIO Pins über PINMode definiert wird, tritt dieses Dauerreset auf.
Bei mir trat dieses Dauerreset bei allen Displayvarianten auf.
Grund:
Bei einem D1 Mini sind also GPIO 12 bis 14 nicht verwertbar (so war es zumindest bei mir). Es bleiben beim D1 Mini also nicht mehr allzuviele Pins nutzbar, denn GPIO 4 und 5 sind für den I2C Bus schon vergeben.
Ich konnte für ein paar Outputs als Temperaturregler 0 und 3 nutzen, und für die Dallas Temperatursonden GPIO2 und das ständige Reset war weg.
In deinem Test waren zwar keine weiteren pinModes definiert, es könnte aber eventuell auf dasselbe Problem hinauslaufen.
Mit einem Arduino Nano hatte ich keinerlei Probleme.
LG
Hubertus
Hallo
Das Grad Symbol wird standardmäßig mit 0xF7 kodiert.
Wenn man aber vorher wie in der Beispielfunktion testdrawchar folgenden Befehl setzt
display.cp437(true); // Use full 256 char ‘Code Page 437’ font
dann wird das Grad Symbol gemäß der Code Page 437 mit 0xF8 kodiert.
Um sicher zu gehen, sollte man diesen Befehl also immer aufnehmen.
LG Peter