MicroPython:Основы/Язык MicroPython и его реализация/Работа с файловыми системами
Работа с файловыми системами[1]
В этой статье рассказывается о том, как MicroPython предоставляет доступ к файловой системе, смонтированной на устройство, позволяя использовать при работе с постоянной энергонезависимой памятью стандартные Python’овские файловые методы чтения/записи.
MicroPython автоматически задает настройки по умолчанию и определяет главную файловую систему, поэтому это руководство будет полезно в первую очередь, если вам нужно переопределить разделы файловой системы, изменить ее тип или воспользоваться блочными устройствами с вашими настройками.
Файловая система обычно монтируется на внутреннюю flash-память устройства, но она также может располагаться на внешней flash-памяти, в RAM-памяти или на блочном устройстве.
На некоторых портах (например, на STM32-порте) доступ к файловой системе можно получить на ПК через USB-накопитель. С помощью инструмента pyboard.py ПК-хост может получить доступ к файловой системе на всех портах.
Примечание: Это касается, как правило, «голых» устройств вроде STM32 или ESP32. На портах с операционной системой (например, на Unix-порте) за файловую систему отвечает ОС хоста.
VFS
В MicroPython используется VFS (от англ. «virtual file system», что можно перевести как «виртуальная файловая система») на манер той, что используется в Unix. Все смонтированные файловые системы комбинируются в одну виртуальную файловую систему с корневой директорией «/». Файловые системы монтируются в директории этой структуры, и при запуске рабочей директорией становится та, куда смонтирована главная файловая система.
На STM32/Pyboard внутренняя flash-память монтируется в «/flash», а опциональная SD-карта – в «/sd». На ESP8266/ESP32 главная файловая система монтируется в «/».
Блочные устройства
Блочное устройство – это экземпляр класса, в котором реализован протокол uos.AbstractBlockDev.
Встроенные блочные устройства
В некоторых портах есть встроенные блочные устройства для доступа к их основной flash-памяти.
При включении MicroPython попытается определить файловую систему на основной flash-памяти, а также автоматически настроить и смонтировать ее. Если файловой системы найдено не будет, MicroPython попытается создать FAT-систему на всей flash-памяти. У используемого вами порта также может быть механизм сброса основной flash-памяти к заводским настройкам – обычно это выполняется нажатием на определенные кнопки при включении.
STM32/Pyboard
На этих платформах доступ к внутренней flash-памяти осуществляется с помощью класса pyb.Flash. На некоторых платах с большой внешней памятью (вроде Pyboard D) именно она будет использоваться вместо внутренней flash-памяти. При этом обязательно нужно указать именованный аргумент (kwarg) start – например, pyb.Flash(start=0).
Примечание: При конструировании без аргументов – например, pyb.Flash() – в этом классе будет реализован только простой блочной интерфейс и отражено виртуальное устройство, показываемое USB-накопителю (т.е. на старте в нем будет виртуальная таблица разделов). Это сделано в целях обратной совместимости.
ESP8266
Внутренняя flash-память отображается в виде объекта блочного устройства, создаваемого на запуске в модуле flashbdev. По умолчанию этот объект добавляется как глобальная переменная, поэтому, как правило, доступ к нему можно получить просто через bdev. Это реализует расширенный интерфейс.
ESP32
Класс esp32.Partition реализует блочное устройство для разделов, заданных для платы. Как и в случае с ESP8266, в нем есть глобальная переменная bdev, в которой задано базовое исходное разделение. Это реализует расширенный интерфейс.
Настраиваемые блочные устройства
Класс ниже реализует простое блочное устройство, которое хранит свои данные в RAM-памяти при помощи bytearray:
class RAMBlockDev:
def __init__(self, block_size, num_blocks):
self.block_size = block_size
self.data = bytearray(block_size * num_blocks)
def readblocks(self, block_num, buf):
for i in range(len(buf)):
buf[i] = self.data[block_num * self.block_size + i]
def writeblocks(self, block_num, buf):
for i in range(len(buf)):
self.data[block_num * self.block_size + i] = buf[i]
def ioctl(self, op, arg):
if op == 4: # считываем количество блоков
return len(self.data) // self.block_size
if op == 5: # считываем размер блока
return self.block_size
Воспользоваться им можно следующим образом:
import os
bdev = RAMBlockDev(512, 50)
os.VfsFat.mkfs(bdev)
os.mount(bdev, '/ramdisk')
Ниже показан пример блочного устройства, поддерживающего простой и расширенный интерфейс – т.е. одновременно сигнатуры и поведение обоих форм методов uos.AbstractBlockDev.readblocks() и uos.AbstractBlockDev.writeblocks():
class RAMBlockDev:
def __init__(self, block_size, num_blocks):
self.block_size = block_size
self.data = bytearray(block_size * num_blocks)
def readblocks(self, block_num, buf, offset=0):
addr = block_num * self.block_size + offset
for i in range(len(buf)):
buf[i] = self.data[addr + i]
def writeblocks(self, block_num, buf, offset=None):
if offset is None:
# сначала стираем, потом записываем:
for i in range(len(buf) // self.block_size):
self.ioctl(6, block_num + i)
offset = 0
addr = block_num * self.block_size + offset
for i in range(len(buf)):
self.data[addr + i] = buf[i]
def ioctl(self, op, arg):
if op == 4: # счетчик блоков
return len(self.data) // self.block_size
if op == 5: # размер блока
return self.block_size
if op == 6: # стирание блока
return 0
Поскольку это блочное устройство поддерживает расширенный интерфейс, его можно использовать с littlefs:
import os
bdev = RAMBlockDev(512, 50)
os.VfsLfs2.mkfs(bdev)
os.mount(bdev, '/ramdisk')
Когда файловая система будет смонтирована, ее (независимо от типа) можно использовать так, как ее обычно используют в Python-коде. Например:
with open('/ramdisk/hello.txt', 'w') as f:
f.write('Hello world')
print(open('/ramdisk/hello.txt').read())
Файловые системы
MicroPython-портами поддерживаются файловые системы FAT, littlefs v1 и littlefs v2.
В таблице ниже показано, какие файловые системы включены в прошивку по умолчанию в разных портах/платах (они, впрочем, могут быть опционально включены в пользовательскую прошивку):
Плата | FAT | littlefs v1 | littlefs v2 |
---|---|---|---|
pyboard 1.0, 1.1, D | Да | Нет | Да |
Другие STM32 | Да | Нет | Нет |
ESP8266 (1M) | Нет | Нет | Да |
ESP8266 (2M+) | Да | Нет | Да |
ESP32 | Да | Нет | Да |
FAT
Главное преимущество файловой системы типа FAT в том, что доступ к ней можно получить с помощью USB-накопителя (на тех платах, где это поддерживается – например, на STM32) безо всяких дополнительных драйверов для ПК-хоста.
Однако FAT не терпима к перебоям питания во время записи – это может привести к повреждению файловой системы. Поэтому в приложениях, не требующих использования USB-накопителя, рекомендуем использовать файловую систему типа littlefs.
Чтобы отформатировать всю flash-память при помощи FAT, выполните следующее:
# ESP8266 и ESP32:
import os
os.umount('/')
os.VfsFat.mkfs(bdev)
os.mount(bdev, '/')
# STM32:
import os, pyb
os.umount('/flash')
os.VfsFat.mkfs(pyb.Flash(start=0))
os.mount(pyb.Flash(start=0), '/flash')
os.chdir('/flash')
Littlefs
Littlefs – это файловая система, разработанная специально устройств, полагающихся на flash-память. Она гораздо более устойчива к повреждениям.
Примечание: По сообщениям некоторых пользователей, littlefs v1 и littlefs v2 в некоторых ситуациях все же дают сбой. Более подробно читайте тут и тут.
Примечание: Доступ к файловой системе littlefs можно получить и при помощи USB-накопителя – для этого нужно воспользоваться драйвером «littlefs-fuse». Помните, чтобы изменить размер блока, вам нужно будет воспользоваться опцией -b=4096.
Чтобы отформатировать всю flash-память при помощи littlefs v2, выполните следующее:
# ESP8266 и ESP32:
import os
os.umount('/')
os.VfsLfs2.mkfs(bdev)
os.mount(bdev, '/')
# STM32:
import os, pyb
os.umount('/flash')
os.VfsLfs2.mkfs(pyb.Flash(start=0))
os.mount(pyb.Flash(start=0), '/flash')
os.chdir('/flash')
Гибридная система (STM32)
Вы можете создавать блочные устройства, занимающие лишь часть flash-памяти – это делается с помощью именованных аргументов start и len в pyb.Flash.
К примеру, чтобы настроить первые 256 Кб как FAT (и сделать их доступными через USB-накопитель), а всю остальную память – как littlefs, выполните следующее:
import os, pyb
os.umount('/flash')
p1 = pyb.Flash(start=0, len=256*1024)
p2 = pyb.Flash(start=256*1024)
os.VfsFat.mkfs(p1)
os.VfsLfs2.mkfs(p2)
os.mount(p1, '/flash')
os.mount(p2, '/data')
os.chdir('/flash')
То есть благодаря этому вы можете настроить доступ к Python-файлам, настройкам и прочему редко изменяемому контенту через USB-накопитель, а часто изменяемые данные приложения – разместить на littlefs, которая более устойчива к перебоям питания и т.д.
Разбитие на разделы со смещением «0» будет смонтировано и определение типа файловой системы будут выполнены автоматически, но если вы хотите сделать раздел «/data», в boot.py можно добавить следующее:
import os, pyb
p2 = pyb.Flash(start=256*1024)
os.mount(p2, '/data')
Гибридная система (ESP32)
Если вы создаете свою прошивку для ESP32, то собственное разбиение на разделы можно задать, отредактировав partitions.csv.
При загрузке раздел под названием «vfs» по умолчанию будет смонтирован в «/», а дополнительные разделы можно создать в boot.py:
import esp32, os
p = esp32.Partition.find(esp32.Partition.TYPE_DATA, label='foo')
os.mount(p, '/foo')