CASE / 03 · 技术笔记
STM32H7 的 FMC 接 FPGA 双口 RAM:照抄官方例程的三个坑,和一条不看 RTL 的反证
客户用 STM32H743 的 FMC(异步 SRAM 模式)挂 FPGA 内部模拟的双口 RAM,交换约 400 个 16 位变量。用指针逐个读,偶尔读到上一个地址的数据;用 MDMA 整块读就正常。这篇整理了四轮答复里可以脱离本案独立成立的部分:照抄 ST 官方例程会踩的三个坑、手册时序里两条反直觉的规定,以及一条不看 FPGA 代码也能下的判断。客户最终没有反馈,根因没有闭环。
适用范围
| 芯片 | STM32H742 / H743 / H753 / H750(RM0433 覆盖的系列);机制对其他 H7 的 FMC 大体适用,寄存器页码以各自手册为准 |
| 场景 | FMC 异步 SRAM 接口挂 FPGA 或其他「对端也会改数据」的存储器,且对端按 NE / NOE 边沿判断访问 |
| 状态 | 技术笔记;客户未再回复,根因未闭环 |
现象
- FMC Bank 1,16 位异步 SRAM,模式 A;fmc_ker_ck = 200 MHz,一个周期 5 ns。
- 程序用指针逐个读结构体里的 16 位成员,偶尔读到上一个地址的数据;时序配得越快越容易出现。
- 同一块区域改用 MDMA 整块读,不出错。
- 客户用示波器看到 FMC 的两根控制信号拉高的时刻不一致,怀疑 MCU 输出有问题。
照抄官方例程的三个坑
官方例程指 STM32CubeH7 V1.13.0 的 Projects/STM32H743I-EVAL/Examples/FMC/FMC_SRAM,外接 16 位异步 SRAM,是 H743 上与本场景最接近的例程。
-
例程配不出模式 A。
main.c第 104 行写了AccessMode = FMC_ACCESS_MODE_A,第 115 行却是ExtendedMode = FMC_EXTENDED_MODE_DISABLE。RM0433 Rev 8 第 842 页,ACCMOD位「are taken into account only when the EXTMOD bit in the FMC_BCRx register is 1」。而FMC_ACCESS_MODE_A这个宏的值本身就是 0(stm32h7xx_ll_fmc.h第 615 行),写不写都一样。要模式 A,必须打开 Extended mode。 -
HAL_SRAM_Read_16b/Write_16b名不副实。stm32h7xx_hal_sram.c里这两个函数用的都是__IO uint32_t *指针(第 448、508 行)。写路径每两个半字先整字写一次,高半字为 0(第 527 行),再用|=读回、合并、写第二次(第 529 行)。对普通 SRAM 无害;对双口 RAM,对端会看到高半字先被清 0 的中间状态。这种场景改用volatile uint16_t *逐个访问。 -
例程把外部 SRAM 区配成了可缓存。
main.c第 340–353 行,注释写「Configure the MPU attributes as WB for SRAM」,IsCacheable = MPU_ACCESS_CACHEABLE。对端会异步改数据的双口 RAM 照抄这段,就会读到 cache 里的旧值。- 更隐蔽的是只关 cache 还不够。PM0253 Rev 6 第 35 页 Table 17:0x6000_0000 起的外部 RAM 区默认是 Normal 类型;第 33 页原文,Normal 类型下处理器「can re-order transactions for efficiency, or perform speculative reads」,只有 Device / Strongly-ordered 才保证访问顺序。推测读会让对端看到软件没有发起过的读。
- 双口 RAM 区要用 MPU 配成 Device 或 Strongly-ordered。
- 本案客户后来把 MPU 改成了 Device,现象没有变,说明本案的主因不在这里;但照抄例程的人仍然会踩到。
手册时序里两条反直觉的规定
两条都在 DS12110 Rev 11 的异步读时序表里,芯片的两个硅版本数值一致:rev Y 见第 147 页 Table 56,rev V 见第 250 页 Table 153。
-
NE 与 NOE 本来就不保证同时拉高。
th(NE_NOE)(NOE 拉高到 NE 拉高的保持时间)Min = 0,Max 没有规定。客户示波器上看到的「两根线不同步」是规格允许的行为,不是故障。 -
地址相对片选没有提前量,相对 NOE 拉高没有保持量。
tv(A_NE)(NE 拉低到地址有效)Max = 0.5 ns,地址与片选下降沿几乎同时变化;th(A_NOE)(NOE 拉高后地址保持)Min = 0。加大 ADDSET 只延长地址相对 NOE 的提前量,相对 NE 始终是同时变。FPGA 如果在 NE 或 NOE 的边沿锁地址,锁到的可能是上一次的值。
不看 RTL 的反证
客户认为 FPGA 逻辑很简单、已经查过,不愿意改,也没有提供 FPGA 代码。能不能只从 MCU 这一侧判断问题在不在 FPGA?可以。
- 两条路径在 MCU 引脚上的唯一差别,是 NE 在两次读之间拉不拉高。 RM0433 第 800 页:AXI 数据总线是 64 位,一次 AXI 访问可能被拆成若干次连续的 32 / 16 / 8 位外部访问,「The FMC Chip Select (FMC_NEx) does not toggle between consecutive accesses」(模式 D 且开 Extended mode 时除外)。MDMA 发的是宽访问,被拆成连续的 16 位读,NE 全程保持低电平;CPU 读一个 16 位结构体成员,本身就是一次 16 位访问,不需要拆分,每次都是独立的访问,NE 在两次之间会拉高。
- 拉高的时间被硬件固定成 1 个周期。 RM0433 第 843 页:两次连续的异步读访问同一个 bank 之间,固定插入 1 个 FMC 时钟周期的总线周转,与编程的 BUSTURN 值无关(复用模式和模式 D 除外)。按本案 200 MHz 算只有 5 ns。
- 所以结论是: 两条路径用的是同一套时序寄存器、同一组引脚。如果 FPGA 的判据与 NE 边沿无关,两者结果必然相同;结果不同,就说明 FPGA 的判据依赖了 NE 边沿,而这正是上一节那两条规定不保证的行为。
第 1 步里「CPU 的 16 位读不会被拆分、NE 会拉高」是对第 800 页原文适用范围的解读,手册没有逐字写出;第 3 步的结论属于推断,要看到 FPGA 代码才能坐实。
给出的建议与现状
- 根因方向:FPGA 改为在访问窗口内按电平采样地址,不依赖 NE / NOE 边沿。
- 不改 FPGA 的做法:读取改用 MDMA 整块搬到内部 SRAM,不降频、不改时序参数。估算 400 个点指针读约 22–24 µs,且全程占着 CPU;MDMA 约 20 µs,CPU 基本不参与。
- 前提:MDMA 的传输宽度要足够宽,外部访问才会被拆分、NE 才不拉高;配成 16 位单次传输就等于没改。客户的 MDMA 配置始终没有提供。
- 现状:四轮答复全部已发出,客户没有再回复。FPGA 代码、MDMA 传输宽度、插延时对照实验的结果都没有拿到,根因没有闭环。
可复用的经验
- 照抄官方例程前,把它的前提和自己的场景对一遍。例程接的是普通 SRAM,默认没有第二个主机在改数据;双口 RAM 不满足这个前提。
- 「两条路径一条正常、一条异常」本身就是证据。找出两者在引脚上唯一的差别,往往不用看对端代码就能判断问题在哪一侧。
- 示波器上看到的「不对齐」,先查规格允不允许。这次的 NE / NOE 不同时拉高,是规格明文允许的。
结论—依据—验证状态对照
| # | 结论 | 依据 | 类型 |
|---|---|---|---|
| 1 | 例程写了模式 A,但 Extended mode 关着,模式 A 不生效 | 例程 main.c 第 104、115 行;RM0433 Rev 8 第 842 页 ACCMOD 位说明 | 源码 手册原文 |
| 2 | FMC_ACCESS_MODE_A 的值为 0 | stm32h7xx_ll_fmc.h 第 615 行 | 源码 |
| 3 | HAL_SRAM_Write_16b 用 32 位指针、读改写两次 | stm32h7xx_hal_sram.c 第 508、527、529 行 | 源码 |
| 4 | 例程把外部 SRAM 区配成可缓存(WB) | 例程 main.c 第 340–353 行 | 源码 |
| 5 | 外部 RAM 区默认 Normal 类型,Normal 允许乱序与推测读 | PM0253 Rev 6 第 35 页 Table 17、第 33 页 | 手册原文 |
| 6 | NE / NOE 拉高不保证同时(th(NE_NOE) 无上限) | DS12110 Rev 11 第 147 页 Table 56(rev Y)、第 250 页 Table 153(rev V) | 手册原文 |
| 7 | 地址与 NE 同时变(0.5 ns),NOE 拉高后地址零保持 | 同上两张表 tv(A_NE)、th(A_NOE) | 手册原文 |
| 8 | 拆分出的连续访问之间 NE 不拉高 | RM0433 Rev 8 第 800 页 §22.5 | 手册原文 |
| 9 | 连续两次异步读之间固定 1 个周期周转 | RM0433 Rev 8 第 843 页 BUSTURN 位说明 | 手册原文 |
| 10 | CPU 的 16 位读不拆分、NE 会拉高 | 对第 800 页原文适用范围的解读 | 推断 |
| 11 | 本案问题出在 FPGA 按 NE 边沿判断访问 | 由 6–10 与「MDMA 正常、指针异常」推出 | 推断 未闭环 |
| 12 | 改 MDMA 整块读能规避 | — | 未验证 |
源码出处均为 STM32CubeH7 V1.13.0。
遇到类似问题
MCU 通过 FMC 接 FPGA、双口 RAM 或其他非标准存储器时读写异常,发来这几项能更快收敛:MCU 型号与硅版本、FMC 模式与时序寄存器的读回值、MPU 对该区的配置、正常与异常两种访问方式各是什么、对端是按电平还是按边沿采样。