Работа с ROMFS

Обзор

ROMFS (Read-Only Memory Filesystem) – это лёгкая файловая система только для чтения, разработанная для устройств MicroPython. Она оптимизирована для микроконтроллеров и встраиваемых систем, где код и данные нужно хранить во флеш-памяти и эффективно обращаться к ним без копирования в RAM.

Основные преимущества ROMFS:

  • Импорт без копирования: файлы байт-кода .mpy, хранящиеся в ROMFS, могут выполняться непосредственно из флеш-памяти (через отображение в память), а не копироваться сначала в RAM. Это похоже на работу замороженных модулей, но не требует повторной перепрошивки всей прошивки.

  • Низкие накладные расходы RAM: константные объекты (строки, байты и т. д.) в файлах .mpy, загруженных из ROMFS, ссылаются напрямую на флеш-память и не дублируются в RAM.

  • Гибкое развёртывание: образ ROMFS можно собрать на хост-ПК и развернуть на устройстве с помощью mpremote, не пересобирая прошивку.

  • Стандартный интерфейс файловой системы: ROMFS монтируется в VFS и доступен через обычные файловые операции Python (open, os.listdir, import и т. д.).

ROMFS дополняет как файловые системы FAT/LittleFS с доступом для чтения и записи (которые располагаются в других разделах флеш-памяти), так и замороженные модули (которые компилируются в саму прошивку).

Поддержка плат

ROMFS включён в прошивку OpenMV на каждой плате камеры, в схеме флеш-памяти которой зарезервирован раздел ROMFS. На этих платах раздел 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)

См. romfs для специфичного для OpenMV помощника, который исследует смонтированный ROMFS в /rom.

Рабочий процесс

Типичный рабочий процесс использования ROMFS таков:

  1. Создайте на своём ПК каталог с файлами Python (или файлами .mpy), которые вы хотите развернуть.

  2. Используйте mpremote romfs deploy <directory>, чтобы собрать и развернуть образ ROMFS на устройстве.

  3. ROMFS будет смонтирован в /rom при следующей загрузке (или может быть смонтирован сразу, если устройство перезагрузить).

  4. Затем код Python на устройстве может выполнять import модулей из ROMFS точно так же, как из любой другой файловой системы.

Например:

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

После программного сброса на устройстве будет доступен для импорта файл /rom/app.py (или /rom/app.mpy, если установлен mpy_cross).

Полное описание подкоманд mpremote см. в разделе подкоманды mpremote romfs ниже.

Python API

Python API для ROMFS предоставляется через модуль vfs.

class vfs.VfsRom(buffer)

Создаёт объект файловой системы ROMFS из buffer, который должен быть объектом, поддерживающим протокол буфера (например, объектом bytes, bytearray или memoryview) и содержащим корректный образ ROMFS.

Конструктор проверяет, что 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)

Возвращает 10-элементный кортеж в стиле os.stat для path. Возбуждает 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) – возвращает раздел ROM с индексом id в виде объекта memoryview. Эту память можно читать, но нельзя записывать напрямую.

  • vfs.rom_ioctl(3, id, length) – подготавливает раздел ROM к записи. Стирает первые length байт раздела с индексом id. Возвращает минимальный размер записи в байтах (выравнивание, требуемое для последующих записей).

  • vfs.rom_ioctl(4, id, offset, buf) – записывает buf (объект, подобный bytes) в раздел ROM с индексом id по байтовому смещению offset.

  • vfs.rom_ioctl(5, id) – завершает последовательность записи в раздел id (выполняет любую необходимую финализацию после записи, например сброс кэша).

Эти операции используются внутренне инструментом mpremote для развёртывания образов ROMFS. Большинству пользователей не нужно вызывать 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.

Это означает, что после развёртывания образа ROMFS с помощью mpremote достаточно программного сброса, чтобы новые модули стали доступны для импорта.

Если в разделе не найден корректный образ ROMFS (например, на только что запрограммированной плате), монтирование молча пропускается.

Использование mpremote для управления ROMFS

Инструмент mpremote предоставляет три подкоманды для управления образами ROMFS на подключённом устройстве.

romfs query

$ mpremote romfs query

Выводит список всех доступных разделов ROMFS на устройстве и их размеры. Также показывает первые 12 байт каждого раздела в шестнадцатеричном виде и сообщает, присутствует ли корректный образ 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>

Собирает образ ROMFS из каталога source на хост-ПК. Образ записывается в output (по умолчанию: <source>.romfs).

Параметры:

  • -o <output>, --output <output>: задаёт путь к выходному файлу.

  • -m, --mpy (по умолчанию): автоматически компилирует файлы .py в .mpy с помощью mpy_cross перед добавлением их в образ. Требует пакет Python mpy_cross (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 (по умолчанию): компилирует .py в .mpy, когда source – это каталог.

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

Просмотр содержимого ROMFS из Python

После монтирования содержимое 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 (закодированных как "RM1" с установленными старшими битами первых двух байт).

  • Остальная часть образа состоит из записей, каждая из которых имеет тег типа (varuint), длину (varuint) и полезную нагрузку.

  • Типы записей включают: заполнение, дословные данные, косвенный указатель данных, каталог, файл.

  • Имена каталогов и файлов хранятся как байтовые строки с префиксом длины.

  • Данные файла могут храниться дословно (встроенно) или через косвенный указатель на другое место в образе, что обеспечивает выравнивание для отображённого в память доступа.

  • Неизвестные типы записей молча пропускаются, обеспечивая совместимость с будущими версиями.

Этот формат определён в файле extmod/vfs_rom.c в исходном коде MicroPython. Реализация на Python, используемая mpremote для сборки образов, находится в tools/mpremote/mpremote/romfs.py.

См. также

Работа с файловыми системами – обзор VFS MicroPython и доступных типов файловых систем.

Файлы манифеста MicroPython – как замораживать модули Python в прошивку.

Файлы .mpy в MicroPython – двоичный формат файлов .mpy MicroPython.

Удалённое управление MicroPython: mpremote – полный справочник команд mpremote.

romfs – помощник OpenMV для исследования смонтированной файловой системы /rom.