使用文件系统 内容 使用文件系统 3 b& N2 F& t( t8 {' j: P! `) ^
虚拟FS 块设备 % I* L' W6 A1 F8 G8 j: Y
内置块设备 ) Q( o4 B* T8 C Q- F' @) A+ L, ]
自定义块设备 g. `/ @* k3 ^& H' {: ]5 W7 k
文件系统
; G# ]1 }/ @; ]5 J* q6 }9 B2 |$ h1 I q2 |/ w/ h- G( r* ^; l- }
0 N/ @$ o/ {1 V9 A- k
/ [* T* H3 O8 f, t% y0 u u6 m& H本教程介绍 MicroPython 如何提供设备上的文件系统,允许将标准 Python 文件 I/O 方法与持久存储一起使用。 MicroPython 会自动创建默认配置并自动检测主文件系统,因此如果您想修改分区、文件系统类型或使用自定义块设备,本教程将非常有用。 文件系统通常由设备上的内部闪存支持,但也可以使用外部闪存、RAM 或自定义块设备。 在某些端口(例如 STM32)上,文件系统也可以通过 USB MSC 连接到主机 PC。pyboard.py 工具还为主机 PC 提供了一种访问所有端口上的文件系统的方法。 注意:这主要用于 STM32 和 ESP32 等裸机端口。在带有操作系统的端口(例如 Unix 端口)上,文件系统由主机操作系统提供。 虚拟FSMicroPython 实现了一个类 Unix 虚拟文件系统 (VFS) 层。所有挂载的文件系统都组合成一个单一的虚拟文件系统,从 root 开始 /。文件系统被挂载到这个结构的目录中,并且在启动时工作目录被更改为主文件系统被挂载的位置。 在 STM32/Pyboard 上,内部闪存安装在 /flash,可选的 SDCard安装在/sd。在 ESP8266/ESP32 上,主文件系统挂载在 /。
) e5 l! \0 W% |) l* u( O- |8 S块设备块设备是实现 uos.AbstractBlockDev协议的类的实例 。 内置块设备端口提供内置块设备来访问它们的主闪存。 开机时,MicroPython 将尝试检测默认闪存上的文件系统并自动配置和挂载它。如果没有找到文件系统,MicroPython 将尝试创建一个跨越整个闪存的 FAT 文件系统。端口还可以提供一种机制来“恢复出厂设置”主闪存,通常是通过在开机时按下按钮的某种组合。 STM32 / Pyboard该pyb.Flash类,可以访问内部闪存。在一些具有较大外部闪存的板上(例如 Pyboard D),它将使用它来代替。该 startkwarg应始终指定,即 pyb.Flash(start=0)。 注意:为了向后兼容,当构造没有参数时(即 pyb.Flash()),它只实现简单的块接口并反映呈现给 USB MSC 的虚拟设备(即它在开始时包含一个虚拟分区表)。
- A: t, L2 ~' }' a# Y" {ESP8266内部闪存作为块设备对象公开,该对象 flashbdev 在启动时在模块中创建 。默认情况下,此对象作为全局变量添加,因此通常可以简单地作为bdev. 这实现了扩展接口。
) _( W( m0 R1 d3 }, g, I' [0 x# L1 WESP32esp32.Partition类用于实现为板限定分区的块设备。与 ESP8266 一样,有一个全局变量 bdev指向默认分区。这实现了扩展接口。 ! [5 y1 Z7 o1 ~0 O8 D& n( T
# L" h2 ]+ ?$ G# _自定义块设备以下类实现了一个简单的块设备,该设备使用以下命令将其数据存储在 RAM 中 bytearray: - class RAMBlockDev:
7 E7 \& M; r) `8 O. ? - def __init__(self, block_size, num_blocks):" p I7 Q7 S2 l
- self.block_size = block_size
! W# v& W" g- p4 ]7 n0 W# u9 s) R' B - self.data = bytearray(block_size * num_blocks)5 K$ o0 _) O% H
- 6 y; }! z0 A, `& J: W0 o1 R! a
- def readblocks(self, block_num, buf):
, ?1 `1 ?" J B - for i in range(len(buf)):
6 M8 N) U/ l0 I( z# y - buf[i] = self.data[block_num * self.block_size + i]
# D1 @# W' B+ ^
0 R- b( F$ a7 c/ ~0 u& \ E* u) Z- def writeblocks(self, block_num, buf):: i5 v& [3 E( p. p2 F5 q
- for i in range(len(buf)):7 M7 {8 z0 c! v7 W0 m( o' s- h
- self.data[block_num * self.block_size + i] = buf[i]
8 w8 r2 b4 Z2 A& V - 8 S n% z4 j# R2 `
- def ioctl(self, op, arg):
2 _, K5 E2 [' _( g3 @$ n( }+ S - if op == 4: # get number of blocks
+ p% z6 g) f; E7 Z- u# v9 N - return len(self.data) // self.block_size6 V0 }2 V3 G; d% r. t+ O
- if op == 5: # get block size
^& q& o5 c# h) D* i2 V/ z - return self.block_size
复制代码 ) q7 \; ^ W1 M9 j
- I- Q; ~0 g3 L9 {
& _' A' _0 }8 o* z4 i# l它可以按如下方式使用: - import os) A7 \: }; K$ s& @
( u3 q7 d6 |( s2 P- bdev = RAMBlockDev(512, 50)
, L& j0 x) A7 X0 F' |+ X4 L - os.VfsFat.mkfs(bdev)8 O* Y1 V- D. ^
- os.mount(bdev, '/ramdisk')
复制代码 4 N% d- Z( Z5 G+ B
! G3 F0 y: L4 z) F( T
9 Z& {1 R/ o; {: \9 v' M) b; R支持简单接口和扩展接口(即 uos.AbstractBlockDev.readblocks() 和 uos.AbstractBlockDev.writeblocks() 方法的签名和行为)的块设备的示例 是: - class RAMBlockDev:
M% E. O7 h# S1 \ - def __init__(self, block_size, num_blocks):) l) Z, M) a/ D% I% f
- self.block_size = block_size' x1 z+ m9 F) R! N( G! R
- self.data = bytearray(block_size * num_blocks)
q# L9 B8 `& B
7 g# U( t6 k7 V- def readblocks(self, block_num, buf, offset=0):
/ }6 Z |+ U, S7 i/ i - addr = block_num * self.block_size + offset$ @/ P: e% k7 @; i: `
- for i in range(len(buf)):, H% }" ?4 w( d. I6 D
- buf[i] = self.data[addr + i]* j4 Z- m$ f/ E7 Q# u
- 2 \4 B( J. R( [. a3 t6 U) E' j
- def writeblocks(self, block_num, buf, offset=None):5 C: t5 a+ d1 }' v
- if offset is None:; j' N3 s# A, D
- # do erase, then write
$ m( C9 v- ]; N" N - for i in range(len(buf) // self.block_size):
3 [3 p6 {3 p# ] - self.ioctl(6, block_num + i)
U. ?" c' N V3 Y$ @. h - offset = 0% z3 ?. Z/ B$ ?! i
- addr = block_num * self.block_size + offset
- T$ ^1 J& k6 L - for i in range(len(buf)):% @; ^0 `4 v+ H
- self.data[addr + i] = buf[i]3 i4 g; f- W. j0 u5 H/ H3 O
3 v+ e1 p; U0 g$ j- def ioctl(self, op, arg):
+ ~/ \) H0 a+ ^; g - if op == 4: # block count4 A5 e& W" _/ V% F* n* v5 F: ]
- return len(self.data) // self.block_size$ e: t' p& U- j6 G
- if op == 5: # block size' d' k5 B: j$ B( f u2 }3 }$ p
- return self.block_size% \. s# b% C/ Z/ ^
- if op == 6: # block erase/ C& z* e$ R: h* ~& u
- return 0
复制代码 5 b2 c0 n; }: `5 V8 r
- y3 B4 N0 G: Y, o. [
) L/ J- i+ A( M3 T2 W1 e由于它支持扩展接口,因此可以用于littlefs: - import os
1 P7 |+ Y3 _% |4 V
2 g" ?( k6 b7 C- bdev = RAMBlockDev(512, 50)+ R7 n, D8 ^) R4 [. G
- os.VfsLfs2.mkfs(bdev)$ @! y+ ]' g* J* y
- os.mount(bdev, '/ramdisk')
复制代码 & S) R" E+ [' ~; d: p. C6 ^
, m$ x. }3 K k- m; P
- N1 |2 f: y( @9 |% T% h5 N一旦挂载,文件系统(无论其类型如何)就可以像通常在 Python 代码中使用的那样使用,例如: - with open('/ramdisk/hello.txt', 'w') as f:
, j# ^* ~. u4 w% F8 X - f.write('Hello world')
1 k, ^5 w) [7 ^! h4 O; `- @# K - print(open('/ramdisk/hello.txt').read())
复制代码
8 R- m7 M5 d1 E+ @. E& I1 O r, L4 N) |
' d% r, ?4 t9 V, t9 ?" W% I9 Y, f* `7 a4 U! N/ W+ }- s
! e, _* I2 ]4 C# V文件系统MicroPython 端口可以提供 FAT、 和 的实现。 littlefs v1 and littlefs v2. 下表显示了固件中默认包含给定端口/板组合的文件系统,但可以在自定义固件构建中选择启用它们。 7 \! M% ]/ C) u' H! @' a
FATFAT 文件系统的主要优点是它可以通过支持的板(例如 STM32)上的 USB MSC 访问,而主机 PC 上不需要任何额外的驱动程序。 但是,FAT 不能容忍写入期间的电源故障,这可能会导致文件系统损坏。对于不需要 USB MSC 的应用,建议使用 littlefs 代替。 要使用 FAT 格式化整个闪存: - # ESP8266 and ESP32
# A" D: D' [8 ]+ f! P. a - import os
8 Z, y( d' c0 p. I - os.umount('/')( S$ P/ X+ [5 Y7 U, x2 c: k
- os.VfsFat.mkfs(bdev)* i. l6 d/ D" X5 u2 i7 g
- os.mount(bdev, '/'). f0 l, C% _$ \2 E
- ! f+ h% h7 j4 |) W8 y1 v
- # STM32; U- ]! h q. l
- import os, pyb, o$ y! W" ?/ D0 p5 q w
- os.umount('/flash')
* E/ K1 T3 z- E+ ^6 m - os.VfsFat.mkfs(pyb.Flash(start=0)); q% c# z, q; z2 A2 b3 y* d
- os.mount(pyb.Flash(start=0), '/flash')
% a8 S9 y1 z) n1 N: H9 m, a# z - os.chdir('/flash')
复制代码 ; t/ b4 U& A& v& S9 ^. |& \& I( _
8 d9 B, V7 s1 Y
9 R' i- T( d& D- C
" l# q0 c+ @1 o% z- z2 {LittlefsLittlefs是专为基于闪存的设备设计的文件系统,对文件系统损坏具有更强的抵抗力。 笔记 有报告称 littlefs v1 和 v2 在某些情况下会失败,有关详细信息,请参阅littlefs issue 347 和 littlefs issue 295.
8 t2 V$ Y4 Y6 t! \' b! |& j9 C( [注意:它仍然可以使用 littlefs FUSE 驱动程序通过 USB MSC 访问。请注意,您必须使用该-b=4096 选项来覆盖块大小。 使用 littlefs v2 格式化整个闪存: - # ESP8266 and ESP326 f! T( J& s4 a' Z4 K7 s1 y5 u
- import os
/ D. g$ a+ U6 ?0 F- O' V& p5 ~ - os.umount('/')
2 D" X' w" }9 w! ~ - os.VfsLfs2.mkfs(bdev)
/ x% J- Q9 R- Y7 W# j - os.mount(bdev, '/')* e0 R) D* K3 D
3 L2 B; |* Y+ a v2 m- t9 x5 J- # STM32
& k* y+ C; U% a8 F8 j) u - import os, pyb
w9 m: ]8 \5 {9 Z0 v0 ^2 [* \. d - os.umount('/flash')
9 D3 y0 l. H* K' M8 M' B - os.VfsLfs2.mkfs(pyb.Flash(start=0))
, K c& K x# d, R1 s0 ~ G - os.mount(pyb.Flash(start=0), '/flash')
% M) m; s4 u' k) A5 C - os.chdir('/flash')
复制代码
, y% }) W& V7 X% r# |) p) w. h! h
, S, B0 i6 i3 V' U; D8 \% y1 E
2 m0 F0 k2 ^" ?/ ^% F; C6 V4 b U& Y( G: u, t
混合 (STM32)通过使用 start 和 len kwargs to pyb.Flash,您可以创建跨越闪存设备子集的块设备。 例如,将第一个 256kiB 配置为 FAT(并通过 USB MSC 可用),其余配置为 littlefs: - import os, pyb) |$ ]2 \# k% z/ M
- os.umount('/flash')) |0 _* ~8 p7 R2 s: b
- p1 = pyb.Flash(start=0, len=256*1024)# K1 Y9 o1 a% r! {/ v/ s
- p2 = pyb.Flash(start=256*1024)
e$ J8 c' `0 M - os.VfsFat.mkfs(p1): a( j$ E2 y" d- \/ L
- os.VfsLfs2.mkfs(p2)
4 x7 c+ b$ x2 e" b4 |3 I - os.mount(p1, '/flash')
0 b( A2 t; T7 |* n5 A - os.mount(p2, '/data')* ^& H* x& l2 c f
- os.chdir('/flash')
复制代码 6 _0 a; ?2 C5 n6 p; u3 I# n+ r
5 v8 W* i- m9 S+ w& b
/ ~! Z5 m$ v% ~8 W8 w% G$ J
这可能有助于使您的 Python 文件、配置和其他很少修改的内容通过 USB MSC 可用,但允许频繁更改的应用程序数据驻留在 littlefs 上,从而具有更好的电源故障恢复能力等。 偏移处的分区 0 将自动挂载(并自动检测文件系统类型),但您可以添加: - import os, pyb
1 _0 I4 x! L8 h3 x- Y; u) O - p2 = pyb.Flash(start=256*1024)! J# c/ Z- F3 K6 V% v4 h$ y8 U Q
- os.mount(p2, '/data')
复制代码
1 S* z: _- ^% i1 e
* A; _# s. f* d( C; z- n2 Q! f p2 u, k* c" L
来 boot.py挂载数据分区。 + Z1 F% Z9 c; ?
混合动力(ESP32)在 ESP32 上,如果您构建自定义固件,您可以修改 partitions.csv以定义任意分区布局。 启动时,名为“vfs”的分区将被/默认挂载,但任何额外的分区都可以boot.py 使用: - import esp32, os
. i z5 _- d( [3 t- N! q6 o - p = esp32.Partition.find(esp32.Partition.TYPE_DATA, label='foo')
: I1 R' k) e8 W0 m$ { K - os.mount(p, '/foo')
复制代码 " X& p5 R/ {9 v6 Q6 i) w6 u- l
% C: w, B& z( K: W8 J* t- {
7 v. `( }8 E" G8 T
" R0 [9 z, ~- }7 o! ?" h
4 q! |7 L q3 w9 y% ^; j2 F8 r6 w3 o5 p' C
|