# 引言 STM32F4 系列(如 STM32F407/429)内置 FMC(Flexible Memory Controller),可无缝连接外部 SDRAM,常用于 LCD 帧缓冲、音频缓存或大容量数据暂存。然而,SDRAM 的时序参数(如 TRCD、TRP、TRC 等)若配置不当,轻则性能低下,重则系统随机崩溃。本文以常见的 IS42S16400J(16M×16bit)为例,从 Datasheet 参数到 FMC 寄存器配置,给出完整的推导与实测调优方法。 # 1. SDRAM 核心时序参数解析 SDRAM 的时序由多个延迟参数决定,STM32F4 的 FMC 控制器通过 `FMC_SDRAMTimingInitTypeDef` 结构体配置。关键参数如下: - **TRCD**(RAS to CAS Delay):行地址选通到列地址选通的延迟,决定行激活后多久可发送读/写命令。 - **TRP**(Row Precharge Delay):预充电命令到下一次行激活的最小间隔。 - **TRC**(Row Cycle Time):两次行激活之间的最小间隔,通常 TRC = TRCD + TRP + 额外余量。 - **TRAS**(Active to Precharge Delay):行激活到预充电的最小时间,影响刷新效率。 - **TWR**(Write Recovery Time):写命令到预充电的最小间隔,防止数据未写入完成。 这些参数在 SDRAM 芯片的 Datasheet 中通常以 ns 为单位给出,而 FMC 配置需要转换为 STM32 的时钟周期数(HCLK 周期)。 # 2. 从 Datasheet 到 FMC 配置的推导 ## 2.1 获取芯片参数 以 IS42S16400J 为例,其典型时序参数(-7 速度等级)如下: | 参数 | 最小值 (ns) | 说明 | |------|------------|------| | TRCD | 20 | RAS 到 CAS 延迟 | | TRP | 20 | 预充电周期 | | TRC | 63 | 行周期 | | TRAS | 42 | 行激活到预充电 | | TWR | 14 | 写恢复时间 | ## 2.2 计算时钟周期数 STM32F4 的 FMC 使用 HCLK 作为时钟源。假设系统主频为 168MHz,则 HCLK = 168MHz,周期 T = 1/168MHz ≈ 5.95ns。 每个时序参数对应的周期数 N 计算公式为: ```c N = ceil(参数时间 / T) + 1 // 加 1 是为了补偿 FMC 内部的同步延迟 ``` 以 TRCD 为例: ```c TRCD_cycles = ceil(20ns / 5.95ns) + 1 = ceil(3.36) + 1 = 4 + 1 = 5 ``` 同理可得: - TRP = 5 周期 - TRC = ceil(63/5.95) + 1 = 11 + 1 = 12 周期 - TRAS = ceil(42/5.95) + 1 = 8 + 1 = 9 周期 - TWR = ceil(14/5.95) + 1 = 3 + 1 = 4 周期 ## 2.3 配置 FMC 寄存器 在 STM32 HAL 库中,配置代码如下: ```c FMC_SDRAM_TimingTypeDef Timing = {0}; Timing.LoadToActiveDelay = 2; // tMRD,模式寄存器加载到激活,通常固定为 2 Timing.ExitSelfRefreshDelay = 7; // tXSR,退出自刷新时间,需根据芯片手册 Timing.SelfRefreshTime = 5; // tRAS,自刷新时行激活时间 Timing.RowCycleDelay = 12; // TRC Timing.WriteRecoveryTime = 4; // TWR Timing.RPDelay = 5; // TRP Timing.RCDDelay = 5; // TRCD // 初始化 SDRAM 控制器 SDRAM_InitTypeDef SdramInit = {0}; SdramInit.SDBank = FMC_SDRAM_BANK1; SdramInit.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; SdramInit.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; SdramInit.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; SdramInit.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; SdramInit.CASLatency = FMC_SDRAM_CAS_LATENCY_3; SdramInit.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; SdramInit.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; // HCLK/2 = 84MHz SdramInit.ReadBurst = FMC_SDRAM_RBURST_DISABLE; SdramInit.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; HAL_SDRAM_Init(&hsdram, &SdramInit, &Timing); ``` # 3. 实测调优:从理论到稳定 ## 3.1 编写内存测试函数 理论值只是起点,实际 PCB 布线、温度、电压波动都会影响稳定性。编写一个遍历写读测试函数,检测数据线和地址线的连通性: ```c uint8_t SDRAM_Test(uint32_t baseAddr, uint32_t size) { uint32_t *p = (uint32_t *)baseAddr; uint32_t i; // 写入递增模式 for (i = 0; i < size/4; i++) { p[i] = i + 0x12345678; } // 读取并比较 for (i = 0; i < size/4; i++) { if (p[i] != (i + 0x12345678)) { return 1; // 错误 } } return 0; } ``` ## 3.2 边界扫描与参数调整 - **降低时钟频率**:若测试失败,首先将 `SDClockPeriod` 从 2(HCLK/2)改为 3(HCLK/3),降低 SDRAM 工作频率,观察是否稳定。 - **增加余量**:将 TRCD、TRP 等参数各加 1-2 个周期,重新测试。通常 TRC 和 TRAS 对稳定性影响最大。 - **CAS Latency**:若芯片支持,可尝试降低 CAS 延迟(从 3 改为 2),但需确认芯片规格。 ## 3.3 使用示波器验证 用示波器测量 SDRAM 的 DQS 和 CLK 信号,观察建立/保持时间是否满足要求。理想情况下,数据有效窗口应占整个时钟周期的 60% 以上。若发现毛刺,需检查 PCB 走线长度匹配。 # 4. 完整代码示例 以下是一个完整的初始化与测试流程(基于 STM32CubeMX 生成的代码): ```c #include "stm32f4xx_hal.h" SDRAM_HandleTypeDef hsdram; void SDRAM_Init(void) { // 假设 GPIO 和时钟已在 CubeMX 中配置 FMC_SDRAM_TimingTypeDef Timing = {0}; SDRAM_InitTypeDef SdramInit = {0}; // 时序参数(基于 168MHz HCLK) Timing.LoadToActiveDelay = 2; Timing.ExitSelfRefreshDelay = 7; Timing.SelfRefreshTime = 5; Timing.RowCycleDelay = 12; Timing.WriteRecoveryTime = 4; Timing.RPDelay = 5; Timing.RCDDelay = 5; SdramInit.SDBank = FMC_SDRAM_BANK1; SdramInit.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; SdramInit.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; SdramInit.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; SdramInit.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; SdramInit.CASLatency = FMC_SDRAM_CAS_LATENCY_3; SdramInit.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; SdramInit.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; SdramInit.ReadBurst = FMC_SDRAM_RBURST_DISABLE; SdramInit.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; if (HAL_SDRAM_Init(&hsdram, &SdramInit, &Timing) != HAL_OK) { Error_Handler(); } // 发送模式寄存器设置命令 uint32_t modeReg = 0x23; // 设置 CAS=3, 突发长度=1 HAL_SDRAM_SendCommand(&hsdram, FMC_SDRAM_CMD_MODE, FMC_SDRAM_MODE_REGISTER, modeReg, 1000); } int main(void) { HAL_Init(); SystemClock_Config(); // 配置 168MHz SDRAM_Init(); // 测试 1MB 区域 if (SDRAM_Test(0xC0000000, 1024*1024) == 0) { // 成功 } else { // 失败,调整时序参数 } while (1); } ``` # 5. 注意事项与常见问题 - **刷新率**:SDRAM 需要周期性刷新(通常 64ms 内 8192 次),FMC 会自动处理,但需确保 `RefreshRate` 寄存器设置正确。在 HAL 中通过 `HAL_SDRAM_ProgramRefreshRate` 设置,计算公式为:`刷新周期 = (刷新时间 / 行数) / 时钟周期`。 - **PCB 布线**:SDRAM 数据线、地址线、控制线长度差应控制在 ±1mm 内,否则高速时会出现数据偏移。 - **温度漂移**:高温下时序参数会恶化,建议在最高工作温度下进行压力测试(如长时间运行内存测试)。 - **不要盲目调大**:增加时序周期会降低性能,应在稳定性和速度间取得平衡。 # 结语 通过从 Datasheet 参数到 FMC 寄存器的精确推导,再结合实测调优,可以确保 STM32F4 外接 SDRAM 的稳定运行。本文提供的方法不仅适用于 IS42S16400J,也适用于其他常见 SDRAM 芯片(如 W9825G6KH)。记住:理论计算是基础,实测验证是关键,两者结合才能打造可靠的产品。