## 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,也可以手动编写初始化函数: ```c #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。可以直接通过指针访问,也可以使用链接脚本定义段。 ```c #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 帧缓冲)的应用,这是一项非常实用的技能。