ROMFS の利用

概要

ROMFS(Read-Only Memory Filesystem)は、MicroPython デバイス向けに設計された軽量な読み取り専用ファイルシステムです。コードやデータをフラッシュメモリに格納し、RAM にコピーすることなく効率的にアクセスする必要があるマイクロコントローラや組み込みシステム向けに最適化されています。

ROMFS の主な利点は次のとおりです。

  • ゼロコピーインポート: ROMFS に格納された .mpy バイトコードファイルは、いったん RAM にコピーされるのではなく、フラッシュメモリから直接(メモリマップして)実行できます。これは フリーズモジュール の仕組みに似ていますが、ファームウェア全体の再フラッシュを必要としません。

  • 低い RAM オーバーヘッド: ROMFS から読み込まれた .mpy ファイル内の定数オブジェクト(文字列、バイト列など)は、RAM に複製されるのではなくフラッシュから直接参照されます。

  • 柔軟なデプロイ: ROMFS イメージはホスト PC 上でビルドし、ファームウェアを再ビルドすることなく mpremote を使ってデバイスにデプロイできます。

  • 標準的なファイルシステムインターフェース: ROMFS は VFS にマウントされ、通常の Python のファイル操作(openos.listdirimport など)でアクセスできます。

ROMFS は、読み書き可能な FAT/LittleFS ファイルシステム(他のフラッシュパーティションに存在します)と フリーズモジュール(ファームウェア自体にコンパイルされます)の両方を補完するものです。

ボードサポート

ROMFS は、フラッシュレイアウト内に ROMFS パーティションが予約されているすべてのカメラボードの OpenMV ファームウェアで有効になっています。これらのボードでは、起動時に ROMFS パーティションが自動検出されて /rom にマウントされます。また、/rom/rom/lib の両方が sys.path に追加されるため、そこに格納されたモジュールを直接インポートできます。

ボード

ROMFS サポート

OpenMV Cam N6

あり

OpenMV AE3

あり

OpenMV Cam RT1062

あり

OpenMV Cam Pure Thermal

あり

OpenMV Cam M4 / M7 / H7 / H7 Plus

あり

Arduino Giga

あり

Arduino Portenta H7

あり

Arduino Nicla Vision

あり

Arduino Nano 33 BLE Sense

なし(ROMFS パーティションなし)

Arduino Nano RP2040 Connect

なし(ROMFS パーティションなし)

/rom にマウントされた ROMFS を検査する OpenMV 固有のヘルパーについては romfs を参照してください。

ワークフロー

ROMFS を使用する際の一般的なワークフローは次のとおりです。

  1. デプロイしたい Python ファイル(または .mpy ファイル)を含むディレクトリを PC 上に作成します。

  2. mpremote romfs deploy <directory> を使用して ROMFS イメージをビルドし、デバイスにデプロイします。

  3. ROMFS は次回起動時に /rom にマウントされます(デバイスを再起動すればすぐにマウントすることもできます)。

  4. その後、デバイス上の Python コードは、他のファイルシステムと同じように ROMFS からモジュールを import できます。

例えば次のようになります:

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

ソフトリセット後、デバイスでは /rom/app.pympy_cross がインストールされている場合は /rom/app.mpy)がインポート可能になります。

mpremote のサブコマンドの詳細については、後述の mpremote romfs サブコマンド セクションを参照してください。

Python API

ROMFS の Python API は vfs モジュールを介して提供されます。

class vfs.VfsRom(buffer)

buffer から ROMFS ファイルシステムオブジェクトを作成します。buffer は、有効な ROMFS イメージを含む、バッファプロトコルをサポートするオブジェクト(例: bytesbytearraymemoryview オブジェクト)でなければなりません。

コンストラクタは、buffer が ROMFS のマジックバイト(b"\xd2\xcd\x31")で始まることを検証します。バッファが小さすぎる場合や有効な ROMFS でない場合は OSError(ENODEV) が発生します。

このコンストラクタで作成したオブジェクトは vfs.mount() を使ってマウントできます。

例:

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')

または、ファイルに格納された ROMFS イメージをマウントするには次のようにします:

import vfs

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

VfsRom オブジェクトでは次のメソッドが利用できます。

VfsRom.open(path, mode)

ROMFS からファイルを開きます。読み取りモード('''r''rt''rb')のみがサポートされます。書き込み用にファイルを開こうとすると OSError(EROFS) が発生します。

返されるファイルオブジェクトは read()seek()tell()close() をサポートします。読み取りモードで開かれたバイナリファイルの場合、返されるオブジェクトはバッファプロトコルもサポートするため、ファイルデータの memoryview を取得できます。これは ROMFS のメモリを直接参照します(ゼロコピー)。

VfsRom.ilistdir(path)

ディレクトリ path 内のエントリを反復処理するイテレータを返します。各エントリはタプル (name, type, inode, size) で、type はファイルの場合 0x8000、ディレクトリの場合 0x4000 です。

VfsRom.stat(path)

path に対して os.stat のような 10 要素のタプルを返します。パスが存在しない場合は OSError(ENOENT) が発生します。

VfsRom.statvfs(path)

ファイルシステムの統計情報を返します。ブロックサイズは 1 として報告され、ブロック数は ROMFS イメージの合計サイズ(バイト単位)を表します。空きブロックと空きファイルは常に 0 です(読み取り専用ファイルシステムのため)。

VfsRom.chdir(path)

ROMFS 内でディレクトリを変更します。サポートされているのはルート('/')のみで、サブディレクトリへの変更は OSError(EOPNOTSUPP) を発生させます。

VfsRom.getcwd()

ROMFS 内の現在の作業ディレクトリを返します。常に '/' を返します。

vfs.rom_ioctl(op, ...)

デバイスの読み取り専用メモリ(ROM)パーティションにアクセスするための低レベルインターフェースです。

サポートされている操作は次のとおりです。

  • vfs.rom_ioctl(1) -- 利用可能な ROM パーティションの数を返します。

  • vfs.rom_ioctl(2, id) -- インデックス id の ROM パーティションを memoryview オブジェクトとして返します。このメモリは読み取りできますが、直接書き込むことはできません。

  • vfs.rom_ioctl(3, id, length) -- ROM パーティションを書き込み用に準備します。インデックス id のパーティションの先頭 length バイトを消去します。最小書き込みサイズ(後続の書き込みに必要なアライメント)をバイト単位で返します。

  • vfs.rom_ioctl(4, id, offset, buf) -- インデックス id の ROM パーティションのバイトオフセット offsetbuf(bytes ライクなオブジェクト)を書き込みます。

  • vfs.rom_ioctl(5, id) -- パーティション id への書き込みシーケンスを完了します(キャッシュフラッシュなど、書き込み後に必要な最終処理を実行します)。

これらの操作は、ROMFS イメージをデプロイするために mpremote によって内部的に使用されます。ほとんどのユーザーは vfs.rom_ioctl() を直接呼び出す必要はありません。

例(利用可能なパーティションの照会):

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")

起動時の自動マウント

ファームウェアで ROMFS サポートが有効になっている場合、MicroPython は初期化中に最初の ROM パーティションを /rom に自動的にマウントしようとします。パーティションに有効な ROMFS イメージが含まれていれば、それがマウントされ、/rom/rom/lib の両方が自動的に sys.path に追加されます。

つまり、mpremote で ROMFS イメージをデプロイした後は、ソフトリセットを行うだけで新しいモジュールをインポート可能にできます。

パーティションに有効な ROMFS イメージが見つからない場合(例: プログラム書き込み直後のボード)は、マウントは何も告げずにスキップされます。

mpremote を使用した ROMFS の管理

mpremote ツールは、接続されたデバイス上で ROMFS イメージを管理するための 3 つのサブコマンドを提供します。

romfs query

$ mpremote romfs query

デバイス上で利用可能なすべての ROMFS パーティションとそのサイズを一覧表示します。また、各パーティションの先頭 12 バイトを 16 進数で表示し、有効な ROMFS イメージが存在するかどうかを報告します。

出力例:

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>

ホスト PC 上のディレクトリ source から ROMFS イメージをビルドします。イメージは output(デフォルト: <source>.romfs)に書き込まれます。

オプション:

  • -o <output>--output <output>: 出力ファイルのパスを指定します。

  • -m--mpy(デフォルト): イメージに追加する前に、mpy_cross を使用して .py ファイルを .mpy に自動的にコンパイルします。mpy_cross Python パッケージ(pip install mpy_cross)が必要です。

  • --no-mpy: .py ファイルの自動コンパイルを無効にします。

例:

$ 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>

ROMFS イメージをデバイスにデプロイします。source には次のいずれかを指定できます。

  • ホスト上のディレクトリ: ROMFS イメージがメモリ内でビルドされ、直接デプロイされます。

  • .romfs または .img ファイル: イメージがディスクから読み込まれてデプロイされます。

オプション:

  • -p <partition>--partition <partition>: 対象パーティションのインデックスを指定します(デフォルト: 0)。

  • -m--mpy(デフォルト): source がディレクトリの場合に .py.mpy にコンパイルします。

  • --no-mpy: .py ファイルの自動コンパイルを無効にします。

デプロイ後、新しい ROMFS を /rom にマウントするには、デバイスをソフトリセットする必要があります。

例:

$ 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

シンプルなアプリケーションのデプロイ

次のような構成のプロジェクトディレクトリ myapp/ があるとします:

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

これをデバイスの ROMFS にデプロイするには次のようにします:

$ mpremote romfs deploy myapp/

ソフトリセット後、モジュールは ROMFS からインポート可能になります:

import main
import utils
from lib import helper

Python から ROMFS の内容を一覧表示する

マウント後、ROMFS の内容は他のファイルシステムと同じように調べることができます:

import os

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

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

OpenMV には、各ファイルのメモリマップアドレスとアライメントを含む整形済みの一覧を表示する小さな romfs ヘルパーも付属しています:

from omv import romfs
romfs.ls_romfs()

ROMFS 内での ROMFS のネスト

外側の ROMFS 内にファイルとして格納された ROMFS イメージは、ネストされたファイルシステムとしてマウントできます。例えば、/rom/inner.romfs が存在する場合です。/rom は ROMFS であるため、そこから開いたファイルオブジェクトはバッファプロトコルをサポートし、ゼロコピーの memoryview を直接取得できます:

import vfs

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

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

ROMFS イメージフォーマット

ROMFS イメージフォーマットは、マイクロコントローラ上でのメモリマップアクセス向けに設計されたコンパクトなバイナリフォーマットです。簡単に説明すると次のようになります。

  • イメージはマジックバイト 0xd2 0xcd 0x31(先頭 2 バイトの上位ビットを立てて "RM1" としてエンコードされたもの)で始まります。

  • イメージの残りの部分は レコード で構成され、それぞれが型タグ(varuint)、長さ(varuint)、ペイロードを持ちます。

  • レコードの型には、パディング、verbatim データ、間接データポインタ、ディレクトリ、ファイルがあります。

  • ディレクトリ名とファイル名は、長さプレフィックス付きのバイト文字列として格納されます。

  • ファイルデータは、verbatim(インライン)で格納するか、イメージ内の別の場所への間接ポインタを介して格納できます。後者はメモリマップアクセスのためのアライメントを可能にします。

  • 不明なレコードの型は何も告げずにスキップされ、前方互換性を提供します。

このフォーマットは MicroPython のソースの extmod/vfs_rom.c で定義されています。mpremote がイメージをビルドするために使用する Python 実装は tools/mpremote/mpremote/romfs.py にあります。

参考

ファイルシステムの利用 -- MicroPython の VFS と利用可能なファイルシステムタイプの概要。

MicroPython マニフェストファイル -- Python モジュールをファームウェアにフリーズする方法。

MicroPython .mpy ファイル -- MicroPython の .mpy バイナリファイルフォーマット。

MicroPython リモートコントロール: mpremote -- mpremote コマンドの完全なリファレンス。

romfs -- マウントされた /rom ファイルシステムを検査するための OpenMV ヘルパー。