TobudOS 中的 MicroPython pyb.Flash 详解:内置 Flash 块设备接口与文件系统定制实战

原创2026-10-11 20:40:521,633 阅读

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 的价值在于两个特殊场景:

  1. 自定义文件系统配置:改变默认分区布局、文件系统类型(FAT / littlefs)、混合多文件系统挂载;
  2. 实现应用专属的低层存储系统:需要绕过文件系统、按块直接管理存储介质。

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 目录)则提供了另一条等价链路,帮助理解块设备在整个系统中的位置:

  1. 块设备注册:示例 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") 完成挂载;
  2. VFS 层桥接:vfs_tos.c 实现 MicroPython 的 VfsTos 类型,把 open/mkdir/rename/stat/ilistdir 等 Python 操作翻译为 tos_vfs_* 系统调用(如 tos_vfs_stat、tos_vfs_unlink、tos_vfs_readdir);
  3. 启动挂载:mpmain.c 在 MicroPython 运行时初始化阶段将 VfsTos 实例挂载到根目录 /,使 boot.py、main.py 及用户脚本能直接进行文件操作;
  4. 功能开关: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 应用定制存储方案的底层钥匙。

登录后查看全文
TobudOS