news 2026/9/20 20:42:47

MicroPython RP2 端口 rp2.Flash 类详解:SPI Flash 的低层访问、块协议实现与分区配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MicroPython RP2 端口 rp2.Flash 类详解:SPI Flash 的低层访问、块协议实现与分区配置

MicroPython RP2 端口 rp2.Flash 类详解:SPI Flash 的低层访问、块协议实现与分区配置

【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython

本文围绕 MicroPythonrp2端口(Raspberry Pi Pico / RP2040、RP2350 平台)中的rp2.Flash类展开,系统讲解它的构造函数、readblocks/writeblocks/ioctl三个方法的块协议语义,并结合 rp2_flash.c 源码解析其 4K 扇区模型、擦写临界区处理、PSRAM 分块写入与MICROPY_HW_FLASH_STORAGE_BYTES分区配置机制。读完后你可以直接用它定制文件系统布局或为应用实现低层存储系统,并理解每次读/写块在硬件层面到底发生了什么。

何时需要 rp2.Flash

rp2.Flash是对板载 SPI flash 存储的直接访问接口,定义于 rp2.Flash.rst。官方文档明确指出:

  • 在大多数情况下,要在设备上保存持久化数据,应使用更高层的抽象,例如通过 Python 标准文件 API 访问的文件系统;
  • rp2.Flash这类接口适合两类场景:定制文件系统配置(见 文件系统文档),或为应用实现低层存储系统

也就是说,日常写文件用open()即可;只有当你需要绕过文件系统直接操作 flash 扇区(例如自建 KV 存储、管理多个存储分区、调试 VFS 块设备行为)时,才直接使用这个类。

构造函数:单例与子分区对象

.. class:: Flash()

Flash()返回访问 SPI flash 的单例对象。对应实现见 rp2_flash.c 中的rp2_flash_make_new

  • 不带参数调用时,返回全局单例rp2_flash_obj,它覆盖从MICROPY_HW_FLASH_STORAGE_BASE开始的整个MICROPY_HW_FLASH_STORAGE_BYTES存储分区;
  • 当前源码还支持两个仅关键字参数startlen(默认均为 -1),用于构造覆盖 flash 存储区间子范围的Flash对象:
    • start:起始字节偏移,必须在[0, MICROPY_HW_FLASH_STORAGE_BYTES)范围内且为块大小(4096 字节)的整数倍,否则抛出ValueError
    • len:长度(字节),必须为正数、start + len <= MICROPY_HW_FLASH_STORAGE_BYTES且为 4096 的整数倍,否则抛出ValueErrorlen = -1表示一直延伸到存储分区末尾。
import rp2 # 默认单例:访问整个 MicroPython 存储分区 flash = rp2.Flash() # 也可以取存储分区中 [1 MiB, 2 MiB) 的子范围 sub = rp2.Flash(start=1024 * 1024, len=1024 * 1024)

从源码结构看,每个Flash对象只持有两个字段:flash_base(相对存储分区基址的偏移)和flash_size(对象定义),因此构造开销极低。

方法详解:readblocks / writeblocks / ioctl

rp2.Flash的三个方法实现了 vfs.AbstractBlockDev 定义的简单接口与扩展接口("block protocol"):

.. method:: Flash.readblocks(block_num, buf) Flash.readblocks(block_num, buf, offset) .. method:: Flash.writeblocks(block_num, buf) Flash.writeblocks(block_num, buf, offset) .. method:: Flash.ioctl(cmd, arg)

readblocks(block_num, buf[, offset])

  • 第一个形式:从块索引block_num开始,读取buf长度对应的整块数据(buf长度必须是块大小的整数倍);
  • 第二个形式(扩展接口):允许在块内任意字节偏移offset处读取任意长度,用于需要子块读写的文件系统(如 littlefs)。

实现上就是直接从 XIP 映射地址XIP_BASE + flash_base + block_num * 4096 + offsetmemcpy(readblocks 实现)。由于 flash 内容本就常驻 XIP 地址空间,读取无需任何 flash 控制器操作。值得注意的细节:每次读块后会调用mp_event_handle_nowait(),源码注释说明这是为了避免启动阶段设备忙于加载文件或扫描文件系统时 USB 注册失败——该调用会在需要时驱动 TinyUSB 任务。

writeblocks(block_num, buf[, offset])

写操作是 flash 存储中最复杂的部分,两种形式的语义与行为不同:

  • writeblocks(block_num, buf)(3 参数形式):写入块对齐的数据,方法内部先执行擦除flash_range_erase),再编程写入(flash_range_program);
  • writeblocks(block_num, buf, offset)(4 参数扩展形式):只写指定字节范围内的数据,不会擦除,调用方必须确保目标块已通过ioctl的块擦除命令提前擦好。

writeblocks 实现 还有两点硬件层面的处理:

  1. PSRAM 分块写入:当启用了MICROPY_HW_ENABLE_PSRAM且数据源缓冲区位于 PSRAM/flash(非 SRAM)地址范围时,无法直接编程,源码会用一个FLASH_PAGE_SIZE(256 字节)大小的 SRAM 暂存缓冲区copy_buffer分块中转写入;数据源在 SRAM 时则直接调用flash_range_program
  2. 临界区保护:每次擦除/编程都包裹在begin_critical_flash_section()/end_critical_flash_section()中,见下文"擦写临界区"。

ioctl(cmd, arg)

ioctl支持块设备协议定义的标准命令,实现 中可确认的行为如下:

命令参数rp2.Flash 的行为
MP_BLOCKDEV_IOCTL_INIT无操作,返回 0
MP_BLOCKDEV_IOCTL_DEINIT无操作,返回 0
MP_BLOCKDEV_IOCTL_SYNC无操作,返回 0
MP_BLOCKDEV_IOCTL_BLOCK_COUNT返回flash_size / 4096,即块数
MP_BLOCKDEV_IOCTL_BLOCK_SIZE返回 4096
MP_BLOCKDEV_IOCTL_BLOCK_ERASE块号擦除指定块(4096 字节)

块大小 4096 并非任意设定,源码中直接定义为BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE)(第 43 行)——一个块就是 SPI flash 的一个扇区,因为扇区是 flash 擦除的最小单位。擦除单个块时同样走flash_range_erase并包裹在临界区内。

源码级原理:擦写临界区与时钟时序

为什么擦写要"停世界"

RP2 系列的 CPU 直接从 flash 取指(XIP,execute in place)。执行擦除/编程命令时必须关闭 XIP,因此 begin_critical_flash_section 做了三件事:

  1. 若另一核心(core1)在运行,用multicore_lockout_start_blocking()挂起它,防止其执行 flash 中的代码;use_multicore_lockout()还额外检查core1_entry != NULL,这是针对 pico-sdk #2201 的 workaround(core1 复位后multicore_lockout_victim_is_initialized仍可能误报);
  2. save_and_disable_interrupts()关中断,保存中断状态;
  3. 若启用 PSRAM,先把维护空间(maintenance space)上段写入 0 以清理 XIP 缓存,提交 PSRAM 中的脏数据。

结束后 end_critical_flash_section 会恢复中断并重新调用rp2_flash_set_timing_internal()——因为 ROM 的编程函数会把 flash 时序寄存器重置为默认值。

Flash 时序自适应

rp2_flash_set_timing_internal 根据系统时钟重算 flash 时钟分频,目标频率由MICROPY_HW_FLASH_MAX_FREQ决定,未定义时默认为SYS_CLK_HZ / PICO_FLASH_SPI_CLKDIV(boot ROM 默认 CLKDIV 为 4),即 第 51-62 行。两条芯片路径的差异:

  • RP2040(SSI):硬件只支持偶数分频,奇数分频会 +1;流程是等待 SSI 空闲 → 关闭 SSI → 写baudr→ 重新使能;
  • RP2350(QMI):无忙标志,需先自旋等待 flash 片选处于非选中状态,再一次性写QMI_M1_TIMING寄存器(RX 采样延迟取等于分频值,注释说明该时序在 133 MHz 下较紧但对 Winbond 闪存芯片可用),最后强制一次 XIP 读以使时序生效。

对外接口rp2_flash_set_timing()/rp2_flash_set_timing_for_freq(clock_hz)由 rp2_flash.h 声明,在系统时钟变更时被调用。

分区模型与构建配置

地址布局

存储分区在 flash 中的位置由 rp2_flash.c 计算,且编译期用static_assert保证合法(ROMFS 与存储分区大小都必须是 4K 的整数倍,且BASE + SIZE <= PICO_FLASH_SIZE_BYTES):

区域基址大小
MicroPython 存储分区MICROPY_HW_FLASH_STORAGE_BASE,未定义时为PICO_FLASH_SIZE_BYTES - MICROPY_HW_FLASH_STORAGE_BYTES(即紧挨芯片 flash 末尾)MICROPY_HW_FLASH_STORAGE_BYTES
ROMFS 分区(只读)MICROPY_HW_ROMFS_BASE,紧邻存储分区下方,位于代码空间上端MICROPY_HW_ROMFS_BYTES,默认 0

此外,源码用 Pico SDK 的bi_decl(bi_block_device(...))把这两个分区标注进固件的二进制元数据(binary info):存储分区标记为可读写(不可重格式化),ROMFS 分区标记为只读(第 94-115 行),可供上位机工具解析固件的分区布局。

当启用 ROMFS 时,mp_vfs_rom_ioctl 会向 VFS ROM 接口报告 1 个只读段,并把静态的rp2_flash_romfs_obj作为块设备返回——这就是 ROMFS 分区能被挂载为只读文件系统的底层支撑。

每块板子的实际取值

MICROPY_HW_FLASH_STORAGE_BYTES由板级配置mpconfigboard.cmake给出(CMake 构建时经 CMakeLists.txt 传入,并通过--defsym导出给链接期使用的代码)。例如 ADAFRUIT_FEATHER_RP2040 配置为7340032(7 MiB),ARDUINO_NANO_RP2040_CONNECT 为14680064(14 MiB)。也就是说同一份rp2.Flash代码在不同板子上管理的空间大小由构建配置决定。

块协议视角与 VFS 的关系

rp2.Flash是 vfs.rst 所定义块设备协议的标准实现者,可以直接作为vfs.FatDevc/vfs.LfsBlockDev等文件系统块设备的后端。协议要点与rp2.Flash的对应关系:

  • readblocks(block_num, buf)的块数由buf长度决定,buf必须是块大小的整数倍;writeblocks的块对齐形式要求被写块先擦除——rp2.Flash在 3 参数形式中自动完成了这一步;
  • 扩展接口(带offset)供 littlefs 这类需要子块写入控制的文件系统使用,rp2.Flash两种形式都实现了;
  • 成功时返回None或 0,失败时应返回对应OSErrorerrno 的负数。rp2.Flash当前实现读写成功路径均返回None,且擦/写返回值标注了TODO check return value(见源码注释),可以推断返回值检查仍是后续可完善点。

实际产品中,RP2 端口的文件系统正是构建在这套接口之上,定制文件系统布局的完整说明见 文件系统文档。

实战示例:查询与擦写一个块

下面是一个可直接在 Pico 上运行的最小示例,演示块设备查询、擦除与写入读回(注意:写入的目标块必须落在 MicroPython 存储分区内,切勿覆盖运行中的文件系统数据):

import rp2 flash = rp2.Flash() # 查询块大小与块数(MP_BLOCKDEV_IOCTL_BLOCK_SIZE / BLOCK_COUNT) block_size = flash.ioctl(5, 0) # 4096 nblocks = flash.ioctl(4, 0) # flash_size // 4096 print("block_size:", block_size, " nblocks:", nblocks) # 选择存储分区末尾的一个块做试验 block_num = nblocks - 1 # 擦除该块(MP_BLOCKDEV_IOCTL_BLOCK_ERASE) flash.ioctl(6, block_num) # 写入一个完整块(3 参数形式内部也会先擦除) buf = bytearray(block_size) buf[:10] = b"Hello RP2!" flash.writeblocks(block_num, buf) # 读回验证 rd = bytearray(block_size) flash.readblocks(block_num, rd) print("readback:", bytes(rd[:10])) # 扩展接口:块内偏移写(需先保证块已擦除) flash.ioctl(6, block_num) # 先擦 flash.writeblocks(block_num, b"extended", 512) # 从块内 512 字节处写

ioctl的命令号与 vfs.rst 中AbstractBlockDev文档列出的MP_BLOCKDEV_IOCTL_*常量一一对应,运行时也可以用vfs模块中定义的常量替代裸数字,提高可读性。

关键结论

  1. rp2.Flashrp2端口访问板载 SPI flash 的块设备接口,实现readblocks/writeblocks(含扩展接口)与ioctl三个方法,块大小固定为 flash 扇区 4096 字节;
  2. 它管理的空间由MICROPY_HW_FLASH_STORAGE_BYTES/MICROPY_HW_FLASH_STORAGE_BASE决定,默认位于芯片 flash 的末尾,各板子的具体数值见对应mpconfigboard.cmake
  3. 擦除与编程操作运行在"关中断 + 挂起另一核心"的临界区内,因为 XIP 取指必须让位于 flash 命令;启用 PSRAM 时写入会经 256 字节 SRAM 缓冲区分块中转;
  4. 日常持久化请用标准文件 API;rp2.Flash面向的是定制文件系统配置和自建低层存储这类进阶场景,其完整协议规范见 vfs 块设备接口文档。

【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 20:40:00

Java 8实战:Lambda、Stream和日期时间API核心详解

简介&#xff1a;这是一份面向Java开发者及初学者的简明教程PDF&#xff0c;围绕Java 8平台的核心更新&#xff0c;系统讲解默认接口方法、Lambda表达式、函数式接口、方法与构造引用、Stream流、Map扩展、新的时间日期API、Optional容器以及并发增强等关键特性&#xff0c;帮助…

作者头像 李华
网站建设 2026/9/20 20:39:47

Vue3移动端扫码实战:基于vue-qrcode-reader实现摄像头二维码识别

1. 项目背景与方案选型1.1 为什么偏偏选了 vue-qrcode-reader先说结论&#xff1a;在 Vue3 技术栈里做移动端 H5 扫码&#xff0c;vue-qrcode-reader 是目前性价比最高的选择&#xff0c;没有之一。我之前做扫码功能时也踩过不少坑。早些年用原生的getUserMedia加BarcodeDetect…

作者头像 李华
网站建设 2026/9/20 20:39:32

Notepad-- 完整指南:3 分钟快速装好免费跨平台文本编辑器

Notepad-- 完整指南&#xff1a;3 分钟快速装好免费跨平台文本编辑器 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器&#xff0c;目标是做中国人自己的编辑器&#xff0c;来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/notepad-- 从…

作者头像 李华
网站建设 2026/9/20 20:39:30

医院数据信息爬虫实战:从数据源到字段清洗的完整方案

简介&#xff1a;一份覆盖全国医院等级、擅长病症、地址、邮箱、电话及官网链接的爬虫程序资源&#xff0c;服务医疗数据分析、公共卫生规划与机构运营等场景&#xff0c;适合有一定Python基础、需要批量采集医院结构化信息的开发者。资源包共3个文件&#xff0c;包含一个.py爬…

作者头像 李华
网站建设 2026/9/20 20:38:02

基于MATLAB的六足机器人步态仿真:三角步态与波动步态实现详解

六足机器人这个坑&#xff0c;我是从一台旧笔记本加一份MATLAB授权开始的。当时手上没有舵机、没有结构件、连一根杜邦线都没有&#xff0c;却特别想弄清楚一个看起来很简单的问题&#xff1a;六条腿到底按什么顺序抬起来&#xff0c;才能走得又稳又快&#xff1f;这个问题的答…

作者头像 李华
网站建设 2026/9/20 20:38:00

Simulink信号与系统仿真课程设计全攻略:建模、滤波与踩坑实录

简介&#xff1a;这是一份基于Matlab Simulink的信号与线性系统仿真课程设计文档&#xff0c;适合电子信息、通信工程等专业学生完成信号与系统相关课设或复习仿真方法。文档以完整课程设计报告形式呈现&#xff0c;涵盖快速傅里叶变换&#xff08;FFT&#xff09;、有限冲击响…

作者头像 李华