Zum Inhalt springen

Lopaka Basics 07: Animationen ​

Ein SSD1306-OLED-Modul, das eine weiße Silhouette eines galoppierenden Pferdes mit Jockey zeigt

Dieses Pferd wurde 1887 fotografiert. Jetzt ist es ein GIF, das auf einem winzigen Arduino-Screen galoppiert: fünfzehn Frames, und sie passen. Mache es Fullscreen und ein wenig flüssiger, und der Sketch passt überhaupt nicht mehr.

In dieser Episode findest du heraus, wie Animation auf einem kleinen Screen funktioniert und wie viel Speicher sie braucht. Dann verwandelst du ein GIF in Lopaka in Code, führst es auf einem simulierten Arduino UNO aus und legst einen ganzen Monat Mondphasen auf einen ESP32.

Was du bauen wirst: ein galoppierendes Pferd aus einem GIF auf einem 128×64-OLED mit einem Arduino UNO und einen Monat Mondphasen aus einem Video auf demselben OLED mit einem ESP32. Was du brauchst: einen Browser und die Simulator-Einrichtung aus Episode 1: ein SSD1306-OLED und die U8g2-Bibliothek. Für das Pferd nutzten wir Eadweard Muybridges galoppierendes Pferd (Animal Locomotion, 1887, gemeinfrei); jedes kurze GIF geht.

Frames und Bildrate ​

Eine Animation ist ein Stapel Bilder ​

Eine Animation ist ein Stapel Standbilder. Zeige sie nacheinander, schnell genug, und dein Auge hört auf, Bilder zu sehen, und beginnt, Bewegung zu sehen.

Diese Geschwindigkeit ist die Bildrate, in Frames pro Sekunde (FPS). Bei zwei Frames pro Sekunde ist das Pferd eine Diashow. Bei zehn galoppiert es.

Die 15 Fotos des galoppierenden Pferdes, eines groß und der Zähler 10 Frames pro Sekunde: Es galoppiert

Die Schleife spielt sie Frame für Frame ​

Ein Mikrocontroller spielt eine Animation auf dieselbe Weise. Jeder Durchlauf von loop() zeichnet den aktuellen Frame, sendet ihn an den Screen, wartet einen Moment und geht zum nächsten Frame. Nach dem letzten Frame springt er zum ersten zurück.

Die Animationsschleife um ein OLED, das das Pferd zeigt: Zeichnen, Senden, Warten, Weiter, mit dem Zähler Frame 5 von 15

Jeder Frame kostet Speicher ​

Ein Fullscreen-Frame sind 1.024 Bytes ​

Das OLED hat 128 × 64 Pixel, und jedes Pixel ist ein Bit: an oder aus. Das sind 8.192 Bits oder 1.024 Bytes für einen Fullscreen-Frame. Ein Frame jeder Größe braucht Breite × Höhe ÷ 8 Bytes.

128 × 64 = 8.192 Pixel, 1 Pixel = 1 Bit, 8.192 Bits = 1.024 Bytes: ein Fullscreen-Frame

Ein Arduino UNO hält 23 Fullscreen-Frames ​

Die Frames leben im Flash-Speicher, neben deinem Programm. Ein Arduino UNO hat 32 KB Flash, und die U8g2-Bibliothek und der Sketch brauchen schon etwa 8 KB davon. Das lässt Platz für 23 Fullscreen-Frames. Wir prüften es mit dem Compiler: Mit 23 Frames nutzt der Sketch 99 % des Flash des UNO, und der 24. Frame macht ihn zu groß.

Der Flash eines Arduino UNO, 32 KB: Bibliothek und Sketch brauchen etwa 8 KB, dann füllen 23 Fullscreen-Frames ihn zu 99 %, und Frame 24 passt nicht

Warum das Pferd passt und Fullscreen nicht ​

Das Pferd ist 96 × 64 Pixel, also braucht ein Frame 96 × 64 ÷ 8 = 768 Bytes. Fünfzehn Frames kommen auf etwa 11 KB, und der Sketch passt in 62 % des Flash.

Der UNO-Flash mit 15 Pferde-Frames von 768 Bytes: etwa 11 KB, 62 %, es passt

Fullscreen mit zwanzig Frames pro Sekunde braucht dreißig Frames von 1.024 Bytes, und der Sketch wächst auf 121 % des Flash. Der Compiler stoppt mit „Sketch too big“. Aber das GIF hat nur fünfzehn Zeichnungen. Um es mit zwanzig Frames pro Sekunde abzuspielen, zeigt der Sketch jede Zeichnung zweimal, also ist die Hälfte dieser dreißig Frames Kopien.

Der UNO-Flash, der bei 121 % mit 30 Fullscreen-Frames überläuft, jeder zweite Frame eine Kopie des vorherigen

Drei Wege, eine Animation passend zu machen ​

  1. Weniger Frames. Behalte die eigene Bildrate des GIF: Eine höhere fügt nur Kopien hinzu.
  2. Kleinere Frames. Jede Zeile und Spalte, die du abschneidest, spart Bytes in jedem Frame.
  3. Ein größeres Board. Ein ESP32 hat 1,25 MB Flash für dein Programm: Platz für etwa tausend Fullscreen-Frames.

Drei Wege, eine Animation passend zu machen: weniger Frames, kleinere Frames, ein größeres Board; ein ESP32 hat 1,25 MB App-Flash, Platz für 1.000 Fullscreen-Frames, während der ganze UNO-Flash ein kleiner Splitter ist

Schritt 1: Importiere ein GIF ​

Erstelle ein neues Projekt wie in Episode 1: + Neues Projekt, Plattform U8g2, Screen-Größe 128×64.

Die Animationsbibliothek ​

Öffne links den Tab Animationen. Lopaka kommt mit fertigen animierten Icons, und die Größenschätzung sitzt neben den Größe- und Qualität-Buttons. Ein 64×64-Icon in Hoher Qualität braucht etwa 14 KB; bei 32×32 und Niedriger Qualität ist es unter einem Kilobyte. Wähle dort ein Icon, wenn eine kleine Animation alles ist, was du brauchst.

Lade dein GIF hoch ​

Klicke für dein eigenes GIF oben im Panel auf Hochladen und lege die Datei in den Dialog. Videos gehen auch, wie du in Schritt 5 sehen wirst.

Lopaka teilt das GIF in Frames und öffnet den Importdialog. Die Frames sind links, die Vorschau in der Mitte, die Einstellungen rechts:

  • Dithering: Wechsle es von Floyd-Steinberg zu Kein Dithering. Das Foto wird zu einer sauberen weißen Silhouette statt einer Wolke aus Punkten. Dithering hilft bei Schattierungen, wie in Episode 2; eine Silhouette braucht keines.
  • Frames pro Sekunde: Behalte 10, die eigene Geschwindigkeit des GIF.

Die Zeile unten macht die Speicherrechnung für dich: 15 Quell-Frames, 15 Ausgabe-Frames, Frame-Größe 96×64, etwa 11,3 KiB. Lopaka skalierte das 300×200-GIF auf die Höhe des Screens, 64 Pixel, was es 96 Pixel breit macht. Ein Frame sind 768 Bytes, die Zahl oben in den Einstellungen.

Der Importdialog mit dem Pferde-GIF: 15 Frames links, die weiße Silhouette in der Vorschau, Dithering auf Kein Dithering, 10 Frames pro Sekunde, 15 Quell- und Ausgabe-Frames, Frame-Größe 96×64 und eine geschätzte Größe von 11,3 KiB

Ein häufiger Fehler ist, die Bildrate zu erhöhen, um das GIF flüssiger zu machen. Bei 20 FPS zeigt der Dialog 15 Quell-Frames, aber 30 Ausgabe-Frames, und die Schätzung verdoppelt sich auf 22,5 KiB: dieselben fünfzehn Zeichnungen, jede zweimal.

Klicke auf Importieren.

Schritt 2: Der Frame-Editor ​

Die Frames öffnen sich im Frame-Editor, mit allen fünfzehn in der Zeitleiste unten.

  • Abspielen (der Dreieck-Button) prüft die Schleife.
  • Mit eingeschaltetem Onion-Schalter scheint der vorherige Frame in Grau durch, sodass du sehen kannst, wie weit sich die Beine von einem Frame zum nächsten bewegen.

Die alten Fotos haben Zahlen am unteren Rand, und der Import verwandelte sie in weiße Markierungen. Entferne sie aus allen Frames auf einmal: Klicke die Frame-Größe neben der Bildrate (96×64 px), setze Höhe auf 60 und klicke auf Größe ändern. Die Pixel bleiben an der oberen linken Ecke verankert, also verschwinden die unteren vier Zeilen auf jedem Frame, und der Speicherzähler fällt von 11,3 auf 10,6 KiB. Wenn ein Frame noch ein paar Punkte einer Zahl am unteren Rand hat, gehe zu ihm und wische sie mit dem Radiergummi weg.

Der Frame-Editor bei Frame 2 von 15: das Pferd bei 96×60 Pixeln mit dem vorherigen Frame in Grau durchscheinend, 10 FPS, 96×60 px und 10,6 KiB unten, Onion an

Klicke auf Speichern. Die Animation sitzt auf dem Screen wie jede andere Ebene, namens horse, bei X 16 und Y 0: (128 − 96) ÷ 2 = 16 legt sie in die Mitte.

Schritt 3: Der Code ​

Frames direkt in den Code ​

Deaktiviere in den Schaltern neben dem Code Animationshelfer. Jetzt schreibt Lopaka die Frames direkt in den Code, und deine eigene loop() entscheidet, wann sie gezeichnet werden:

cpp
// [BEGIN lopaka generated]
static const unsigned char animation_horse_frames[15][720] U8X8_PROGMEM = {
  {0x00,0x00,0x00, /* ... */ 0x00,0x00},   // frame 1: 720 bytes, shortened here
  // ... 13 more frames ...
  {0x00,0x00,0x00, /* ... */ 0xff,0xff}    // frame 15
};
static int animation_horse_frame = 0;
void drawScreen_1(void) {
    u8g2.setFontMode(1);
    u8g2.setBitmapMode(1);
    // horse
    u8g2.drawXBMP(16, 0, 96, 60, animation_horse_frames[animation_horse_frame]);
    animation_horse_frame = (animation_horse_frame + 1) % 15;
}
// [END lopaka generated]

Was die Zahlen bedeuten:

  • [15][720]: ein Array von fünfzehn Frames, je 720 Bytes. Eine Zeile mit 96 Pixeln braucht 96 ÷ 8 = 12 Bytes, und 60 Zeilen ergeben 720. Insgesamt sind das 10.800 Bytes, die 10,6 KiB aus dem Frame-Editor.
  • U8X8_PROGMEM hält die Frames im Flash, wie bei den Bildern in Episode 2. Ohne es würde ein UNO versuchen, sie in seine 2 KB RAM zu kopieren.
  • drawXBMP(16, 0, 96, 60, ...) zeichnet den aktuellen Frame an Position und Größe der Ebene.
  • (animation_horse_frame + 1) % 15 geht zum nächsten Frame. % 15 ist der Rest nach dem Teilen durch 15, also springt der Zähler nach Frame 14 (dem fünfzehnten, von null gezählt) zurück auf 0.

Der Rest des Sketches ​

Schreibe um den generierten Block herum den Rest des Sketches. Du kannst ihn im Code-Panel von Lopaka tippen: Zeilen über // [BEGIN lopaka generated] und unter // [END lopaka generated] sind deine, sie bleiben im Projekt, wenn du das Design änderst, und Kopieren nimmt sie mit.

cpp
#include <U8g2lib.h>

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();
  drawScreen_1();
  u8g2.sendBuffer();
  delay(56);
}

Jeder Durchlauf von loop() ist ein Frame: löschen, zeichnen, senden, warten. Das ist die Schleife vom Anfang des Artikels.

Das Code-Panel in Lopaka mit Include und Display über dem generierten Block, dem Frames-Array animation_horse_frames[15][720] U8X8_PROGMEM, der Zeichenfunktion und setup und loop mit delay(56) darunter; Animationshelfer ist deaktiviert

Warum die Verzögerung 56 Millisekunden ist ​

Einen vollen Frame über I2C ans OLED zu senden, braucht auf dem simulierten UNO etwa 44 Millisekunden. Füge eine 56-Millisekunden-Verzögerung hinzu, und jeder Frame braucht 100 Millisekunden: zehn Frames pro Sekunde, die Geschwindigkeit des GIF. Auf einem echten Board hängt die Zeit zum Senden eines Frames von der I2C-Geschwindigkeit ab; wenn das Pferd zu schnell oder zu langsam läuft, ändere die Verzögerung.

Klicke auf Kopieren.

Schritt 4: Führe ihn auf einem Arduino UNO aus ​

Öffne dein Simulatorprojekt aus Episode 1: ein Arduino UNO und ein SSD1306-OLED, verdrahtet mit GND, 5V, A5 (SCL) und A4 (SDA), mit der U8g2-Bibliothek. Füge den Code in sketch.ino ein und drücke Play.

Das Pferd galoppiert. Für einen UNO kompiliert, meldet der Sketch:

Sketch uses 19324 bytes (59%) of program storage space. Maximum is 32256 bytes.
Global variables use 1558 bytes (76%) of dynamic memory, leaving 490 bytes for local variables. Maximum is 2048 bytes.

Das sind 59 % des Flash, runter von 62 %, bevor wir die vier Zeilen abschnitten. Wie in Episode 1 ist der größte Teil des RAM der 1.024-Byte-Frame-Puffer.

Schritt 5: Ein Video auf einem ESP32 ​

Importiere das Video ​

Lopaka importiert auch Video (MP4, MOV und WebM). Wir nutzten einen Clip des NASA Scientific Visualization Studio, der einen ganzen Monat Mondphasen in zehn Sekunden zeigt.

Erstelle ein weiteres U8g2-Projekt bei 128×64, öffne Animationen und klicke auf Hochladen. Der Importdialog für ein Video macht die Rechnung, während du die Einstellungen änderst:

  1. Bei den 30 Frames pro Sekunde des Videos ergeben zehn Sekunden 300 Frames bei 64×64: 150 KiB.
  2. Ein langsamer Mond sieht bei 12 Frames pro Sekunde gut aus: Tippe 12, und es werden 120 Frames, 60 KiB.
  3. Schalte Ergebnis invertieren ein. Lopaka beleuchtet die dunklen Teile eines Bildes, weshalb das dunkle Pferd auf hellem Hintergrund weiß herauskam. Der Mond ist das Gegenteil, hell auf dunklem Himmel, also invertiere das Ergebnis: Jetzt leuchtet der Mond und der Himmel bleibt dunkel.

Behalte diesmal Floyd-Steinberg-Dithering: Die Punkte zeichnen die Schattierung der Mondoberfläche.

Der Video-Importdialog mit dem Mond: der Vollmond in der Vorschau als Muster aus Punkten, Floyd-Steinberg-Dithering, Ergebnis invertieren an, 12 Frames pro Sekunde, Dauer 0:10, 120 Frames, Frame-Größe 64×64 und eine geschätzte Größe von 60,0 KiB

Klicke auf Importieren, dann im Frame-Editor auf Speichern. Lopaka zentriert die 64×64-Animation auf dem Screen, bei X 32. Deaktiviere Animationshelfer wieder, und der Code bekommt animation_moon_frames[120][512] und die Zeile animation_moon_frame = (animation_moon_frame + 1) % 120;.

60 KB: zweimal der UNO, unter 5 % eines ESP32 ​

Bei 64 × 64 Pixeln ist jeder Frame 64 × 64 ÷ 8 = 512 Bytes, also brauchen 120 Frames 61.440 Bytes, 60 KB. Das ist etwa zweimal der ganze Flash des UNO.

Der 60-KB-Mondclip, 120 Frames, quer über den 32-KB-Flash eines Arduino UNO gelegt: etwa 2 Mal der ganze UNO

Ein ESP32 hat 1.310.720 Bytes Flash für das Programm, und derselbe Clip braucht 4,7 % davon.

Führe ihn auf dem ESP32 aus ​

Nutze denselben Sketch mit dem Code des Mondes an seiner Stelle und ändere die Verzögerung zu delay(32);. Der ESP32 sendet einen Frame im Simulator in etwa 51 Millisekunden, also braucht jeder Frame etwa 83 Millisekunden: 12 Frames pro Sekunde.

Erstelle im Simulator ein ESP32-Projekt, füge das SSD1306-OLED und die U8g2-Bibliothek hinzu und verdrahte das Display mit den I2C-Pins des ESP32: GND mit GND, VCC mit 3V3, SCL mit GPIO 22 und SDA mit GPIO 21. Füge den Sketch ein und drücke Play. Der Compiler meldet 352.240 Bytes, 26 % des Flash, und der Mond wächst und schrumpft durch einen ganzen Monat.

Ein ESP32, im Simulator mit einem SSD1306-OLED verdrahtet; das Display zeigt den in Punkten gezeichneten Vollmond

Zusammenfassung ​

  1. Eine Animation ist ein Stapel Frames, schnell genug gezeigt, um wie Bewegung auszusehen. Jeder Durchlauf von loop() löscht, zeichnet einen Frame, sendet ihn und wartet.
  2. Jeder Frame kostet Breite × Höhe ÷ 8 Bytes Flash. Ein Fullscreen-Frame auf diesem OLED ist ein Kilobyte, und ein UNO hält etwa zwanzig davon.
  3. Um ein GIF passend zu machen, nutze weniger Frames, kleinere Frames oder ein größeres Board.

Nächste Episode: Touch-Oberfläche mit LVGL. Touchscreens, mit Buttons und Schiebereglern, wie auf einem Telefon. Lies Episode 8 oder schaue sie auf YouTube.

Bereit, es zu versuchen? Öffne Lopaka und bringe dein eigenes GIF zum Leben.