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

См.также

Внешние ссылки