TobudOS 中的 MicroPython pyb.Flash 详解:内置 Flash 块设备接口与文件系统定制实战
TobudOS 中的 MicroPython pyb.Flash 详解:内置 Flash 块设备接口与文件系统定制实战
pyb.Flash 是 MicroPython 提供的访问板载主 Flash 存储的低层块设备接口,它让你绕过文件系统直接读写物理 Flash,从而自定义文件系统分区、实现底层存储方案。本文以 TobudOS 仓库内 MicroPython 文档(pyb.Flash.rst)为主体,结合仓库中块设备协议(os.rst)、文件系统教程(filesystem.rst)以及 TobudOS 的 MicroPython 移植代码,讲解 pyb.Flash 的构造方式、块协议方法、文件系统定制实战与硬件注意点。读完本文,你将掌握如何在基于 STM32/PyBoard 类板的 MicroPython 固件中,通过 pyb.Flash 完成 Flash 分区规划、格式化(mkfs)与自定义挂载。
pyb.Flash 是什么:访问内置 Flash 存储的块设备接口
Flash 类允许直接访问 pyboard(以及同类 STM32 板)上的主 Flash 设备。它是一个**块设备(block device)**抽象——所谓块设备,是实现了 os.AbstractBlockDev 所定义块协议的对象,物理硬件(这里是 Flash 存储器)被包装成具有"按块读写"能力的类实例,可被格式化(mkfs)并挂载为文件系统(见 os.rst 的 Block devices 章节)。
在大多数场景下,要在设备上保存持久化数据,你并不需要直接操作它:MicroPython 会自动检测主 Flash 上的文件系统并完成挂载,应用通过 Python 标准文件 API(open() / read() / write())读写即可。pyb.Flash 的价值在于两个特殊场景:
- 自定义文件系统配置:改变默认分区布局、文件系统类型(FAT / littlefs)、混合多文件系统挂载;
- 实现应用专属的低层存储系统:需要绕过文件系统、按块直接管理存储介质。
TobudOS 仓库中,MicroPython 移植正是通过 VFS 层将 Python 文件 API 映射到底层存储:启动时在 mpmain.c 中将 TobudOS 的 VFS(VfsTos)挂载到根目录 /,应用层的 open/mkdir/rename 等操作由 vfs_tos.c 翻译为 tos_vfs_* 调用;而块设备(如 SD 卡、QSPI Flash)则在示例 micropython_demo.c 中通过 tos_vfs_block_device_register 注册后挂载 FAT 文件系统到 /fs。理解 pyb.Flash 的块协议,正是理解这条链路底层原理的基础。
构造函数:两种创建方式
pyb.Flash 提供两个构造函数,分别对应"面向 USB MSC 的虚拟设备"与"按物理偏移访问"两种语义。
pyb.Flash():面向 USB 大容量存储的虚拟设备(已废弃)
import pyb
flash = pyb.Flash()
该构造函数创建并返回一个代表呈现给 USB 大容量存储(USB MSC)接口的 Flash 设备的块设备对象。其特点如下:
- 对象起始处包含一个虚拟分区表;
- 实际 Flash 数据从块
0x100(即 256 号块,按 512 字节/块计算为 128 KiB 偏移)开始; - 该构造形式已被标记为 deprecated(废弃),将在 MicroPython 未来版本中移除。
向后兼容性说明:当以无参数形式构造时,该对象只实现简单块接口(simple block interface),且反映的是呈现给 USB MSC 的虚拟设备视图(含起始虚拟分区表),这与带 start 参数构造时直接映射物理 Flash 的行为不同(见 filesystem.rst 的 STM32/Pyboard 小节)。
pyb.Flash() 仍然出现在一些官方 API 用例中,例如在 pyb.rst 中启用 USB MSC 模式时,可用 msc=(pyb.Flash(), pyb.SDCard()) 将 Flash 与 SD 卡同时暴露为 SCSI LUN。
pyb.Flash(*, start=-1, len=-1):按指定偏移访问 Flash
import pyb
# 从物理 Flash 起始处访问,覆盖整个设备
flash = pyb.Flash(start=0)
# 只访问从 256 KiB 偏移开始的区域,长度取设备剩余空间
data = pyb.Flash(start=256 * 1024)
# 访问 0 ~ 256 KiB 的固定区域
part = pyb.Flash(start=0, len=256 * 1024)
该构造形式创建并返回一个访问指定偏移处 Flash 的块设备对象,参数语义如下:
| 参数 | 含义 | 默认值 | 约束 |
|---|---|---|---|
start |
起始偏移,单位为字节 | -1(文档建议始终显式指定,见下) |
必须是块大小的整数倍 |
len |
设备长度,单位为字节 | 默认为设备的剩余大小 | 必须是块大小的整数倍 |
其中:
start与len均以字节为单位,且必须是块大小(block size)的整数倍——内部 Flash 的块大小通常为 512 字节;- 若省略
len,则默认取从start到设备末尾的剩余大小; - filesystem.rst 明确建议:
start关键字参数应当总是显式指定,即写成pyb.Flash(start=0)的形式。
块协议方法:readblocks / writeblocks / ioctl
Flash 实例提供三个方法,完整实现 os.AbstractBlockDev 定义的块协议:
flash.readblocks(block_num, buf) # 简单接口:整块读取
flash.readblocks(block_num, buf, offset) # 扩展接口:块内任意偏移读取
flash.writeblocks(block_num, buf) # 简单接口:整块写入
flash.writeblocks(block_num, buf, offset) # 扩展接口:块内任意偏移写入
flash.ioctl(cmd, arg) # 设备控制与参数查询
简单接口与扩展接口
块协议存在两种兼容的签名形式(详见 os.rst 的 Simple and extended interface 小节):
- 简单接口:
readblocks(block_num, buf)/writeblocks(block_num, buf)。按整块对齐读写,buf长度为块大小的整数倍,块数以len(buf) / block_size计算;writeblocks在必要时负责先擦除再写入。 - 扩展接口:
readblocks(block_num, buf, offset)/writeblocks(block_num, buf, offset)。允许在块内任意偏移处读写任意长度字节,适用于"子块级写入"场景。
一些文件系统(如 littlefs)需要对写入操作有更细的控制(如在不擦除的情况下写入子块区域),因此要求块设备支持扩展接口。一个块设备可以只实现其中一种形式,也可以两种都实现。
ioctl 操作码
ioctl(cmd, arg) 用于控制设备并查询参数,cmd 为以下整数操作码(定义见 os.rst):
| 操作码 | 功能 | 返回说明 |
|---|---|---|
1 |
初始化设备 | arg 未使用 |
2 |
关闭设备 | arg 未使用 |
3 |
同步设备 | arg 未使用 |
4 |
获取块数量 | 返回整数 |
5 |
获取块大小(字节) | 返回整数;返回 None 时按默认值 512 处理 |
6 |
擦除指定块 | arg 为要擦除的块号 |
要点:
- 作为最低要求,
ioctl(4, ...)(块数量)必须被拦截处理;使用 littlefs 时还必须拦截ioctl(6, ...)(擦除块); - littlefs 在调用
writeblocks(block, ...)之前总是先发出ioctl(6, block),让驱动在写入前完成擦除(若硬件需要);驱动也可以拦截ioctl(6, block)并返回 0(成功),自行承担检测擦除需要的责任; - 未拦截的操作码可以返回
None;操作码 4、5 的返回值如上表,其他操作成功返回 0、失败返回非零(值为OSError的 errno 码)。
在 filesystem.rst 中给出了自定义块设备的完整示例实现(基于 bytearray 的 RAM 块设备),分别演示了仅简单接口与同时支持简单/扩展接口两种写法,其中扩展接口版本通过 ioctl(6, ...) 处理擦除,可直接配合 os.VfsLfs2 使用——pyb.Flash 在 C 层面实现的就是同一套协议。
实战:用 pyb.Flash 定制与重建文件系统
查询 Flash 设备参数
在规划分区前,先查询设备的块数量与块大小:
import pyb
f = pyb.Flash(start=0)
block_count = f.ioctl(4, 0) # 块数量
block_size = f.ioctl(5, 0) # 块大小(字节)
print(block_count, block_size)
格式化整个 Flash 为 FAT
FAT 文件系统的主要优势是:在支持的板卡(如 STM32)上可通过 USB MSC 无驱动访问,宿主机 PC 直接可见。但 FAT 对写入过程中的掉电不敏感,可能造成文件系统损坏;若应用不依赖 USB MSC,官方建议改用 littlefs。STM32 上格式化整个 Flash 为 FAT 的完整流程:
import os, pyb
os.umount('/flash') # 先卸载
os.VfsFat.mkfs(pyb.Flash(start=0)) # 格式化
os.mount(pyb.Flash(start=0), '/flash')
os.chdir('/flash')
格式化整个 Flash 为 littlefs v2
littlefs 是为 Flash 设备设计的文件系统,对文件系统损坏的抵抗力更强:
import os, pyb
os.umount('/flash')
os.VfsLfs2.mkfs(pyb.Flash(start=0))
os.mount(pyb.Flash(start=0), '/flash')
os.chdir('/flash')
说明:上例中的
pyb.Flash(start=0)每次调用都会创建新实例,官方示例即如此书写;在实际工程中也可以先bdev = pyb.Flash(start=0)复用一个实例。
格式化之后,还可以通过 pyb.Flash 查询 littlefs 所需参数(供 PC 端 littlefs FUSE 驱动挂载时使用):
import pyb
f = pyb.Flash(start=0)
f.ioctl(1, 1) # 以 littlefs 原始块模式初始化 Flash
block_count = f.ioctl(4, 0)
block_size = f.ioctl(5, 0)
混合分区:FAT + littlefs
利用 start 与 len 参数,可以在同一物理 Flash 上划分多个块设备,配置不同的文件系统。官方示例将前 256 KiB 划为 FAT(可通过 USB MSC 访问),其余部分划为 littlefs(存放频繁变更的应用数据,掉电更安全):
import os, pyb
os.umount('/flash')
p1 = pyb.Flash(start=0, len=256 * 1024) # 分区 1:0 ~ 256 KiB
p2 = pyb.Flash(start=256 * 1024) # 分区 2:256 KiB ~ 末尾
os.VfsFat.mkfs(p1)
os.VfsLfs2.mkfs(p2)
os.mount(p1, '/flash')
os.mount(p2, '/data')
os.chdir('/flash')
分区挂载说明:偏移 0 处的分区会在启动时被自动挂载并自动检测文件系统类型;额外的数据分区可在 boot.py 中手动挂载:
import os, pyb
p2 = pyb.Flash(start=256 * 1024)
os.mount(p2, '/data')
这种布局适用于:把 Python 脚本、配置等很少修改的内容放在 FAT 分区并通过 USB MSC 与 PC 交换,而把频繁读写的应用数据放在 littlefs 分区以获得更好的掉电可靠性。
硬件说明:外部 SPIFlash 与 MCU 内部 Flash
pyb.Flash 访问的"主 Flash"到底落在哪个物理介质上,取决于板卡设计(原文 Hardware Note):
- 带外部 SPI Flash 的板卡(如 Pyboard D):MicroPython 固件会被配置为使用外部 SPIFlash 作为主 Flash 存储;
- 其他板卡:使用 MCU 内部 Flash 作为主存储。
换言之,pyb.Flash 是一个"主 Flash 抽象",其物理后端(内部 Flash 或外部 SPIFlash)由固件配置决定,对 Python 层透明。TobudOS 仓库中同类思路可见于示例 micropython_demo.c:当宏 MP_USING_QSPI_FLASH 打开时,会先调用 w25qxx_init() 初始化外部 W25QXX 系列 SPI Flash 并使其进入 memory-mapped 模式,再启动 MicroPython 任务——这正是"外部 Flash 作为主存储后端"的板级初始化路径。
在 TobudOS 中的上下文:从块设备到 VFS 的完整链路
pyb.Flash 是 STM32/PyBoard 移植的官方块设备实现,而 TobudOS 的 MicroPython 移植(port 目录)则提供了另一条等价链路,帮助理解块设备在整个系统中的位置:
- 块设备注册:示例 micropython_demo.c 中,
tos_vfs_block_device_register("/dev/sd", &sd_dev)将 SD 卡块设备注册进 TobudOS VFS,tos_vfs_fs_register("fatfs_sd", &fatfs_ops)注册 FAT 文件系统驱动,随后tos_vfs_fs_mount("/dev/sd", "/fs", "fatfs_sd")完成挂载; - VFS 层桥接:vfs_tos.c 实现 MicroPython 的
VfsTos类型,把open/mkdir/rename/stat/ilistdir等 Python 操作翻译为tos_vfs_*系统调用(如tos_vfs_stat、tos_vfs_unlink、tos_vfs_readdir); - 启动挂载:mpmain.c 在 MicroPython 运行时初始化阶段将
VfsTos实例挂载到根目录/,使boot.py、main.py及用户脚本能直接进行文件操作; - 功能开关:mpconfigport.h 中由
MP_USING_VFS宏控制MICROPY_VFS_TOS等编译选项,决定固件是否包含 VFS 能力(MICROPY_VFS_FAT默认关闭、MICROPY_VFS_TOS打开)。
因此,无论使用官方 pyb.Flash 直接操作块设备,还是通过 VfsTos/tos_vfs 走文件系统链路,二者共享同一套设计原则:应用层文件 API → VFS → 块设备协议(readblocks/writeblocks/ioctl)→ 物理 Flash。掌握了 pyb.Flash 的块协议,就等于掌握了为 TobudOS 上的 MicroPython 应用定制存储方案的底层钥匙。