Lopaka Basics 02: Text, Schriftarten & Icons

Eine Uhr, das Wetter und ein Bild vom Mond, alles auf einem Screen von der Größe einer Briefmarke. Text, Schriftarten, Icons und Bilder machen einen kleinen Screen nützlich, und auf einem Mikrocontroller kostet jedes davon Speicher.
In dieser Episode findest du heraus, was eine Schriftart auf einem Mikrocontroller ist, wo sie gespeichert wird und wie viel Platz sie braucht. Dann baust du ein kleines Morgen-Dashboard in Lopaka mit zwei Screens, einer Statusleiste mit invertiertem Text und deinem eigenen Bild, in Punkte verwandelt, und führst es auf einem simulierten Arduino UNO aus.
Was du bauen wirst: ein 128×64-OLED-Dashboard mit zwei Screens. Morning zeigt die Zeit, das Datum, WLAN- und Akku-Icons und eine Wetterleiste. Night zeigt ein Bild vom Mond und die Zeit. Was du brauchst: einen Browser und das Simulatorprojekt aus Episode 1. Ein echtes Board ist optional.
Eine Schriftart ist ein Satz von Bildern
Ein Bild pro Zeichen
Ein Mikrocontroller weiß nicht, was Buchstaben sind. Um „07:45“ zu zeigen, braucht die Grafikbibliothek ein kleines Bild für jedes Zeichen: eines für 0, eines für 7, eines für : und so weiter. Eine Schriftart auf einem Mikrocontroller ist ein Satz winziger Bilder, eines für jedes Zeichen, das sie zeichnen kann.
Größerer Text, größere Bilder
Jedes Bild ist ein Raster aus Pixeln. Eine größere Schriftart bedeutet größere Raster: Dieselbe Ziffer 2 ist in einer kleinen Schriftart 5×9 Pixel groß und in einer großen 12×18. Jedes dieser Bilder muss auf dem Chip gespeichert werden, also braucht eine größere Schriftart mehr Speicher.

Zwei Arten von Speicher
Flash ist das Bücherregal, RAM ist der Schreibtisch
Ein Mikrocontroller hat zwei Arten von Speicher:
- Flash hält dein Programm, Schriftarten und Bilder. Er funktioniert wie ein Bücherregal: Alles, was das Programm brauchen könnte, wird dort gespeichert, und es bleibt dort, wenn der Strom aus ist.
- RAM ist der kleine Schreibtisch, an dem die Arbeit passiert: die Werte, an denen das Programm gerade arbeitet.
Auf einem Arduino UNO hält das Bücherregal 32 KB und der Schreibtisch nur 2 KB.
Wie viel eine Schriftart kostet
Eine kleine Schriftart braucht etwa ein Kilobyte Flash, eine große zwei bis vier. Wir maßen es, indem wir einen Sketch für einen UNO mit einem Text in einer einzigen Schriftart kompilierten und ihn mit einem Sketch ohne Text verglichen: 4x6 fügte 1,1 KB hinzu, profont22 2,1 KB, profont29 2,5 KB und timR24 3,7 KB.

Jede Schriftart wird einmal gespeichert, egal wie viele Texte sie nutzen. Die praktische Regel: zwei oder drei Schriftarten pro Screen. Um zu sehen, welche Schriftarten dein Design nutzt, schau dir die setFont-Zeilen im Code-Panel von Lopaka an: Jeder unterschiedliche Schriftartname dort ist eine weitere Schriftart auf dem Bücherregal.
Schritt 1: Ein neues Projekt
Gleicher Start wie in Episode 1: Klicke auf + Neues Projekt, wähle die Plattform U8g2, behalte die Screen-Größe bei 128×64 und gib dem Projekt einen Titel, „Morning Dashboard“. Klicke auf Speichern.
Schritt 2: Text
Die Zeit in einer großen Schriftart
Wähle das Text-Werkzeug (Aa), klicke auf die Zeichenfläche und wähle die neue Ebene string 1 aus. Ersetze im Inspektor den Text durch 07:45, öffne die Liste Schriftart und wähle profont29, eine Schriftart, die sich leicht aus der anderen Ecke des Raumes lesen lässt.
Klicke oben im Inspektor auf den Button Horizontal zentrieren und setze Y auf 40. Der Code liest jetzt:
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
Beide Zahlen lassen sich von Hand prüfen. Jedes Zeichen von profont29 ist 16 Pixel breit, also ist „07:45“ 5 × 16 = 80 Pixel breit, und (128 − 80) / 2 = 24 legt es in die Mitte. Die 40 ist die Grundlinie, die Linie, auf der die Ziffern stehen, wie in Episode 1: Die Ziffern füllen die Zeilen darüber.
Das Datum in einer kleinen Schriftart
Füge einen weiteren Text hinzu, FRI 24 OCT. Ein neuer Text startet in der letzten Schriftart, die du genutzt hast, also wechsle ihn zur kleinen 5x8. Zentriere ihn horizontal und setze Y auf 11. Zehn Zeichen mit 5 Pixeln machen 50 Pixel, was drawStr(39, 11, "FRI 24 OCT") ergibt.
Deine eigene Schriftart
Willst du einen anderen Look? Der erste Eintrag der Liste Schriftart, Schriftart importieren..., nimmt eine TrueType-Datei (.ttf; .otf, .woff und .bdf gehen auch), und Lopaka verwandelt sie in Pixel mit der Höhe, die du wählst. Im Video importierten wir Press Start 2P, eine kostenlose Pixel-Schriftart, und setzten Schrifthöhe auf 8. Die Vorschau zeigt die ganze Schriftart: 95 winzige Bilder, jedes druckbare Zeichen vom Leerzeichen bis ~.

Klicke auf Importieren, und die Schriftart ist einsatzbereit. Der ausgewählte Text wechselt zu ihr, und der Code erhält eine zusätzliche Zeile, #include "Press_Start_2P_8.h". Dieser Header hält die Schriftartdaten. Ein Button mit demselben Namen erscheint unter dem Code: Lade die Datei herunter und lege sie neben deinen Sketch, sonst kompiliert der Sketch nicht.
Das Dashboard behält die eingebauten Schriftarten. Wenn du den Import an der Zeit ausprobiert hast, wechsle sie zurück zu profont29.
Schritt 3: Icons
Suchen und hinzufügen
Lopaka hat mehr als zweitausend Icons eingebaut. Öffne links den Tab Bilder, tippe in das Suchfeld und klicke auf ein Icon, um es zum Screen hinzuzufügen. Füge drei hinzu:
wifi_25(suche „wifi“),battery_30(suche „battery“),weather_sun(suche „sun“).
Alle drei sind im Gaai-Set.
Bringe jedes Icon an seinen Platz
Ziehe die Icons auf der Zeichenfläche an ihren Platz: WLAN in die obere linke Ecke, den Akku in die obere rechte Ecke, die Sonne in die untere linke Ecke.
Ein Icon sind 32 Bytes
Ein Icon ist auch ein winziges Bild. Die Sonne ist 15 Pixel breit und 16 hoch, und auf einem einfarbigen Screen ist jedes Pixel ein Bit: an oder aus. Acht Pixel passen in ein Byte. Eine Zeile mit 15 Pixeln braucht zwei Bytes, und das letzte Bit bleibt ungenutzt, also ist das ganze Icon 2 Bytes × 16 Zeilen = 32 Bytes.
![]()
Im Code wird das Icon zu dieser Liste von Zahlen. Die Bibliothek liest sie und zeichnet die Pixel:
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);drawXBMP nimmt die Position, die Breite und Höhe des Bildes und das Array. In diesem Format, XBM genannt, ist das erste Pixel einer Zeile das niedrigste Bit des Bytes. Deshalb liest sich die hervorgehobene Zeile von links nach rechts 00010000, wird aber als 0x08 gespeichert.
Die anderen beiden Icons sind 19×16 und 24×16 Pixel. Beide brauchen drei Bytes pro Zeile, je 48 Bytes.
Schritt 4: Invertierter Text
Eine weiße Leiste mit dunklem Text
Zeichne für eine Statusleiste ein gefülltes Rechteck und lege Text darüber:
- Wähle das Rechteck-Werkzeug, ziehe ein Rechteck auf der Zeichenfläche und setze X
0, Y46, W128, H18. Schalte Gefüllt ein. Die Leiste deckt jetzt die unteren 18 Zeilen des Screens ab, Zeilen 46 bis 63. - Das neue Rechteck ist die oberste Ebene und versteckt die Sonne. Drücke Strg + ↓ (Cmd + ↓ auf einem Mac), um es eine Ebene nach unten zu schieben, unter
weather_sun. - Wähle
weather_sunaus und schalte Invertiert (XOR) ein. - Füge einen Text
Sunny 23C(mit zwei Leerzeichen) hinzu, wähle die Schriftart6x10, setze X28und Y59und schalte Invertiert (XOR) ein.
Sonne und Text schneiden in Schwarz durch die weiße Leiste.

XOR: Wo Formen überlappen, kippen die Pixel
Invertiert nutzt einen Trick namens XOR. Wo immer eine neue Form etwas bereits Gezeichnetes überlappt, kippen die Pixel: Ein leuchtendes Pixel unter der Form wird dunkel, ein dunkles leuchtet auf. Auf einem einfarbigen Screen bekommst du so dunklen Text auf einer hellen Leiste.

Im Code ist das eine Zeile vor der Sonne:
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");setDrawColor(2) schaltet U8g2 in den XOR-Modus, und der Modus bleibt an, bis der Code ihn wieder ändert. Deshalb braucht der Text nach der Sonne keinen zweiten Aufruf. Die Reihenfolge der Ebenen zählt hier: Die Leiste muss zuerst gezeichnet werden, damit Sonne und Text etwas zum Kippen haben.
Schritt 5: Dein eigenes Bild
Lade ein Bild hoch
Du kannst auch deine eigenen Bilder mitbringen. Klicke im Tab Bilder auf Hochladen und wähle eine Bilddatei. Lopaka öffnet einen Importdialog, der es für das Display umwandelt. Wir nutzten ein fotoähnliches Bild vom Mond.
Setze Maße auf W 48 und H 48 und schalte Palette invertieren ein: Der Mond ist hell auf dunklem Hintergrund, und das macht den Mond zu den leuchtenden Pixeln auf dem OLED.
Dithering verwandelt Grau in Punkte
Dieser Screen hat kein Grau. Jedes Pixel ist schwarz oder weiß. Die einfachste Umwandlung schneidet bei halber Helligkeit: Hellere Pixel gehen an, dunklere aus. Auf dem Mond bleibt davon nur ein weißer Fleck, und die ganze Schattierung ist verloren.
Dithering behebt das. Es mischt schwarze und weiße Punkte, dichter, wo das Bild heller ist, sodass dein Auge aus der Entfernung Graustufen sieht.

Wähle einen Stil in der Liste Dithering: Floyd-Steinberg, Atkinson, Bayer 8x8 oder Kein Dithering. Wir behielten Floyd-Steinberg. Klicke auf Importieren.

Bei 48 × 48 Pixeln braucht das Bild 48 / 8 × 48 = 288 Bytes.
Ein zweiter Screen für die Nacht
Geben wir dem Mond einen eigenen Screen. Ein Projekt kann so viele Screens halten, wie du brauchst.
- Doppelklicke auf den Namen des Screens über der Zeichenfläche und benenne
Screen 1inMorningum. - Klicke im Ebenen-Panel auf Screen hinzufügen und benenne den neuen Screen in
Nightum.
Lege den Mond auf den Night-Screen bei X 6, Y 8 und füge zwei Texte hinzu:
Good nightin6x10bei X62, Y26,22:30inprofont22bei X62, Y50.

Jeder Screen wird im Code zu seiner eigenen Funktion: drawMorning() und drawNight(), benannt nach den Screens. Das Code-Panel hat einen Tab für jeden Screen. Dein Sketch entscheidet, welchen er zeigt, und wann.
Schritt 6: PROGMEM
Eine Einstellung, die es wert ist, sie zu kennen: Als PROGMEM deklarieren, in der Liste der Schalter neben dem Code. Sie ist standardmäßig an.

Auf einem AVR-Board wie dem UNO wird ein konstantes Array beim Programmstart ins RAM kopiert, wenn es nicht für Flash markiert ist. U8X8_PROGMEM hält die Bilddaten im Flash, auf dem Bücherregal, sodass sie den kleinen RAM-Schreibtisch nicht überfüllen, und drawXBMP (mit dem P) liest sie von dort. Mit ausgeschaltetem Schalter schreibt Lopaka drawXBM und die Arrays ohne U8X8_PROGMEM.
Der Unterschied für dieses Dashboard: Die vier Bilder brauchen 416 Bytes. Für einen UNO kompiliert, nutzt der Sketch 78 % des RAM mit PROGMEM und 98 % ohne. Ohne bleiben nur 32 Bytes für alles übrig, was das Programm während des Laufens tut, und der Sketch kann abstürzen oder sich seltsam verhalten.

Schritt 7: Führe ihn aus
Der Code
Klicke im Tab Morning auf Kopieren, füge ihn in deinen Sketch ein und mache dann dasselbe im Tab Night. Kopieren nimmt den Code des offenen Tabs. Das ist der Code, den Lopaka für die zwei Screens generiert:
// [BEGIN lopaka generated]
static const unsigned char image_battery_30_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0xf0,0xff,0x7f,0x08,0x00,0x80,0x08,0x00,0xaa,0x0e,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x0e,0x00,0xaa,0x08,0x00,0xaa,0x08,0x00,0x80,0xf0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00};
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};
static const unsigned char image_wifi_25_bits[] U8X8_PROGMEM = {0x80,0x0f,0x00,0x60,0x30,0x00,0x18,0xc0,0x00,0x84,0x0f,0x01,0x62,0x30,0x02,0x11,0x40,0x04,0x0a,0x87,0x02,0xc4,0x18,0x01,0x28,0xa0,0x00,0x10,0x47,0x00,0xa0,0x2f,0x00,0xc0,0x1d,0x00,0x80,0x0a,0x00,0x00,0x07,0x00,0x00,0x02,0x00,0x00,0x00,0x00};
void drawMorning(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// string 1
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
// string 2
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(39, 11, "FRI 24 OCT");
// wifi_25
u8g2.drawXBMP(2, 0, 19, 16, image_wifi_25_bits);
// battery_30
u8g2.drawXBMP(102, 0, 24, 16, image_battery_30_bits);
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");
}
// [END lopaka generated]
// [BEGIN lopaka generated]
static const unsigned char image_moon_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd0,0x0a,0x00,0x00,0x00,0x00,0x7f,0x57,0x00,0x00,0x00,0xc0,0xee,0xad,0x00,0x00,0x00,0xf0,0xbf,0xbb,0x05,0x00,0x00,0xd8,0xfb,0x56,0x09,0x00,0x00,0xfc,0xbf,0xbf,0x12,0x00,0x00,0xfe,0xff,0xd5,0x06,0x00,0x00,0xdf,0x6f,0xbf,0x2a,0x00,0x80,0x7f,0xbb,0xd5,0x42,0x00,0xc0,0xdf,0x7f,0xbf,0x2a,0x00,0xc0,0xff,0xfe,0xd6,0x8a,0x00,0xe0,0xff,0xdf,0xbb,0x2a,0x00,0x60,0xff,0xff,0x6e,0x95,0x00,0xf0,0xff,0xbf,0xb7,0x4a,0x00,0xd0,0xff,0xef,0xdd,0x12,0x02,0xf0,0xff,0xff,0xb7,0x56,0x00,0xf0,0xff,0xbd,0x6e,0x09,0x01,0xf8,0xbf,0xef,0x5b,0x53,0x00,0xe8,0xfd,0xff,0xd6,0x14,0x10,0x78,0xff,0xdd,0xad,0x8a,0x00,0xe8,0x6f,0x77,0x5b,0x11,0x00,0xf8,0xff,0xdf,0x56,0x45,0x04,0xd0,0x5e,0x7d,0xab,0x10,0x00,0xf8,0xfb,0xd7,0x56,0x05,0x00,0x50,0xef,0xbe,0x55,0x41,0x00,0xf0,0xbd,0x6b,0x55,0x0a,0x04,0x50,0xf7,0xd6,0xaa,0x00,0x00,0xd0,0xad,0xbd,0x0a,0x25,0x00,0xa0,0x76,0xab,0x52,0x00,0x02,0x60,0xdb,0x56,0x25,0x01,0x00,0x40,0xad,0x55,0x49,0x08,0x00,0x80,0x6a,0x55,0x15,0x82,0x00,0x80,0x5a,0x55,0x82,0x00,0x00,0x00,0xa5,0x4a,0x29,0x00,0x00,0x00,0x2a,0x29,0x00,0x00,0x00,0x00,0x48,0x92,0x02,0x20,0x00,0x00,0xa4,0x04,0x08,0x04,0x00,0x00,0x00,0x51,0x80,0x00,0x00,0x00,0x20,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void drawNight(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// moon
u8g2.drawXBMP(6, 8, 48, 48, image_moon_bits);
// string 9
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(62, 26, "Good night");
// string 10
u8g2.setFont(u8g2_font_profont22_tr);
u8g2.drawStr(62, 50, "22:30");
}
// [END lopaka generated]6x10 erscheint in beiden Funktionen, aber es ist dieselbe Schriftart und wird einmal im Flash gespeichert. Das ganze Design nutzt vier Schriftarten: profont29, 5x8, 6x10 und profont22.
Der Sketch
Beginne mit dem Sketch der letzten Episode und ändere loop() so, dass er jeden Screen drei Sekunden lang zeigt:
#include <U8g2lib.h>
#include <Wire.h>
// 128x64 OLED display on I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// Paste the Lopaka code here
void setup() {
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
// show each screen for 3 seconds
if ((millis() / 3000) % 2 == 0) drawMorning();
else drawNight();
u8g2.sendBuffer();
}millis() zählt die Millisekunden seit dem Start des Boards. Geteilt durch 3000 wächst es jede drei Sekunden um eins, und % 2 verwandelt das in 0, 1, 0, 1. Bei 0 zeichnet der Sketch Morning, bei 1 Night. Wie in Episode 1 zeichnen beide Funktionen zwischen clearBuffer() und sendBuffer().
Führe ihn im Simulator aus
Öffne das Simulatorprojekt aus Episode 1: ein Arduino UNO, ein SSD1306-OLED, verdrahtet mit GND, 5V, A5 (SCL) und A4 (SDA), und die U8g2-Bibliothek. Ersetze den Sketch durch den obigen, füge den Lopaka-Code an seiner Stelle ein und drücke Play. Das Display wechselt jede drei Sekunden zwischen den zwei Screens.

Wir prüften beide Screens Pixel für Pixel: Die Lopaka-Zeichenfläche, der von der U8g2-Bibliothek gezeichnete Screen und das simulierte Display sind identisch.
Wie viel Speicher das alles braucht
Das Ganze, mit vier Schriftarten, drei Icons und einem Bild, passt in weniger als die Hälfte des Flash des UNO. Der Compiler meldet:
Sketch uses 15394 bytes (47%) of program storage space. Maximum is 32256 bytes.
Global variables use 1600 bytes (78%) of dynamic memory, leaving 448 bytes for local variables. Maximum is 2048 bytes.Der größte Teil der 1.600 Bytes RAM ist der 1.024-Byte-Frame-Puffer aus Episode 1. Für diesen Sketch ist das in Ordnung.
Zusammenfassung
- Schriftarten und Icons sind Bilder: Jedes Zeichen einer Schriftart und jedes Icon ist ein kleines Raster aus Pixeln, ein Bit pro Pixel.
- Bilder kosten Speicher: Eine Schriftart braucht ein bis vier Kilobyte Flash, also nutze zwei oder drei pro Screen und halte Bilder mit PROGMEM im Flash.
- Dithering verwandelt Grau in Punkte: Wähle einen Dithering-Stil, wenn du ein Foto importierst, und der einfarbige Screen zeigt Schattierungen.
Nächste Episode: Farbe. Dieselbe Art von Oberfläche auf einem Farb-TFT-Display, und was RGB565 bedeutet. Lies Episode 3 oder schaue sie auf YouTube.
Bereit, es zu versuchen? Öffne Lopaka und baue dein eigenes Dashboard.