资讯动态

STM32H743移植MicroPython:外扩32M SQPI Flash与32M SDRAM实战

发布时间:2026/10/13 14:10:51 来源:尧图企业网站定制
简介这份源码面向使用STM32H743开发板并希望运行MicroPython的嵌入式开发者重点解决官方移植未覆盖外扩32M SQPI Flash与32M SDRAM的板级适配问题。资源包共5个文件约4KB包含board_init.c、mpconfigboard.mk、mpconfigboard.h、stm32h7xx_hal_conf.h及pins.csv等分别承担板级初始化、编译配置、引脚映射与HAL外设裁剪等职责放入micropython/ports/stm32/boards/目录后即可通过make命令编译固件。内容涉及STM32H7系列时钟树、SQPI Flash与SDRAM的引脚与存储映射配置适合具备一定STM32与MicroPython移植基础的读者参考。目前已有1363人学习下载可帮助读者省去从零编写板级配置的时间快速获得可编译的移植工程并对照理解外扩存储的初始化流程与配置要点。1. STM32H743 上跑 MicroPython为什么外扩 32M SQPI Flash 和 32M SDRAM 是绕不开的一步STM32H743 这颗片子Cortex-M7 内核跑到 480MHz片上 SRAM 有 1MB 左右Flash 2MB单看参数在 MCU 里算富裕。但一旦决定在上面移植 MicroPython情况立刻变了MicroPython 固件本身编译出来轻松超过 1MB再加上文件系统、用户脚本、堆内存片上资源根本不够分。所以标题里说的「外扩 32M SQPI Flash 和 32M SDRAM」不是炫技是让 MicroPython 真正能用的最低配置。SQPI Flash 用来放固件和文件系统SDRAM 用来当 MicroPython 的堆和帧缓冲两者分工明确。这套方案适合已经玩过 STM32 基础外设、想把手里的 H743 开发板变成能跑 Python 脚本的嵌入式平台的开发者。下面把我实际移植时踩过的路、调过的参数、翻过的车按顺序讲清楚。2. 硬件链路先打通SQPI Flash 和 SDRAM 的引脚与时钟配置2.1 为什么选 QUADSPI 而不是普通 SPISTM32H743 的 QUADSPI 外设支持单线、双线、四线模式时钟最高可以到 100MHz 以上受 Flash 芯片本身限制。普通 SPI 接 Flash 做 XIP就地执行基本不可行因为指令取指延迟太大。MicroPython 的固件如果放在外部 Flash 里直接执行必须走 QUADSPI 的内存映射模式。我一般会把 QUADSPI 配成四线模式时钟先降到 50MHz 调通再往上摸。SDRAM 那边用 FMC 外设32 位带宽时钟和 H743 的 FMC 时钟域对齐。2.2 引脚分配与 CubeMX 配置要点以常见的 H743 开发板为例QUADSPI 的引脚通常是CLK、CS、IO0~IO3。SDRAM 的引脚更多包括地址线、数据线、片选、行列选通、写使能等。用 CubeMX 配置时QUADSPI 要开「内存映射模式」SDRAM 要选对 Bank 和 CAS 延迟。下面是一段 SDRAM 初始化代码的骨架基于 HAL 库// SDRAM 初始化结构体参数根据具体芯片手册调整 FMC_SDRAM_TimingTypeDef Timing {0}; FMC_SDRAM_CommandTypeDef Command {0}; // 时序参数这些值必须查 SDRAM 芯片手册不能照抄 Timing.LoadToActiveDelay 2; // tRCD Timing.ExitSelfRefreshDelay 7; // tXSR Timing.SelfRefreshTime 4; // tRAS Timing.RowCycleDelay 7; // tRC Timing.WriteRecoveryTime 2; // tWR Timing.RPDelay 2; // tRP Timing.RCDDelay 2; // tRCD // 初始化 SDRAM 控制器 HAL_SDRAM_Init(hsdram1, Timing); // 发送命令序列时钟配置使能 - 预充电 - 刷新 - 加载模式寄存器 Command.CommandMode FMC_SDRAM_CMD_CLK_ENABLE; Command.CommandTarget FMC_SDRAM_CMD_TARGET_BANK1; Command.AutoRefreshNumber 1; Command.ModeRegisterDefinition 0; HAL_SDRAM_SendCommand(hsdram1, Command, 0xFFFF); // 后面还有预充电、自动刷新、加载模式寄存器等步骤顺序不能乱逻辑说明SDRAM 上电后必须按固定顺序发命令先给时钟再预充电所有 Bank然后连续刷新几次最后写模式寄存器设置突发长度和 CAS 延迟。参数说明LoadToActiveDelay对应 tRCDExitSelfRefreshDelay对应 tXSR这些值如果设小了SDRAM 读写会随机出错而且这种错误用调试器很难抓因为大部分时候数据是对的偶尔翻车。注意SDRAM 的时序参数必须查你所用的具体芯片手册不同品牌、不同型号的 tRCD、tRP 可能差一两个时钟周期照抄别人的配置是血泪教训的高发区。2.3 验证外扩存储是否可用调通硬件后先别急着上 MicroPython。写一段简单的测试代码往 SDRAM 里写图案再读回来往 QUADSPI Flash 里擦写一个扇区再读回来。这一步能过后面才有意义。我一般会跑一个 32 位递增图案测试覆盖整个 32M 空间虽然慢但能暴露地址线虚焊、时序临界的问题。3. 把 MicroPython 移植到 H743从源码裁剪到固件生成3.1 获取源码与目录结构MicroPython 官方源码里ports/stm32是 STM32 系列的移植目录。H743 属于 H7 系列官方已经有基础支持但默认配置通常只针对片上 Flash 和 SRAM。我们需要在boards目录下新建一个自己的板级定义或者修改现有的 H743 板定义。常见做法是复制一份NUCLEO_H743ZI的配置改名为自己的板子然后调整mpconfigboard.mk和mpconfigboard.h。3.2 关键配置堆内存指向 SDRAMMicroPython 的堆默认放在片上 SRAM大小有限。要让 Python 脚本能申请大块内存必须把堆重定向到 SDRAM。在mpconfigboard.h里找到MICROPY_HEAP_SIZE相关的定义或者在链接脚本里把_heap_start和_heap_end指向 SDRAM 地址范围。下面是一个链接脚本片段的示例/* 链接脚本片段把 MicroPython 堆放到 SDRAM */ MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 2048K RAM (xrw) : ORIGIN 0x24000000, LENGTH 512K SDRAM (xrw) : ORIGIN 0xC0000000, LENGTH 32768K } /* 堆区放到 SDRAM */ _heap_start ORIGIN(SDRAM); _heap_end ORIGIN(SDRAM) LENGTH(SDRAM);逻辑说明ORIGIN(SDRAM)是 SDRAM 映射后的起始地址H743 的 FMC Bank1 通常映射到0xC0000000。参数说明LENGTH写 32768K 就是 32M但实际可用堆大小要减去 MicroPython 自身占用的部分。如果链接脚本改错编译能过但运行时会 HardFault因为堆指针指向了不可访问的区域。3.3 文件系统放到 SQPI FlashMicroPython 支持多种文件系统常用的是 littlefs。要让文件系统落在外部 SQPI Flash 上需要实现一个块设备驱动把 QUADSPI 的擦除、读写接口对接给 littlefs。在ports/stm32里有flashbdev.c可以参考但那是针对内部 Flash 的。外部 Flash 需要自己写一个bdev结构注册到 MicroPython 的 VFS 层。下面是一个简化的块设备接口// 外部 SQPI Flash 块设备读函数 static int ext_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { uint32_t addr block_num * BLOCK_SIZE; // 调用 QUADSPI 读接口注意地址要加上 Flash 的基址偏移 BSP_QSPI_Read(dest, addr, num_blocks * BLOCK_SIZE); return 0; } // 写函数先擦除再写littlefs 会保证擦除对齐 static int ext_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { uint32_t addr block_num * BLOCK_SIZE; BSP_QSPI_Write(src, addr, num_blocks * BLOCK_SIZE); return 0; } // 擦除函数按扇区擦除 static int ext_flash_erase_block(uint32_t block_num) { uint32_t addr block_num * BLOCK_SIZE; BSP_QSPI_Erase_Block(addr); return 0; }逻辑说明littlefs 以块为单位管理存储块大小通常设为 4096 字节。读函数直接调 QUADSPI 读写函数需要先擦后写擦除函数按扇区操作。参数说明BLOCK_SIZE要和 littlefs 配置一致否则文件系统挂载会失败。这里最容易翻车的地方是 QUADSPI 的地址映射内存映射模式下读地址是0x90000000起始但擦写操作要用命令模式地址是 Flash 内部的偏移地址两者不能混。3.4 编译与烧录配置完成后在ports/stm32目录下执行make BOARD你的板名。编译产物是一个.dfu或.hex文件。烧录可以用 ST-Link 或者 DFU 模式。第一次烧录后串口应该能看到 MicroPython 的 REPL 提示符。如果没出来先检查时钟配置和串口引脚。4. 避坑与排查移植过程中最容易翻车的五个点4.1 现象REPL 能出来但一执行 import 就 HardFault原因堆指针指向了 SDRAM但 SDRAM 初始化没成功或者 MPU 配置把 SDRAM 区域设成了不可缓存但实际访问方式不对。H743 有 CacheSDRAM 区域如果配成 Write-Through 但没做 Cache 维护读写会不一致。解决先确认 SDRAM 测试通过再检查 MPU 配置。我一般会把 SDRAM 区域配成 Normal、Write-Back、Read-Write Allocate然后在 MicroPython 启动前执行一次 Cache 无效化。4.2 现象文件系统挂载失败报 “no filesystem”原因外部 Flash 的块设备读函数返回了错误数据或者擦除没成功。QUADPI 在内存映射模式下读没问题但擦除命令需要退出内存映射模式再发。解决在擦除和写操作前先调HAL_QSPI_Abort或者退出内存映射发完命令再重新进入。另外检查 Flash 的扇区大小有些 Flash 是 4K 扇区有些是 64Klittlefs 的块大小要匹配。4.3 现象编译通过但固件大小超过内部 Flash原因MicroPython 固件加上文件系统镜像很容易超过 2MB。如果没把文件系统放到外部 Flash链接时就会溢出。解决确认mpconfigboard.mk里关闭了不必要的模块比如MICROPY_PY_BLUETOOTH、MICROPY_PY_ETHERNET等。另外把文件系统明确指向外部 Flash 的块设备。4.4 现象SDRAM 读写偶尔出错跑久了必挂原因时序参数太激进或者 FMC 时钟分频不对。H743 的 FMC 时钟来自 HCLK3如果 HCLK3 是 240MHzSDRAM 时钟就是 120MHz很多 SDRAM 芯片跑不到这个频率。解决把 FMC 时钟分频调大降到 100MHz 以下。时序参数里的LoadToActiveDelay等值适当放宽。用示波器看 SDRAM 时钟和数据线的信号质量如果有过冲加串阻。4.5 现象QUADSPI 读速度慢Python 脚本加载卡顿原因QUADSPI 的 dummy cycles 设少了或者时钟太低。内存映射模式下的读延迟直接影响 XIP 性能。解决查 Flash 手册确认在最高时钟下需要的 dummy cycles 数在 QUADSPI 初始化里设对。时钟可以先从 50MHz 开始稳定后逐步提到 80MHz 或 100MHz。5. 进阶技巧让 MicroPython 在 H743 上跑得更稳的几个习惯5.1 用 MPU 保护关键区域H743 的 MPU 可以配置多个区域。我习惯把 SDRAM 配成可缓存把 QUADSPI 映射区配成不可缓存但可执行把片上 SRAM 配成不可缓存。这样能避免 Cache 一致性问题导致的玄学错误。配置 MPU 时注意对齐要求区域大小必须是 2 的幂。5.2 给 littlefs 留足冗余空间32M 的 Flash 看起来很大但 littlefs 需要预留元数据空间。实际可用容量大概是总容量的 80% 左右。如果文件系统频繁写还要考虑磨损均衡。我一般会把文件系统分区设成 16M剩下 16M 留作固件备份或数据区。5.3 用 mpconfigboard.h 精细裁剪功能MicroPython 默认编译很多模块但 H743 上不一定都用得到。在mpconfigboard.h里关掉MICROPY_PY_THREAD、MICROPY_PY_SSL等大块头能省下不少 Flash 和 RAM。下面是一个裁剪示例// 关闭不用的模块节省空间 #define MICROPY_PY_THREAD (0) #define MICROPY_PY_SSL (0) #define MICROPY_PY_BLUETOOTH (0) #define MICROPY_PY_ETHERNET (0) // 打开需要的模块 #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_ADC (1)逻辑说明这些宏定义控制 MicroPython 编译时包含哪些功能。参数说明设为 0 表示不编译设为 1 表示编译。裁剪时要小心依赖关系比如关掉MICROPY_PY_THREAD后某些依赖线程的模块也要一并关掉。5.4 验证方法跑一个内存压力测试移植完成后别只跑个print(hello)就完事。写一个 Python 脚本反复申请和释放大块内存同时读写文件系统跑上半小时。如果不出错基本可以认为稳定。下面是一个简单的压力测试脚本# MicroPython 内存与文件系统压力测试 import gc import os # 反复申请大列表触发垃圾回收 for i in range(100): data [0] * 100000 # 约 400KB del data gc.collect() print(iter, i, free, gc.mem_free()) # 反复写文件 for i in range(50): with open(/flash/test.bin, wb) as f: f.write(bx * 4096) os.remove(/flash/test.bin) print(file iter, i)逻辑说明这个脚本先测试堆内存的申请释放再测试文件系统的写入删除。参数说明100000个整数的列表在 32 位平台上大约占 400KB能有效触发 SDRAM 堆的分配。如果 SDRAM 时序有问题这个测试跑几轮就会 HardFault。5.5 一个我常犯的错误早期移植时我总想把所有功能都打开结果固件编译出来 1.8MB文件系统只剩 200KB随便写几个脚本就满了。后来学乖了先明确这块板子要干什么只编译需要的模块。另外SDRAM 的时序参数我习惯先用保守值跑通再逐步收紧而不是一上来就照着手册的典型值填。这个习惯帮我省了很多调试时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑