Zum Inhalt springen
Arduino Tutorials Fortgeschrittene · 2 Std. · Aktualisiert am 21. Juli 2026

Bilder auf einem OLED-Display anzeigen

Projekt mit Bild auf OLED-Display

Text auf einem OLED-Display auszugeben, hast du vermutlich schon hinter dir. Aber ein Bild? Eine JPEG-Datei kannst du deinem Arduino jedenfalls nicht einfach überreichen – er hat weder Dateisystem noch Decoder dafür. In diesem Tutorial lernst du, wie du ein Bild in Code umwandelst und es auf einem monochromen OLED-Display anzeigst. Du erfährst, warum dein Bild dabei zum Icon wird, was das Schlüsselwort PROGMEM mit dem knappen Speicher deines Arduinos zu tun hat und wie du das Icon anschließend frei auf dem Display platzierst.

Diese Bauteile brauchst du

Affiliate-Links: Kaufst du über diese Links, erhält Pollux Labs eine kleine Provision – für dich ohne Mehrkosten.

Warum aus deinem Bild ein Icon wird

Das gängigste OLED-Display für den Arduino misst 0.96″ in der Diagonale und hat eine Auflösung von 128×64 Pixeln. Das ist der gesamte Platz, der dir zur Verfügung steht – ein Bild kann also niemals größer sein. Zum Vergleich: Ein Foto von deinem Smartphone hat rund tausendmal so viele Pixel.

Hinzu kommt die zweite Einschränkung: Das Display ist monochrom. Jedes einzelne Pixel kennt genau zwei Zustände – an oder aus. Keine Farben, keine Graustufen, kein Verlauf. Mit dem schwarzen Hintergrund ergibt das immerhin zwei „Farben“: Du zeichnest entweder etwas Weißes auf schwarzem Grund oder färbst den Hintergrund weiß und zeichnest darüber in Schwarz.

Aus beidem folgt: Sprich lieber von einem Icon als von einem Bild. Klare Formen, kräftige Linien, wenig Details – das funktioniert. Ein Porträt oder ein Landschaftsfoto wird auf 128×64 Pixeln in Schwarzweiß dagegen zu Matsch.

Und noch etwas ändert sich: Weil dein Arduino keine Bilddateien lesen kann, wandelst du dein Icon vorab in ein Array aus Bytes um. Jedes Bit darin steht für genau ein Pixel. Dieses Array schreibst du direkt in deinen Sketch – das Bild wird also Teil deines Programms.

Ein passendes Icon aussuchen

Nehmen wir als Beispiel dieses UFO:

Es bringt zwei gute Voraussetzungen mit: Es ist bereits schwarz-weiß, und mit 64×64 Pixeln ist es genauso hoch wie das Display. Solche Icons findest du in großer Zahl bei Flaticon – achte dort auf die Lizenzbedingungen und lade die PNG-Variante herunter.

Ein Tipp zur Auswahl: Suche nach Icons im „Glyph“- oder „Solid“-Stil. Filigrane Umrisslinien verschwinden bei der Umwandlung gern ganz oder brechen auf – gefüllte Flächen überstehen den Weg aufs Display deutlich besser.

Das Icon in Code umwandeln

Für die Umwandlung nutzt du am besten ein Online-Tool. Ein bewährtes ist image2cpp – es läuft komplett in deinem Browser, dein Bild wird also nirgendwohin hochgeladen.

Wähle im ersten Bereich der Seite deine Bilddatei aus. Im zweiten Bereich nimmst du die eigentlichen Einstellungen vor. Angenommen, du möchtest ein weißes UFO auf schwarzem Hintergrund, zentriert auf dem Display:

  • Stelle die Canvas size auf die Größe deines Displays, also 128 x 64.
  • Wähle Black als Background color.
  • Setze einen Haken bei Invert image colors. Dein Icon ist schwarz auf weiß – auf dem Display soll es aber weiß leuchten.
  • Setze einen weiteren Haken bei Centerhorizontally. Vertikal füllt das 64 Pixel hohe Icon das Display ohnehin schon aus.

___STEADY_PAYWALL___

Einstellungen für das Icon im Tool image2cpp
Schritt 2: Canvas, Hintergrund, Invertierung und Zentrierung einstellen

Direkt darunter zeigt dir image2cpp eine Vorschau – und zwar genau so, wie das Icon später auf deinem Display aussehen wird. Nutze sie ausgiebig: Am Brightness threshold entscheidet sich, ab welcher Helligkeit ein Pixel als „an“ gilt. Verschiebst du den Regler, werden die Linien deines Icons kräftiger oder feiner. Bei Icons mit dünnen Strichen ist das oft der Unterschied zwischen erkennbar und unbrauchbar.

Vorschau des Icons in image2cpp
Die Vorschau zeigt dir das Ergebnis, bevor du überhaupt kompilierst

Fast geschafft. Im letzten Bereich wählst du unter Code output format die Einstellung Arduino code. Unter Identifier gibst du deinem Icon einen Namen – hier alien –, mit dem du es später im Sketch aufrufst. Klicke dann auf Generate code und kopiere dir den Code aus dem Feld darunter.

Schau dir den Namen im generierten Code genau an: Je nach Version stellt image2cpp deinem Identifier noch ein epd_bitmap_ voran. Aus alien wird dann epd_bitmap_alien. Nimm im Sketch später exakt den Namen, der wirklich im Code steht – oder kürze ihn direkt auf alien.

Was du da eigentlich kopiert hast

Der Code, der dabei herauskommt, sieht ungefähr so aus:

const unsigned char alien [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // ... hier folgen die übrigen Zeilen ...
};

Jeder dieser Werte ist ein Byte, also acht Bit – und jedes Bit steht für ein Pixel. Der Wert 0x00 bedeutet: acht Pixel aus. 0xff bedeutet: acht Pixel an. Die Bytes werden dabei zeilenweise von links oben nach rechts unten gelesen.

Daraus ergibt sich auch die Größe: 128 x 64 Pixel sind 8.192 Pixel, geteilt durch 8 Pixel pro Byte macht 1.024 Byte. Und genau hier wird es für einen Arduino UNO interessant, denn der hat insgesamt nur 2.048 Byte Arbeitsspeicher (SRAM). Ein einziges bildschirmfüllendes Icon würde also die Hälfte davon belegen – für Variablen, Display-Puffer und Bibliotheken bliebe kaum noch etwas übrig.

Die Lösung steht schon im generierten Code: das Schlüsselwort PROGMEM. Es weist den Compiler an, das Array nicht in den knappen Arbeitsspeicher zu legen, sondern im deutlich größeren Programmspeicher (Flash) zu belassen – beim UNO sind das 32.768 Byte. Dort liegt es ohnehin schon, denn dein Sketch wird ja komplett in den Flash geschrieben. PROGMEM verhindert also lediglich, dass beim Start eine überflüssige Kopie im RAM angelegt wird.

Lösche dieses Schlüsselwort also auf keinen Fall aus dem kopierten Code. Auf einem ESP32 oder ESP8266 mit reichlich Speicher würdest du den Unterschied kaum merken – auf einem UNO ist er der Grund, warum dein Sketch am Ende läuft oder sich seltsam verhält.

Der komplette Sketch

Jetzt bringst du das Icon aufs Display. Den kopierten Code setzt du dafür oben in deinen Sketch – noch vor die Funktion setup(). Falls du dein OLED-Display noch nie angeschlossen hast, findest du die Verkabelung und die nötigen Bibliotheken im Tutorial Ein OLED-Display am Arduino anschließen.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH   128
#define SCREEN_HEIGHT  64
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C   // ggf. 0x3D

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Hier folgt der Code aus image2cpp:
const unsigned char alien [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // ... hier folgen die übrigen Zeilen ...
};

void setup() {
  Serial.begin(9600);

  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("Display nicht gefunden"));
    while (true);
  }

  display.clearDisplay();
  display.drawBitmap(0, 0, alien, 128, 64, SSD1306_WHITE);
  display.display();
}

void loop() {
}

Weil sich das Icon nicht verändert, genügt es, es einmal in setup() zu zeichnen – der loop() bleibt leer. Wichtig sind die beiden letzten Zeilen davor: drawBitmap() schreibt das Icon zunächst nur in den Puffer im Speicher. Erst display.display() überträgt diesen Puffer tatsächlich aufs Display. Vergisst du diese Zeile, bleibt der Bildschirm schwarz – ein Klassiker.

Findet der Sketch das Display nicht, hältst du das Programm mit while (true) { delay(100); } an. Das delay() darin sieht überflüssig aus, ist es aber nicht: Auf einem ESP8266 würde eine leere Endlosschleife den Watchdog auslösen und das Board in einen Neustart-Kreislauf schicken. Mit dem delay() hält der Sketch auf allen Boards sauber an.

Auf dem ESP8266 und ESP32

Der Sketch oben läuft unverändert auch auf einem ESP8266 oder ESP32 – du musst weder am Bitmap noch am Code etwas anpassen. Unterschiedlich ist nur, wo du das Display ansteckst, denn jedes Board hat eigene Standard-Pins für I2C:

  • Arduino UNO: SDA an A4, SCL an A5
  • ESP8266 (NodeMCU): SDA an D2 (GPIO4), SCL an D1 (GPIO5)
  • ESP32: SDA an GPIO21, SCL an GPIO22

Diese Pins nutzt die Wire-Bibliothek von sich aus – ein Wire.begin() brauchst du also nicht. Möchtest du andere Pins verwenden, legst du sie mit Wire.begin(SDA_PIN, SCL_PIN); fest, und zwar vor dem Aufruf von display.begin(...).

Beim Speicher kannst du auf den ESPs entspannter sein: Ein ESP32 hat rund 320 KB RAM statt der 2 KB eines UNO – die 1.024 Byte deines Icons fallen dort kaum ins Gewicht. Lass PROGMEM trotzdem stehen. Es schadet nicht, hält deinen Code portabel, und drawBitmap() erwartet die Daten ohnehin im Programmspeicher.

drawBitmap() im Detail

Ein Blick auf die sechs Parameter lohnt sich, denn damit hast du dein Icon vollständig unter Kontrolle:

display.drawBitmap(x, y, bitmap, breite, hoehe, farbe);
  • x und y legen fest, wo die linke obere Ecke des Icons landet. Der Nullpunkt (0, 0) liegt links oben auf dem Display, y zählt nach unten.
  • bitmap ist der Name deines Arrays – also genau der Identifier, den du in image2cpp vergeben hast.
  • breite und hoehe müssen exakt zur Canvas size passen, die du in image2cpp eingestellt hast. Stimmen sie nicht überein, siehst du ein verschobenes Muster statt deines Icons.
  • farbe ist bei einem monochromen Display SSD1306_WHITE für leuchtende Pixel oder SSD1306_BLACK, um vorhandene Pixel wieder zu löschen. Ältere Tutorials nutzen hier noch das schlichte WHITE – das funktioniert dank der Kompatibilitäts-Defines meist noch, aber die aktuelle Schreibweise ist eindeutiger.

Ein Tipp zum Speicher: Du musst das Icon gar nicht auf einer 128×64 großen Canvas zentrieren. Konvertiere es stattdessen mit einer Canvas size von 64 x 64 – das Array ist dann nur noch 512 Byte groß. Die Zentrierung übernimmst du beim Zeichnen selbst:

display.drawBitmap(32, 0, alien, 64, 64, SSD1306_WHITE);

Die 32 ergeben sich aus (128 − 64) ÷ 2. Das Ergebnis sieht identisch aus, spart aber die Hälfte des Speichers – und du kannst das Icon jederzeit woanders hinsetzen, ohne es neu zu konvertieren.

Das Icon in Bewegung

Sobald du x und y zur Laufzeit veränderst, wird aus dem Standbild eine Animation. Lass das UFO doch einmal über den Bildschirm fliegen – ersetze dafür den loop() im Sketch oben:

void loop() {
  for (int x = -64; x <= 128; x++) {
    display.clearDisplay();
    display.drawBitmap(x, 0, alien, 64, 64, SSD1306_WHITE);
    display.display();
    delay(20);
  }
}

Dieser Loop setzt voraus, dass du dein Icon mit einer Canvas size von 64 x 64 konvertiert hast. Bei jedem Durchlauf löschst du zuerst den Puffer, zeichnest das Icon eine Position weiter rechts und überträgst das Ergebnis. Der Startwert −64 sorgt dafür, dass das UFO sauber von außen ins Bild fliegt: Negative Koordinaten sind völlig in Ordnung, was außerhalb des Displays liegt, wird schlicht nicht gezeichnet.

Das clearDisplay() ist hier entscheidend. Lässt du es weg, bleiben alle vorherigen Positionen stehen und du malst dir eine weiße Fläche. Und über das delay() steuerst du das Tempo.

Wenn es nicht funktioniert

  • Das Display bleibt komplett schwarz. Fehlt am Ende ein display.display()? Ohne diesen Aufruf bleibt alles im Puffer. Erscheint auch sonst nie etwas, liegt es meist an der I2C-Adresse – probiere 0x3D statt 0x3C.
  • Statt des Icons erscheint ein wirres Streifenmuster. Dann passen Breite und Höhe in drawBitmap() nicht zur Canvas size aus image2cpp. Beide Werte müssen exakt übereinstimmen.
  • Das Icon erscheint als Negativ – weißer Hintergrund, schwarzes Motiv. Dann hast du den Haken bei Invert image colors vergessen (oder einen zu viel gesetzt).
  • Der Compiler meldet, der Bezeichner sei nicht deklariert. Sehr wahrscheinlich heißt dein Array im generierten Code epd_bitmap_alien und nicht alien. Vergleiche den Namen im Array mit dem in drawBitmap().
  • Der Sketch kompiliert, verhält sich aber wirr – etwa Neustarts oder Zeichensalat. Achte auf die Warnung der Arduino-IDE zum knappen Arbeitsspeicher und prüfe, ob hinter deinem Array wirklich noch PROGMEM steht.
  • Vom Icon sind nur Fragmente zu sehen. Spiele mit dem Brightness threshold in image2cpp – oder such dir ein Icon mit kräftigeren, gefüllten Formen.

Wie geht es weiter?

Du weißt jetzt, wie du ein Bild in ein Byte-Array umwandelst, es speicherschonend im Flash ablegst und frei auf deinem Display platzierst. Damit kannst du deine Projekte weit über reine Textausgaben hinaus gestalten: ein Wetter-Icon zur Vorhersage, ein Batteriesymbol, ein kleines Logo beim Start.

Und wenn dir 128×64 monochrome Pixel irgendwann zu wenig werden, wirf einen Blick auf das TFT-Display – dort erwarten dich Farben und deutlich mehr Auflösung.