最近整理了一块板子的核心参考设计:STM32H745IIT6 外挂一颗 IS42S83200J 的 SDRAM,容量 16MB,数据总线 32bit。可能有人会想,H745 已经是双核 480MHz + 240MHz,内部还带 1MB SRAM,为什么还要再挂 SDRAM?这个问题的答案,恰恰是这类参考设计真正有价值的地方:当你要跑 LVGL 界面、做音频缓存、或者放一帧摄像头图像时,1MB 内部 SRAM 根本不够用,而外挂 SDRAM 是性价比最高、驱动最成熟的大内存方案。
这篇记录面向正在画 H745 板卡、准备扩展外部内存的硬件工程师,也适合把 H7 工程从内部 RAM 迁移到外部 SDRAM 的嵌入式开发参考。我会把方案选型、FMC 接口原理、原理图与布线要点、初始化配置、以及实际调通时踩过的坑一次性写清楚,尽量做到能直接照着做。
1. 方案选型:为什么是 H745 配 IS42S83200J
1.1 STM32H745IIT6 的核心价值
STM32H745IIT6 属于 STM32H7 系列里的双核型号,Cortex-M7 跑 480MHz、Cortex-M4 跑 240MHz,Flash 有 2MB,内部 SRAM 加起来有 1MB。I 后缀表示工业级温度范围,T 是 LQFP 封装,6 代表 176 pin。这颗料能同时跑两个核,M7 负责重负载算法,M4 可以单独处理通讯或者实时性要求高的任务,架构上非常适合处理器密集和 I/O 密集混合的项目。
不过很多人容易忽略一点:H745 的内部 SRAM 虽然看着有 1MB,但它被分成了 AXI SRAM、SRAM1、SRAM2、SRAM3、SRAM4 好几块,大块内存分配并不是完全自由的。真要放一个 1280x800 的 RGB565 显存,一张就要 2MB,内部 RAM 直接爆掉。所以外挂 SDRAM 不是可选,而是这个方案的刚需。
1.2 IS42S83200J 这颗 SDRAM 的真实定位
IS42S83200J 是 ISSI 家的一颗 128Mb 同步动态随机存储器,组织方式是 4M x 32bit,换算下来正好 16MB。它内部有 4 个 bank,行地址 12 位,列地址 8 位,供电 3.3V,工作频率一般能到 143MHz 或 166MHz(不同速度等级对应不同尾缀)。要说性能,它在现在这个年头不算快,但胜在稳定、成熟、便宜,而且 32bit 位宽配合 FMC 接口访问效率很高。
选这颗颗粒还有一个现实原因:H745 的 FMC 控制器原生支持 16bit 和 32bit SDRAM,而 IS42S83200J 的 32bit 总线正好能把 FMC 的数据总线宽度用满。同样跑 50MHz 到 100MHz 的 SDRAM 时钟,32bit 位宽比 16bit 位宽带宽直接翻一倍,这对要拿作显存或者摄像头的缓冲区的场景来说差别非常大。
1.3 没有外部内存时,1MB 内部 SRAM 够不够用
如果只是做简单控制逻辑,内部 1MB SRAM 永远用不完。可一旦涉及图像、音频、界面框架,内存就立刻变成瓶颈。比如一个 240x240 的 RGB565 屏幕缓冲,一帧就要 112KB;摄像头 VGA 分辨率 YUV422,一帧也要 300KB 以上;做音频算法如果需要几十毫秒的延迟缓冲,几个缓冲区叠下来,1MB 根本不够。
外挂 SDRAM 后,可以把 LVGL 的显存、片外 framebuffer、DMA 缓冲区、音频 FIFO、甚至日志缓冲区全放进去。SDRAM 虽然延迟比内部 SRAM 高,但顺序访问带宽足够,用好了完全能撑起 GUI 刷新和摄像头抓取这类场景。还有一个隐性好处:外挂 SDRAM 还能作为 M7 和 M4 之间的共享内存,双核跑数据交换时不用占用昂贵的内部 SRAM,省心不少。
2. FMC 接口深度拆解:SDRAM 控制器的原理与配置
2.1 FMC 是怎么把 SDRAM 变成一段内存的
H745 的 FMC(Flexible Memory Controller)负责对外部存储器做访问控制。它不仅仅管 SDRAM,还能接 NOR Flash、SRAM、NAND Flash,但 SDRAM 部分有独立的命令控制器。简单理解,FMC 把 CPU 发出的地址和数据请求翻译成 SDRAM 需要的行激活、列读写、预充电、刷新这些时序操作。也就是说,CPU 只需要像读内部内存一样读 0xC0000000 这个地址段,FMC 会自动完成 SDSDRAM 的复杂时序。
SDRAM 和静态存储器不一样,它需要周期性刷新,否则内部存储的电荷会丢失。FMC 的 SDRAM 控制器里有刷新计数器,可以硬件自动刷新,不需要 CPU 干预。这也是为什么同样是外部内存,SDRAM 比 SRAM 便宜得多,但初始化流程和时序要求更挑剔。实际使用中,只要初始化序列没有严格执行,后面读写就会出现各种随机错误。
2.2 地址线、数据线与 Bank 映射
STM32H745 的 FMC 可以接两片 SDRAM,分别用 FMC_SDNE0 和 FMC_SDNE1 做片选,对应地址段是 0xC0000000 和 0xD0000000。我们的参考设计只用一片,挂在 Bank1,也就是 0xC0000000 起始地址。数据线 FMC_D0 到 FMC_D31 共 32 根,地址线这里用到 FMC_A0 到 FMC_A11,以及 bank 选择线 FMC_BA0、FMC_BA1,控制线包括 FMC_SDCLK、FMC_SDCKE0、FMC_NRAS、FMC_NCAS、FMC_NWE、FMC_SDNWE,另外还有字节屏蔽 FMC_NBL0 到 FMC_NBL3。
这里有一个很常见的误读:地址线 FMC_A0-A11 在 SDRAM 里是行地址和列地址复用的,不是像 SRAM 那样一次性把所有地址高低电平给出。FMC 先发行地址,再发列地址,通过 RAS 和 CAS 信号的先后顺序区分。所以不要用"容量等于 2 的地址线次方"去硬套,SDRAM 的寻址能力要看行数、列数和 bank 数的乘积。IS42S83200J 是 4 个 bank、每 bank 1M 个字,数据总线 32bit,整体就是 4M x 32bit = 16MB。
2.3 时序参数和刷新机制,必须搞懂的两个细节
FMC 时序初始化时有一堆参数,比如 RCDDelay、RPDelay、RowCycleDelay、WriteRecoveryTime、ExitSelfRefreshDelay、LoadToActiveDelay。这些参数的本质是把 SDRAM 数据手册里的 tRCD、tRP、tRC、tWR、tXSR、tMRD 转换成 SDCLK 周期个数。转换方法是:物理时间除以时钟周期,向上取整,再留一点余量。比如 SDCLK 跑 100MHz,周期 10ns,tRCD 是 15ns,那么至少需要 1.5 个时钟周期,实际取 2 个周期。
刷新机制是另一个容易出错的地方。IS42S83200J 数据手册会给"刷新周期数/刷新时间",常见是 4096 行/64ms,也就是每 64ms 要把所有行至少刷一遍。FMC 的刷新计数器会匀速发出自动刷新命令,刷新周期寄存器里的计数值要按实际 SDCLK 频率计算。如果算错,刷新过密会浪费带宽,刷新过疏则会出现数据保持错误。初始化时一定要对照数据手册确认行数,不要照抄别的型号配置。
3. 硬件设计实操:从原理图到 PCB 的关键约束
3.1 原理图设计要点与信号连接
原理图阶段的重点是把 FMC 信号和 SDRAM 一一对上,同时给每个电源引脚配上足够去耦。IS42S83200J 是 3.3V 器件,H745 的 FMC 引脚如果配置为 3.3V 电平,可以直接连接,不用加电平转换。连接时注意 FMC_NBL0-NBL3 要接到 SDRAM 的 DQM0-DQM3,这个在原理图检查时经常被漏看,一旦漏接,16bit 或 8bit 写操作就会出错。
电源去耦方面,SDRAM 的供电引脚和 I/O 供电引脚最好都就近放一个 100nF 陶瓷电容,然后板级再放一个 10uF 左右的钽电容或者多层陶瓷电容。H745 的供电要严格按数据手册来,特别注意 VCAP 引脚上的电容值和 ESR 要求,这是内部 LDO 稳压的关键,很多自制板跑不稳都是 VCAP 电容选错导致的。原理图评审时我一般还会核对复位引脚的上拉、BOOT 引脚默认状态以及 SWD 调试接口的完整性,这些和 SDRAM 看似无关,但实际上影响调试效率。
3.2 布局布线经验:别让 SDRAM 死在信号完整性上
PCB 布局上,SDRAM 要尽量靠近 MCU,走线距离越短越好。H745 的 FMC 总线工作在几十到一百多 MHz,对于 SDRAM 这种并行总线,数据线和地址线如果走得太长,反射和串扰会非常明显。我的经验是,SDRAM 和 MCU 放在同一面,两者中间不要穿插其他连接器或者高噪声器件,FMC 信号尽量集中在一个区域。
布线约束建议分三个层次处理:
| 信号组 | 约束建议 | 说明 |
|---|---|---|
| 数据线 D[31:0] 和 NBL | 组内等长误差控制在 50mil 以内 | 数据线是双向的,时序裕量最紧张 |
| 地址线 A[11:0]、BA[1:0] 和控制线 | 组内等长,相对时钟线误差控制在 100-150mil | 地址和控制线是单向的,要求略宽松 |
| SDCLK 时钟线 | 单独走,包地或与地间距保持 2 倍线宽 | 时钟是所有信号的参考基准,最不能含糊 |
布线时我会尽量保持整组信号在同一层,减少过孔数量,因为过孔会带来不连续的阻抗变化。如果有条件,用 4 层板,保证有一个完整的地平面和一个完整的电源平面。串阻方面,每个 FMC 信号串 22 到 33 欧姆的电阻,放在驱动端附近,能有效抑制过冲。对于 100MHz 级别的 SDRAM 接口,这些措施不是玄学,而是能不能稳定过温度测试的分水岭。
3.3 电源和去耦设计,容易被忽略的坑
H745 这类高性能 MCU,电源设计比 SDRAM 部分更容易踩坑。MCU 的 VDD 电源平面要足够宽,并在每个电源引脚旁放 100nF 电容,大容量的 bulk 电容也要跟上。SDRAM 对电源纹波比较敏感,如果 3.3V 纹波过大,可能表现为偶发读写错误,而且这种错误很难复现。示波器测 SDRAM 电源引脚时,纹波最好控制在 50mV 以内。
还有一点:SDRAM 的数据总线在翻转时会产生较大的瞬态电流,这个瞬态电流会在地平面上造成噪声。如果 MCU 的地和 SDRAM 的地分开铺,反而容易形成电位差。正确做法是保证整个板子地平面连续,让回流路径最短。省地层是绝对不可取的,会直接导致信号完整性问题。另外,H745 的 LQFP176 封装底部有裸露焊盘,一定要良好接地并做好散热,满载运行时内核温度控制全靠这个焊盘。
4. 软件初始化与验证:让 SDRAM 真正跑起来
4.1 CubeMX 中 FMC SDRAM 参数怎么填
用 CubeMX 配置时,在 FMC 里启用 SDRAM Bank1,然后把颗粒参数按 IS42S83200J 填写。列地址 8bit,行地址 12bit,内部 bank 数 4,数据位宽 32bit,CAS latency 根据你的时序余量选 2 或 3。SDCLK 周期一般选 HCLK 分频,比如 HCLK 200MHz 时选除以 2,得到 100MHz 的 SDCLK。这里不要贪快,SDRAM 本身能跑 166MHz,但 FMC 控制器和 PCB 走线未必跟得上。
CubeMX 还会要求填写刷新率,这个值是根据颗粒刷新周期算出来的时间值。IS42S83200J 按 64ms/4096 行计算,刷新间隔约 15.6us。在 CubeMX 里写刷新率时,系统会按你的 SDCLK 频率换算成刷新计数。我的习惯是先按 15.6us 填入,跑内存测试确认稳定后,再考虑是否需要调整。如果发现刷新过于频繁导致带宽下降,可以在满足数据手册要求的前提下适当放宽。
4.2 初始化时序计算与代码落实
SDRAM 上电后必须严格按顺序执行初始化命令,否则颗粒无法进入正常读写状态。首先是上电等待约 100us,然后发 NOP 命令,接着执行 PRECHARGE ALL,把所有 bank 预充电;再发两个以上 AUTO REFRESH 命令;最后是 LOAD MODE REGISTER,把 CAS latency、burst length、write burst 模式写进颗粒的模式寄存器。HAL 库的 HAL_SDRAM_Init 之后,需要手动调用 HAL_SDRAM_SendCommand 按这个顺序走一遍。
以 100MHz SDCLK 为例,时序寄存器可以这样估算:tRCD 约 15ns 记 2 个周期,tRP 约 15ns 记 2 个周期,tRC 约 63ns 记 7 个周期,tWR 约 14ns 记 2 个周期,tXSR 约 70ns 记 7 个周期,tMRD 约 12ns 记 2 个周期。这些数值不要照抄,要打开颗粒数据手册对着换算。模式寄存器的典型值,CAS=2、顺序突发、burst length 选 1 或者 4/8,需要结合你的访问模式决定。跑 GUI 时我一般选较短的 burst,避免突发太长占用总线。
初始化完成后,建议在 0xC0000000 地址段做一次基础读写。比如往里面写 0x00000000 和 0xFFFFFFFF 两个极端值,再写 0x5A5A5A5A 和 0xA5A5A5A5,读回校验。如果能通过,说明 SDRAM 已经能响应基本读写,后面再上压力测试。不要急着跑应用,先把这块内存的底摸清楚。
4.3 内存自检与带宽评估,实测数据说话
内存自检不能只写一种 pattern,我习惯做三类测试。第一类是数据线测试,用 walking 1 和 walking 0 逐位翻转,用来找出数据线短路、虚焊或者交叉连接。第二类是地址线测试,在地址值编码进数据,例如在地址 0x100 写入 0x100,然后全片扫描,可以发现地址线的高位或低位有无粘连。第三类是随机数据压力测试,用伪随机序列反复写读,跑足够长时间。
对应代码如下,可以直接在工程里调用:
int sdram_self_test(uint32_t base, uint32_t len) { uint32_t i; volatile uint32_t *p = (volatile uint32_t *)base; // walking 1 测试 for (i = 0; i < len / 4; i++) { p[i] = 0x1UL << (i % 32); } for (i = 0; i < len / 4; i++) { if (p[i] != (0x1UL << (i % 32))) return -1; } // 地址相关性测试 for (i = 0; i < len / 4; i++) { p[i] = i; } for (i = 0; i < len / 4; i++) { if (p[i] != i) return -2; } // 翻转 pattern 测试 for (i = 0; i < len / 4; i++) { p[i] = 0x55555555; } for (i = 0; i < len / 4; i++) { if (p[i] != 0x55555555) return -3; } return 0; }带宽评估可以用 DWT 计数器来测批量 memcpy 的时间。比如复制 1MB 数据到 SDRAM,读 DWT->CYCCNT 计算周期数,再根据 CPU 频率换算带宽。我实测在 100MHz SDCLK、32bit 总线时,连续读写的有效带宽大概在 300MB/s 到 380MB/s 之间,比纯理论值低,因为还有刷新和行切换开销。这个数据对评估 GUI 刷屏是否流畅有直接参考价值。
5. 问题排查与稳定性优化实录
5.1 数据偶发错误,先从这几个方向查
SDRAM 出现偶发数据错误时,先不要急着怀疑颗粒,按照概率排序排查:第一是时序寄存器配置过紧,第二是 SDCLK 频率过高,第三是供电纹波,第四是 PCB 等长和参考平面,最后才是颗粒本身。多数情况下,把 RCDDelay 或 RowCycleDelay 加一个周期,现象就消失了。这说明时序裕量不足,不是颗粒坏了。
如果只在温度升高后出现错误,优先检查电源散热和 SDCLK 信号质量。用示波器看 SDCLK、DQS(如果有)和数据的建立保持时间比较麻烦,但最基本的方法是看波形有没有明显过冲、振铃和上升沿塌陷。没有示波器的话,就把系统时钟降档试试,比如 SDCLK 从 100MHz 降到 66MHz,如果错误消失,基本可以判断是信号完整性或时序裕量问题。
5.2 刷新与 CPU 访问冲突,用什么手段规避
SDRAM 刷新操作会占用总线周期,如果 CPU 正在连续读取,刷新请求会把当前访问打断,造成额外的等待周期。对于大多数应用,这种延迟影响不大,但如果你在跑实时性要求高的 DMA 传输,刷新带来的等待可能导致 DMA 带宽抖动。FMC 的刷新计数器是均匀插入刷新的,问题通常不会集中在某一刻,所以实际影响一般可控。
不过有一个常见冲突值得注意:在低功耗模式下,如果关闭了 FMC 时钟或者进入 Stop 模式,SDRAM 会停止刷新,数据就可能丢失。需要保留 SDRAM 内容时,要么让系统进入 SDRAM Self-Refresh 模式,要么避免在持有 SDRAM 数据时进入低功耗。H745 双核架构下,还要特别注意 M7 和 M4 同时访问 SDRAM 时的总线仲裁,建议把共享数据区域做好互斥访问,防止逻辑上的竞争。
5.3 M7 的 D-Cache 与 SDRAM 一致性,最容易踩的坑
这个坑我必须单独拿出来说。H745 的 Cortex-M7 带 D-Cache,默认情况下 CPU 写到 SDRAM 的数据可能先停在 Cache 里,并不会立刻写回 SDRAM。如果这时 DMA 外设直接去 SDRAM 读数据,读到的可能是旧数据。反过来,DMA 往 SDRAM 写数据后,M7 再去读,可能读到 Cache 里的旧缓存,导致数据不一致。
解决办法有两种。第一种是给映射到 SDRAM 的内存区域配置 MPU,设置为 non-cacheable,这样所有访问都直接穿透到 SDRAM,逻辑最简单。第二种是保留 Cache,但在 DMA 传输前后手动执行 Clean 和 Invalidate 操作,比如调用 SCB_CleanDCache_by_Addr 或 SCB_InvalidateDCache_by_Addr。如果这块内存要兼顾 GUI 刷新和摄像头 DMA,我更推荐用 MPU 划分区域,按需 cacheable,避免每次 DMA 都要手动刷缓存。
5.4 高温和长时间稳定性测试方法
SDRAM 常见的问题往往不是一开始就出现的,而是在温度升高或者长时间运行后暴露。我的习惯是先把板子在常温下跑内存自检 30 分钟,然后放进温箱做 60 度到 85 度的热循环测试,每个温度点停留至少 30 分钟,期间持续跑随机读写。如果温度升高后出错率明显上升,优先怀疑 SDCLK 时序裕量不足,因为温度会影响信号传播延迟和芯片内部时序。
长时间稳定性测试还要包括反复进入和退出低功耗模式的场景。比如系统每 5 秒进一次 Stop 模式,唤醒后立即读写 SDRAM,跑几个小时看是否出现数据损坏。这类问题在常温下很难复现,但一旦发生就非常难查。提前在自检代码里加入 CRC 校验,连续检测数据完整性,能快速定位是刷新问题还是总线竞争问题。
最后再分享一点个人体会
我在实际做 H745 + IS42S83200J 这个方案时,最大的感触是:这类老接口的 SDRAM 虽然技术上不新,但稳定性和性价比依旧能打。画板时把信号完整性放在心上,初始化时序认真换算,内存自检跑足,基本不会翻车。反而是那些看起来和 SDRAM 无关的问题,比如 D-Cache 一致性、低功耗刷新、双核共享内存,才是真正费时间的地方。如果你正准备搞类似设计,建议先拿最小系统把 SDRAM 验证透,再往上叠 GUI 和算法,这样后面排查起来会轻松很多。