Skip to content
miblo

SDK

How Miblo animates a screen with 80 KB of RAM

Miblo's memory: what is fixed, what a scene costs, the 8 and 12 KB floors, what is lent to the network and how to read the diagnostics when a scene does not load.

Miblo has 80 KB of RAM for everything: the firmware, Wi-Fi, your sessions, the pet, the card and the app's scene on the screen. There is no frame buffer (a picture of the screen would take 115,200 bytes) and no virtual memory: what does not fit does not run. This page says where that memory goes, what a scene costs, what Miblo lends to the network and how to read the diagnostics when a scene does not load. The animation guide (Animations on Miblo's screen) explains how to animate; this is the arithmetic behind it.

The budget

From the app to final pixelsApp + SDKdata, artwork and motionBridge on the computervalidation and planningResident scenecompact tiles and commandsCardtext and componentsScanline renderingbackground, sprites, effectsComposition → SPI → screenthe region's final pixels
The computer prepares the scene. The device combines motion and content before sending the region's pixels to the panel.

Of the 80 KB, the firmware takes a fixed part, there before any connection: 51.3 KB on Miblo 1.28.1 (54.8 KB on 1.28.0). The rest is the heap, which feeds the Wi-Fi's buffers, every open connection, every request from the bridge while it is read, the app's card, the pet's buffers, the items' captures and the scene. On a paired Miblo with a card set and the bridge's stream up, about 17 to 19 KB are free between one request and the next (measured on Amon). A scene has to fit inside those KB, beside the floors below.

Three numbers organise everything:

NumberWhat it is
12 KBThe network's reserve. A request that needs N bytes (its body, the JSON document of its answer) is served only when N + 12 KB are free, in one whole block of N + 4 KB; otherwise the answer is 503 busy. It is what keeps Wi-Fi alive
8 KBThe player's floor. A scene loads when its need + 8 KB are free (in a block of need + 4 KB), and gives its memory back when the heap drops under 8 KB after a request
16 KBThe reserve of the ordinary off-screen drawing layers (a filled mesh item, for instance). The items' composition strips do not use this reserve: they use the 8 KB floor

What is fixed

Inside the 51.3 KB are the firmware's state, the fonts and drawings kept in RAM, the sessions' limits, the alert queues and the formats of its own. Since 1.28.1 three things that were fixed follow the use instead:

  • The sessions' texts. The names, tools, models and details of the snapshot's rows sat in a fixed 1,536-byte block. They now live in a pool sized to the content, with every repeated text stored once: 20 typical sessions use 234 bytes. The cap is still 1,536 bytes (a larger snapshot leaves its last rows without text, never a cut text). That is 2.4 KB more free.

  • The pet's buffers. A bitmap pet (MPET1) draws from a 1,152-byte frame; a vector pet (MPET2) uses scratch tables. Miblo gives those buffers back whenever the pet is off the screen (the App screen, the Overview, an app alert, the display off) and when the pet's format changes; the logo and the identity stay, and the pet's next draw reloads what it needs.

  • The constants. Some of Miblo's own formats moved to the flash.

The app's card, when there is one, takes up to 1 KB of heap while it is on the screen.

What a scene costs

The cache adapts to each tile's colors8 × 8 tile · 64 pixels1 colorconstant · 1 B2 colors1 bit/pixel · 9 B3–4 colors2 bits/pixel · 18 B5–16 colors4 bits/pixel · 32 BDirectory + data: use only if the total is smaller
The directory costs 2 bytes per tile. Compact storage is selected only when the total saves memory, preserving every pixel.

A tile scene (the screen.animate path for art that fits in 256 tiles, with or without a per-line effect) costs three parts in RAM:

PartBytes
The resident tilesthe set's compacted size (below)
The player3,104
The palette cache528

The tiles are the part that varies. In the file every tile takes 32 bytes (64 pixels of 4 bits). In RAM Miblo keeps each one in the shortest form that reproduces its pixels exactly, decided when the scene loads:

Colours in the tileHow it is keptBytes
1a constant1
2two indices and 1 bit per pixel9
3 or 4four indices and 2 bits per pixel18
5 to 16the file's 4 bits per pixel32

Plus a 2-byte directory entry per tile. The compact form is used only when the total, directory included, is smaller than the file's 32 bytes × tiles; no colour is dropped, nothing changes in the file or in the SDK, and drawing keeps reading from RAM, never from the flash. The Radar, with 237 tiles, went from 7,584 to 3,816 bytes of tiles: the whole scene from 11,724 to 7,448 bytes. With the 8 KB floor it loads when about 15.6 KB are free in a block of 11.5 KB. A small scene (few tiles, flat colours) costs 4 to 5 KB and loads with about 12 to 13 KB free.

What helps, in the art: fewer distinct tiles and fewer colours per tile. A flat background is one byte per tile; a gradient is a 32-byte tile in every cell. The rules of the guide's chapter 4 hold here for the same reason they hold for speed.

What helps, in the fps: nothing. The rate does not change a scene's memory. The player reads each step's map from the flash in 512-byte pieces; a step that only moves sprites does not read the map again.

Items over the animation

Text and animation in the same transferSeparate drawingBackground → paneltext briefly erasedLetters → paneltext reappearsStrip compositionBackground + text in RAM240 × 16 pixel stripFinal pixels → paneltext already over animation
Each strip is composed before transfer. The panel no longer receives a background without its letters first in that region.

The card is drawn over the scene. A still item (big, text or bar with no fx and no live) is composed in memory with the animation's pixels, strip by strip of 240x16 pixels and only on the rows where it has ink, and goes to the panel in one push: the panel never receives the background without the letters. That holds the first time the item appears, when its value changes and at every frame moving under it.

The memory of that:

  • A 240x16 strip at 4 bits asks for 1,953 bytes. It is admitted with the 8 KB floor (not the 16 KB reserve of the ordinary layers), so it fits beside a loaded scene.

  • When memory is to spare, Miblo keeps the capture of every still item (up to 6 KB for all of them, taken only from what is left above 12 KB + 4 KB) and composes without drawing the item again. When it is not, it composes with a temporary strip and returns it before the network needs it.

  • The captures are the first memory Miblo lends, before the scene.

An item with fx, a ring, a spark or a mesh does not go this way: at every step its box is repainted whole (the animation's pixels first, then the item). That is why the guide asks for small items and few effects over the art.

What is lent, and when

A scene is a cache of the flash: Miblo may drop it at any moment and load it again where it was, at the same step, with the effect's clock carrying on. While the memory is lent, the scene's picture stays on the glass (there is no frame buffer to erase), and the items keep being drawn the old way.

The order of the loans, for a request that does not fit with the 12 KB reserve:

  1. The items' captures go first (their budget goes to zero).

  2. The scene goes next, if the request asks for more than 2 KB, or if, asking for less, it does not fit beside the scene with 8 KB to spare. A small request (a poll, a 1 KB document) runs beside the scene.

And the floor: after a request, when under 8 KB are left with the scene loaded, the captures and then the scene are given back to the network.

The return:

  • Never while a request is being read.

  • After a hold: 150 ms after a loan to a request, 2 s after the floor.

  • When the scene's need + 8 KB are free (the captures count as free: they go first) in one whole block of need + 4 KB. A shortage at that instant is tried again 250 ms later; a file that does not open or a player that fails, 5 s later.

  • Only with the App screen on the panel. Three seconds after the App screen leaves, the scene's memory is given back for good.

POST /api/screen/anim-play answers 503 busy only when the scene would not fit even after the request is over (need + 8 KB over the free heap + what is reclaimable + 4 KB). Otherwise it is taken and loads when the heap has room.

How to read the diagnostics

Everything below is read on the device, with the paired computer's token (Authorization: Bearer), or through the bridge where said.

GET /api/info

FieldWhat it is
heapThe free heap at the instant of the answer, inside the handler, after it allocated its own document (~2 KB). Do not compare it with idleHeap
idleHeapThe free heap sampled by the main loop, outside any request. This is the number to compare between readings and between versions
maxBlockThe largest whole free block. Much smaller than idleHeap means fragmentation
minHeapParseThe lowest free heap seen while a snapshot was read
petDrawingBytes, guestDrawingBytesThe drawing buffers of your pet and of the visiting pet, if loaded (0 when given back)
petLogoBytesThe custom pet's logo, which stays
snapshotTextBytes, snapshotTextUsedThe sessions' text pool: allocated and used

GET /api/screen

FieldWhat it is
panelWhat is on the glass now: app, main, desk, summary, alert, pet, limits, preview, boot, setup, code, disconnected, updating, off (display off) or other. shown only says whether the App screen is pinned; it does not say it is on the glass
anim.loadedThe scene is loaded in RAM
anim.yieldedIt is not, because its memory was lent; it comes back by itself
anim.needWhat loading costs: compacted tiles + player + cache (while it is not loaded)
anim.whyWhile it is not loaded: how many passes of the loop were refused for each reason since the last load. notShown (the App screen is not on the panel), pending (a request being read), hold (the wait after a loan), free (need + 8 KB were not free; lastFree says how much was), room (no whole block; lastFree and lastBlock), failed (the file or the player)
anim.yields, anim.yieldNeedHow many times the scene's memory was lent to a request since it started playing, and what the last one asked for
anim.floorYieldsHow many times it was given back by the 8 KB floor
anim.composeok: strips composed over the animation; noLayer: strips that did not fit in memory at that instant (the item was repainted the old way); inexact: captures that did not represent the item exactly (the same). ok counts strips, not frames, and an item drawn from its kept capture does not count
anim.heap, anim.tileBytes, anim.tileRawBytesWith the scene loaded: what it takes, the compacted resident tiles and what they would be uncompacted
anim.real{ fps, drawMs }: the rate achieved and the last draw
governorEvery animating kind (anim, fx, meshWire, meshFlat, transition, live): its current fps, its last draw and the average

miblo screen status

The bridge reads loaded, yielded, why and yields from the device and prints a reason on the animation's line:

Animação: blocos de Clima · 8 fps pedidos · — fps real · — ms · não carregada (sem memória livre: 7,9 KB livres).
Animação: blocos de Radar · 10 fps pedidos · 9 fps real · 48 ms · memória emprestada 3×.

panel, floorYields and compose do not go through the bridge: read them on the device.

Why the scene did not load

miblo screen status saysanim.whyWhat is going onWhat to do
"a tela do app não está no Miblo"notShownThe App screen is not on the panel (another screen, the rotation on another app, the display off). The scene loads only with it on the glassNothing: it loads on the app's turn. Check panel
"uma requisição em curso"pendingThe bridge is sending something (a card, a live batch) on every passNormal for moments; constant, it is a bridge sending too much (a card every second, for instance)
"devolvendo a memória emprestada"holdThe 150 ms or 2 s wait after a loanNothing
"sem memória livre: N KB livres"freeneed + 8 KB were not free. lastFree says how much wasA smaller scene (fewer distinct tiles, fewer colours per tile); fewer things open on the Miblo (another computer's stream, a visiting pet)
"sem um bloco de memória inteiro: N KB livres"roomThere are free bytes, but not in one block of need + 4 KB: fragmentationAs above; maxBlock in /api/info shows the block
"o arquivo ou o player falhou"failedThe file in the slot does not open or does not validateSend the scene again (screen.animate); the SDK rewrites the slot
"(memória emprestada a uma requisição)"yielded with no countsThe scene was loaded and gave way to a request; it is back in 150 msNothing
"memória emprestada N×"yieldsThe scene plays, but gives way oftenNormal a few times a minute; every second, it is the bridge sending requests too large (big cards, snapshots every second)

A scene stuck on free or room with the App screen on the glass is this page's arithmetic: its need (anim.need) + 8 KB does not fit in idleHeap. The anim.compose numbers say whether the items are being composed (ok growing) or repainted the old way (noLayer growing): in the second case the heap is at its limit during the draw, even with the scene loaded.