← 返回案例列表 JonTan

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 边沿判断访问
状态技术笔记;客户未再回复,根因未闭环

现象

照抄官方例程的三个坑

官方例程指 STM32CubeH7 V1.13.0 的 Projects/STM32H743I-EVAL/Examples/FMC/FMC_SRAM,外接 16 位异步 SRAM,是 H743 上与本场景最接近的例程。

  1. 例程配不出模式 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。
  2. HAL_SRAM_Read_16b / Write_16b 名不副实。 stm32h7xx_hal_sram.c 里这两个函数用的都是 __IO uint32_t * 指针(第 448、508 行)。写路径每两个半字先整字写一次,高半字为 0(第 527 行),再用 |= 读回、合并、写第二次(第 529 行)。对普通 SRAM 无害;对双口 RAM,对端会看到高半字先被清 0 的中间状态。这种场景改用 volatile uint16_t * 逐个访问。
  3. 例程把外部 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。

  1. NE 与 NOE 本来就不保证同时拉高。 th(NE_NOE)(NOE 拉高到 NE 拉高的保持时间)Min = 0,Max 没有规定。客户示波器上看到的「两根线不同步」是规格允许的行为,不是故障。
  2. 地址相对片选没有提前量,相对 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?可以。

  1. 两条路径在 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 在两次之间会拉高。
  2. 拉高的时间被硬件固定成 1 个周期。 RM0433 第 843 页:两次连续的异步读访问同一个 bank 之间,固定插入 1 个 FMC 时钟周期的总线周转,与编程的 BUSTURN 值无关(复用模式和模式 D 除外)。按本案 200 MHz 算只有 5 ns。
  3. 所以结论是: 两条路径用的是同一套时序寄存器、同一组引脚。如果 FPGA 的判据与 NE 边沿无关,两者结果必然相同;结果不同,就说明 FPGA 的判据依赖了 NE 边沿,而这正是上一节那两条规定不保证的行为。

第 1 步里「CPU 的 16 位读不会被拆分、NE 会拉高」是对第 800 页原文适用范围的解读,手册没有逐字写出;第 3 步的结论属于推断,要看到 FPGA 代码才能坐实。

给出的建议与现状

可复用的经验

  1. 照抄官方例程前,把它的前提和自己的场景对一遍。例程接的是普通 SRAM,默认没有第二个主机在改数据;双口 RAM 不满足这个前提。
  2. 「两条路径一条正常、一条异常」本身就是证据。找出两者在引脚上唯一的差别,往往不用看对端代码就能判断问题在哪一侧。
  3. 示波器上看到的「不对齐」,先查规格允不允许。这次的 NE / NOE 不同时拉高,是规格明文允许的。

结论—依据—验证状态对照

#结论依据类型
1例程写了模式 A,但 Extended mode 关着,模式 A 不生效例程 main.c 第 104、115 行;RM0433 Rev 8 第 842 页 ACCMOD 位说明源码 手册原文
2FMC_ACCESS_MODE_A 的值为 0stm32h7xx_ll_fmc.h 第 615 行源码
3HAL_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 页手册原文
6NE / 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 位说明手册原文
10CPU 的 16 位读不拆分、NE 会拉高对第 800 页原文适用范围的解读推断
11本案问题出在 FPGA 按 NE 边沿判断访问由 6–10 与「MDMA 正常、指针异常」推出推断 未闭环
12改 MDMA 整块读能规避—未验证

源码出处均为 STM32CubeH7 V1.13.0。

遇到类似问题

MCU 通过 FMC 接 FPGA、双口 RAM 或其他非标准存储器时读写异常,发来这几项能更快收敛:MCU 型号与硅版本、FMC 模式与时序寄存器的读回值、MPU 对该区的配置、正常与异常两种访问方式各是什么、对端是按电平还是按边沿采样。