本文介绍了MicroPython如何提供并使用设备上的文件系统,以及如何使用Python标准的I/O方法进行持久存储。MicroPython会自动创建默认配置并侦测主文件系统,同时支持修改分区、文件系统类型或自定义块设备。
文件系统通常由设备上的内部闪存支持,但也可以使用外部闪存、RAM 或自定义块设备。在某些端口(如 STM32)上,文件系统可以通过 USB MSC 连接到PC主机。pyboard.py 工具还为主机访问所有端口上的文件系统提供了一种途径。
**注意:**这主要用于 STM32 和 ESP32 等裸机端口,在带有操作系统的端口上(如 Unix 端口),文件系统由主机操作系统提供。
MicroPython实现了类似Unix的虚拟文件系统层,所有挂载的文件系统都被整合到一个根路径为/
的虚拟文件系统。文件系统被挂载到该目录结构,并且在启动时工作目录会被更改到主文件系统的挂载目录。
在STM32/Pyboard
上,内置闪存被挂载在/flash
,可选的SD卡被挂载在/sd
.而ESP8266/ESP32
的主文件系统则被挂载在/
。
块设备是实现了os.AbstractBlockDev
协议类的实例。
端口提供内置块设备以访问其主闪存。
开机时,MicroPython 会尝试侦测默认闪存上的文件系统,并自动进行配置并加载,如果找不到文件系统,MicroPython 会尝试为整个闪存创建一个FAT文件系统。端口还可以提供"出厂重置"主闪存的机制,通常是在开机时通过按键组合来实现。
pyb.Flash
类可以访问内部闪存,对于某些有大容量外部闪存的板子(如:Pyboard D)则会使用外部闪存,必须要指定start
参数,如:pyb.Flash(start=0)
。
**注意:**为实现向后兼容性,不带参数的情况下(即 pyb.Flash()),仅实现简单的块接口,并反映 USB MSC 所显示的虚拟设备(即在开始时包含一个虚拟分区表)。
内部闪存以块设备对象的形式呈现,启动时在 flashbdev
模块中创建。默认情况下,该块设备对象被添加为全局变量,通常只需使用 bdev
即可访问。它实现了扩展接口。
esp32.Partition
类为电路板定义的分区实现了一个块设备。与 ESP8266 类似,有一个指向默认分区的全局变量 bdev
。它实现了扩展接口。
下面的类实现了一个简单的块设备,它使用bytearry
将数据存储在内存中:
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 文件系统的主要优点是可以通过已支持的电路板(如 STM32)上的 USB MSC 进行访问,无需在主机 PC 上安装任何额外的驱动程序。
不过,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 v1 和 v2 在某些情况下会出现故障,详情请参见 littlefs 第 347 期和 littlefs 第 295 期。
使用 littlefs v2 格式化整个闪存:
# ESP8266 and 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 FUSE 驱动程序,还可以在电脑上通过 USB MSC 访问 littlefs 文件系统。请注意,必须同时指定 --block_size 和 --block_count 选项才能覆盖默认值。例如(在构建 littlefs-fuse 可执行文件后):
$ ./lfs --block_size=4096 --block_count=512 -o allow_other /dev/sdb1 mnt
这将允许在 mnt
目录下访问主板的 littlefs
文件系统。要获取 block_size
和 block_count
的正确值,请使用:
import pyb
f = pyb.Flash(start=0)
f.ioctl(1, 1) # 在 littlefs raw-block 模式下,
block_count = f.ioctl(4, 0)
block_size = f.ioctl(5, 0)
通过使用 pyb.Flash
的 start
和 len
参数,可以创建跨闪存设备子集的块设备。
例如,将前 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')
这对于通过 USB MSC 提供 Python 文件、配置和其他很少修改的内容可能很有用,但也允许将频繁更改的应用程序数据驻留在 littlefs 上,以更好地应对断电等情况。
偏移量 0 处的分区将自动挂载(文件系统类型也会自动检测),但也可以添加以下内容:
import os, pyb
p2 = pyb.Flash(start=256*1024)
os.mount(p2, '/data')
到 boot.py 以挂载数据分区。
在 ESP32 上,如果构建了自定义固件,可以修改 partitions.csv 来定义任意分区布局。
启动时,名为 "vfs "的分区默认挂载在/位置,但也可以使用 boot.py 挂载其他分区:
import esp32, os
p = esp32.Partition.find(esp32.Partition.TYPE_DATA, label='foo')
os.mount(p, '/foo')