Einbinden eines OLED-Displays auf Basis des SSD1306
https://bildung.social/deck/@oerinformatik/
https://oer-informatik.de/esp_ssd1306
tl/dr; (ca. 3 min Lesezeit): Kleine OLED-Displays eignen sich hervorragend, um bei MCU-Projekten Messwerte und den Systemstatus auszugeben. Einige beliebte Display bauen auf dem Chip SSD1306 auf. In diesem Artikel finden sich Codeausschnitte und Verdrahtungspläne, um ein einfaches “Hello World” auf dem Display auszugeben. (Zuletzt geändert am 06.04.2025)
Ein günstiges und häufig genutztes Display für Microcontrollerprojekte sind Breakout-Boards auf Basis von SSD1306-Displays. Diese können häufig per I2C und (seltener) per SPI angesprochen werden.
Generelle Überlegungen zu Installation und Inbetriebnahme von Devices am Microcontrollerboard
Das Vorgehen ist eigentlich bei allen Geräten im Arduino-Kosmos identisch, unabhängig davon, ob es ein Sensor, ein Aktor, ein Display oder etwas völlig anderes ist:
Typenbezeichnung suchen: Zuerst muss die Art und Typ des neuen Geräts herausgefunden werden. Manchmal steht dies auf der Platine, manchmal muss man im Netz nach Fotos/Pinouts suchen (siehe unten).
Bibliothek wählen und installieren: Wird mit der Arduino-IDE, so muss im Menü
Werkzeuge
/Bibliotheken verwalten
eine passende Bibliothek für das Gerät gesucht werden. Häufig sind die Bibliotheken von Adafruit relativ gut.Pins des Geräts recherchieren und Logiklevel prüfen: Zum Anschließen des Geräts benötigen wir die Pinbezeichnungen der jeweils genutzten Platine. Nicht immer ist der Aufdruck auf den Platinen komplett, häufig wirkt ein zusätzliches Pinout (Bildersuche nach “Pinout Gerätebezeichnung”) Wunder. Dabei sollte im Datenblatt auch geprüft werden, ob das Logiklevel der Platine mit dem des Microcontrollers kompatibel ist (Arduino Uno: 5V, ESPs, Arduino Mega: 3,3V).
Freie Pins des Microcontrollers wählen: Bevor wir verdrahten können, sollten wir uns noch entscheiden, welche Schnittstelle wir verwenden wollen (wenn das Gerät mehrere anbietet - z.B. SPI oder I2C). Dazu benötigen wir eine Liste der freien Pins unseres Microcontrollers, der für diese Schnittstelle geeignet ist (für einige ESPs beispielsweise in diesem Artikel). Ggf. hilft auch ein Blick in die Beispielquelltexte mit Hinweisen (s.u.).
Verdrahten und mit Beispielcode erkunden: Dann kann das Gerät verdrahtet werden und über Beispiel-Quelltexte (
Datei
/Beispiele
häufig ganz unten: “Beispiele angepasster Bibliotheken”) erkundet werden.
Zu (1) Typenbezeichnung suchen: SSD1306 - siehe oben
Zu (2) Bibliothek wählen und installieren: Werkzeuge
/ Bibliotheken verwalten
Die Suche nach “SSD1306” liefert u.a. die Bibliothek “Adafruit SSD1306”, die ich in dem Beispiel nutze - andere Bibliotheken können aber ebenso eingesetzt werden.

Die Bibliothek benötigt eine Reihe von weiteren Abhängigkeiten, die direkt mit installiert werden können:

Aufbau und Beispielcode mit der I2C-Schnittstelle
Zu (3) Pins des Geräts recherchieren
Ich schließe das Display per I2C-Schnittstelle an - hierfür sind neben der Spannungsversorgung (3V3
, GND
) noch die Signale Clock (SCL
oder SCK
) und Data (SDA
) nötig. Falls weitere Pins existieren (z.B. CSB
, SDO
) sind diese wahrscheinlich für die Ansteuerung per SPI-Schnittstelle gedacht.
Zu (4) Freie Pins des Microcontrollers wählen
Da wir das Display per I2C-Schnittstelle anschließen wollen, nutzen wir einfach die vorgesehenen Pins:
bei ESP32 ist dies:
GPIO21
fürSDA
undGPIO22
fürSCL
.bei ESP8266 ist dies:
D2
fürSDA
undD1
fürSCL
.bei Arduino Uno ist dies:
A4
fürSDA
undA5
fürSCL
.
Wenn bereits andere I2C Geräte an diesen Leitungen angeschlossen sind, dann kann das Display zusätzlich parallel angeschlossen werden - schließlich handelt es sich hierbei um ein Bus-System, dass genau für den Anschluss mehrere Geräte gedacht ist.
Es wären auch andere Pins möglich (siehe unten), aber so sparen wir uns etwas Konfigurationsarbeit.
Zu (5) Verdrahten und mit Beispielcode erkunden:
Der Aufbau sieht bei mir mit einem ESP32 Dev V1 so aus (vorsicht: ggf. ist die Pinleiste andersherum aufgelötet und alle Pins gespiegelt):

Natürlich müssen bei einem anderen Microcontroller auch andere Pins verwendet werden, da die Bibliothek die I2C-Standardpins der jeweiligen Boards verwendet. (Wie man von den Standardports abweichen kann erkläre ich weiter unten).
Die I2C-Schnittstelle identifiziert Geräte am Bus über eine eindeutige Adresse. Jedes Gerät erkennt anhand dieser Adresse, wer Empfänger der Nachrichten am Bus sein soll. Die Adresse umfasst sieben Bit, es gibt also 2^7 = 128 verschiedene Adressen ( also 0
und 127
dezimal, 0x00
-0x7F
hexadezimal, 0b00000000
- 0b01111111
binär). Damit mehrere Geräte des gleichen Typs angeschlossen werden können, wird häufig nur ein Teil dieser Adresse durch den Hersteller fest vorgegeben, einige Bit können manuell konfiguriert werden.
Das SSD1306-DIsplay wird das letzte Bit angepasst - es können die I2C-Adressen 0x3C
(binär: 0b00111100
) oder 0x3D
(binär: 0b00111101
) konfiguriert werden.
Ein minimales Programm, das das Display über die Adafruit-Bibliothek ansteuert, könnte also so aussehen:
#include <Adafruit_SSD1306.h>
#define SCREEN_ADDRESS 0x3C // Die Displays haben häufig die I2C-Adressen 0x3D oder 0x3C
Adafruit_SSD1306 display(128, 64); // Anzahl der Pixel - üblich sind 128x32 oder 128x64
void setup(){
Serial.begin(115200);
if(!display.begin(-1, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
}
}
void loop(){
display.clearDisplay(); // Lösche alles
display.setCursor(0,0); // Den Cursor auf Anfang (die obere linke Ecke) setzen
display.setTextSize(2); // Etwas größere, leichter lesbare Schrift (4 Zeilen, 10 Zeichen)
display.setTextColor(SSD1306_WHITE); // Helle Schrift (auf dunklem Hintergrund)
display.println("Hello");
display.println("World!");
display.display(); // Anzeigen des zusammengestellten Bilds
delay(1000);
}
Der seriellen Monitor (Strg
+ Shift
+M
) sollte bei 115200 Baud
eingestellt jetzt folgendes ausgeben (beim BME zusätzlich noch die Spalte “Humidity”):
#include <Adafruit_SSD1306.h>
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(128, 64);
unsigned int seitenNr = 0;
unsigned long milliSekundenZeit = 0;
void setup(){
Serial.begin(115200);
if(!display.begin(-1, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
}
milliSekundenZeit = millis();
}
void loop(){
display.clearDisplay(); // Lösche alles
display.setCursor(0,0); // Den Cursor auf Anfang (die obere linke Ecke) setzen
display.setTextSize(2); // Etwas größere, leichter lesbare Schrift (4 Zeilen, 10 Zeichen)
display.setTextColor(SSD1306_WHITE); // Helle Schrift (auf dunklem Hintergrund)
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.cp437(true); //https://de.wikipedia.org/wiki/Codepage_437
display.setTextSize(2); // Textsize 2 => ca. 10 Buchstaben pro Zeile, 4 Zeilen möglich
display.println("ABCDEFGHIJ");
display.setTextSize(1); // Textsize 1 => ca. 21 Buchstaben pro Zeile, 8 Zeilen möglich
display.println("ABCDEFGHIJKLMNOPQRSTU");
display.display(); // Anzeigen des zusammengestellten Bilds
delay(1000);
char textBuffer[21]; // Formatierte Zahlen lassen sich z.B. über Char-Buffer erzeugen
sprintf(textBuffer, "Seite : %3i ", seitenNr);
display.println(textBuffer);
sprintf(textBuffer, "Zeit [s] : %6.1f", (millis()-milliSekundenZeit)/1000.0);
Serial.println(textBuffer);
display.println(textBuffer);
seitenNr++;
display.display();
delay(1000);
display.setTextSize(1);
display.println("Zu langen bricht mitten im Wort um."); // Aufgepasst: zu viel Text fällt hinten rüber...
int16_t xpos = display.getCursorX(); // Text kann exakt positioniert werden, hier wird aktuelle
int16_t ypos = display.getCursorY(); // position ausgelesen.
display.display();
delay(1000);
display.setCursor(xpos+60,ypos-16); //y: Zeile(Size=1)=8, Zeile(Size=2) = 16 / x: Buchstabe(Size=1)=6, Buchstabe(Size=2)=12
display.println("Text");
display.setCursor(xpos,ypos); // Setze Cursor an oben ausgelesen Position am Ende des Texts.
display.display();
delay(1000);
display.print("Umlaute: ae="); // Test wird regulär mit US-Zeichencodierung ausgegeben. Umlaute liegen dort an anderen
display.print(char(0x84)); // Positionen. Siehe https://de.wikipedia.org/wiki/Codepage_437
display.print(", ue="); // Hexadezimalwert entspricht Zeile und Spalte der oben verlinkten Tabelle
display.print(char(0x81)); // Das ü steht in Zeilenindex 8 / Spaltenindex 1 (Zählung startet bei 0)
display.display();
delay(5000);
}
Wenn dieser Programmcode oder die vom Treiber mitgelieferten Beispielsketche (Datei
/ Beispiele
/ ganz unten: ) nicht gehen, dann hat das häufig die folgenden Ursachen:
Could not find a valid BME280 sensor, check wiring, i2c address
: Die I2C-Adresse ist falsch konfiguriert oder wird nicht erkannt. Welche Adresse hat der Sensor überhaupt und ist alles richtig verdrahtet? Hier hilft ein Scan des I2C-Buses über das Beispielprogramm:Datei
/Beispiele
/Wire
/WireScan
. Im Normalfall reicht es, sowohl die0x76
als auch die0x77
als Adresse zu versuchen.Ausgabe der Werte ist unrealistisch, z.B.
0.0 °C | 0.0 hPa | 44330.0 m |
: Es handelt sich um den falschen Treiber für den Sensor - bitte nochmals prüfen, ob es ein BME280 oder BMP280 ist (siehe oben).Die Pins wurden vertauscht - das passiert insbesondere, wenn die Beschriftung auf der Unterseite ist und man sich alles “auf dem Kopf” vorstellen muss.
Individuelle Konfiguration per I2C-Schnittstelle
Sofern nicht die Standardports der I2C-Schnittstelle verwendet werden sollen (weil diese z.B. zwingend anderweitig vergeben werden müssen), können wir die Pins auch individuell konfigurieren.
Intern nutzt der Adafruit_BMx280-Treiber eine Arduino-Bibliothek namens Wire
, um die I2C-Kommunikation abzuwickeln. Mit Hilfe dieser Bibliothek können wir die I2C-Verbindung auch auf andere Pins legen, indem wir zu Beginn unseres Programms die Pin-Bezeichnungen über den Aufruf Wire.begin(MCU_SDA, MCU_SCL);
übergeben. Natürlich müssen die beiden Variablen, die ich hier mal MCU_SDA
und MCU_SCL
genannt habe, vorher deklariert und initialisiert werden. Alternativ können auch direkt die Pinbezeichnungen übergeben werden (Wire.begin(18, 19);
), war aber den Quelltext schlecht wartbar machen würde.
Quellen und weitere Informationen
Der Verdrahtungsplan wurde erstellt mit Fritzing, als SSD1306-Part nutze ich das von Daniel Eichhorn / https://blog.squix.org/ unter diese Link: squix78/esp8266-fritzing-parts. Weitere Informationen dazu, welche Parts ich von Fritzing nutze in diesem Artikel.
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Einbinden eines OLED-Displays auf Basis des SSD1306” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/esp_ssd1306 veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/mcu/arduino-esp. Stand: 06.04.2025.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]