1. FMC 是什么?

FMC(Flexible Memory Controller)是 STM32 系列中的灵活存储控制器,它源于早期的 FSMC(Flexible Static Memory Controller),在 F4/F7/H7 等系列中更名为 FMC,但功能更加强大。FMC 可以连接 NOR Flash、SRAM、PSRAM、SDRAM 以及 LCD 并行接口,并支持 8/16/32 位数据总线宽度。

对于 SRAM/PSRAM,FMC 本质上是一个外置总线主控:CPU 发起访问时,FMC 自动生成地址、数据、读/写使能等时序信号,从而把片外存储器映射到 ARM 的寻址空间。

2. SRAM/PSRAM 接口原理

典型异步 SRAM 接口包含以下信号:

  • 地址线 A[0:N-1]
  • 数据线 DQ[0:M-1]
  • 芯片使能 CE#(FMC 中对应 NE 片选)
  • 读使能 OE#(FMC 中对应 NOE)
  • 写使能 WE#(FMC 中对应 NWE)
  • 字节选择 LB#/UB#(FMC 中对应 NBL[1:0])

FMC 把片外 SRAM 映射到 AHB 地址空间,例如 STM32F4 的 Bank1 起始地址 0x60000000。访问地址时,FMC 会驱动相应的片选信号并产生读写时序。

关键时序参数

异步访问需要配置以下时间参数(单位为 HCLK 周期):

  • AddressSetup:地址建立时间(从地址有效到片选有效/命令有效)
  • AddressHold:地址保持时间
  • DataSetup:数据建立时间(读时用于采样,写时用于数据有效时间)
  • BusTurnaround:总线周转时间,防止读/写切换冲突

对于高速 PSRAM 通常使用同步突发模式,而普通 SRAM 则使用异步模式。开发时需要参考 SRAM 数据手册中的时序图来调节这些参数。

3. 硬件连接示例

以 16 位异步 SRAM(如 IS62WV51216)为例,连接 STM32F407ZGT6:

  • 数据线:D[15:0] -> FSMC_D[15:0]
  • 地址线:A[18:0] -> FSMC_A[18:0]
  • 片选:CE# -> FSMC_NE1(Bank1,起始地址 0x60000000)
  • 读使能:OE# -> FSMC_NOE
  • 写使能:WE# -> FSMC_NWE
  • 字节选择:UB# -> FSMC_NBL1,LB# -> FSMC_NBL0

注意,若 SRAM 为 16 位,STM32 的 FSMC_A[0] 通常内部连接到存储器的 A[1],即地址左移一位,以支持字节选择。这在使用指针访问时需留意地址对齐。

4. 配置步骤

4.1 使用 STM32CubeMX 配置

  1. 启用 FMC,选择 Bank1,SRAM/PSRAM 模式。
  2. 设置数据总线宽度(8/16 位)。
  3. 配置时序参数:根据 SRAM 手册计算。典型值(HCLK=168MHz,周期约 6ns):
    • AddressSetup = 9(约 54ns)
    • DataSetup = 9(约 54ns)
    • AddressHold = 1
    • BusTurnaround = 0
  4. 生成代码,HAL 库会自动生成 MX_FMC_Init()HAL_SRAM_Init()

4.2 手动代码配置(HAL)

如果不想用 CubeMX,也可以手动编写初始化函数:

#include "stm32f4xx_hal.h"

SRAM_HandleTypeDef hsram;
FMC_NORSRAM_TimingTypeDef SramTiming;

void FMC_SRAM_Init(void)
{
    // 使能相关时钟
    __HAL_RCC_FMC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    // 省略 GPIO 初始化,按实际引脚配置为复用功能 AF12
    
    hsram.Instance = FMC_NORSRAM_DEVICE;
    hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
    
    hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
    hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
    hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_16;
    hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
    hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
    hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
    hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
    hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
    hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
    hsram.Init.WriteFifo = FMC_WRITE_FIFO_DISABLE;
    hsram.Init.PageSize = FMC_PAGE_SIZE_NONE;
    
    // 时序参数,单位:HCLK 周期数
    SramTiming.AddressSetupTime = 9;
    SramTiming.AddressHoldTime = 1;
    SramTiming.DataSetupTime = 9;
    SramTiming.BusTurnAroundDuration = 0;
    SramTiming.CLKDivision = 16;
    SramTiming.DataLatency = 17;
    SramTiming.AccessMode = FMC_ACCESS_MODE_A;
    
    hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
    
    HAL_SRAM_Init(&hsram, &SramTiming, &SramTiming);
    
    // 开启 FMC 写保护解除(某些 STM32 默认写保护)
    __HAL_FMC_NORSRAM_ENABLE(&hsram);
}

5. 完整代码示例:读写外部 SRAM

初始化后,外部 SRAM 的地址映射在 0x60000000。可以直接通过指针访问,也可以使用链接脚本定义段。

#define SRAM_BASE  0x60000000U
#define SRAM_SIZE  (1024U * 512U)  // 假设 512KB

void SRAM_Test(void)
{
    uint16_t *sram = (uint16_t *)SRAM_BASE;
    uint32_t i;
    uint32_t errors = 0;

    // 写入 0xAA55 模式
    for (i = 0; i < SRAM_SIZE / 2; i++)
    {
        sram[i] = 0xAA55;
    }

    // 读出并校验
    for (i = 0; i < SRAM_SIZE / 2; i++)
    {
        if (sram[i] != 0xAA55)
        {
            errors++;
        }
    }

    if (errors == 0)
    {
        // 测试通过
    }
    else
    {
        // 错误处理
    }
}

注意:对于 16 位 SRAM,使用 uint16_t 指针可确保每次访问 16 位数据,地址自动递增 2 字节。若要按字节访问,请使用 uint8_t 指针,但 FMC 会通过 NBL 引脚自动控制高/低字节。

6. 注意事项

6.1 时序调校

不同 SRAM 速度等级不同,时序参数需仔细查阅数据手册。若时序过紧,数据采样会出错;若过松,则性能下降。推荐先用示波器观察 NOE/NWE/地址有效沿,再微调参数。

6.2 电压电平匹配

STM32 FMC 引脚电压必须与外部 SRAM 的 I/O 电压一致。若 SRAM 为 3.3V,STM32 选用 VDD=3.3V;若为 1.8V SRAM,需选用对应供电的 MCU 或加电平转换芯片。

6.3 PCB 布线

外部总线的信号完整性至关重要。数据线、地址线尽量等长,控制信号(NOE/NWE/NE)要避免过长走线,建议加串联电阻(如 22Ω)抑制过冲。电源去耦电容靠近 SRAM 电源引脚。

6.4 缓存一致性与 DMA

如果使用 STM32F7/H7,CPU 的 D-Cache 会导致 DMA 与 CPU 访问外部 SRAM 时数据不一致。处理方式:

  • 对 SRAM 区域配置为设备/强序模式(Device or Strongly-ordered),关闭缓存;
  • 或使用 SCB_CleanDCache() / SCB_InvalidateDCache() 手动维护一致性。

6.5 片选与地址重叠

确保 FMC 的多个外设(如 LCD、NOR Flash)不使用同一个 Bank 或相同片选,否则会访问冲突。

7. 总结

FMC 将复杂的外部存储器时序转换为简单的内存映射访问,极大简化了设计。正确配置时序参数和硬件连接后,外部 SRAM/PSRAM 可以像内部 RAM 一样使用。对于需要大容量平滑数据缓冲(如音频、LCD 帧缓冲)的应用,这是一项非常实用的技能。