ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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.Flashrp2.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存储分区当前源码还支持两个仅关键字参数start和len默认均为 -1用于构造覆盖 flash 存储区间子范围的Flash对象start起始字节偏移必须在[0, MICROPY_HW_FLASH_STORAGE_BYTES)范围内且为块大小4096 字节的整数倍否则抛出ValueErrorlen长度字节必须为正数、start len MICROPY_HW_FLASH_STORAGE_BYTES且为 4096 的整数倍否则抛出ValueErrorlen -1表示一直延伸到存储分区末尾。import rp2 # 默认单例访问整个 MicroPython 存储分区 flash rp2.Flash() # 也可以取存储分区中 [1 MiB, 2 MiB) 的子范围 sub rp2.Flash(start1024 * 1024, len1024 * 1024)从源码结构看每个Flash对象只持有两个字段flash_base相对存储分区基址的偏移和flash_size对象定义因此构造开销极低。方法详解readblocks / writeblocks / ioctlrp2.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 offset做memcpyreadblocks 实现。由于 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_programwriteblocks(block_num, buf, offset)4 参数扩展形式只写指定字节范围内的数据不会擦除调用方必须确保目标块已通过ioctl的块擦除命令提前擦好。writeblocks 实现 还有两点硬件层面的处理PSRAM 分块写入当启用了MICROPY_HW_ENABLE_PSRAM且数据源缓冲区位于 PSRAM/flash非 SRAM地址范围时无法直接编程源码会用一个FLASH_PAGE_SIZE256 字节大小的 SRAM 暂存缓冲区copy_buffer分块中转写入数据源在 SRAM 时则直接调用flash_range_program。临界区保护每次擦除/编程都包裹在begin_critical_flash_section()/end_critical_flash_section()中见下文擦写临界区。ioctl(cmd, arg)ioctl支持块设备协议定义的标准命令实现 中可确认的行为如下命令参数rp2.Flash 的行为MP_BLOCKDEV_IOCTL_INIT无无操作返回 0MP_BLOCKDEV_IOCTL_DEINIT无无操作返回 0MP_BLOCKDEV_IOCTL_SYNC无无操作返回 0MP_BLOCKDEV_IOCTL_BLOCK_COUNT无返回flash_size / 4096即块数MP_BLOCKDEV_IOCTL_BLOCK_SIZE无返回 4096MP_BLOCKDEV_IOCTL_BLOCK_ERASE块号擦除指定块4096 字节块大小 4096 并非任意设定源码中直接定义为BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE)第 43 行——一个块就是 SPI flash 的一个扇区因为扇区是 flash 擦除的最小单位。擦除单个块时同样走flash_range_erase并包裹在临界区内。源码级原理擦写临界区与时钟时序为什么擦写要停世界RP2 系列的 CPU 直接从 flash 取指XIPexecute in place。执行擦除/编程命令时必须关闭 XIP因此 begin_critical_flash_section 做了三件事若另一核心core1在运行用multicore_lockout_start_blocking()挂起它防止其执行 flash 中的代码use_multicore_lockout()还额外检查core1_entry ! NULL这是针对 pico-sdk #2201 的 workaroundcore1 复位后multicore_lockout_victim_is_initialized仍可能误报save_and_disable_interrupts()关中断保存中断状态若启用 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_CLKDIVboot ROM 默认 CLKDIV 为 4即 第 51-62 行。两条芯片路径的差异RP2040SSI硬件只支持偶数分频奇数分频会 1流程是等待 SSI 空闲 → 关闭 SSI → 写baudr→ 重新使能RP2350QMI无忙标志需先自旋等待 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_BYTESROMFS 分区只读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 配置为73400327 MiBARDUINO_NANO_RP2040_CONNECT 为1468006414 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] bHello 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, bextended, 512) # 从块内 512 字节处写ioctl的命令号与 vfs.rst 中AbstractBlockDev文档列出的MP_BLOCKDEV_IOCTL_*常量一一对应运行时也可以用vfs模块中定义的常量替代裸数字提高可读性。关键结论rp2.Flash是rp2端口访问板载 SPI flash 的块设备接口实现readblocks/writeblocks含扩展接口与ioctl三个方法块大小固定为 flash 扇区 4096 字节它管理的空间由MICROPY_HW_FLASH_STORAGE_BYTES/MICROPY_HW_FLASH_STORAGE_BASE决定默认位于芯片 flash 的末尾各板子的具体数值见对应mpconfigboard.cmake擦除与编程操作运行在关中断 挂起另一核心的临界区内因为 XIP 取指必须让位于 flash 命令启用 PSRAM 时写入会经 256 字节 SRAM 缓冲区分块中转日常持久化请用标准文件 APIrp2.Flash面向的是定制文件系统配置和自建低层存储这类进阶场景其完整协议规范见 vfs 块设备接口文档。【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表