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
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_tfgibt dir vier Zeilen,u8g2_font_profont15_tfzwei,u8g2_font_logisoso16_tneine große Zahl plus eine kleine Zeile. -
Nur Ziffern-Fonts sparen viel Flash. Fonts mit der Endung
_tnenthalten 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 festeschar-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
- 0,91 Zoll OLED Modul 128x32 – kompakt, sparsam, I2C
- 0,96 Zoll OLED 128x64 – wenn doch mehr Zeilen gebraucht werden
- Arduino Nano V3 (USB-C) – klein genug für dasselbe Gehäuse
- ESP32 DEV KIT WROOM32 – reichlich RAM für U8g2 mit Full Buffer
- BMP280 Sensor – Temperatur und Luftdruck über denselben Bus
- DHT22 / AM2302 – Luftfeuchte für die Balkenanzeige
- Rotary Encoder KY-040 – durch die Seiten blättern
- 600er Widerstands-Sortiment – stärkere I2C-Pullups bei langen Kabeln
- Breadboard 400 Kontakte und Dupont-Jumperkabel
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.