0,91 Zoll OLED 128x32 mit Arduino ansteuern: Statuszeile, Laufschrift & Balkenanzeige mit U8g2

Nicht jedes Projekt braucht ein großes Display. Ein Sensorknoten, der Temperatur und WLAN-Status zeigt, kommt mit zwei Zeilen aus – und genau dafür ist das 0,91 Zoll OLED mit 128x32 Pixeln gebaut. Es ist 12 mm hoch, kostet wenig, braucht zwei Kabel und halbiert den RAM-Bedarf gegenüber dem großen 128x64-Bruder. Dieser Artikel zeigt dir, wie du es anschließt, welche Bibliothek auf welchem Board sinnvoll ist – und wie du auf 32 Pixeln Höhe trotzdem Laufschrift, Balken und Messverläufe unterbringst.

128x32 oder 128x64 – was passt zu deinem Projekt?

Beide Module nutzen denselben SSD1306-Controller und dieselben Bibliotheken. Der Unterschied liegt in der Höhe – und der hat mehr Konsequenzen als nur die Optik:

Merkmal 0,91 Zoll / 128x32 0,96 Zoll / 128x64
Aktive Fläche ca. 22 × 5,5 mm ca. 22 × 11 mm
Modulhöhe ca. 12 mm ca. 27 mm
Framebuffer im RAM 512 Byte 1024 Byte
Textzeilen (Standardfont) 4 Zeilen à 21 Zeichen 8 Zeilen à 21 Zeichen
Große Zahl + Beschriftung gerade noch bequem
Stromaufnahme (typisch) ca. 6 mA ca. 12 mA

💡 Die 512 Byte sind auf einem Arduino Nano ein echtes Argument. Der ATmega328P hat insgesamt 2048 Byte RAM. Ein 128x64-Framebuffer belegt davon die Hälfte – zusammen mit Wire-Puffer und Stack bleibt kaum noch Platz für eigene Variablen. Mit dem 128x32-Modul hast du 512 Byte mehr Luft, und genau das entscheidet oft darüber, ob der Sketch stabil läuft oder unerklärlich abstürzt. Mehr zum Thema im Artikel Arduino Speicher voll? RAM sparen.

Das brauchst du

0,91 Zoll OLED Modul 128x32 (SSD1306) – das Display, I2C mit vier Pins
Arduino Nano V3 oder ESP32 DEV KIT – beide funktionieren, mit Unterschieden
BMP280 Sensor – Temperatur und Luftdruck, teilt sich den I2C-Bus
DHT22 / AM2302 – Luftfeuchte für die Balkenanzeige
Rotary Encoder KY-040 – zum Blättern zwischen den Seiten
0,96 Zoll OLED 128x64 – optional als zweites Display am selben Bus
600er Widerstands-Sortiment – falls externe I2C-Pullups nötig werden

1Anschließen und Adresse finden

Vier Pins, mehr nicht. Die Pullups für SDA und SCL sitzen bereits auf dem Modul:

OLED Arduino Nano / UNO ESP32 ESP8266 (D1 Mini)
VCC 5 V 3V3 3V3
GND GND GND GND
SDA A4 GPIO21 D2 (GPIO4)
SCL A5 GPIO22 D1 (GPIO5)

Die I2C-Adresse ist bei diesen Modulen fast immer 0x3C. Prüfen kostet aber nur eine Minute:

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin();
  delay(200);

  Serial.println(F("I2C-Scan laeuft..."));
  uint8_t gefunden = 0;

  for (uint8_t addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print(F("Geraet bei 0x"));
      if (addr < 16) Serial.print('0');
      Serial.print(addr, HEX);

      if (addr == 0x3C || addr == 0x3D) Serial.print(F("  <- SSD1306 OLED"));
      if (addr == 0x76 || addr == 0x77) Serial.print(F("  <- BMP280/BME280"));
      if (addr == 0x68)                 Serial.print(F("  <- DS3231 oder MPU6050"));
      Serial.println();
      gefunden++;
    }
  }

  if (gefunden == 0) {
    Serial.println(F("Nichts gefunden - SDA/SCL vertauscht oder VCC fehlt?"));
  } else {
    Serial.print(gefunden);
    Serial.println(F(" Geraet(e) gefunden."));
  }
}

void loop() { }

ℹ️ 5 V am 128x32-Modul sind in Ordnung. Die üblichen Module haben einen Spannungsregler und Pegelanpassung an Bord und vertragen 3,3 bis 5 V. Am ESP32 solltest du trotzdem 3,3 V nehmen – nicht wegen des Displays, sondern weil die I2C-Pullups sonst 5 V auf die ESP32-Pins ziehen. Ausführlich erklärt im Artikel I2C-Bus mit Arduino verstehen.

2Adafruit_SSD1306 oder U8g2? Die ehrliche Antwort

Beide Bibliotheken können dasselbe Display ansteuern, verfolgen aber gegensätzliche Ansätze:

Kriterium Adafruit_SSD1306 + GFX U8g2
RAM-Bedarf (128x32) 512 Byte (Full Buffer) 512 / 256 / 128 Byte wählbar
Schriftarten eine, skalierbar in Stufen über 200 mitgeliefert
Umlaute nur mit Zusatzfont eingebaut (_te-Fonts)
Einstieg sehr einfach Konstruktor wirkt kryptisch
Flash-Bedarf klein größer, je nach Fonts

💡 Faustregel: Auf einem ESP32 nimm U8g2 – RAM ist reichlich da, und die Schriftauswahl ist auf 32 Pixeln Höhe Gold wert. Auf einem Nano mit knappem Flash nimm Adafruit – oder U8g2 im Page-Buffer-Modus (_1_ im Konstruktor), der nur 128 Byte belegt.

Derselbe Bildschirminhalt, einmal in jeder Bibliothek:

// ============ Variante A: Adafruit ============
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 oled(128, 32, &Wire, -1);

void setup() {
  Wire.begin();
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (true) { }          // Display nicht gefunden
  }
  oled.clearDisplay();

  oled.setTextColor(SSD1306_WHITE);
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.print(F("Wohnzimmer"));

  oled.setTextSize(2);
  oled.setCursor(0, 12);
  oled.print(F("21.4 C"));

  oled.drawFastHLine(0, 30, 128, SSD1306_WHITE);
  oled.display();             // erst jetzt wird gesendet!
}

void loop() { }
// ============ Variante B: U8g2 (Full Buffer) ============
#include <Wire.h>
#include <U8g2lib.h>

// Namensschema: U8G2_<Controller>_<Aufloesung>_<Puffer>_<Bus>
//   _F_ = Full Buffer (512 Byte, alles auf einmal)
//   _1_ = Page Buffer (128 Byte, in 4 Durchlaeufen)
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

void setup() {
  u8g2.begin();
  u8g2.enableUTF8Print();          // Umlaute direkt schreibbar

  u8g2.clearBuffer();

  u8g2.setFont(u8g2_font_6x10_te);
  u8g2.drawStr(0, 8, "Wohnzimmer");

  u8g2.setFont(u8g2_font_logisoso16_tn);
  u8g2.drawStr(0, 28, "21.4");

  u8g2.setFont(u8g2_font_6x10_te);
  u8g2.drawUTF8(62, 28, "°C");

  u8g2.drawFrame(90, 14, 36, 14);

  u8g2.sendBuffer();
}

void loop() { }

⚠️ Der Y-Wert bedeutet in beiden Bibliotheken etwas anderes. Bei Adafruit ist setCursor(x, y) die obere linke Ecke des Textes. Bei U8g2 ist drawStr(x, y, ...) die Grundlinie – also die Unterkante der Großbuchstaben. Wer U8g2-Code mit y = 0 schreibt, sieht gar nichts, weil der Text komplett oberhalb des Bildschirms liegt. Auf 32 Pixeln fällt das besonders auf.

⚠️ Der falsche U8g2-Konstruktor ist der häufigste Fehler. Für dieses Display brauchst du 128X32_UNIVISION. Nimmst du versehentlich 128X64_NONAME, bleibt der Bildschirm schwarz oder zeigt gestauchten Unsinn – obwohl derselbe Controller drinsteckt.

3Laufschrift, die den Sketch nicht blockiert

Auf 21 Zeichen Breite passen längere Meldungen nicht. Eine Laufschrift löst das – aber nur, wenn sie ohne delay() auskommt, damit dein Sensor nebenher weiterläuft:

#include <Wire.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

const char* TICKER_TEXT =
  "Sensorknoten Garten  -  WLAN verbunden  -  "
  "Batterie 78 Prozent  -  letzte Messung vor 4 Minuten  -  ";

int16_t  tickerX      = 128;
uint16_t tickerBreite = 0;
uint32_t letzterSchritt = 0;

const uint8_t  SCHRITT_PX = 2;      // Pixel pro Schritt
const uint16_t TEMPO_MS   = 40;     // Zeit zwischen den Schritten

void tickerInit() {
  u8g2.setFont(u8g2_font_6x10_te);
  tickerBreite = u8g2.getUTF8Width(TICKER_TEXT);
  tickerX = 128;
}

// Muss regelmaessig aufgerufen werden - blockiert nie
void tickerSchritt() {
  if (millis() - letzterSchritt < TEMPO_MS) return;
  letzterSchritt = millis();

  tickerX -= SCHRITT_PX;
  if (tickerX < -(int16_t)tickerBreite) tickerX = 128;
}

float aktuelleTemperatur = 21.4;
uint8_t akkuProzent      = 78;

void zeichne() {
  u8g2.clearBuffer();

  // Obere Zeile: feste Statusanzeige
  u8g2.setFont(u8g2_font_6x10_te);
  char kopf[24];
  snprintf(kopf, sizeof(kopf), "%.1f°C  Akku %u%%",
           aktuelleTemperatur, akkuProzent);
  u8g2.drawUTF8(0, 9, kopf);

  u8g2.drawHLine(0, 13, 128);

  // Untere Zeile: Laufschrift
  u8g2.drawUTF8(tickerX, 27, TICKER_TEXT);

  u8g2.sendBuffer();
}

void setup() {
  u8g2.begin();
  u8g2.enableUTF8Print();
  tickerInit();
}

void loop() {
  tickerSchritt();
  zeichne();

  // Hier laeuft dein restlicher Code weiter -
  // kein delay() blockiert die Laufschrift.
}

ℹ️ Warum 2 Pixel pro Schritt und nicht 1? Bei einem Pixel pro Schritt und 40 ms Takt braucht ein 300 Pixel breiter Text zwölf Sekunden für einen Durchlauf – das fühlt sich zäh an. Zwei Pixel wirken flüssig, weil das OLED keine Nachleuchtzeit hat. Bei drei oder mehr Pixeln beginnt der Text zu ruckeln. Das Prinzip hinter millis() statt delay() erklärt der Artikel Arduino millis() statt delay().

4Balken, Icons und ein Messverlauf auf 32 Pixeln

Zahlen allein sind langweilig. Mit Balken und einer kleinen Verlaufskurve wird aus dem schmalen Streifen ein echtes Mini-Dashboard:

#include <Wire.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// --- Horizontaler Balken mit Rahmen und Prozentwert ---
void balken(uint8_t x, uint8_t y, uint8_t breite, uint8_t hoehe,
            uint8_t prozent) {
  if (prozent > 100) prozent = 100;

  u8g2.drawFrame(x, y, breite, hoehe);

  uint8_t innen = breite - 4;
  uint8_t fuell = (uint16_t) innen * prozent / 100;
  if (fuell > 0) u8g2.drawBox(x + 2, y + 2, fuell, hoehe - 4);
}

// --- Batteriesymbol, 12 x 7 Pixel ---
void akkuIcon(uint8_t x, uint8_t y, uint8_t prozent) {
  u8g2.drawFrame(x, y, 11, 7);
  u8g2.drawBox(x + 11, y + 2, 2, 3);          // Pluspol

  uint8_t fuell = (uint16_t) 9 * prozent / 100;
  if (fuell > 0) u8g2.drawBox(x + 1, y + 1, fuell, 5);
}

// --- WLAN-Balken nach RSSI ---
void wlanIcon(uint8_t x, uint8_t y, int16_t rssi) {
  uint8_t stufen = 0;
  if      (rssi > -55) stufen = 4;
  else if (rssi > -65) stufen = 3;
  else if (rssi > -75) stufen = 2;
  else if (rssi > -85) stufen = 1;

  for (uint8_t i = 0; i < 4; i++) {
    uint8_t h = 2 + i * 2;
    if (i < stufen) u8g2.drawBox(x + i * 3, y + 8 - h, 2, h);
    else            u8g2.drawPixel(x + i * 3, y + 7);
  }
}

// --- Sparkline: Verlauf der letzten 64 Messwerte ---
#define VERLAUF_N 64
uint8_t verlauf[VERLAUF_N];
uint8_t verlaufIdx = 0;
bool    verlaufVoll = false;

void verlaufHinzu(uint8_t wert) {
  verlauf[verlaufIdx] = wert;
  verlaufIdx++;
  if (verlaufIdx >= VERLAUF_N) { verlaufIdx = 0; verlaufVoll = true; }
}

void verlaufZeichnen(uint8_t x, uint8_t y, uint8_t hoehe) {
  uint8_t n = verlaufVoll ? VERLAUF_N : verlaufIdx;
  if (n < 2) return;

  // Min und Max fuer die automatische Skalierung
  uint8_t mn = 255, mx = 0;
  for (uint8_t i = 0; i < n; i++) {
    uint8_t w = verlauf[i];
    if (w < mn) mn = w;
    if (w > mx) mx = w;
  }
  if (mx == mn) mx = mn + 1;         // Division durch null vermeiden

  uint8_t vorherY = 0;
  for (uint8_t i = 0; i < n; i++) {
    uint8_t idx = verlaufVoll ? (verlaufIdx + i) % VERLAUF_N : i;
    uint8_t w   = verlauf[idx];

    uint8_t py = y + hoehe - 1
               - (uint16_t)(w - mn) * (hoehe - 1) / (mx - mn);

    if (i > 0) u8g2.drawLine(x + i - 1, vorherY, x + i, py);
    vorherY = py;
  }
}

float   temperatur = 21.4;
uint8_t feuchte    = 62;
uint8_t akku       = 78;
int16_t rssi       = -61;

void setup() {
  u8g2.begin();
  u8g2.enableUTF8Print();

  // Testdaten fuer den Verlauf
  for (uint8_t i = 0; i < VERLAUF_N; i++) {
    verlaufHinzu(50 + (uint8_t)(20.0 * sin(i * 0.2)));
  }
}

void loop() {
  u8g2.clearBuffer();

  // Zeile 1: Messwert gross plus Icons rechts
  u8g2.setFont(u8g2_font_profont15_tf);
  char txt[12];
  snprintf(txt, sizeof(txt), "%.1fC", temperatur);
  u8g2.drawStr(0, 12, txt);

  akkuIcon(102, 3, akku);
  wlanIcon(86, 3, rssi);

  // Zeile 2: Feuchtebalken mit Beschriftung
  u8g2.setFont(u8g2_font_5x7_tf);
  u8g2.drawStr(0, 24, "rH");
  balken(16, 17, 60, 8, feuchte);

  snprintf(txt, sizeof(txt), "%u%%", feuchte);
  u8g2.drawStr(79, 24, txt);

  // Zeile 3: Verlauf ganz unten
  verlaufZeichnen(0, 26, 6);

  u8g2.sendBuffer();
  delay(200);
}

💡 Automatische Skalierung macht die Sparkline erst nützlich. Bei fester Skala von 0 bis 100 ist eine Temperaturschwankung von 21,2 auf 21,6 Grad eine gerade Linie. Mit Min-Max-Skalierung nutzt die Kurve immer die volle Höhe – auf sechs Pixeln ist das der Unterschied zwischen „sieht man was" und „sieht man nichts".

5Seiten blättern mit dem Rotary Encoder

Vier Zeilen sind schnell voll. Statt zu quetschen, teilst du die Anzeige auf Seiten auf und blätterst mit einem KY-040 Drehgeber durch:

#include <Wire.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

#define ENC_CLK  2
#define ENC_DT   3
#define ENC_SW   4

volatile int8_t encDelta = 0;
volatile uint8_t letzterZustand = 0;

void encoderIsr() {
  uint8_t a = digitalRead(ENC_CLK);
  uint8_t b = digitalRead(ENC_DT);
  uint8_t zustand = (a << 1) | b;

  if (zustand == letzterZustand) return;

  // Gray-Code auswerten: 00 -> 01 -> 11 -> 10 -> 00
  if ((letzterZustand == 0b00 && zustand == 0b01) ||
      (letzterZustand == 0b01 && zustand == 0b11) ||
      (letzterZustand == 0b11 && zustand == 0b10) ||
      (letzterZustand == 0b10 && zustand == 0b00)) {
    encDelta++;
  } else {
    encDelta--;
  }
  letzterZustand = zustand;
}

const uint8_t SEITEN_ANZAHL = 4;
uint8_t seite = 0;

float   temperatur = 21.4;
float   druck      = 1013.2;
uint8_t feuchte    = 62;
uint32_t laufzeitS = 0;

void seiteMesswerte() {
  u8g2.setFont(u8g2_font_profont15_tf);
  char t[16];
  snprintf(t, sizeof(t), "%.1f C", temperatur);
  u8g2.drawStr(0, 14, t);

  u8g2.setFont(u8g2_font_5x7_tf);
  snprintf(t, sizeof(t), "%u %% rH", feuchte);
  u8g2.drawStr(0, 28, t);
}

void seiteDruck() {
  u8g2.setFont(u8g2_font_profont15_tf);
  char t[16];
  snprintf(t, sizeof(t), "%.1f", druck);
  u8g2.drawStr(0, 14, t);

  u8g2.setFont(u8g2_font_5x7_tf);
  u8g2.drawStr(0, 28, "hPa Luftdruck");
}

void seiteLaufzeit() {
  u8g2.setFont(u8g2_font_5x7_tf);
  u8g2.drawStr(0, 10, "Laufzeit");

  char t[20];
  uint32_t h = laufzeitS / 3600;
  uint8_t  m = (laufzeitS % 3600) / 60;
  uint8_t  s = laufzeitS % 60;
  snprintf(t, sizeof(t), "%lu:%02u:%02u", h, m, s);

  u8g2.setFont(u8g2_font_profont15_tf);
  u8g2.drawStr(0, 28, t);
}

void seiteInfo() {
  u8g2.setFont(u8g2_font_5x7_tf);
  u8g2.drawStr(0, 10, "Sensorknoten v1.2");
  u8g2.drawStr(0, 20, "MAKEROO Testaufbau");
  u8g2.drawStr(0, 30, "Drehen = blaettern");
}

void seitenIndikator() {
  // Punkte am rechten Rand zeigen, wo man ist
  for (uint8_t i = 0; i < SEITEN_ANZAHL; i++) {
    uint8_t y = 4 + i * 7;
    if (i == seite) u8g2.drawBox(124, y, 3, 3);
    else            u8g2.drawPixel(125, y + 1);
  }
}

void setup() {
  pinMode(ENC_CLK, INPUT_PULLUP);
  pinMode(ENC_DT,  INPUT_PULLUP);
  pinMode(ENC_SW,  INPUT_PULLUP);

  letzterZustand = (digitalRead(ENC_CLK) << 1) | digitalRead(ENC_DT);

  attachInterrupt(digitalPinToInterrupt(ENC_CLK), encoderIsr, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENC_DT),  encoderIsr, CHANGE);

  u8g2.begin();
  u8g2.enableUTF8Print();
}

void loop() {
  laufzeitS = millis() / 1000;

  // Encoder-Delta atomar auslesen
  noInterrupts();
  int8_t d = encDelta;
  encDelta = 0;
  interrupts();

  if (d != 0) {
    // Vier Flanken pro Rastung - deshalb durch 4 teilen
    int8_t schritte = d / 4;
    if (schritte != 0) {
      int8_t neu = (int8_t) seite + schritte;
      while (neu < 0) neu += SEITEN_ANZAHL;
      seite = neu % SEITEN_ANZAHL;
    }
  }

  if (digitalRead(ENC_SW) == LOW) {
    seite = 0;                    // Tastendruck: zurueck zur Startseite
    delay(200);
  }

  u8g2.clearBuffer();
  switch (seite) {
    case 0: seiteMesswerte(); break;
    case 1: seiteDruck();     break;
    case 2: seiteLaufzeit();  break;
    case 3: seiteInfo();      break;
  }
  seitenIndikator();
  u8g2.sendBuffer();

  delay(30);
}

ℹ️ Zwei Displays am selben I2C-Bus gehen – mit einem Lötpunkt. Die meisten SSD1306-Module haben auf der Rückseite eine Brücke, mit der du die Adresse von 0x3C auf 0x3D umstellst. So kannst du ein 128x32 und ein 128x64-Display parallel betreiben. In U8g2 setzt du die Adresse mit u8g2.setI2CAddress(0x3D * 2) – beachte die Verdopplung, U8g2 erwartet die 8-Bit-Form.

Troubleshooting

⚠️ Der Bildschirm bleibt komplett schwarz: In neun von zehn Fällen fehlt display() beziehungsweise sendBuffer(). Beide Bibliotheken zeichnen zuerst in den RAM-Puffer und senden erst auf Befehl. Ohne diesen Aufruf passiert am Display nichts.

⚠️ Das Bild ist gestaucht oder doppelt: Falscher Konstruktor. Bei U8g2 muss 128X32_UNIVISION stehen, bei Adafruit Adafruit_SSD1306(128, 32, ...). Mit den 128x64-Varianten sieht das Ergebnis genau so aus.

⚠️ Der Sketch hängt beim Start: Das Display antwortet nicht auf I2C. Prüfe mit dem Scanner aus Schritt 1, ob 0x3C erscheint. Meist sind SDA und SCL vertauscht – die Beschriftung auf günstigen Modulen stimmt nicht immer.

⚠️ Auf dem Nano stürzt der Sketch sporadisch ab: RAM-Mangel. Ein Full-Buffer belegt 512 Byte, dazu kommen Wire-Puffer und deine Variablen. Wechsle auf den U8g2-Page-Buffer (_1_ statt _F_ im Konstruktor) – das spart 384 Byte, kostet aber vier statt einem Zeichendurchlauf.

⚠️ Umlaute erscheinen als kryptische Zeichen: Bei U8g2 brauchst du einen Font mit der Endung _te oder _tf plus enableUTF8Print() und drawUTF8() statt drawStr(). Bei Adafruit gibt es keine saubere Lösung – dort ist es meist einfacher, die Beschriftung umlautfrei zu formulieren.

⚠️ Statische Anzeigen brennen sich ein: OLEDs altern dort schneller, wo dauerhaft helle Pixel stehen. Nach einigen Monaten Dauerbetrieb bleibt ein Schatten. Gegenmittel: Anzeige nach einiger Zeit abschalten (u8g2.setPowerSave(1)) oder den Inhalt alle paar Minuten um ein bis zwei Pixel verschieben.

⚠️ Das Display flackert bei jedem Update: Du löschst und sendest zu oft. 5 bis 10 Bilder pro Sekunde reichen völlig; alles darüber belastet nur den I2C-Bus. Setz ein millis()-Intervall vor den Zeichenblock.

Praxistipps

  • Font zuerst wählen, dann Layout planen. Auf 32 Pixeln entscheidet die Schrifthöhe alles. u8g2_font_5x7_tf gibt dir vier Zeilen, u8g2_font_profont15_tf zwei, u8g2_font_logisoso16_tn eine große Zahl plus eine kleine Zeile.
  • Nur Ziffern-Fonts sparen viel Flash. Fonts mit der Endung _tn enthalten ausschließlich Zahlen und Satzzeichen – oft ein Zehntel der Größe eines vollen Zeichensatzes. Für eine Messwertanzeige reicht das.
  • I2C-Takt hochsetzen. Wire.setClock(400000) vervierfacht die Übertragungsrate. Ein volles 128x32-Bild braucht dann statt 5 ms nur noch gut 1 ms – spürbar bei Animationen.
  • Kabel kurz halten. I2C ist bei 5 V und 400 kHz nicht für Meterlangen gemacht. Ab etwa 50 cm brauchst du stärkere Pullups – 2,2 kΩ aus dem Widerstands-Sortiment statt der 10 kΩ auf dem Modul.
  • Im Batteriebetrieb abschalten. u8g2.setPowerSave(1) senkt den Verbrauch auf unter 10 µA. Bei einem Gerät, das nur auf Tastendruck etwas anzeigen soll, ist das der größte Hebel überhaupt.
  • Sensoren teilen sich den Bus problemlos. BMP280 auf 0x76, Display auf 0x3C, DS3231 auf 0x68 – alle drei an denselben zwei Leitungen.
  • Puffer statt String. snprintf() in ein festes char-Array erzeugt keine Speicherfragmentierung. Auf dem Nano ist das der Unterschied zwischen einem Sketch, der Tage läuft, und einem, der nach Stunden abstürzt.

🚀 Bauteile für dein Mini-Dashboard

Fazit

Das 0,91 Zoll OLED wird oft übersehen, weil es „nur" halb so hoch ist. Genau das ist aber sein Vorteil: 512 Byte statt 1024 Byte Framebuffer, halber Stromverbrauch und eine Bauhöhe, bei der ein Gehäuse nicht mehr wie ein Klotz aussieht. Für alles, was einen Messwert plus Status anzeigen soll, reicht es völlig.

Die zwei Dinge, die dich am Anfang Zeit kosten, sind der richtige U8g2-Konstruktor (128X32_UNIVISION) und die Erkenntnis, dass y bei U8g2 die Grundlinie meint. Danach ist der Rest Layout-Arbeit – und mit den Bausteinen aus diesem Artikel, Balken, Icons, Laufschrift und Sparkline, bekommst du auf 32 Pixeln erstaunlich viel unter.

Zurück zum Blog