About this tool
An animation on a small display is just a list of pictures shown one after another, each for a set time. The Animation Studio is where that list is built: you collect frames from the Pixel Editor or the video converter, arrange them on a timeline, set how long each one stays on screen, preview the result, and export it as an animated GIF or as Arduino code that plays it on a real screen.
Frames are held in your browser. Save a draft or export JSON to keep your work. The sections below explain the timeline, the export, and how much memory an animation needs.
Every frame in one animation must have the same width and height. The studio refuses a mix, because the exported code draws every frame into the same screen area.
| Key | Action |
|---|---|
| Space | Play or stop the preview |
| ← / → | Select the previous or next frame |
| Ctrl + A | Select all frames |
| Ctrl + D | Duplicate the selected frame |
| Delete / Backspace | Delete the selected frame |
| Esc | Clear the selection |
Frames per second and milliseconds per frame are the same idea said two ways: milliseconds per frame = 1000 ÷ frames per second.
| Frames per second | Milliseconds per frame |
|---|---|
| 5 | 200 |
| 8 | 125 |
| 10 | 100 |
| 12.5 | 80 |
| 20 | 50 |
| 24 | 41.7 |
A loop's length is the number of frames times the duration of each: 12 frames at 100 ms loop every 1.20 seconds, and so do 30 frames at 40 ms. The smoother version costs two and a half times the memory for the same length of loop.
Between 8 and 12 frames per second is a good starting point for a small OLED. Faster is not always possible, because every frame has to travel to the display over I2C. A 128 × 64 frame is 1024 bytes, and each byte takes 9 clock pulses (8 data bits and an acknowledge). At 400 kHz that is at least 23 ms per frame - at most 43 frames a second before the sketch does anything else. At the slower 100 kHz it is at least 92 ms, or about 11 frames a second.
Export Arduino .ino writes animation.h and animation.ino. The header holds the data:
#define ANIM_FRAME_COUNT N
#define ANIM_FRAME_WIDTH W
#define ANIM_FRAME_HEIGHT H
#define ANIM_FRAME_BYTES B // = ceil(W/8) * H
const uint16_t PROGMEM anim_delays_ms[N] = { ... };
const uint8_t PROGMEM anim_frames[N][B] = { ... };
and the sketch plays them for ever:
void loop() {
for (uint16_t i = 0; i < ANIM_FRAME_COUNT; i++) {
display.clearDisplay();
display.drawBitmap(0, 0, anim_frames[i], SCREEN_WIDTH, SCREEN_HEIGHT, WHITE);
display.display();
delay(pgm_read_word(&anim_delays_ms[i]));
}
}
(N, W, H and B are filled in with your numbers.)
Both arrays are in PROGMEM, so they stay in flash. That is why the delay is read with
pgm_read_word() rather than used directly: on AVR boards such as the Uno, flash has to be
read with special instructions.
Export GIF is for sharing - a README, a slide, a message. The JSON export is the studio's own format, for opening the animation again later.
Each frame costs (width ÷ 8, rounded up) × height bytes: 1024 bytes at 128 × 64, 512 at 128 × 32, and 384 at 64 × 48. An Arduino Uno has 32,256 bytes of flash for everything, including the display library.
If you can spare 20 KB (20,480 bytes) for the animation, that is 20 frames at 128 × 64, 40 at 128 × 32 or 53 at 64 × 48. Ways to fit more:
Related: draw frames in the Pixel Editor, or make them from a clip
with Video to Animation. The
Embedded C volume on time and timers
explains why a loop built on delay() blocks everything else, and how to avoid it.