Arbeiten mit ROMFS

Überblick

ROMFS (Read-Only Memory Filesystem) ist ein leichtgewichtiges, schreibgeschütztes Dateisystem, das für MicroPython-Geräte konzipiert wurde. Es ist für Mikrocontroller und eingebettete Systeme optimiert, bei denen Code und Daten im Flash-Speicher abgelegt und effizient zugegriffen werden müssen, ohne in den RAM kopiert zu werden.

Die wichtigsten Vorteile von ROMFS sind:

  • Zero-Copy-Importe: .mpy-Bytecode-Dateien, die in einem ROMFS gespeichert sind, können direkt aus dem Flash-Speicher (speicherabgebildet) ausgeführt werden, anstatt zuerst in den RAM kopiert zu werden. Dies ähnelt der Funktionsweise von eingefrorenen Modulen, erfordert aber kein erneutes Flashen der gesamten Firmware.

  • Geringer RAM-Verbrauch: Konstante Objekte (Strings, Bytes usw.) in .mpy-Dateien, die aus ROMFS geladen werden, werden direkt aus dem Flash referenziert und nicht im RAM dupliziert.

  • Flexible Bereitstellung: Ein ROMFS-Image kann auf einem Host-PC erstellt und mit mpremote auf dem Gerät bereitgestellt werden, ohne die Firmware neu zu erstellen.

  • Standard-Dateisystemschnittstelle: Ein ROMFS wird im VFS eingebunden und über normale Python-Dateioperationen (open, os.listdir, import usw.) angesprochen.

ROMFS ergänzt sowohl die les- und schreibbaren FAT-/LittleFS-Dateisysteme (die in anderen Flash-Partitionen liegen) als auch eingefrorene Module (die in die Firmware selbst kompiliert werden).

Board-Unterstützung

ROMFS ist in der OpenMV-Firmware auf jedem Kamera-Board aktiviert, das in seinem Flash-Layout eine reservierte ROMFS-Partition besitzt. Auf diesen Boards wird die ROMFS-Partition beim Booten automatisch erkannt und unter /rom eingebunden; sowohl /rom als auch /rom/lib werden zu sys.path hinzugefügt, sodass dort gespeicherte Module direkt importiert werden können.

Board

ROMFS-Unterstützung

OpenMV Cam N6

Ja

OpenMV AE3

Ja

OpenMV Cam RT1062

Ja

OpenMV Cam Pure Thermal

Ja

OpenMV Cam M4 / M7 / H7 / H7 Plus

Ja

Arduino Giga

Ja

Arduino Portenta H7

Ja

Arduino Nicla Vision

Ja

Arduino Nano 33 BLE Sense

Nein (keine ROMFS-Partition)

Arduino Nano RP2040 Connect

Nein (keine ROMFS-Partition)

Siehe romfs für einen OpenMV-spezifischen Helfer, der das unter /rom eingebundene ROMFS inspiziert.

Arbeitsablauf

Der typische Arbeitsablauf für die Verwendung von ROMFS ist:

  1. Erstellen Sie auf Ihrem PC ein Verzeichnis mit den Python-Dateien (oder .mpy-Dateien), die Sie bereitstellen möchten.

  2. Verwenden Sie mpremote romfs deploy <directory>, um das ROMFS-Image zu erstellen und auf dem Gerät bereitzustellen.

  3. Das ROMFS wird beim nächsten Booten unter /rom eingebunden (oder kann sofort eingebunden werden, wenn das Gerät neu gestartet wird).

  4. Python-Code auf dem Gerät kann dann Module aus dem ROMFS genauso importieren wie aus jedem anderen Dateisystem.

Zum Beispiel:

# On the host PC, with a directory "myapp/" containing app.py:
$ mpremote romfs deploy myapp/

Nach einem Soft-Reset verfügt das Gerät über /rom/app.py (oder /rom/app.mpy, falls mpy_cross installiert ist) zum Importieren.

Vollständige Details zu den mpremote-Unterbefehlen finden Sie im Abschnitt mpremote romfs Unterbefehle weiter unten.

Python-API

Die ROMFS-Python-API wird über das Modul vfs bereitgestellt.

class vfs.VfsRom(buffer)

Erstellt ein ROMFS-Dateisystemobjekt aus buffer, das ein Objekt sein muss, das das Buffer-Protokoll unterstützt (z. B. ein bytes-, bytearray- oder memoryview-Objekt) und ein gültiges ROMFS-Image enthält.

Der Konstruktor überprüft, ob buffer mit den ROMFS-Magic-Bytes (b"\xd2\xcd\x31") beginnt. Wenn der Puffer zu klein oder kein gültiges ROMFS ist, wird OSError(ENODEV) ausgelöst.

Von diesem Konstruktor erstellte Objekte können mit vfs.mount() eingebunden werden.

Beispiel:

import vfs

# Load a ROMFS image from flash into a memoryview.
dev = vfs.rom_ioctl(2, 0)   # get partition 0 as a memoryview
fs = vfs.VfsRom(dev)
vfs.mount(fs, '/rom')

Oder, um ein in einer Datei gespeichertes ROMFS-Image einzubinden:

import vfs

with open('/flash/app.romfs', 'rb') as f:
    romfs_data = f.read()
fs = vfs.VfsRom(romfs_data)
vfs.mount(fs, '/rom2')

Die folgenden Methoden sind für ein VfsRom-Objekt verfügbar:

VfsRom.open(path, mode)

Öffnet eine Datei aus dem ROMFS. Nur Lesemodi ('', 'r', 'rt', 'rb') werden unterstützt. Der Versuch, eine Datei zum Schreiben zu öffnen, löst OSError(EROFS) aus.

Das zurückgegebene Dateiobjekt unterstützt read(), seek(), tell() und close(). Bei im Lesemodus geöffneten Binärdateien unterstützt das zurückgegebene Objekt auch das Buffer-Protokoll, sodass ein memoryview der Dateidaten erhalten werden kann, das direkt in den ROMFS-Speicher verweist (Zero-Copy).

VfsRom.ilistdir(path)

Gibt einen Iterator über die Einträge im Verzeichnis path zurück. Jeder Eintrag ist ein Tupel (name, type, inode, size), wobei type 0x8000 für eine Datei oder 0x4000 für ein Verzeichnis ist.

VfsRom.stat(path)

Gibt ein os.stat-ähnliches 10-Tupel für path zurück. Löst OSError(ENOENT) aus, wenn der Pfad nicht existiert.

VfsRom.statvfs(path)

Gibt Dateisystemstatistiken zurück. Die Blockgröße wird als 1 angegeben und die Blockanzahl entspricht der Gesamtgröße des ROMFS-Images in Bytes. Freie Blöcke und freie Dateien sind immer 0 (schreibgeschütztes Dateisystem).

VfsRom.chdir(path)

Wechselt das Verzeichnis innerhalb des ROMFS. Nur das Wurzelverzeichnis ('/') wird unterstützt; der Wechsel in ein Unterverzeichnis löst OSError(EOPNOTSUPP) aus.

VfsRom.getcwd()

Gibt das aktuelle Arbeitsverzeichnis innerhalb des ROMFS zurück. Gibt immer '/' zurück.

vfs.rom_ioctl(op, ...)

Low-Level-Schnittstelle für den Zugriff auf die schreibgeschützte(n) Speicherpartition(en) (ROM) des Geräts.

Die unterstützten Operationen sind:

  • vfs.rom_ioctl(1) – Gibt die Anzahl der verfügbaren ROM-Partitionen zurück.

  • vfs.rom_ioctl(2, id) – Gibt die ROM-Partition mit dem Index id als memoryview-Objekt zurück. Der Speicher kann gelesen, aber nicht direkt geschrieben werden.

  • vfs.rom_ioctl(3, id, length) – Bereitet eine ROM-Partition zum Schreiben vor. Löscht die ersten length Bytes der Partition mit dem Index id. Gibt die minimale Schreibgröße in Bytes zurück (die für nachfolgende Schreibvorgänge erforderliche Ausrichtung).

  • vfs.rom_ioctl(4, id, offset, buf) – Schreibt buf (ein bytes-ähnliches Objekt) an den Byte-offset der ROM-Partition mit dem Index id.

  • vfs.rom_ioctl(5, id) – Schließt eine Schreibsequenz für die Partition id ab (führt alle nach dem Schreiben erforderlichen Abschlussschritte aus, z. B. das Leeren des Caches).

Diese Operationen werden intern von mpremote verwendet, um ROMFS-Images bereitzustellen. Die meisten Benutzer müssen vfs.rom_ioctl() nicht direkt aufrufen.

Beispiel (Abfrage der verfügbaren Partitionen):

import vfs

n = vfs.rom_ioctl(1)
print("Number of ROM partitions:", n)
for i in range(n):
    dev = vfs.rom_ioctl(2, i)
    print(f"  Partition {i}: {len(dev)} bytes")

Automatisches Einbinden beim Booten

Wenn die ROMFS-Unterstützung in der Firmware aktiviert ist, versucht MicroPython während der Initialisierung automatisch, die erste ROM-Partition unter /rom einzubinden. Wenn die Partition ein gültiges ROMFS-Image enthält, wird sie eingebunden und sowohl /rom als auch /rom/lib werden automatisch zu sys.path hinzugefügt.

Das bedeutet, dass nach der Bereitstellung eines ROMFS-Images mit mpremote ein Soft-Reset ausreicht, um die neuen Module importierbar zu machen.

Wenn in der Partition kein gültiges ROMFS-Image gefunden wird (z. B. auf einem frisch programmierten Board), wird das Einbinden stillschweigend übersprungen.

mpremote zur Verwaltung von ROMFS verwenden

Das Werkzeug mpremote stellt drei Unterbefehle zur Verwaltung von ROMFS-Images auf einem angeschlossenen Gerät bereit.

romfs query

$ mpremote romfs query

Listet alle verfügbaren ROMFS-Partitionen auf dem Gerät und ihre Größen auf. Zeigt außerdem die ersten 12 Bytes jeder Partition in Hexadezimaldarstellung an und meldet, ob ein gültiges ROMFS-Image vorhanden ist.

Beispielausgabe:

ROMFS0 partition has size 131072 bytes (32 blocks of 4096 bytes each)
  Raw contents: d2:cd:31:XX:XX:XX:XX:XX:XX:XX:XX:XX ...
  ROMFS image size: 1234

romfs build

$ mpremote romfs [-o <output>] build <source>

Erstellt ein ROMFS-Image aus dem Verzeichnis source auf dem Host-PC. Das Image wird nach output geschrieben (Standard: <source>.romfs).

Optionen:

  • -o <output>, --output <output>: Gibt den Pfad der Ausgabedatei an.

  • -m, --mpy (Standard): Kompiliert .py-Dateien automatisch mit mpy_cross zu .mpy, bevor sie dem Image hinzugefügt werden. Erfordert das Python-Paket mpy_cross (pip install mpy_cross).

  • --no-mpy: Deaktiviert die automatische Kompilierung von .py-Dateien.

Beispiel:

$ mpremote romfs build myapp/
Building romfs filesystem, source directory: myapp/
/
|-- main.py -> .mpy
\-- lib/
    \-- helper.py -> .mpy
Writing 2048 bytes to output file myapp.romfs

romfs deploy

$ mpremote romfs [-p <partition>] deploy <source>

Stellt ein ROMFS-Image auf dem Gerät bereit. source kann entweder sein:

  • Ein Verzeichnis auf dem Host: Das ROMFS-Image wird im Speicher erstellt und direkt bereitgestellt.

  • Eine .romfs- oder .img-Datei: Das Image wird von der Festplatte gelesen und bereitgestellt.

Optionen:

  • -p <partition>, --partition <partition>: Gibt den Index der Zielpartition an (Standard: 0).

  • -m, --mpy (Standard): Kompiliert .py zu .mpy, wenn source ein Verzeichnis ist.

  • --no-mpy: Deaktiviert die automatische Kompilierung von .py-Dateien.

Nach der Bereitstellung muss das Gerät einen Soft-Reset durchführen, damit das neue ROMFS unter /rom eingebunden wird.

Beispiel:

$ mpremote romfs deploy myapp/
Building romfs filesystem, source directory: myapp/
/
|-- main.py -> .mpy
\-- lib/
    \-- helper.py -> .mpy
Image size is 2048 bytes
ROMFS0 partition has size 131072 bytes (32 blocks of 4096 bytes each)
Preparing ROMFS0 partition for writing
Deploying ROMFS to ROMFS0 partition
ROMFS image deployed

$ mpremote soft-reset

Beispiele

Bereitstellung einer einfachen Anwendung

Angenommen, Sie haben ein Projektverzeichnis myapp/ mit der folgenden Struktur:

myapp/
    main.py
    utils.py
    lib/
        helper.py

Um es im ROMFS des Geräts bereitzustellen:

$ mpremote romfs deploy myapp/

Nach einem Soft-Reset sind die Module aus dem ROMFS importierbar:

import main
import utils
from lib import helper

ROMFS-Inhalte aus Python auflisten

Nach dem Einbinden können die ROMFS-Inhalte wie bei jedem anderen Dateisystem durchsucht werden:

import os

for entry in os.ilistdir('/rom'):
    print(entry)

# Or simply:
print(os.listdir('/rom'))

OpenMV liefert außerdem einen kleinen romfs-Helfer mit, der eine formatierte Auflistung einschließlich der speicherabgebildeten Adresse und Ausrichtung jeder Datei ausgibt:

from omv import romfs
romfs.ls_romfs()

Verschachteln eines ROMFS innerhalb eines ROMFS

Ein als Datei innerhalb eines äußeren ROMFS gespeichertes ROMFS-Image kann als verschachteltes Dateisystem eingebunden werden. Zum Beispiel, wenn /rom/inner.romfs existiert. Da /rom ein ROMFS ist, unterstützen daraus geöffnete Dateiobjekte das Buffer-Protokoll, sodass direkt ein Zero-Copy-memoryview erhalten werden kann:

import vfs

with open('/rom/inner.romfs', 'rb') as f:
    inner = vfs.VfsRom(memoryview(f))
vfs.mount(inner, '/inner')

print(os.listdir('/inner'))

ROMFS-Imageformat

Das ROMFS-Imageformat ist ein kompaktes Binärformat, das für den speicherabgebildeten Zugriff auf Mikrocontrollern konzipiert ist. Ein kurzer Überblick:

  • Das Image beginnt mit den Magic-Bytes 0xd2 0xcd 0x31 (kodiert als "RM1" mit gesetzten höchstwertigen Bits der ersten beiden Bytes).

  • Der Rest des Images besteht aus Records, von denen jeder ein Typ-Tag (varuint), eine Länge (varuint) und einen Payload besitzt.

  • Zu den Record-Typen gehören: Padding, Verbatim-Daten, indirekter Datenzeiger, Verzeichnis, Datei.

  • Verzeichnis- und Dateinamen werden als längenpräfixierte Byte-Strings gespeichert.

  • Dateidaten können verbatim (inline) oder über einen indirekten Zeiger an eine andere Stelle im Image gespeichert werden, was eine Ausrichtung für den speicherabgebildeten Zugriff ermöglicht.

  • Unbekannte Record-Typen werden stillschweigend übersprungen, was Vorwärtskompatibilität bietet.

Dieses Format ist in extmod/vfs_rom.c im MicroPython-Quellcode definiert. Die von mpremote zum Erstellen von Images verwendete Python-Implementierung befindet sich in tools/mpremote/mpremote/romfs.py.

Siehe auch

Arbeiten mit Dateisystemen – Überblick über das MicroPython-VFS und die verfügbaren Dateisystemtypen.

MicroPython-Manifest-Dateien – Wie man Python-Module in die Firmware einfriert.

MicroPython .mpy-Dateien – MicroPython-.mpy-Binärdateiformat.

MicroPython-Fernsteuerung: mpremote – Die vollständige mpremote-Befehlsreferenz.

romfs – OpenMV-Helfer zum Inspizieren des eingebundenen /rom-Dateisystems.