ファイルシステムの利用¶
目次
このチュートリアルでは、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 347 と littlefs 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_size
と block_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)¶
start
と len
という 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')