16.2.2.2. Building a ROMFS image¶
A ROMFS image is a flash-resident, read-only filesystem the
runtime auto-mounts at /rom. It solves the asset problem the
previous page closed on: machine-learning model files, label
tables, JSON configuration, image templates – anything
the application opens and reads but never writes – ride into
the build without paying the cost of being embedded as Python
literals.
Three things make ROMFS the right tool for shipped assets:
The filesystem is part of the firmware image. End users cannot delete a file out of
/rom, edit one, or replace one with their own.Files in
/romare accessible in place. Consumers like themlmodule loading a model file get a direct view into flash with no RAM copy – a multi-megabyte model on/rom“loads” essentially for free, where the same file on/sdcardis read into RAM at load time and stays there for the lifetime of the reference. Ordinaryopen()+readcopies on demand: eachread(n)call copiesnbytes from flash to RAM at the moment of the call, with a bareread()asking for the whole file./romand/rom/libare added tosys.pathat boot. Python packages dropped into the image are importable by name; nothing special at the call site.
16.2.2.2.1. Building an image¶
ROMFS images are created, edited, and flashed through the IDE. Use it as the source of truth for the contents of every shipped ROMFS partition.
The reason this matters: model files come with alignment
requirements that the loader at runtime enforces. .tflite
files must be padded to 16-byte boundaries, and the N6’s NPU
requires 32-byte alignment for compiled models. The IDE
applies that padding automatically when it writes the image.
Tools that walk the source tree without applying the padding
– mpremote romfs in particular – produce an image that
mounts cleanly but whose models fail at the first inference
call.
The IDE’s ROMFS editor is an interactive view of the image’s
contents. Files and folders can be added, renamed, and
deleted in memory; saving writes the result out as an
.img file ready to flash. A typical structure for an
application that ships a model alongside some assets and a
Python package looks like:
model.tflite
labels.txt
config.json
templates/
calibration.jpg
lib/
mylib/
__init__.py
helpers.py
Tip
Both the IDE and mpremote cross-compile .py files
to .mpy bytecode on the way into a ROMFS image, so the
cam imports them without paying the parse cost at load
time. Source files in the editor stay .py; the image
contains .mpy.
Once the image is flashed, the tree is visible from
MicroPython at /rom/:
>>> import os
>>> os.listdir('/rom')
['model.tflite', 'labels.txt', 'config.json', 'templates', 'lib']
>>> import mylib
>>> mylib.helpers
<module 'mylib.helpers' from '/rom/lib/mylib/helpers.mpy'>
16.2.2.2.2. Most of the application lives in ROMFS¶
ROMFS is the right home for almost everything an application ships: the libraries it imports, the model files it loads, the configuration it reads, any asset whose output came from a build tool that emits a file tree (model converters, image pipelines, asset packers), and – importantly – the application code itself.
The frozen-modules side should stay small: boot.py for
pre-REPL setup, main.py as a thin entry point, and only
the libraries the cam genuinely cannot boot without.
Everything else goes in ROMFS, where iterating on it is a
fresh .img saved out of the IDE and reflashed – no
firmware rebuild required, no toolchain on hand to do it.
The pattern that falls out is a main.py that does
nothing but delegate into the ROMFS-resident application:
# main.py (frozen)
import app
app.run()
# /rom/app/__init__.py (in ROMFS)
def run():
...
A change to app is a ROMFS edit and a reflash. The
firmware build stays put for the lifetime of the product
unless something on the frozen side actually has to change.