STM32H743外扩SDRAM实战:从内存告急到稳定运行32MB
发布时间:2026/10/5 5:51:07 作者:尧图编辑部 阅读量:1,286

两年前我第一次拿 STM32H743 跑带 LVGL 的完整 GUI 工程时编译能过上电却直接进 HardFault折腾了一整天最后发现是 RAM 不够。H743 官方标称有接近 1MB 的 SRAM听起来怎么都够用但真要被 JPEG 硬解、LTDC 显存、LVGL draw buffer、网络协议栈一起瓜分的时候512KB 的 AXI SRAM 根本撑不住。后来老实按方案在 FMC 上外扩了一片华邦 W9825G6KH-632MB SDRAMCubeMX 配置、HAL 库初始化、内存测试一路走通问题才彻底解决。这篇文章就是我从内存告急到SDRAM 稳定跑起来的完整记录适合准备给 H743 外扩 SDRAM 做 GUI、图像采集或者大缓冲应用的人参考。1. 先别急着接外设说清楚H743那900多KB到底去哪了1.1 内部SRAM的真实布局TCM、AXI SRAM、SRAM1-4STM32H743 的 RAM 账面数字很好看Flash 2MBRAM 加起来接近 1MB听起来根本不用外扩。但 H7 的 RAM 和 F1/F4 那种一块连续 SRAM完全不是一个概念它被物理分成了好几块而且每块的访问路径不一样。DTCM/ITCM各 128KB挂在 CPU 内核私有总线上访问延迟最低但 DMA、DMA2D、LTDC、JPEG 全都访问不到。TCM 只能给 CPU 跑代码、放栈、放临界变量。AXI SRAM512KB地址 0x24000000通过 AXI 总线矩阵挂在 CPU 和外设之间这是最常用的主存绝大多数 malloc 和全局大数组都放这里。SRAM1/SRAM2/SRAM3/SRAM4分别为 128KB、128KB、32KB、64KB地址分散在 0x30000000 和 0x38000000 附近带宽路径和 AXI SRAM 不一样可以用来做 DMA 缓冲但工程上很少有人把主堆栈放那里。关键问题在于CPU 能用 1MB不代表你的程序能把 1MB 全部用完。TCM 对 DMA 不可见SRAM1-4 又是零散分布的实际能作为一个连续大堆使用的只有 512KB AXI SRAM。你写一个static uint8_t big_buffer[600 * 1024]链接器根本没地方放。1.2 哪些吃内存大户会瞬间榨干它外扩 SDRAM 之前先想清楚你的内存到底被谁吃掉了。我当时的工程有四个大户第一个是 JPEG 硬件编解码器。H743 内置硬件 JPEG 模块非常实用解一张 1920x1080 的 JFIF 图输出 RGB565 格式需要 1920 * 1080 * 2 约 4MB 缓冲输出 ARGB8888 直接翻倍到 8MB。512KB AXI SRAM 连一张全高清的 RGB565 输出都装不下。第二个是 LVGL。LVGL 的 draw buffer 越大渲染性能和帧率越稳。一个 800x480 的 RGB565 屏幕单缓冲 768KB如果做双缓冲就是 1.5MB。这还没算 LVGL 内部为控件、字体、图片解码预留的动态内存。第三个是 LTDC 显存。如果直接用 LTDC 驱动 RGB 屏帧缓冲一般放在外部 SDRAM 里。一个 1024x600 的 RGB888 屏一帧就是 1.84MB双缓冲直接奔 3.7MB 去。第四个是 DMA2D 的图像处理中转区。用 DMA2D 做图片缩放、颜色格式转换、Alpha 混合时需要源缓冲、目标缓冲同时驻留内存操作一张大图很容易一次性吃掉好几 MB。当你把这些部件同时放进一个工程内部 RAM 的处境就是不是省一省够用而是结构性不够用。所以外扩 SDRAM 不是可选项是这类运行内存大户应用的刚需。2. W9825G6KH-6这颗SDRAM的选型与硬件连接2.1 容量、位宽、引脚特性拆解W9825G6KH-6 是华邦生产的 256Mbit SDRAM换算过来是 32MB数据宽度 16bit。内部由 4 个 bank 组成每个 bank 有 4096 行、1024 列行地址 12 位A0-A11列地址 10 位A0-A9两个 bank 选择引脚 BA0/BA1。这个结构决定了 FMC 配置时 RowBitsNumber 要填 12ColumnBitsNumber 要填 10InternalBankNumber 填 4。后缀 -6 表示时钟周期为 6ns对应最高约 166MHz。在 H743 的 FMC 上SDRAM 时钟通常用 HCLK/2也就是 100MHz-120MHz 左右完全在它的工作范围内余量充足。这个型号是非常成熟的通用料QFP 类的贴片手工焊都没问题某宝和正规代理都能买到价格便宜假货相对少是 32MB 这个容量档位里性价比很高的选择。2.2 为什么选SDRAM而不是并口SRAM或者PSRAM从 F103 时代很多人就外扩 SRAM比如 IS62WV512161MB 并口 SRAM。这种芯片接口简单、时序简单、随机访问快但容量到 4MB 以上时价格、引脚数量、布线复杂度全部失控。32MB 并口 SRAM 需要 22 根地址线加 16 根数据线加控制线接近 45 根线布 4 层板非常痛苦。SDRAM 的优势在于地址线和数据线是时分复用的行地址、列地址共用同一组引脚32MB 容量只占 13 根地址线加 16 根数据线总共三十多根线就能搞定。代价是访问需要周期性刷新有行激活、预充电这些状态机操作随机访问延迟比 SRAM 高。但 H743 的 FMC 控制器把 SDRAM 的状态机全管起来了CPU 这边看起来就是一块连续内存所以几乎不需要你关心 SDRAM 内部的刷新时序细节。对比同价的 PSRAM比如 APMemory 系的 SPI PSRAM它走 QuadSPI 接口虽然引脚少但带宽和随机访问能力都远不如 FMC 上的并行 SDRAM做显存容易碰到带宽瓶颈。FMC 外扩 SDRAM 是容量、带宽、成本、布线难度这几个维度里最均衡的一条路。2.3 原理图连接与PCB布线的几个关键点连接上并不复杂。H743 的 FMC 外设提供了专门的 SDRAM 控制器接口SDNE0 做片选对应 SDRAM Bank1 地址 0xC0000000SDCKE0 做时钟使能SDCLK 输出时钟SDNRAS、SDNCAS、SDNWE 分别是行地址选通、列地址选通、写使能。片选、RAS、CAS、WE 这些信号一一对应接到 W9825G6KH 的相应引脚就行。硬件上容易栽的坑有三个。第一个是去耦电容一定不能省SDRAM 每个 VDD 和 VDDQ 引脚附近都放 0.1uF 陶瓷电容电源入口再放 10uF 钽电容或电解电容。SDCLK 是高频信号电源不干净会导致偶发读写错误而且这种错误很难定位。第二个是数据线、地址线、控制线尽量做等长处理4 层板的话把所有 SDRAM 信号走同一层避免换层过多引入过孔延迟差异。第三个是如果板上还有其他大电流负载比如电机驱动、加热丝SDRAM 供电最好单独走一小块电源岛或者至少用磁珠和数字部分隔开否则负载切换时电压跌落会让 SDRAM 直接丢数据。3. CubeMX配置时序不是看着填是算出来的3.1 时钟树与FMC时钟摆到多少合适CubeMX 配置的第一步是确定 FMC 的时钟。H743 的 FMC 挂在 AHB3 总线上FMC 的 SDRAM 时钟 SDCLK 可配置为 HCLK 本身、HCLK/2、HCLK/3 这几个档位。我用的工程是 CPU 跑 480MHzHCLK 为 240MHz所以 SDCLK 选 HCLK/2 就是 120MHz。可能会有朋友想W9825G6KH-6 能跑 166MHz为什么不用 HCLK 直接 240MHz 给 SDRAM第一是 FMC 的 SDCLK 最高档是 HCLK240MHz 已经超过 SDRAM 上限第二是 FMC 本身内部有时序余量的限制SDRAM 数据和命令引脚在这么高的频率下要保持建立保持时间会很紧120MHz 才是 H743 工程里最常见、最稳的档位留给布线延迟的余量足够大。3.2 行列地址位、CAS、突发长度的填法CubeMX 的 FMC 页签下SDRAM 配置项很多。以 W9825G6KH-6 为例我填的参数如下Row address bits12Column address bits10Number of internal banks4Data bus width16 bitCAS latency3SDClock periodHCLK/2Read burstEnableRead pipe delay1Write protectionDisable这里 CAS latency 和模式寄存器里的 CL 必须是同一个值否则初始化后读写必挂。120MHz 下周期约 8.33nsCL3 表示从发出读命令到数据有效需要 3 个时钟周期也就是约 25ns。W9825G6KH 在 166MHz 下 CL 可以到 3在 120MHz 下 CL3 是保证可靠性的稳妥选择。如果追求极限性能可以试 CL2但我个人不建议在 GUI 工程里为了这一两个时钟周期去赌时序裕量。Read burst 一般建议 Enable。这个选项让 FMC 在 CPU 做连续多字访问时自动把 SDRAM 的突发读能力用起来对 memcpy、DMA2D 这种大块传输性能提升非常明显。3.3 关键时序从手册到寄存器的换算过程CubeMX 的时序参数需要自己从 W9825G6KH-6 数据手册里查再根据 SDCLK 时钟周期换算成时钟周期数填进去。我当时的计算过程如下照着这个思路走就不会错。SDRAM 时钟频率 120MHz一个周期 8.33ns。手册几个关键参数的标准值tRCD行激活到列命令延迟最小 20nstRP预充电周期最小 20nstRAS行激活最短时间最小 42nstRC行周期最小 66ns写恢复时间 tWR 约 2 个时钟周期。把 ns 值除以 8.33ns 再向上取整得到tRCD20ns 需要 2.4 个周期 填 3tRP20ns 填 3tRAS42ns 填 642 / 8.33 5.04向上取整必须到 6tRC66ns 填 866 / 8.33 7.92CubeMX 里这些字段填的是时钟周期数代码生成后直接写进 FMC 的 SDTR 寄存器。我这里用的是一个典型值你实际画板时务必打开 W9825G6KH-6 的最新数据手册核对尤其是温度范围和电压范围超出常规条件时时序会更加收紧。还要注意 SDRAM 的自动刷新周期设置。SDRAM 要求每行在 64ms 内至少被刷新一次W9825G6KH 共有 8192 行所以每次刷新间隔为 64ms / 8192 7.8125us。FMC 的 SDRAM 刷新计数寄存器 SDRTR 的计算公式是刷新周期计数 刷新间隔时间 / SDRAM 时钟周期 - 20减 20 是给自动刷新命令本身的执行时间 tRFC 留出余量。代入 7.8125us / 8.33ns ≈ 938再减 20 得到 918。这就是 CubeMX 里 Refresh 字段要填的值。如果填大了刷新频率过低SDRAM 会因电荷泄漏丢数据填小了刷新频繁占用总线带宽白白损失。3.4 引脚复用冲突排查CubeMX 自动分配 SDRAM 引脚基本不会错但 H743 封装引脚密集最容易出问题的是 SDRAM 引脚和调试口、以太网、USB 的复用冲突。我在一次工程里把 SDRAM 数据线 DQ 分配到了 PA15、PB3、PB4 这些引脚上结果和 SWD 调试口冲突上电后调试器直接连不上只能按住复位瞬间连接非常被动。配置完引脚后一定要在 CubeMX 的 Pinout 视图里看引脚颜色。橙色和红色代表有冲突需要手动指定到其他可复用引脚。使用 SWD 调试时尽量保持 PA13、PA14、PA15、PB3、PB4 不被 SDRAM 占用否则调试体验会非常痛苦。如果引脚实在不够用我的建议是换更大封装而不是强行在复用里做文章后面 Debug 的时间成本远超芯片差价。4. 初始化代码跑通从HAL库到内存被系统认领4.1 CubeMX生成的初始化代码与SDRAM序列化流程CubeMX 生成的代码默认只做了 FMC 寄存器级别的初始化真正的 SDRAM 上电序列还需要手动调用。HAL 库的HAL_SDRAM_Init()函数内部会调用HAL_SDRAM_MspInit()配置引脚和时钟然后需要手动按顺序向 SDRAM 发送初始化命令。CubeMX 生成的main()里会有一句SDRAM_InitSequence()或者类似的函数调用看起来像是这样void SDRAM_InitSequence(void) { FMC_SDRAM_CommandTypeDef cmd; // 1. 发送 NOP 命令等待至少 200us让 SDRAM 完成上电稳定 cmd.CommandMode FMC_SDRAM_CMD_NOP; cmd.CommandTarget FMC_SDRAM_CMD_TARGET_BANK1; cmd.AutoRefreshNumber 1; cmd.ModeRegisterDefinition 0; HAL_SDRAM_SendCommand(hsdram1, cmd, 0x1000); HAL_Delay(10); // 2. 发送预充电命令关闭所有行 cmd.CommandMode FMC_SDRAM_CMD_PRECHARGE_ALL; HAL_SDRAM_SendCommand(hsdram1, cmd, 0x1000); // 3. 发送自动刷新命令ST 例程一般刷 8 次确保初始刷新完成 cmd.CommandMode FMC_SDRAM_CMD_AUTOREFRESH_MODE; cmd.AutoRefreshNumber 8; HAL_SDRAM_SendCommand(hsdram1, cmd, 0x1000); // 4. 发送加载模式寄存器命令设置 CAS3突发长度1 cmd.CommandMode FMC_SDRAM_CMD_LOAD_MODE; cmd.ModeRegisterDefinition 0x23; // CL3BL1 HAL_SDRAM_SendCommand(hsdram1, cmd, 0x1000); // 5. 设置刷新定时器 HAL_SDRAM_ProgramRefreshRate(hsdram1, 918); }上电稳定这一步最容易翻车。SDRAM 上电后需要至少 200us 的延时才能开始接收初始化命令有些人把 NOP 命令发得太后导致后续预充电命令被 SDRAM 忽略最终表现为数据写进去读出来全错位。代码里加一个HAL_Delay(10)是保守做法实际 200us 以上的延时即可。如果你用 RTOS这个阶段要在调度器启动前完成。4.2 模式寄存器设置CL3和突发长度为什么必须一致模式寄存器设置是整个初始化里最需要严谨的一步。W9825G6KH 模式寄存器的各位定义包括突发长度、突发类型、CAS 延迟、运行模式等初始化时通过地址线 A0-A12 送入。我这里填的 0x23拆开看是二进制 0b100011其中 A3 为 0 表示突发类型为顺序突发BA10、BA00 是模式寄存器选择CAS 位 A6-A4 为 0b011 表示 CL3A2-A0 为 0b011 表示突发长度为 8这里有个容易混淆的地方实际很多 ST 例程在 FMC SDRAM 下填 0x23 时对应的突发长度是 1 或者 8取决于 FMC 如何解析 ModeRegisterDefinition。工程上的关键原则是模式寄存器里的 CL 必须和 CubeMX 里配置的 CAS Latency 一致两者不一致时 SDRAM 的实际行为是未知的轻则随机数据错误重则完全不可读。突发长度方面FMC 控制器内部会自己管理突发访问Mode Register 里设成 1 并不会让连续内存访问变慢因为 FMC 会拆成多次单字访问实际性能由 FMC 的 Read Burst 配置决定。我没在这个字段上纠结使用芯片厂商提供的初始化参考值前提是确保 CL3 这一项与前面配置一致。4.3 链接脚本增加SDRAM区域和变量放置初始化完成后0xC0000000 起就是一块连续 32MB 的内存。但链接器默认不知道它的存在需要用链接脚本显式声明。GCC 工具链的 .ld 文件里在 MEMORY 段追加一段 SDRAMMEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 2048K DTCMRAM (rwx) : ORIGIN 0x20000000, LENGTH 128K RAM (rwx) : ORIGIN 0x24000000, LENGTH 512K SDRAM (rwx) : ORIGIN 0xC0000000, LENGTH 32M }然后在 SECTIONS 段里增加一个自定义输出段.sdram (NOLOAD) : { . ALIGN(16); *(.sdram) *(.sdram.*) } SDRAMC 代码里就可以用编译器属性把大数组放到 SDRAM__attribute__((section(.sdram))) uint8_t jpeg_output_buffer[1920 * 1080 * 2]; __attribute__((section(.sdram))) lv_color_t lv_draw_buffer[800 * 480];如果你用的是 IAR操作方式类似在 .icf 文件里定义一个place in SDRAM_region { section .sdram };这样的大段规则然后照样用__attribute__((section(.sdram)))指定。VSCode 里配 CMake 或 Makefile 工程的朋友记得把链接脚本路径写对CubeMX 生成的 .ld 文件如果手动改过重新生成工程前要先备份。4.4 MPU与D-CacheCPU写得快、外设读不到的老大难H743 的 D-Cache 是性能利器也是 SDRAM 应用里最大的隐形坑。默认情况下如果开了 D-CacheCPU 写数据到 SDRAM 时可能只写进 Cache Line并没有真正落到 SDRAM 物理内存。如果 LTDC 控制器直接从 SDRAM 读显存DMA2D 从 SDRAM 搬运数据JPEG 硬件编码器往 SDRAM 写数据这些外设不走 CPU 的 Cache 路径立刻就会遇到CPU 认为写好了、外设看到的却是旧数据的问题。解决方案有两种。第一种是把 SDRAM 区域用 MPU 配置为普通不可缓存区域也就是 Normal Non-cacheable。这个方案最简单CPU 每次写 SDRAM 都是直写性能会损失一些但对于 120MHz 的 SDRAM 来说CPU 直写本来也快不到哪里去省心更重要。第二种是保留 Cache但手动做缓存一致性维护CPU 写 SDRAM 时如果后续要被外设读就调用SCB_CleanDCache()外设往 SDRAM 写完CPU 要去读时就调用SCB_InvalidateDCache()。以 JPEG 解码为例正确流程是JPEG 外设解码完成后先执行一次SCB_InvalidateDCache()把对应地址的 Cache Line 全部作废再让 CPU 去读输出缓冲否则 CPU 可能直接从 Cache 里拿到旧数据。我个人的工程习惯是LTDC 显存区配成 Write-Through写直达或 Non-cacheable因为显示刷新对显存写入的实时性要求高Cache 带来的写延迟反而会干扰帧缓冲更新JPEG 解码的中间数据缓冲和 LVGL 的 draw buffer 保留 Cache由代码显式控制 Clean/Invalidate在性能和正确性之间取平衡。MPU 配置要在 main 函数早期Cache 使能之前或之后立即完成保证外设访问 SDRAM 之前区域属性已经生效。5. 读写测试别等GUI跑起来才发现问题5.1 基础读写全地址32位写读验证SDRAM 初始化完不能直接跑业务得先证明 32MB 真的能读能写。最简单的方法是暴力遍历从起始地址开始以 32 位宽度写入固定 pattern再读回对比。C 代码大概是这样#define SDRAM_START 0xC0000000u #define SDRAM_SIZE (32u * 1024u * 1024u) int SDRAM_Test_DataBus(void) { volatile uint32_t *p; uint32_t patterns[] {0xAAAAAAAA, 0x55555555, 0xDEADBEEF, 0x12345678}; uint32_t errors 0; for (int i 0; i sizeof(patterns)/sizeof(patterns[0]); i) { for (uint32_t addr 0; addr SDRAM_SIZE; addr 4) { p (volatile uint32_t *)(SDRAM_START addr); *p patterns[i]; } for (uint32_t addr 0; addr SDRAM_SIZE; addr 4) { p (volatile uint32_t *)(SDRAM_START addr); if (*p ! patterns[i]) { errors; } } } return errors; }注意这里不能用普通 C 编译器的 memcpy 或者连续赋值优化必须用 volatile 指针直接访问否则编译器可能把 RAM 里的副本优化掉测了个寂寞。第一次跑全量测试会花一点时间32MB 遍历双循环 4 个 pattern在 120MHz SDRAM 上大概几秒钟属于正常。5.2 地址线完整性测试排除错位和硬件虚焊基础读写测试能通过不代表地址线没问题。地址高位错位、某个数据引脚虚焊、片选或时钟焊盘连锡这些硬件问题表现出的症状往往是某一段地址读出重复数据或者特定 pattern 读回全 0 全 1。我常用的地址线测试方法是递增位移法往addr地址写addr值再读回比对。由于 SDRAM 内部行地址和列地址是复用的如果 A10、A7 这类特殊引脚虚焊错误地址会以 2 的幂次体现方便快速定位。int SDRAM_Test_AddressLines(void) { volatile uint32_t *p; uint32_t errors 0; for (uint32_t addr 0; addr SDRAM_SIZE; addr 4) { p (volatile uint32_t *)(SDRAM_START addr); *p addr; } for (uint32_t addr 0; addr SDRAM_SIZE; addr 4) { p (volatile uint32_t *)(SDRAM_START addr); if (*p ! addr) { errors; } } return errors; }这个测试最好在高温或低温环境做一遍SDRAM 的时序和虚焊在临界温度下最容易暴露。我踩过一次坑常温 25 度下全量测试全过设备运行到 60 度时偶发花屏最后查出是 SDCLK 线上一个过孔阻抗不连续导致高速信号反射高温下时序裕量进一步恶化。这种问题软件上只能优化时序配置去兼容根治还得靠改板子。5.3 带宽实测结果参考内存测试之外实际带宽决定了 SDRAM 能不能胜任显存这一角色。我自己在 H743 480MHz、FMC SDRAM 120MHz 16bit 配置下调用了精简版的带宽测试工具用循环连续读写加 DWT 时钟周期计数测出的结果大致如下操作实测带宽备注memcpy 32KB 读约 170-190 MB/s连续地址突发读memcpy 32KB 写约 140-160 MB/s写操作包含内部预充电和写入恢复随机 4 字节读写约 20-40 MB/s每次访问都要行激活和预充电AXI SRAM 连续读约 400 MB/s作为对照这个数据说明一个问题SDRAM 适合大块连续传输不适合频繁随机小访问。做 GUI 的时候LVGL 刷新用的矩形填充、DMA2D 的块拷贝都能达到接近峰值带宽运行体验很好但如果你把 malloc 的对象频繁分配释放分散到 SDRAM 里可能触发大量随机访问性能下降明显。所以我的分配策略是把大缓冲显存、图像帧、draw buffer、协议栈大包缓存放 SDRAM把高频小对象留在 AXI SRAM。5.4 把SDRAM跑进实际业务JPEG解码和LVGL的收益带宽测试通过后我直接用外扩 SDRAM 跑通了 JPEG 硬解。解码一张 1920x1080 的 JPEG输出 RGB565 到 SDRAM buffer从 JPEG 外设中断触发到SCB_InvalidateDCache()完成整体耗时在几十毫秒量级对于 GUI 相册应用完全够用。对比之前用内部 SRAM 只能解小图的窘境32MB 直接让我可以把多张全高清图同时驻留在内存里做缩略图轮播。LVGL 方面draw buffer 从 64KB 提升到 800x480 的全分辨率缓冲后滑块、列表滚动的拖影明显减少。配合 DMA2D 做图层混合动画帧率能从二十几帧跳到四五十帧。这里注意 LVGL 的lv_init()和 buffer 分配要在 SDRAM 初始化完成之后最好在main()中明确调用一次 SDRAM 自检函数失败就点亮错误 LED 并停留在 while(1) 里别带着隐患继续跑业务。6. 我踩过的坑按从常见到冷门排个序6.1 能写不能读、读出来全0xFF的排查链路这恐怕是外扩 SDRAM 最常见的故障现象。写入不报错但读回来的数据全部是 0xFF 或者 0x00。我的排查路径如下照着做能省很多时间。先确认 FMC 有没有真的把片选、RAS、CAS、WE 这些控制信号送到 SDRAM。用示波器量 SDNE0 和 SDCKE0 引脚的电压初始化完成后这些引脚应该保持有效电平如果没有任何活动问题多半在 FMC 时钟没有使能或者引脚复用配置错。再检查初始化序列顺序。SDRAM 对命令顺序非常敏感NOP 命令发出后必须等 200us 以上预充电必须在自动刷新之前自动刷新次数不能太少。很多能写不能读的案例是干脆没调用SDRAM_InitSequence()只执行了HAL_SDRAM_Init()。HAL 库的 Init 函数只配寄存器不上电序列。最后检查 CubeMX 配置的列地址位数和行地址位数。如果 SDRAM 的行地址实际是 12 位你配置成了 11 位FMC 访问地址时行地址会错位表现出来就是读写都能成功但数据错乱、或者越界区域读回全 F。用my_mem_read在 0xC0000000 附近和 0xC8000000 附近各读几个值对比跳变规律就能判断是不是行地址位数不匹配。6.2 偶发死机与刷新计数的关系SDRAM 需要在 64ms 内完成 8192 次刷新平均 7.8125us 一次。如果 SDRTR 的刷新计数设置错误比如填了 4095 这种极大值SDRAM 刷新间隔会远超规范表现为系统运行几分钟到几十分钟后偶发 HardFault 或数据随机错误非常难定位。这种偶发故障的排查思路是看故障概率是不是随时间累积。如果设备运行时间越长、死机概率越高大概率就是刷新配置有问题。HAL_SDRAM_ProgramRefreshRate(hsdram1, 918)最后一个参数直接影响刷新频率建议在量产固件里做成宏定义方便按不同主频的板卡调整。另外如果 FMC 时钟被重新配置刷新定时器要跟着重新设置否则用着旧刷新计数在新时钟下周期会偏移。6.3 硬件焊接与去耦不足的坑SDRAM 的 TSOP54 封装引脚间距很窄手工焊接容易连锡。连锡的典型症状很诡异数据线 DQ3 和 DQ4 短路时读回数据在字节内出现镜像错误比如写入 0x08 读回 0x04。排查这类硬件问题软件测试只能做嫌疑定位最终确认还得靠万用表蜂鸣档或显微镜目检。去耦不足的问题更难察觉。SDRAM 在页操作和大块刷写时电流变化剧烈如果 VDD 上的 0.1uF 电容离引脚太远电源纹波会直接导致读写出错。我在一次使用长飞线连接外部 SDRAM 面包板调试时测出偶发随机数据错误飞线一捏紧就消失松开又复现最终确认是接触不良加电源噪声。所以能用 PCB 解决的事绝对不要用飞线。6.4 CubeMX重新生成工程对链接脚本的影响CubeMX 有个机制重新生成代码时会覆盖部分用户文件但不会覆盖位于指定目录下的链接脚本。然而如果你直接改了根目录的.ld文件下次生成工程很可能被还原成默认状态。我在一次升级 CubeMX 版本后重新生成工程忘了 SDRAM 区域被还原程序链接时所有 SDRAM 段全报错花了大半天排查才发现是链接脚本被静默覆盖。解决办法是把自定义链接脚本放到 CubeMX 不会覆盖的路径比如Core/Src/linker/下或者用 CubeMX 的Linker Settings指定自定义脚本路径。Git 提交前留意.ld文件变更记录如果发现不需要的改动立刻回退。这个坑很冷门但一旦出现就是白天的 Debug 时间白白消耗。SDRAM 外扩这件事配置本身就那么几个步骤硬件上也无非几十根线真正难的是把每一步背后的时序逻辑吃透。每次新画一块板子我都会先跑一遍第五节的测试程序再开始写 GUI确认了内存可靠上层应用再复杂也只是工程量问题。W9825G6KH 这颗芯片价格不高、驱动成熟、资料丰富配合 STM32H743 的 FMC 控制器算是在大内存嵌入式的世界里少踩很多坑的组合。希望这篇记录能帮你的 H743 项目少走我走过的弯路。