ファイルシステムの利用

このチュートリアルでは、MicroPython がデバイスでファイルシステムを提供し、永続ストレージで Python の標準的なファイル I/O メソッドを使えるようにする方法を説明します。

MicroPython はデフォルトの構成を自動的に作成し、プライマリファイルシステムを自動で検出します。ですから、このチュートリアルはパーティショニングやファイルシステムの種別を変更したい場合やカスタムブロックデバイスを使いたい場合に役立つでしょう。

ファイルシステムは通常、デバイスの内蔵フラッシュメモリ上に存在しますが、外部フラッシュ、RAM、カスタムブロックデバイスでも使えます。

いくつかのポート(STM32 など)では、USB MSC を介してホスト PC にファイルシステムをマウントできます。 The pyboard.py tool ツールは、ホスト PC がすべてのポートのファイルシステムにアクセスする方法を提供します。

注記: このチュートリアルで扱うファイルシステムは、主に STM32 や ESP32 のようなベアメタルポートで使うためのものです。オペレーティングシステムがあるポート(Unix ポートなど)では、ホストOSがファイルシステムを提供します。

VFS

MicroPython は Unix ライクな仮想ファイルシステム(VFS)レイヤーを実装しています。マウントしたファイルシステムはすべて、ルート / 配下の1つの仮想ファイルシステムにまとめられます。ファイルシステムはこの構造のディレクトリにマウントされ、起動時にはプライマリファイルシステムがマウントされた場所に作業ディレクトリが変更されます。

STM32 / Pyboard では、内蔵フラッシュは /flash に、SD カードは /sd にマウントされます。ESP8266/ESP32 では、プライマリファイルシステムは / にマウントされます。

ブロックデバイス

ブロックデバイスは os.AbstractBlockDev プロトコルを実装したクラスのインスタンスです。

組込みブロックデバイス

ポートは、プライマリフラッシュにアクセスするための組込みブロックデバイスを提供します。

電源投入時、MicroPython はデフォルトのフラッシュ上のファイルシステムを検出し、自動的に構成してマウントしようとします。ファイルシステムが見つからない場合、MicroPython はフラッシュ全体の FAT ファイルシステムを作成しようとします。ポートはプライマリフラッシュを「工場出荷時にリセット」するための機構も提供できます。

STM32 / Pyboard

pyb.Flash クラスは内部フラッシュへのアクセスを提供します。大きな外部フラッシュを持つボード(Pyboard D など)でも、このクラスを使うことがあります。キーワード引数 start は常に指定する必要があります。つまり pyb.Flash(start=0) のように呼び出してオブジェクトを作成します。

注記: 下位互換性のために、引数なしで構築された場合(つまり pyb.Flash() のように呼び出してオブジェクトを作成した場合)、シンプルなブロックインターフェースのみを実現し、USB MSC に提示した仮想デバイスを反映します(すなわち、開始時に仮想パーティションテーブルを含みます)。

ESP8266

内蔵フラッシュは、起動時に flashbdev モジュールで作成されるブロックデバイスオブジェクトとして公開されます。このオブジェクトはデフォルトでグローバル変数として追加されているので、通常は単純に bdev としてアクセスできます。これは拡張インターフェースを実装しています。

ESP32

esp32.Partition クラスは、このボード用に定義したパーティションのブロックデバイスを実装しています。ESP8266と同様に、デフォルトのパーティションを指すグローバル変数 bdev があります。これは拡張インターフェースを実装しています。

カスタムブロックデバイス

次のクラスは bytearray を使って RAM にデータを格納するシンプルなブロックデバイスを実装しています:

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

次に示すのは、シンプルインタフェースと拡張インタフェースの両方(つまり、 os.AbstractBlockDev.readblocks()os.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 ファイルシステムの主な利点は、ホスト PC 上で追加ドライバーを必要とせずに、サポートしているボード(STM32など)に USB MSC を介してアクセスできることです。

しかし、FAT は書き込み中の電源断には耐性がなく、ファイルシステムの破損につながる可能性があります。USB MSC を必要としないアプリケーションでは、代わりに littlefs の利用をお勧めします。

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 はフラッシュベースのデバイス向けに設計されたファイルシステムで、ファイルシステムの破損に非常に強くなっています。

注釈

littlefs v1 と v2 では特定の状況で失敗するという報告があります。詳しくは littlefs issue 347littlefs issue 295 を参照してください。

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

littlefs ファイルシステムは、 littlefs FUSE ドライバ を使って、USB MSC 経由で PC からアクセスできます。デフォルトを上書きするためには --block_size--block_count の両方のオプションを指定しなければならないことに注意してください。たとえば、(littlefs-fuse 実行ファイルをビルドした後に)次のようにします:

$ ./lfs --block_size=4096 --block_count=512 -o allow_other /dev/sdb1 mnt

これにより、ボードの littlefs ファイルシステムが mnt ディレクトリでアクセスできるようになります。 block_sizeblock_count の正しい値を得るには、次のようにします:

import pyb
f = pyb.Flash(start=0)
f.ioctl(1, 1)  # initialise flash in littlefs raw-block mode
block_count = f.ioctl(4, 0)
block_size = f.ioctl(5, 0)

ハイブリッド (STM32)

startlen という pyb.Flash のキーワード引数を使えば、フラッシュデバイスを分割して複数のブロックデバイスを作成できます。

たとえば次のようにすると、最初の 256kiB を FAT (USB MSCで利用可能)として設定し、残りを 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 MSC 経由で利用できる一方で、頻繁に変更されるアプリケーションのデータを停電などへの耐性を持った littlefs 上に置けるようになります。

オフセット 0 のパーティションは自動的にマウントされますが(ファイルシステムの種別は自動で検出されます)、次のようにしてマウントを追加することもできます:

import os, pyb
p2 = pyb.Flash(start=256*1024)
os.mount(p2, '/data')

このようなパーティションのマウントの記述は boot.py に書くとよいでしょう。

ハイブリッド (ESP32)

ESP32 では、カスタムファームウェアを作成するときに、 partitions.csv を変更して任意のパーティションレイアウトを定義できます。

ブート時、デフォルトでは "vfs" という名前のパーティションが / にマウントされますが、追加のパーティションは boot.py で以下のようにしてマウントできます:

import esp32, os
p = esp32.Partition.find(esp32.Partition.TYPE_DATA, label='foo')
os.mount(p, '/foo')